or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

browser-control.mdindex.mdmobile-device.mdnetwork-api.mdpage-interaction.mdtesting-framework.mdvisual-debugging.md

testing-framework.mddocs/

0

# Testing Framework

1

2

Comprehensive test framework with fixtures for browser automation, parallel execution, extensive assertion library, and test configuration management.

3

4

## Capabilities

5

6

### Core Test Functions

7

8

Main test and assertion functions with built-in Playwright fixtures.

9

10

```typescript { .api }

11

/**

12

* Main test function with Playwright fixtures

13

*/

14

const test: TestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;

15

16

/**

17

* Base test function without built-in fixtures

18

*/

19

const _baseTest: TestType<{}, {}>;

20

21

/**

22

* Assertion library with Playwright-specific matchers

23

*/

24

const expect: Expect<{}>;

25

26

interface TestType<TestArgs extends {}, WorkerArgs extends {}> {

27

/** Define a test */

28

(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

29

/** Skip a test */

30

skip(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

31

skip(condition: boolean, title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

32

/** Mark test as failing */

33

fail(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

34

fail(condition: boolean, title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

35

/** Run test only */

36

only(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

37

/** Slow test */

38

slow(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

39

slow(condition: boolean, title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

40

/** Fixme test */

41

fixme(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

42

fixme(condition: boolean, title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

43

44

/** Create test step */

45

step(title: string, body: (args: TestArgs) => Promise<void> | void): Promise<void>;

46

47

/** Describe test suite */

48

describe(title: string, callback: () => void): void;

49

/** Before each test hook */

50

beforeEach(hookFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

51

/** After each test hook */

52

afterEach(hookFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;

53

/** Before all tests hook */

54

beforeAll(hookFunction: (args: WorkerArgs, workerInfo: WorkerInfo) => Promise<void> | void): void;

55

/** After all tests hook */

56

afterAll(hookFunction: (args: WorkerArgs, workerInfo: WorkerInfo) => Promise<void> | void): void;

57

58

/** Use fixtures */

59

use(fixtures: Partial<TestArgs & WorkerArgs>): void;

60

/** Extend with custom fixtures */

61

extend<T extends {}, W extends {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;

62

}

63

```

64

65

**Usage Examples:**

66

67

```typescript

68

import { test, expect } from 'playwright/test';

69

70

// Basic test

71

test('homepage has title', async ({ page }) => {

72

await page.goto('https://playwright.dev/');

73

await expect(page).toHaveTitle(/Playwright/);

74

});

75

76

// Test with steps

77

test('user login flow', async ({ page }) => {

78

await test.step('navigate to login', async () => {

79

await page.goto('/login');

80

});

81

82

await test.step('fill credentials', async () => {

83

await page.fill('#email', 'user@example.com');

84

await page.fill('#password', 'password');

85

});

86

87

await test.step('submit login', async () => {

88

await page.click('#login-button');

89

await expect(page).toHaveURL('/dashboard');

90

});

91

});

92

93

// Test suite

94

test.describe('User Management', () => {

95

test.beforeEach(async ({ page }) => {

96

await page.goto('/users');

97

});

98

99

test('create user', async ({ page }) => {

100

await page.click('#create-user');

101

// ... test implementation

102

});

103

104

test('delete user', async ({ page }) => {

105

await page.click('.user-item .delete');

106

// ... test implementation

107

});

108

});

109

110

// Conditional tests

111

test.skip(process.platform === 'win32', 'skip on Windows', async ({ page }) => {

112

// ... test implementation

113

});

114

```

115

116

### Test Fixtures

117

118

Built-in and custom fixture system for test isolation and resource management.

119

120

