agentsclimarketplace

Playwright patterns

Skill Inteligentsensingsolutions/tdd-dev-workflow/skills/playwright-patterns

This skill should be used when the user asks about "Playwright API", "Playwright config", "playwright.config.ts", "Playwright fixtures", "Playwright selectors", "Playwright locators", "page.waitFor", "Playwright trace viewer", "Playwright debugging", "Playwright CI setup", "Playwright visual regression", "Playwright codegen", or needs Playwright-specific API patterns, configuration, and tooling. For general E2E test strategy (when to test, what to test, test structure principles), see e2e-testing instead. Provides Playwright implementation patterns.From its SKILL.md

Install
npx -y skills add Inteligentsensingsolutions/tdd-dev-workflow --skill playwright-patterns

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

12.5 KB, as published. Nobody here has run it

Playwright Patterns

Setup & Configuration

Define a single playwright.config.ts at the project root:

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

export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: process.env.CI ? 'github' : 'html',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'mobile', use: { ...devices['iPhone 14'] } },
  ],
  webServer: {
    command: 'pnpm dev',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
});

Key Configuration Decisions

  • fullyParallel: true -- run tests in parallel by default; isolate state per test.
  • forbidOnly: !!process.env.CI -- prevent .only() from leaking into CI.
  • retries: 2 in CI -- absorb transient failures while flagging persistent ones.
  • webServer -- auto-start the dev server; skip if already running locally.

Test Structure

Basic Test

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

test.describe('Feature Name', () => {
  test('should do X when Y', async ({ page }) => {
    await page.goto('/path');
    await page.getByRole('button', { name: 'Submit' }).click();
    await expect(page.getByText('Success')).toBeVisible();
  });
});

Naming Conventions

  • Group related tests with test.describe('Feature Name', ...).
  • Name tests as 'should [expected behavior] when [condition]'.
  • Mirror user stories: 'should display error when credentials are invalid'.

Selector Priority

Choose selectors in this order of preference:

PriorityMethodWhen to Use
1getByRoleButtons, links, headings, form elements with ARIA roles
2getByLabelForm inputs with associated labels
3getByTextStatic content, messages, headings without roles
4getByTestIdElements with explicit data-testid attributes
5CSS selectorsLast resort only; fragile and couples tests to markup
// Preferred
page.getByRole('button', { name: 'Submit' })
page.getByLabel('Email address')
page.getByText('Registration successful')
page.getByTestId('user-avatar')

// Avoid
page.locator('.btn-primary.mt-3')
page.locator('div > form > button:nth-child(2)')

Page Object Model

Encapsulate page interactions in classes. Tests read as user stories, and selector changes propagate from one place.

// e2e/pages/login.page.ts
import { type Page, type Locator } from '@playwright/test';

export class LoginPage {
  private readonly emailInput: Locator;
  private readonly passwordInput: Locator;
  private readonly submitButton: Locator;

  constructor(private page: Page) {
    this.emailInput = page.getByLabel('Email');
    this.passwordInput = page.getByLabel('Password');
    this.submitButton = page.getByRole('button', { name: 'Sign in' });
  }

  async goto() {
    await this.page.goto('/login');
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    await this.passwordInput.fill(password);
    await this.submitButton.click();
  }

  async getErrorMessage() {
    return this.page.getByRole('alert').textContent();
  }
}

Fixtures

Extend the base test with custom fixtures to inject page objects and shared state:

// e2e/fixtures.ts
import { test as base } from '@playwright/test';
import { LoginPage } from './pages/login.page';
import { DashboardPage } from './pages/dashboard.page';

type Fixtures = {
  loginPage: LoginPage;
  dashboardPage: DashboardPage;
};

export const test = base.extend<Fixtures>({
  loginPage: async ({ page }, use) => {
    await use(new LoginPage(page));
  },
  dashboardPage: async ({ page }, use) => {
    await use(new DashboardPage(page));
  },
});

export { expect } from '@playwright/test';

Use in tests:

import { test, expect } from './fixtures';

test('user can log in', async ({ loginPage, dashboardPage }) => {
  await loginPage.goto();
  await loginPage.login('[email protected]', 'password');
  await expect(dashboardPage.heading).toBeVisible();
});

Waiting & Assertions

Auto-Waiting

Playwright auto-waits for elements to be actionable before performing actions. Do not add manual waits before clicks, fills, or checks.

Assertion Retries

expect(locator) assertions retry automatically until timeout:

await expect(page.getByText('Welcome')).toBeVisible();
await expect(page.getByRole('list')).toHaveCount(5);
await expect(page).toHaveURL('/dashboard');
await expect(page).toHaveTitle(/Dashboard/);

Waiting for Network

Wait for specific API responses when tests depend on server data:

const responsePromise = page.waitForResponse(
  resp => resp.url().includes('/api/users') && resp.ok()
);
await page.getByRole('button', { name: 'Load' }).click();
await responsePromise;

Hard Rule

Never use page.waitForTimeout(). Always wait for a specific condition -- visible element, URL change, or network response.


Visual Regression

Capture and compare screenshots to detect unintended visual changes:

await expect(page).toHaveScreenshot('homepage.png', {
  maxDiffPixelRatio: 0.01,
});

await expect(page.getByTestId('chart')).toHaveScreenshot('chart.png', {
  maxDiffPixelRatio: 0.02,
});

Update baselines after intentional UI changes:

npx playwright test --update-snapshots

