CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-playwright

A high-level API to automate web browsers and comprehensive framework for web testing

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

visual-debugging.mddocs/

Visual Testing & Debugging

Screenshot comparison, video recording, tracing, debugging tools, and accessibility testing for comprehensive test development and maintenance.

Capabilities

Screenshot Testing

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'
});

Visual Assertions

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')
    ]
  });
});

Video Recording

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
  }
});

Tracing & Timeline

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
});

Debug Tools & Inspector

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);

Accessibility Testing

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;
}

Console & Error Monitoring

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');
});

Types

Clock & Time Manipulation

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();
});

Visual Configuration Types

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

docs

browser-control.md

index.md

mobile-device.md

network-api.md

page-interaction.md

testing-framework.md

visual-debugging.md

tile.json