```typescript { .api }

121

/**

122

* Test-scoped fixtures (new instance per test)

123

*/

124

interface PlaywrightTestArgs {

125

/** Page instance for this test */

126

page: Page;

127

/** Browser context for this test */

128

context: BrowserContext;

129

}

130

131

/**

132

* Worker-scoped fixtures (shared across tests in worker)

133

*/

134

interface PlaywrightWorkerArgs {

135

/** Browser instance for this worker */

136

browser: Browser;

137

/** Browser name */

138

browserName: string;

139

/** Playwright instance */

140

playwright: typeof import('playwright-core');

141

}

142

143

/**

144

* Test configuration options

145

*/

146

interface PlaywrightTestOptions {

147

/** Action timeout */

148

actionTimeout?: number;

149

/** Navigation timeout */

150

navigationTimeout?: number;

151

/** Test timeout */

152

testTimeout?: number;

153

/** Expect timeout */

154

expectTimeout?: number;

155

/** Screenshot mode */

156

screenshot?: ScreenshotMode;

157

/** Video recording */

158

video?: VideoMode;

159

/** Trace recording */

160

trace?: TraceMode;

161

}

162

163

/**

164

* Worker configuration options

165

*/

166

interface PlaywrightWorkerOptions {

167

/** Browser to use */

168

browserName?: 'chromium' | 'firefox' | 'webkit';

169

/** Browser launch options */

170

launchOptions?: LaunchOptions;

171

/** Context options */

172

contextOptions?: BrowserContextOptions;

173

/** Headless mode */

174

headless?: boolean;

175

/** Browser channel */

176

channel?: string;

177

}

178

179

type ScreenshotMode = 'off' | 'on' | 'only-on-failure' | 'on-first-failure';

180

type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry';

181

type TraceMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'retain-on-first-failure';

182

183

/**

184

* Custom fixture definition

185

*/

186

type TestFixture<R, Args extends {}> = (args: Args, use: (r: R) => Promise<void>, testInfo: TestInfo) => any;

187

type WorkerFixture<R, Args extends {}> = (args: Args, use: (r: R) => Promise<void>, workerInfo: WorkerInfo) => any;

188

189

type Fixtures<T extends {} = {}, W extends {} = {}, PT extends {} = {}, PW extends {} = {}> = {

190

[K in keyof T]: TestFixture<T[K], PT> | [TestFixture<T[K], PT>, { scope?: 'test' }];

191

} & {

192

[K in keyof W]: WorkerFixture<W[K], PW> | [WorkerFixture<W[K], PW>, { scope: 'worker' }];

193

};

194

```

195

196

**Usage Examples:**

197

198

```typescript

199

// Use built-in fixtures

200

test('test with page', async ({ page, context, browser }) => {

201

console.log('Browser:', browser.version());

202

console.log('Context pages:', context.pages().length);

203

await page.goto('https://example.com');

204

});

205

206

// Custom fixtures

207

const myTest = test.extend<{ todoPage: TodoPage }>({

208

todoPage: async ({ page }, use) => {

209

const todoPage = new TodoPage(page);

210

await todoPage.goto();

211

await use(todoPage);

212

await todoPage.cleanup();

213

}

214

});

215

216

myTest('test with custom fixture', async ({ todoPage }) => {

217

await todoPage.addItem('Buy milk');

218

await expect(todoPage.items).toHaveCount(1);

219

});

220

221

// Worker-scoped fixture

222

const testWithDB = test.extend<{}, { db: Database }>({

223

db: [async ({}, use) => {

224

const db = await Database.connect();

225

await use(db);

226

await db.close();

227

}, { scope: 'worker' }]

228

});

229

230

testWithDB('test with database', async ({ page, db }) => {

231

const user = await db.createUser();

232

await page.goto(`/users/${user.id}`);

233

// ... test implementation

234

});

235

```

236

237

### Test Configuration

238

239

Configuration management for test projects, environments, and execution options.

240

241