Store baseline images in version control under the test directory.


API Testing

Use Playwright's built-in request context for complementary API tests:

test('API returns user list', async ({ request }) => {
  const response = await request.get('/api/users');
  expect(response.ok()).toBeTruthy();

  const data = await response.json();
  expect(data.users).toHaveLength(3);
});

Combine UI and API testing in the same suite for full-stack validation.


Debugging

Interactive Debug Mode

npx playwright test --debug

Opens the Playwright Inspector with step-through controls and selector highlighting.

Trace Viewer

npx playwright test --trace on
npx playwright show-trace trace.zip

Traces capture DOM snapshots, network requests, and console logs for every action. Use trace: 'on-first-retry' in config to capture traces only for retried failures.

In-Test Breakpoints

await page.pause(); // Opens Inspector at this point

Headed Mode

npx playwright test --headed

Watch tests run in a visible browser window.

HTML Report

npx playwright show-report

Interactive report with screenshots, traces, and step-by-step execution details.


CI Integration

Install Dependencies

npx playwright install --with-deps

Install browsers and OS-level dependencies in CI. Run once, cache the result.

Headless Execution

Playwright runs headless by default. No configuration needed for CI.

Artifact Upload

Upload traces and screenshots on failure for post-mortem debugging:

# GitHub Actions example
- uses: actions/upload-artifact@v4
  if: failure()
  with:
    name: playwright-traces
    path: test-results/
    retention-days: 7

Test Sharding

Split tests across CI workers for faster pipelines:

npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3

Dev Server Management

Single Server

Use the webServer config to auto-start and auto-stop the dev server:

webServer: {
  command: 'pnpm dev',
  port: 3000,
  reuseExistingServer: !process.env.CI,
  timeout: 120_000,
},

Multiple Servers

For frontend + backend, use an array:

webServer: [
  { command: 'pnpm dev:api', port: 4000, reuseExistingServer: !process.env.CI },
  { command: 'pnpm dev:web', port: 3000, reuseExistingServer: !process.env.CI },
],

Playwright starts them in order and waits for each port to respond.


Network Interception

Mock API responses to test UI behavior independently of the backend:

await page.route('**/api/users', async (route) => {
  await route.fulfill({
    status: 200,
    contentType: 'application/json',
    body: JSON.stringify({ users: [{ id: 1, name: 'Mock User' }] }),
  });
});

Modify Responses

Intercept and modify real responses to test edge cases:

await page.route('**/api/config', async (route) => {
  const response = await route.fetch();
  const body = await response.json();
  body.featureFlags.newFeature = true;
  await route.fulfill({ response, body: JSON.stringify(body) });
});

Abort Requests

Block analytics, tracking, or other non-essential requests:

await page.route('**/analytics/**', (route) => route.abort());

Test Data Management

Isolation Principle

Each test must create its own data and clean up after itself. Never rely on data from other tests or a shared database state.

API-Based Seeding

Seed data via API calls in beforeEach for speed and reliability:

test.beforeEach(async ({ request }) => {
  await request.post('/api/test/seed', {
    data: { scenario: 'user-with-orders' },
  });
});

test.afterEach(async ({ request }) => {
  await request.post('/api/test/cleanup');
});

Storage State for Auth

Save and reuse authenticated browser state across tests to avoid repeated login:

// Save state after login
await page.context().storageState({ path: 'e2e/.auth/user.json' });

// Reuse in config
use: { storageState: 'e2e/.auth/user.json' }

Anti-Patterns

Anti-PatternWhy It FailsCorrect Approach
page.waitForTimeout(3000)Arbitrary; slows tests or causes flakesWait for specific conditions
Brittle CSS selectorsBreak on styling or markup changesUse getByRole, getByLabel, getByTestId
Testing third-party UIsOutside your control; breaks unpredictablyMock at the API boundary
Skipping webServer configManual server management; inconsistent CIUse webServer in config
Ignoring flaky testsErodes trust in the test suiteInvestigate root cause; use retries sparingly
Shared mutable state between testsTest order dependence; parallel failuresIsolate state per test
Screenshots without maxDiffPixelRatioFont rendering differences cause false failuresSet a small tolerance
Asserting internal JS stateCouples tests to implementation detailsAssert visible UI outcomes only

Quick Reference

ConcernApproach
SelectorsgetByRole > getByLabel > getByText > getByTestId
WaitsAuto-wait + expect().toBeVisible() + waitForResponse()
Page encapsulationPage Object Model classes
Reusable setupCustom fixtures via test.extend<>()
Visual testingtoHaveScreenshot() with maxDiffPixelRatio
Debugging--debug, --headed, page.pause(), trace viewer
CI--shard, headless, upload trace artifacts
ConfigSingle playwright.config.ts with webServer

References

  • For the complete Playwright API reference (selectors, actions, waiting, assertions, network, visual testing, mobile, debugging, CI/CD, accessibility), read references/api-reference.md.
  • For common Playwright recipes and advanced patterns, read references/common-recipes.md.

Dependencies

  • testing-patterns -- foundational test structure, factory functions, assertion patterns, and TDD workflow integration.
  • e2e-testing -- end-to-end testing principles, when to write E2E tests, and integration with the TDD development cycle.
  • typescript-pro (transitive via testing-patterns) -- type safety for page objects, fixtures, and test utilities.

Used By

None -- this is a top-layer integration skill.

Keep looking

Skills are one crate of 628,853. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.