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
Screenshot comparison, video recording, tracing, debugging tools, and accessibility testing for comprehensive test development and maintenance.
Capture and compare screenshots for visual regression testing.
interface Page {
/** Take page screenshot */
screenshot(options?: PageScreenshotOptions): Promise<Buffer>;
}
interface Locator {
/** Take element screenshot */
screenshot(options?: LocatorScreenshotOptions): Promise<Buffer>;
}
interface PageScreenshotOptions {
/** Screenshot file path */
path?: string;
/** Screenshot type */
type?: 'png' | 'jpeg';
/** Image quality (0-100, JPEG only) */
quality?: number;
/** Omit default background */
omitBackground?: boolean;
/** Full page screenshot */
fullPage?: boolean;
/** Clipping rectangle */
clip?: { x: number; y: number; width: number; height: number };
/** Screenshot animations */
animations?: 'disabled' | 'allow';
/** Screenshot caret */
caret?: 'hide' | 'initial';
/** Screenshot scale */
scale?: 'css' | 'device';
/** Screenshot mask */
mask?: Locator[];
/** Screenshot timeout */
timeout?: number;
}
interface LocatorScreenshotOptions {
/** Screenshot file path */
path?: string;
/** Screenshot type */
type?: 'png' | 'jpeg';
/** Image quality (0-100, JPEG only) */
quality?: number;
/** Omit default background */
omitBackground?: boolean;
/** Screenshot animations */
animations?: 'disabled' | 'allow';
/** Screenshot caret */
caret?: 'hide' | 'initial';
/** Screenshot scale */
scale?: 'css' | 'device';
/** Screenshot mask */
mask?: Locator[];
/** Screenshot timeout */
timeout?: number;
}Usage Examples:
// Basic page screenshot
await page.screenshot({ path: 'page.png' });
// Full page screenshot
await page.screenshot({
path: 'full-page.png',
fullPage: true
});
// Element screenshot
const element = page.locator('.hero-section');
await element.screenshot({ path: 'hero.png' });
// Screenshot with clipping
await page.screenshot({
path: 'clipped.png',
clip: { x: 0, y: 0, width: 800, height: 600 }
});
// Screenshot with masks (hide dynamic content)
await page.screenshot({
path: 'masked.png',
mask: [
page.locator('.timestamp'),
page.locator('.user-avatar')
]
});
// High quality JPEG
await page.screenshot({
path: 'page.jpg',
type: 'jpeg',
quality: 90
});
// Screenshot without animations
await page.screenshot({
path: 'static.png',
animations: 'disabled'
});Built-in visual comparison assertions for screenshot testing.
interface PlaywrightAssertions<T> {
/** Page screenshot assertion */
toHaveScreenshot(options?: PageAssertionsToHaveScreenshotOptions): Promise<void>;
toHaveScreenshot(name?: string, options?: PageAssertionsToHaveScreenshotOptions): Promise<void>;
/** Locator screenshot assertion */
toHaveScreenshot(options?: LocatorAssertionsToHaveScreenshotOptions): Promise<void>;
toHaveScreenshot(name?: string, options?: LocatorAssertionsToHaveScreenshotOptions): Promise<void>;
}
interface PageAssertionsToHaveScreenshotOptions {
/** Screenshot animations */
animations?: 'disabled' | 'allow';
/** Screenshot caret */
caret?: 'hide' | 'initial';
/** Clipping rectangle */
clip?: { x: number; y: number; width: number; height: number };
/** Full page screenshot */
fullPage?: boolean;
/** Screenshot mask */
mask?: Locator[];
/** Screenshot mode */
mode?: 'light' | 'dark';
/** Omit background */
omitBackground?: boolean;
/** Screenshot scale */
scale?: 'css' | 'device';
/** Comparison threshold */
threshold?: number;
/** Screenshot timeout */
timeout?: number;
/** Update snapshots */
updateSnapshots?: 'all' | 'none' | 'missing';
}
interface LocatorAssertionsToHaveScreenshotOptions {
/** Screenshot animations */
animations?: 'disabled' | 'allow';
/** Screenshot caret */
caret?: 'hide' | 'initial';
/** Screenshot mask */
mask?: Locator[];
/** Omit background */
omitBackground?: boolean;
/** Screenshot scale */
scale?: 'css' | 'device';
/** Comparison threshold */
threshold?: number;
/** Screenshot timeout */
timeout?: number;
/** Update snapshots */
updateSnapshots?: 'all' | 'none' | 'missing';
}Usage Examples:
import { test, expect } from 'playwright/test';
test('visual regression test', async ({ page }) => {
await page.goto('https://example.com');
// Page screenshot assertion
await expect(page).toHaveScreenshot();
await expect(page).toHaveScreenshot('homepage.png');
// Element screenshot assertion
const header = page.locator('header');
await expect(header).toHaveScreenshot('header.png');
// Full page with threshold
await expect(page).toHaveScreenshot('full-page.png', {
fullPage: true,
threshold: 0.1 // Allow 10% difference
});
// Dark mode screenshot
await page.emulateMedia({ colorScheme: 'dark' });
await expect(page).toHaveScreenshot('dark-mode.png', {
mode: 'dark'
});
// Masked screenshot (hide dynamic content)
await expect(page).toHaveScreenshot('masked.png', {
mask: [
page.locator('.timestamp'),
page.locator('.live-counter')
]
});
});Record browser sessions as video files for debugging and documentation.
interface BrowserContextOptions {
/** Video recording options */
recordVideo?: {
/** Video output directory */
dir: string;
/** Video dimensions */
size?: ViewportSize;
/** Video mode */
mode?: 'off' | 'on' | 'retain-on-failure' | 'on-first-retry';
};
}
interface Page {
/** Get video recording */
video(): Video | null;
}
interface Video {
/** Get video file path */
path(): Promise<string>;
/** Delete video file */
delete(): Promise<void>;
/** Save video to path */
saveAs(path: string): Promise<void>;
}Usage Examples:
// Enable video recording for context
const context = await browser.newContext({
recordVideo: {
dir: './videos/',
size: { width: 1280, height: 720 }
}
});
const page = await context.newPage();
await page.goto('https://example.com');
await page.click('button');
// Get video path
const video = page.video();
if (video) {
const videoPath = await video.path();
console.log('Video saved to:', videoPath);
// Save video with custom name
await video.saveAs('./custom-video.webm');
}
await context.close();
// Video recording in test configuration
// playwright.config.ts
export default defineConfig({
use: {
video: 'retain-on-failure', // Only save videos for failed tests
}
});Record detailed execution traces for debugging and performance analysis.
interface BrowserContext {
/** Start tracing */
tracing: Tracing;
}
interface Tracing {
/** Start trace recording */
start(options?: TracingStartOptions): Promise<void>;
/** Start trace chunk */
startChunk(options?: TracingStartChunkOptions): Promise<void>;
/** Stop trace chunk */
stopChunk(options?: TracingStopChunkOptions): Promise<void>;
/** Stop trace recording */
stop(options?: TracingStopOptions): Promise<void>;
}
interface TracingStartOptions {
/** Trace file name */
name?: string;
/** Trace title */
title?: string;
/** Trace screenshots */
screenshots?: boolean;
/** Trace snapshots */
snapshots?: boolean;
/** Trace sources */
sources?: boolean;
}
interface TracingStopOptions {
/** Trace output path */
path?: string;
}
interface TracingStartChunkOptions {
/** Trace title */
title?: string;
}
interface TracingStopChunkOptions {
/** Trace output path */
path?: string;
}Usage Examples:
// Start tracing
await context.tracing.start({
screenshots: true,
snapshots: true,
sources: true
});
// Perform actions
await page.goto('https://example.com');
await page.click('button');
await page.fill('input', 'test');
// Stop tracing and save
await context.tracing.stop({
path: 'trace.zip'
});
// Chunked tracing for long tests
await context.tracing.start();
// First chunk
await context.tracing.startChunk({ title: 'Login' });
await doLogin(page);
await context.tracing.stopChunk({ path: 'login-trace.zip' });
// Second chunk
await context.tracing.startChunk({ title: 'Navigation' });
await navigateApp(page);
await context.tracing.stopChunk({ path: 'navigation-trace.zip' });
await context.tracing.stop();
// Tracing in tests
test('with tracing', async ({ page, context }) => {
await context.tracing.start({ screenshots: true });
// Test actions
await page.goto('/app');
await page.click('#start');
// Auto-save trace on failure via test configuration
});Browser developer tools integration and debugging utilities.
interface Page {
/** Pause execution and open debugger */
pause(): Promise<void>;
/** Wait for debugger to attach */
waitForDebugger(): Promise<void>;
/** Bring page to front */
bringToFront(): Promise<void>;
}
interface BrowserContext {
/** Create CDP session */
newCDPSession(page: Page): Promise<CDPSession>;
}
interface CDPSession {
/** Send CDP command */
send(method: string, params?: any): Promise<any>;
/** Detach from target */
detach(): Promise<void>;
}
interface LaunchOptions {
/** Open browser devtools */
devtools?: boolean;
/** Run in slow motion */
slowMo?: number;
}Usage Examples:
// Launch with devtools open
const browser = await chromium.launch({
headless: false,
devtools: true
});
// Slow motion for debugging
const browserSlow = await chromium.launch({
headless: false,
slowMo: 1000 // 1 second delay between actions
});
// Pause execution for debugging
test('debug test', async ({ page }) => {
await page.goto('https://example.com');
// Pause here - opens browser with debugger
await page.pause();
await page.click('button');
});
// CDP access for advanced debugging
const cdpSession = await context.newCDPSession(page);
// Enable runtime events
await cdpSession.send('Runtime.enable');
// Set breakpoint
await cdpSession.send('Debugger.enable');
await cdpSession.send('Debugger.setBreakpointByUrl', {
lineNumber: 10,
url: 'https://example.com/script.js'
});
// Get performance metrics
const metrics = await cdpSession.send('Performance.getMetrics');
console.log('Performance metrics:', metrics);Built-in accessibility testing and reporting capabilities.
interface Page {
/** Get accessibility tree */
accessibility: Accessibility;
}
interface Accessibility {
/** Get accessibility snapshot */
snapshot(options?: AccessibilitySnapshotOptions): Promise<AccessibilityNode | null>;
}
interface AccessibilitySnapshotOptions {
/** Include interesting nodes only */
interestingOnly?: boolean;
/** Root element */
root?: ElementHandle;
}
interface AccessibilityNode {
/** Node role */
role?: string;
/** Node name */
name?: string;
/** Node value */
value?: string | number;
/** Node description */
description?: string;
/** Keyboard shortcut */
keyshortcuts?: string;
/** Role description */
roledescription?: string;
/** Node orientation */
orientation?: string;
/** Auto-complete attribute */
autocomplete?: string;
/** Has popup */
haspopup?: string;
/** Hierarchical level */
level?: number;
/** Disabled state */
disabled?: boolean;
/** Expanded state */
expanded?: boolean;
/** Focused state */
focused?: boolean;
/** Modal state */
modal?: boolean;
/** Multiline state */
multiline?: boolean;
/** Multiselectable state */
multiselectable?: boolean;
/** Readonly state */
readonly?: boolean;
/** Required state */
required?: boolean;
/** Selected state */
selected?: boolean;
/** Checked state */
checked?: boolean | 'mixed';
/** Pressed state */
pressed?: boolean | 'mixed';
/** Current value */
valuemin?: number;
/** Maximum value */
valuemax?: number;
/** Current value */
valuenow?: number;
/** Value text */
valuetext?: string;
/** Child nodes */
children?: AccessibilityNode[];
}Usage Examples:
// Get accessibility snapshot
test('accessibility test', async ({ page }) => {
await page.goto('https://example.com');
// Full accessibility tree
const snapshot = await page.accessibility.snapshot();
console.log('Accessibility tree:', JSON.stringify(snapshot, null, 2));
// Interesting nodes only
const interestingNodes = await page.accessibility.snapshot({
interestingOnly: true
});
// Check specific element accessibility
const button = page.locator('button');
const buttonSnapshot = await page.accessibility.snapshot({
root: await button.elementHandle()
});
// Verify accessibility properties
expect(buttonSnapshot?.role).toBe('button');
expect(buttonSnapshot?.name).toBeTruthy();
// Check for accessibility violations
const violations = findA11yViolations(snapshot);
expect(violations).toHaveLength(0);
});
// Custom accessibility checker
function findA11yViolations(node: AccessibilityNode | null): string[] {
const violations: string[] = [];
if (!node) return violations;
// Check for missing alt text on images
if (node.role === 'img' && !node.name) {
violations.push('Image missing alt text');
}
// Check for missing form labels
if (node.role === 'textbox' && !node.name) {
violations.push('Input missing label');
}
// Check for insufficient color contrast
if (node.role === 'button' && !node.name) {
violations.push('Button missing accessible name');
}
// Recursively check children
if (node.children) {
for (const child of node.children) {
violations.push(...findA11yViolations(child));
}
}
return violations;
}Monitor browser console messages, JavaScript errors, and page events.
interface Page {
/** Listen for console messages */
on(event: 'console', listener: (message: ConsoleMessage) => void): void;
/** Listen for page errors */
on(event: 'pageerror', listener: (error: Error) => void): void;
/** Listen for dialog events */
on(event: 'dialog', listener: (dialog: Dialog) => void): void;
/** Listen for download events */
on(event: 'download', listener: (download: Download) => void): void;
/** Listen for file chooser events */
on(event: 'filechooser', listener: (fileChooser: FileChooser) => void): void;
}
interface ConsoleMessage {
/** Message type */
type(): 'log' | 'debug' | 'info' | 'error' | 'warning' | 'dir' | 'dirxml' | 'table' | 'trace' | 'clear' | 'startGroup' | 'startGroupCollapsed' | 'endGroup' | 'assert' | 'profile' | 'profileEnd' | 'count' | 'timeEnd';
/** Message text */
text(): string;
/** Message arguments */
args(): JSHandle[];
/** Message location */
location(): { url: string; lineNumber: number; columnNumber: number };
/** Get page */
page(): Page;
}
interface Dialog {
/** Dialog message */
message(): string;
/** Dialog type */
type(): 'alert' | 'beforeunload' | 'confirm' | 'prompt';
/** Default prompt value */
defaultValue(): string;
/** Accept dialog */
accept(promptText?: string): Promise<void>;
/** Dismiss dialog */
dismiss(): Promise<void>;
/** Get page */
page(): Page;
}Usage Examples:
// Monitor console messages
const consoleMessages: ConsoleMessage[] = [];
page.on('console', message => {
consoleMessages.push(message);
console.log(`Console ${message.type()}: ${message.text()}`);
});
// Monitor JavaScript errors
const pageErrors: Error[] = [];
page.on('pageerror', error => {
pageErrors.push(error);
console.error('Page error:', error.message);
});
// Handle dialogs automatically
page.on('dialog', async dialog => {
console.log('Dialog:', dialog.type(), dialog.message());
if (dialog.type() === 'confirm') {
await dialog.accept();
} else if (dialog.type() === 'prompt') {
await dialog.accept('test input');
} else {
await dialog.dismiss();
}
});
// Monitor downloads
page.on('download', async download => {
console.log('Download started:', download.suggestedFilename());
const path = await download.path();
console.log('Download completed:', path);
});
// Test error handling
test('error monitoring', async ({ page }) => {
const errors: Error[] = [];
page.on('pageerror', error => errors.push(error));
await page.goto('https://example.com');
await page.click('#trigger-error');
// Verify no JavaScript errors occurred
expect(errors).toHaveLength(0);
// Or verify specific error was caught
const errorMessages = errors.map(e => e.message);
expect(errorMessages).not.toContain('Uncaught TypeError');
});Control time and date for testing time-dependent functionality.
interface BrowserContext {
/** Get clock interface */
clock: Clock;
}
interface Clock {
/** Install fake timers */
install(options?: ClockInstallOptions): Promise<void>;
/** Fast forward time */
fastForward(ticksOrTime: number | string): Promise<void>;
/** Pause clock */
pauseAt(time: number | string | Date): Promise<void>;
/** Resume clock */
resume(): Promise<void>;
/** Set fixed time */
setFixedTime(time: number | string | Date): Promise<void>;
/** Set system time */
setSystemTime(time: number | string | Date): Promise<void>;
/** Restore real timers */
restore(): Promise<void>;
/** Run pending timers */
runFor(ticks: number | string): Promise<void>;
}
interface ClockInstallOptions {
/** Time to install fake timers at */
time?: number | string | Date;
/** Timer APIs to fake */
toFake?: ('setTimeout' | 'clearTimeout' | 'setInterval' | 'clearInterval' | 'Date' | 'performance')[];
}Usage Examples:
test('time-dependent test', async ({ page, context }) => {
// Install fake timers at specific time
await context.clock.install({ time: new Date('2024-01-01T00:00:00Z') });
await page.goto('/dashboard');
// Fast forward 5 minutes
await context.clock.fastForward('05:00');
// Check time-dependent UI updates
await expect(page.locator('.time-display')).toHaveText('00:05:00');
// Set fixed time
await context.clock.setFixedTime('2024-01-01T12:00:00Z');
await page.reload();
await expect(page.locator('.date-display')).toHaveText('Jan 1, 2024');
// Restore real timers
await context.clock.restore();
});interface ViewportSize {
width: number;
height: number;
}
interface WebError {
/** Error name */
name?: string;
/** Error message */
message?: string;
/** Error stack */
stack?: string;
}
interface Download {
/** Cancel download */
cancel(): Promise<void>;
/** Delete downloaded file */
delete(): Promise<void>;
/** Get download failure reason */
failure(): string | null;
/** Get download page */
page(): Page;
/** Get download path */
path(): Promise<string>;
/** Save download as */
saveAs(path: string): Promise<void>;
/** Get suggested filename */
suggestedFilename(): string;
/** Get download URL */
url(): string;
}
interface FileChooser {
/** Get associated element */
element(): ElementHandle;
/** Check if multiple files allowed */
isMultiple(): boolean;
/** Get page */
page(): Page;
/** Set files */
setFiles(files: string | string[] | { name: string; mimeType: string; buffer: Buffer } | { name: string; mimeType: string; buffer: Buffer }[], options?: FileChooserSetFilesOptions): Promise<void>;
}
interface FileChooserSetFilesOptions {
/** No wait after setting files */
noWaitAfter?: boolean;
/** Timeout */
timeout?: number;
}Install with Tessl CLI
npx tessl i tessl/npm-playwright