```typescript { .api }

242

/**

243

* Define type-safe test configuration

244

*/

245

function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig;

246

function defineConfig<T>(config: PlaywrightTestConfig<T>): PlaywrightTestConfig<T>;

247

function defineConfig<T, W>(config: PlaywrightTestConfig<T, W>): PlaywrightTestConfig<T, W>;

248

249

interface PlaywrightTestConfig<TestArgs = {}, WorkerArgs = {}> {

250

/** Test directory */

251

testDir?: string;

252

/** Test files pattern */

253

testMatch?: string | RegExp | (string | RegExp)[];

254

/** Test ignore pattern */

255

testIgnore?: string | RegExp | (string | RegExp)[];

256

/** Global timeout */

257

globalTimeout?: number;

258

/** Test timeout */

259

timeout?: number;

260

/** Expect timeout */

261

expect?: { timeout?: number };

262

/** Output directory */

263

outputDir?: string;

264

/** Forbid-only in CI */

265

forbidOnly?: boolean;

266

/** Fully parallel */

267

fullyParallel?: boolean;

268

/** Global setup */

269

globalSetup?: string;

270

/** Global teardown */

271

globalTeardown?: string;

272

/** Maximum failures */

273

maxFailures?: number;

274

/** Preserve output */

275

preserveOutput?: 'always' | 'never' | 'failures-only';

276

/** Reporter configuration */

277

reporter?: ReporterDescription | ReporterDescription[];

278

/** Retry configuration */

279

retries?: number | { mode?: 'always' | 'only-on-failure' };

280

/** Shard configuration */

281

shard?: { total: number; current: number };

282

/** Update snapshots */

283

updateSnapshots?: 'all' | 'none' | 'missing';

284

/** Worker configuration */

285

workers?: number | string;

286

/** Use shared options */

287

use?: PlaywrightTestOptions & PlaywrightWorkerOptions & TestArgs & WorkerArgs;

288

/** Project configuration */

289

projects?: PlaywrightTestProject<TestArgs, WorkerArgs>[];

290

/** Dependencies between projects */

291

dependencies?: string[];

292

}

293

294

type PlaywrightTestProject<TestArgs = {}, WorkerArgs = {}> = {

295

/** Project name */

296

name?: string;

297

/** Test directory for this project */

298

testDir?: string;

299

/** Test files pattern */

300

testMatch?: string | RegExp | (string | RegExp)[];

301

/** Test ignore pattern */

302

testIgnore?: string | RegExp | (string | RegExp)[];

303

/** Output directory */

304

outputDir?: string;

305

/** Retry configuration */

306

retries?: number;

307

/** Project dependencies */

308

dependencies?: string[];

309

/** Use options */

310

use?: PlaywrightTestOptions & PlaywrightWorkerOptions & TestArgs & WorkerArgs;

311

};

312

313

type ReporterDescription =

314

| ['blob'] | ['blob', BlobReporterOptions]

315

| ['dot']

316

| ['line']

317

| ['list'] | ['list', ListReporterOptions]

318

| ['github']

319

| ['junit'] | ['junit', JUnitReporterOptions]

320

| ['json'] | ['json', JsonReporterOptions]

321

| ['html'] | ['html', HtmlReporterOptions]

322

| ['null']

323

| [string] | [string, any];

324

```

325

326

**Usage Examples:**

327

328

```typescript

329

// playwright.config.ts

330

import { defineConfig, devices } from 'playwright/test';

331

332

export default defineConfig({

333

testDir: './tests',

334

fullyParallel: true,

335

forbidOnly: !!process.env.CI,

336

retries: process.env.CI ? 2 : 0,

337

workers: process.env.CI ? 1 : undefined,

338

reporter: [

339

['html'],

340

['junit', { outputFile: 'results.xml' }]

341

],

342

343

use: {

344

baseURL: 'http://127.0.0.1:3000',

345

trace: 'on-first-retry',

346

screenshot: 'only-on-failure'

347

},

348

349

projects: [

350

{

351

name: 'chromium',

352

use: { ...devices['Desktop Chrome'] },

353

},

354

{

355

name: 'firefox',

356

use: { ...devices['Desktop Firefox'] },

357

},

358

{

359

name: 'webkit',

360

use: { ...devices['Desktop Safari'] },

361

},

362

{

363

name: 'Mobile Chrome',

364

use: { ...devices['Pixel 5'] },

365

}

366

],

367

368

webServer: {

369

command: 'npm run start',

370

url: 'http://127.0.0.1:3000',

371

reuseExistingServer: !process.env.CI,

372

},

373

});

374

```

375

376

### Test Execution Context

377

378

Access test execution information and control test flow.

379

380

