A high-level API to automate web browsers and comprehensive framework for web testing
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Comprehensive test framework with fixtures for browser automation, parallel execution, extensive assertion library, and test configuration management.
Main test and assertion functions with built-in Playwright fixtures.
/**
* Main test function with Playwright fixtures
*/
const test: TestType<PlaywrightTestArgs & PlaywrightTestOptions, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
/**
* Base test function without built-in fixtures
*/
const _baseTest: TestType<{}, {}>;
/**
* Assertion library with Playwright-specific matchers
*/
const expect: Expect<{}>;
interface TestType<TestArgs extends {}, WorkerArgs extends {}> {
/** Define a test */
(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
/** Skip a test */
skip(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
skip(condition: boolean, title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
/** Mark test as failing */
fail(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
fail(condition: boolean, title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
/** Run test only */
only(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
/** Slow test */
slow(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
slow(condition: boolean, title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
/** Fixme test */
fixme(title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
fixme(condition: boolean, title: string, testFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
/** Create test step */
step(title: string, body: (args: TestArgs) => Promise<void> | void): Promise<void>;
/** Describe test suite */
describe(title: string, callback: () => void): void;
/** Before each test hook */
beforeEach(hookFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
/** After each test hook */
afterEach(hookFunction: (args: TestArgs, testInfo: TestInfo) => Promise<void> | void): void;
/** Before all tests hook */
beforeAll(hookFunction: (args: WorkerArgs, workerInfo: WorkerInfo) => Promise<void> | void): void;
/** After all tests hook */
afterAll(hookFunction: (args: WorkerArgs, workerInfo: WorkerInfo) => Promise<void> | void): void;
/** Use fixtures */
use(fixtures: Partial<TestArgs & WorkerArgs>): void;
/** Extend with custom fixtures */
extend<T extends {}, W extends {}>(fixtures: Fixtures<T, W, TestArgs, WorkerArgs>): TestType<TestArgs & T, WorkerArgs & W>;
}Usage Examples:
import { test, expect } from 'playwright/test';
// Basic test
test('homepage has title', async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
});
// Test with steps
test('user login flow', async ({ page }) => {
await test.step('navigate to login', async () => {
await page.goto('/login');
});
await test.step('fill credentials', async () => {
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'password');
});
await test.step('submit login', async () => {
await page.click('#login-button');
await expect(page).toHaveURL('/dashboard');
});
});
// Test suite
test.describe('User Management', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/users');
});
test('create user', async ({ page }) => {
await page.click('#create-user');
// ... test implementation
});
test('delete user', async ({ page }) => {
await page.click('.user-item .delete');
// ... test implementation
});
});
// Conditional tests
test.skip(process.platform === 'win32', 'skip on Windows', async ({ page }) => {
// ... test implementation
});Built-in and custom fixture system for test isolation and resource management.
/**
* Test-scoped fixtures (new instance per test)
*/
interface PlaywrightTestArgs {
/** Page instance for this test */
page: Page;
/** Browser context for this test */
context: BrowserContext;
}
/**
* Worker-scoped fixtures (shared across tests in worker)
*/
interface PlaywrightWorkerArgs {
/** Browser instance for this worker */
browser: Browser;
/** Browser name */
browserName: string;
/** Playwright instance */
playwright: typeof import('playwright-core');
}
/**
* Test configuration options
*/
interface PlaywrightTestOptions {
/** Action timeout */
actionTimeout?: number;
/** Navigation timeout */
navigationTimeout?: number;
/** Test timeout */
testTimeout?: number;
/** Expect timeout */
expectTimeout?: number;
/** Screenshot mode */
screenshot?: ScreenshotMode;
/** Video recording */
video?: VideoMode;
/** Trace recording */
trace?: TraceMode;
}
/**
* Worker configuration options
*/
interface PlaywrightWorkerOptions {
/** Browser to use */
browserName?: 'chromium' | 'firefox' | 'webkit';
/** Browser launch options */
launchOptions?: LaunchOptions;
/** Context options */
contextOptions?: BrowserContextOptions;
/** Headless mode */
headless?: boolean;
/** Browser channel */
channel?: string;
}
type ScreenshotMode = 'off' | 'on' | 'only-on-failure' | 'on-first-failure';
type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry';
type TraceMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'retain-on-first-failure';
/**
* Custom fixture definition
*/
type TestFixture<R, Args extends {}> = (args: Args, use: (r: R) => Promise<void>, testInfo: TestInfo) => any;
type WorkerFixture<R, Args extends {}> = (args: Args, use: (r: R) => Promise<void>, workerInfo: WorkerInfo) => any;
type Fixtures<T extends {} = {}, W extends {} = {}, PT extends {} = {}, PW extends {} = {}> = {
[K in keyof T]: TestFixture<T[K], PT> | [TestFixture<T[K], PT>, { scope?: 'test' }];
} & {
[K in keyof W]: WorkerFixture<W[K], PW> | [WorkerFixture<W[K], PW>, { scope: 'worker' }];
};Usage Examples:
// Use built-in fixtures
test('test with page', async ({ page, context, browser }) => {
console.log('Browser:', browser.version());
console.log('Context pages:', context.pages().length);
await page.goto('https://example.com');
});
// Custom fixtures
const myTest = test.extend<{ todoPage: TodoPage }>({
todoPage: async ({ page }, use) => {
const todoPage = new TodoPage(page);
await todoPage.goto();
await use(todoPage);
await todoPage.cleanup();
}
});
myTest('test with custom fixture', async ({ todoPage }) => {
await todoPage.addItem('Buy milk');
await expect(todoPage.items).toHaveCount(1);
});
// Worker-scoped fixture
const testWithDB = test.extend<{}, { db: Database }>({
db: [async ({}, use) => {
const db = await Database.connect();
await use(db);
await db.close();
}, { scope: 'worker' }]
});
testWithDB('test with database', async ({ page, db }) => {
const user = await db.createUser();
await page.goto(`/users/${user.id}`);
// ... test implementation
});Configuration management for test projects, environments, and execution options.
/**
* Define type-safe test configuration
*/
function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig;
function defineConfig<T>(config: PlaywrightTestConfig<T>): PlaywrightTestConfig<T>;
function defineConfig<T, W>(config: PlaywrightTestConfig<T, W>): PlaywrightTestConfig<T, W>;
interface PlaywrightTestConfig<TestArgs = {}, WorkerArgs = {}> {
/** Test directory */
testDir?: string;
/** Test files pattern */
testMatch?: string | RegExp | (string | RegExp)[];
/** Test ignore pattern */
testIgnore?: string | RegExp | (string | RegExp)[];
/** Global timeout */
globalTimeout?: number;
/** Test timeout */
timeout?: number;
/** Expect timeout */
expect?: { timeout?: number };
/** Output directory */
outputDir?: string;
/** Forbid-only in CI */
forbidOnly?: boolean;
/** Fully parallel */
fullyParallel?: boolean;
/** Global setup */
globalSetup?: string;
/** Global teardown */
globalTeardown?: string;
/** Maximum failures */
maxFailures?: number;
/** Preserve output */
preserveOutput?: 'always' | 'never' | 'failures-only';
/** Reporter configuration */
reporter?: ReporterDescription | ReporterDescription[];
/** Retry configuration */
retries?: number | { mode?: 'always' | 'only-on-failure' };
/** Shard configuration */
shard?: { total: number; current: number };
/** Update snapshots */
updateSnapshots?: 'all' | 'none' | 'missing';
/** Worker configuration */
workers?: number | string;
/** Use shared options */
use?: PlaywrightTestOptions & PlaywrightWorkerOptions & TestArgs & WorkerArgs;
/** Project configuration */
projects?: PlaywrightTestProject<TestArgs, WorkerArgs>[];
/** Dependencies between projects */
dependencies?: string[];
}
type PlaywrightTestProject<TestArgs = {}, WorkerArgs = {}> = {
/** Project name */
name?: string;
/** Test directory for this project */
testDir?: string;
/** Test files pattern */
testMatch?: string | RegExp | (string | RegExp)[];
/** Test ignore pattern */
testIgnore?: string | RegExp | (string | RegExp)[];
/** Output directory */
outputDir?: string;
/** Retry configuration */
retries?: number;
/** Project dependencies */
dependencies?: string[];
/** Use options */
use?: PlaywrightTestOptions & PlaywrightWorkerOptions & TestArgs & WorkerArgs;
};
type ReporterDescription =
| ['blob'] | ['blob', BlobReporterOptions]
| ['dot']
| ['line']
| ['list'] | ['list', ListReporterOptions]
| ['github']
| ['junit'] | ['junit', JUnitReporterOptions]
| ['json'] | ['json', JsonReporterOptions]
| ['html'] | ['html', HtmlReporterOptions]
| ['null']
| [string] | [string, any];Usage Examples:
// playwright.config.ts
import { defineConfig, devices } from 'playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html'],
['junit', { outputFile: 'results.xml' }]
],
use: {
baseURL: 'http://127.0.0.1:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure'
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
}
],
webServer: {
command: 'npm run start',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});Access test execution information and control test flow.
interface TestInfo {
/** Test title */
title: string;
/** Test file path */
file: string;
/** Test line number */
line: number;
/** Test column number */
column: number;
/** Test function */
fn: Function;
/** Repeat each index */
repeatEachIndex: number;
/** Retry number */
retry: number;
/** Worker index */
workerIndex: number;
/** Parallel index */
parallelIndex: number;
/** Project configuration */
project: Project;
/** Test configuration */
config: Config;
/** Expected status */
expectedStatus: TestStatus;
/** Test timeout */
timeout: number;
/** Test annotations */
annotations: TestAnnotation[];
/** Test attachments */
attachments: TestAttachment[];
/** Set test timeout */
setTimeout(timeout: number): void;
/** Skip test */
skip(condition?: boolean, description?: string): void;
/** Mark test as failing */
fail(condition?: boolean, description?: string): void;
/** Mark test as slow */
slow(condition?: boolean, description?: string): void;
/** Mark test as fixme */
fixme(condition?: boolean, description?: string): void;
/** Add attachment */
attach(name: string, options: { path?: string; body?: string | Buffer; contentType?: string }): Promise<void>;
/** Get output directory */
outputDir(): string;
/** Get output path */
outputPath(...pathSegments: string[]): string;
/** Get snapshot path */
snapshotPath(...pathSegments: string[]): string;
}
interface WorkerInfo {
/** Worker index */
workerIndex: number;
/** Parallel index */
parallelIndex: number;
/** Project configuration */
project: Project;
/** Test configuration */
config: Config;
}
type TestStatus = 'passed' | 'failed' | 'timedOut' | 'skipped' | 'interrupted';
interface TestAnnotation {
type: string;
description?: string;
}
interface TestAttachment {
name: string;
contentType: string;
path?: string;
body?: Buffer;
}Usage Examples:
test('test with info', async ({ page }, testInfo) => {
console.log('Test:', testInfo.title);
console.log('Retry:', testInfo.retry);
console.log('Worker:', testInfo.workerIndex);
// Conditional skip
testInfo.skip(process.platform === 'darwin', 'Not supported on macOS');
// Set timeout
testInfo.setTimeout(60000);
// Add attachment
await page.screenshot({ path: 'screenshot.png' });
await testInfo.attach('screenshot', { path: 'screenshot.png', contentType: 'image/png' });
// Add text attachment
await testInfo.attach('debug-log', {
body: JSON.stringify({ url: page.url(), title: await page.title() }),
contentType: 'application/json'
});
});Extended assertion library with Playwright-specific matchers.
interface Expect<ExtendedMatchers = {}> {
/** Create assertion */
<T>(actual: T): PlaywrightAssertions<T> & ExtendedMatchers;
/** Configure expect */
configure(options: { timeout?: number; soft?: boolean }): Expect<ExtendedMatchers>;
/** Extend with custom matchers */
extend<T>(matchers: T): Expect<ExtendedMatchers & T>;
/** Soft assertions */
soft<T>(actual: T): PlaywrightAssertions<T> & ExtendedMatchers;
/** Poll assertion */
poll<T>(callback: () => T | Promise<T>, options?: { timeout?: number; intervals?: number[] }): PlaywrightAssertions<T> & ExtendedMatchers;
}
interface PlaywrightAssertions<T> {
/** Standard Jest matchers */
toBe(expected: T): Promise<void>;
toEqual(expected: T): Promise<void>;
toBeCloseTo(expected: number, precision?: number): Promise<void>;
toBeDefined(): Promise<void>;
toBeFalsy(): Promise<void>;
toBeGreaterThan(expected: number): Promise<void>;
toBeInstanceOf(expected: any): Promise<void>;
toBeNull(): Promise<void>;
toBeTruthy(): Promise<void>;
toBeUndefined(): Promise<void>;
toContain(expected: any): Promise<void>;
toContainEqual(expected: any): Promise<void>;
toHaveLength(expected: number): Promise<void>;
toMatch(expected: string | RegExp): Promise<void>;
toMatchObject(expected: any): Promise<void>;
toThrow(expected?: string | RegExp | Error): Promise<void>;
/** Playwright-specific matchers for Page */
toHaveTitle(expected: string | RegExp): Promise<void>;
toHaveURL(expected: string | RegExp): Promise<void>;
/** Playwright-specific matchers for Locator */
toBeAttached(): Promise<void>;
toBeChecked(): Promise<void>;
toBeDisabled(): Promise<void>;
toBeEditable(): Promise<void>;
toBeEmpty(): Promise<void>;
toBeEnabled(): Promise<void>;
toBeFocused(): Promise<void>;
toBeHidden(): Promise<void>;
toBeInViewport(): Promise<void>;
toBeVisible(): Promise<void>;
toContainText(expected: string | RegExp | (string | RegExp)[]): Promise<void>;
toHaveAccessibleDescription(expected: string | RegExp): Promise<void>;
toHaveAccessibleName(expected: string | RegExp): Promise<void>;
toHaveAttribute(name: string, value?: string | RegExp): Promise<void>;
toHaveClass(expected: string | RegExp | (string | RegExp)[]): Promise<void>;
toHaveCount(count: number): Promise<void>;
toHaveCSS(name: string, value: string | RegExp): Promise<void>;
toHaveId(id: string | RegExp): Promise<void>;
toHaveJSProperty(name: string, value: any): Promise<void>;
toHaveRole(role: string): Promise<void>;
toHaveScreenshot(options?: LocatorScreenshotOptions): Promise<void>;
toHaveText(expected: string | RegExp | (string | RegExp)[]): Promise<void>;
toHaveValue(expected: string | RegExp): Promise<void>;
toHaveValues(expected: (string | RegExp)[]): Promise<void>;
}Usage Examples:
import { test, expect } from 'playwright/test';
test('assertions examples', async ({ page }) => {
await page.goto('https://example.com');
// Page assertions
await expect(page).toHaveTitle(/Example/);
await expect(page).toHaveURL('https://example.com/');
// Locator assertions
const heading = page.locator('h1');
await expect(heading).toBeVisible();
await expect(heading).toHaveText('Welcome');
await expect(heading).toHaveClass('main-heading');
// Form assertions
const input = page.locator('#email');
await expect(input).toBeEnabled();
await expect(input).toBeEmpty();
await input.fill('test@example.com');
await expect(input).toHaveValue('test@example.com');
// Count assertions
const items = page.locator('.item');
await expect(items).toHaveCount(5);
// Soft assertions (don't stop test on failure)
await expect.soft(page.locator('#optional')).toBeVisible();
await expect.soft(page.locator('#another')).toHaveText('Expected');
// Poll assertions (wait for condition)
await expect.poll(async () => {
const response = await page.request.get('/api/status');
return response.status();
}).toBe(200);
});type BlobReporterOptions = {
outputDir?: string;
fileName?: string;
};
type ListReporterOptions = {
printSteps?: boolean;
};
type JUnitReporterOptions = {
outputFile?: string;
stripANSIControlSequences?: boolean;
includeProjectInTestName?: boolean;
};
type JsonReporterOptions = {
outputFile?: string;
};
type HtmlReporterOptions = {
outputFolder?: string;
open?: 'always' | 'never' | 'on-failure';
host?: string;
port?: number;
attachmentsBaseURL?: string;
title?: string;
noSnippets?: boolean;
};/**
* Merge multiple test types into one
*/
function mergeTests<List extends any[]>(...tests: List): MergedTestType<List>;
/**
* Merge multiple expect objects into one
*/
function mergeExpects<List extends any[]>(...expects: List): MergedExpect<List>;
type MergedTestType<List extends any[]> = TestType<
UnionToIntersection<List[number] extends TestType<infer T, any> ? T : {}>,
UnionToIntersection<List[number] extends TestType<any, infer W> ? W : {}>
>;
type MergedExpect<List extends any[]> = Expect<
UnionToIntersection<List[number] extends Expect<infer M> ? M : {}>
>;Install with Tessl CLI
npx tessl i tessl/npm-playwright