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
```