```typescript { .api }

381

interface TestInfo {

382

/** Test title */

383

title: string;

384

/** Test file path */

385

file: string;

386

/** Test line number */

387

line: number;

388

/** Test column number */

389

column: number;

390

/** Test function */

391

fn: Function;

392

/** Repeat each index */

393

repeatEachIndex: number;

394

/** Retry number */

395

retry: number;

396

/** Worker index */

397

workerIndex: number;

398

/** Parallel index */

399

parallelIndex: number;

400

/** Project configuration */

401

project: Project;

402

/** Test configuration */

403

config: Config;

404

/** Expected status */

405

expectedStatus: TestStatus;

406

/** Test timeout */

407

timeout: number;

408

/** Test annotations */

409

annotations: TestAnnotation[];

410

/** Test attachments */

411

attachments: TestAttachment[];

412

413

/** Set test timeout */

414

setTimeout(timeout: number): void;

415

/** Skip test */

416

skip(condition?: boolean, description?: string): void;

417

/** Mark test as failing */

418

fail(condition?: boolean, description?: string): void;

419

/** Mark test as slow */

420

slow(condition?: boolean, description?: string): void;

421

/** Mark test as fixme */

422

fixme(condition?: boolean, description?: string): void;

423

424

/** Add attachment */

425

attach(name: string, options: { path?: string; body?: string | Buffer; contentType?: string }): Promise<void>;

426

/** Get output directory */

427

outputDir(): string;

428

/** Get output path */

429

outputPath(...pathSegments: string[]): string;

430

/** Get snapshot path */

431

snapshotPath(...pathSegments: string[]): string;

432

}

433

434

interface WorkerInfo {

435

/** Worker index */

436

workerIndex: number;

437

/** Parallel index */

438

parallelIndex: number;

439

/** Project configuration */

440

project: Project;

441

/** Test configuration */

442

config: Config;

443

}

444

445

type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';

446

447

interface TestAnnotation {

448

type: string;

449

description?: string;

450

}

451

452

interface TestAttachment {

453

name: string;

454

contentType: string;

455

path?: string;

456

body?: Buffer;

457

}

458

```

459

460

**Usage Examples:**

461

462

```typescript

463

test('test with info', async ({ page }, testInfo) => {

464

console.log('Test:', testInfo.title);

465

console.log('Retry:', testInfo.retry);

466

console.log('Worker:', testInfo.workerIndex);

467

468

// Conditional skip

469

testInfo.skip(process.platform === 'darwin', 'Not supported on macOS');

470

471

// Set timeout

472

testInfo.setTimeout(60000);

473

474

// Add attachment

475

await page.screenshot({ path: 'screenshot.png' });

476

await testInfo.attach('screenshot', { path: 'screenshot.png', contentType: 'image/png' });

477

478

// Add text attachment

479

await testInfo.attach('debug-log', {

480

body: JSON.stringify({ url: page.url(), title: await page.title() }),

481

contentType: 'application/json'

482

});

483

});

484

```

485

486

### Assertions & Expectations

487

488

Extended assertion library with Playwright-specific matchers.

489

490

```typescript { .api }

491

interface Expect<ExtendedMatchers = {}> {

492

/** Create assertion */

493

<T>(actual: T): PlaywrightAssertions<T> & ExtendedMatchers;

494

/** Configure expect */

495

configure(options: { timeout?: number; soft?: boolean }): Expect<ExtendedMatchers>;

496

/** Extend with custom matchers */

497

extend<T>(matchers: T): Expect<ExtendedMatchers & T>;

498

/** Soft assertions */

499

soft<T>(actual: T): PlaywrightAssertions<T> & ExtendedMatchers;

500

/** Poll assertion */

501

poll<T>(callback: () => T | Promise<T>, options?: { timeout?: number; intervals?: number[] }): PlaywrightAssertions<T> & ExtendedMatchers;

502

}

503

504

interface PlaywrightAssertions<T> {

505

/** Standard Jest matchers */

506

toBe(expected: T): Promise<void>;

507

toEqual(expected: T): Promise<void>;

508

toBeCloseTo(expected: number, precision?: number): Promise<void>;

509

toBeDefined(): Promise<void>;

510

toBeFalsy(): Promise<void>;

511

toBeGreaterThan(expected: number): Promise<void>;

512

toBeInstanceOf(expected: any): Promise<void>;

513

toBeNull(): Promise<void>;

514

toBeTruthy(): Promise<void>;

515

toBeUndefined(): Promise<void>;

516

toContain(expected: any): Promise<void>;

517

toContainEqual(expected: any): Promise<void>;

518

toHaveLength(expected: number): Promise<void>;

519

toMatch(expected: string | RegExp): Promise<void>;

520

toMatchObject(expected: any): Promise<void>;

521

toThrow(expected?: string | RegExp | Error): Promise<void>;

522

523

/** Playwright-specific matchers for Page */

524

toHaveTitle(expected: string | RegExp): Promise<void>;

525

toHaveURL(expected: string | RegExp): Promise<void>;

526

527

/** Playwright-specific matchers for Locator */

528

toBeAttached(): Promise<void>;

529

toBeChecked(): Promise<void>;

530

toBeDisabled(): Promise<void>;

531

toBeEditable(): Promise<void>;

532

toBeEmpty(): Promise<void>;

533

toBeEnabled(): Promise<void>;

534

toBeFocused(): Promise<void>;

535

toBeHidden(): Promise<void>;

536

toBeInViewport(): Promise<void>;

537

toBeVisible(): Promise<void>;

538

toContainText(expected: string | RegExp | (string | RegExp)[]): Promise<void>;

539

toHaveAccessibleDescription(expected: string | RegExp): Promise<void>;

540

toHaveAccessibleName(expected: string | RegExp): Promise<void>;

541

toHaveAttribute(name: string, value?: string | RegExp): Promise<void>;

542

toHaveClass(expected: string | RegExp | (string | RegExp)[]): Promise<void>;

543

toHaveCount(count: number): Promise<void>;

544

toHaveCSS(name: string, value: string | RegExp): Promise<void>;

545

toHaveId(id: string | RegExp): Promise<void>;

546

toHaveJSProperty(name: string, value: any): Promise<void>;

547

toHaveRole(role: string): Promise<void>;

548

toHaveScreenshot(options?: LocatorScreenshotOptions): Promise<void>;

549

toHaveText(expected: string | RegExp | (string | RegExp)[]): Promise<void>;

550

toHaveValue(expected: string | RegExp): Promise<void>;

551

toHaveValues(expected: (string | RegExp)[]): Promise<void>;

552

}

553

```

554

555

**Usage Examples:**

556

557

```typescript

558

import { test, expect } from 'playwright/test';

559

560

test('assertions examples', async ({ page }) => {

561

await page.goto('https://example.com');

562

563

// Page assertions

564

await expect(page).toHaveTitle(/Example/);

565

await expect(page).toHaveURL('https://example.com/');

566

567

// Locator assertions

568

const heading = page.locator('h1');

569

await expect(heading).toBeVisible();

570

await expect(heading).toHaveText('Welcome');

571

await expect(heading).toHaveClass('main-heading');

572

573

// Form assertions

574

const input = page.locator('#email');

575

await expect(input).toBeEnabled();

576

await expect(input).toBeEmpty();

577

await input.fill('test@example.com');

578

await expect(input).toHaveValue('test@example.com');

579

580

// Count assertions

581

const items = page.locator('.item');

582

await expect(items).toHaveCount(5);

583

584

// Soft assertions (don't stop test on failure)

585

await expect.soft(page.locator('#optional')).toBeVisible();

586

await expect.soft(page.locator('#another')).toHaveText('Expected');

587

588

// Poll assertions (wait for condition)

589

await expect.poll(async () => {

590

const response = await page.request.get('/api/status');

591

return response.status();

592

}).toBe(200);

593

});

594

```

595

596

## Types

597

598

### Reporter Configuration Types

599

600

```typescript { .api }

601

type BlobReporterOptions = {

602

outputDir?: string;

603

fileName?: string;

604

};

605

606

type ListReporterOptions = {

607

printSteps?: boolean;

608

};

609

610

type JUnitReporterOptions = {

611

outputFile?: string;

612

stripANSIControlSequences?: boolean;

613

includeProjectInTestName?: boolean;

614

};

615

616

type JsonReporterOptions = {

617

outputFile?: string;

618

};

619

620

type HtmlReporterOptions = {

621

outputFolder?: string;

622

open?: 'always' | 'never' | 'on-failure';

623

host?: string;

624

port?: number;

625

attachmentsBaseURL?: string;

626

title?: string;

627

noSnippets?: boolean;

628

};

629

```

630

631

### Test Merging Functions

632

633

```typescript { .api }

634

/**

635

* Merge multiple test types into one

636

*/

637

function mergeTests<List extends any[]>(...tests: List): MergedTestType<List>;

638

639

/**

640

* Merge multiple expect objects into one

641

*/

642

function mergeExpects<List extends any[]>(...expects: List): MergedExpect<List>;

643

644

type MergedTestType<List extends any[]> = TestType<

645

UnionToIntersection<List[number] extends TestType<infer T, any> ? T : {}>,

646

UnionToIntersection<List[number] extends TestType<any, infer W> ? W : {}>

647

>;

648

649

type MergedExpect<List extends any[]> = Expect<

650

UnionToIntersection<List[number] extends Expect<infer M> ? M : {}>

651

>;

652

```