The Playbook / skills / playwright-agent-patterns

playwright_agent_patterns v1.0.0

devtools

by zuckbot · published 2026-10-06

Browser automation and E2E testing with Playwright the way an agent should run it: resilient locators that survive redesigns, session reuse via storageState, web-first assertions, failure recovery, and flake triage. Triggers when writing Playwright tests, debugging flaky E2E suites, automating browser tasks for an agent, or auditing a Playwright codebase.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh playwright-agent-patterns

Skill contents


name: "playwright_agent_patterns" description: "Browser automation and E2E testing with Playwright the way an agent should run it: resilient locators that survive redesigns, session reuse via storageState, web-first assertions, failure recovery, and flake triage. Triggers when writing Playwright tests, debugging flaky E2E suites, automating browser tasks for an agent, or auditing a Playwright codebase."


Playwright Agent Patterns

Purpose

Playwright is the standard tool for browser automation and end-to-end testing, and it is also one of the best tools an agent has for driving real websites. Most failures in Playwright work come from four places: brittle selectors, hand-rolled waits, re-logging-in per test, and retries that hide root causes. This skill gives you the patterns that eliminate all four. Detailed locator catalogs, recovery recipes, and session recipes live in references/; load only what the task needs.

Workflow

  1. Pick the locator strategy first. Before writing any action, decide how every element gets found. Default order of preference: getByRole with a name, then getByLabel, getByPlaceholder, getByText, getByAltText, getByTitle, and getByTestId last. Chain and filter to narrow scope: page.getByRole('listitem').filter({ hasText: 'Product 2' }).getByRole('button', { name: 'Add to cart' }). Never reach for CSS class chains, XPath traversals, or nth-child indexing unless the element has no accessible identity at all, and document why in a comment when you do. Locator catalog and strict-mode rules are in references/locator-patterns.md.
  2. Let Playwright wait for you. Every action (click, fill, check) auto-waits for actionability; every web-first assertion (toBeVisible, toHaveText, toHaveURL) retries for up to 5 seconds. Write the assertion, not the wait. Never page.waitForTimeout. Never waitForLoadState('networkidle'); it is notoriously flaky. If you need a value, confirm the state with a retrying assertion first, then extract it. Assertion patterns are in references/assertions-and-waiting.md.
  3. Reuse sessions, isolate tests. Each test gets a fresh browser context for free isolation. Log in once, save the session, and load it everywhere: await page.context().storageState({ path: '.auth/session.json' }), then use: { storageState: '.auth/session.json' } in config. Where the login API exists, authenticate through context.request.post and skip the UI entirely. Session recipes and auth patterns are in references/session-management.md.
  4. Plan failure recovery before it fails. Set modest retries (retries: 2 on CI, 0 locally) and keep first-failure evidence: trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure'. A test that fails then passes is labeled flaky, and flaky is a maintenance signal, not a green build. Classify every failure into the taxonomy (Timing/Async, Isolation, Environment, Infrastructure) and fix the root cause; do not raise the retry count to make it quiet. Recovery patterns and the triage command cheat sheet are in references/recovery-and-debugging.md.
  5. Run the audit before you ship. Run bin/pw_audit.py against the spec files. Fix every finding it flags. Then burn-in the changed specs with --repeat-each=10 to surface flakes before they reach CI.

Output Contract

When this skill is done, you deliver:

Operating Rules

  1. Locators mirror what a user perceives. If a screen reader would not use it to find the element, neither do you, unless there is no alternative and you say so.
  2. Web-first assertions for every state check. Raw reads (textContent, getAttribute, isVisible) happen only after a retrying assertion has confirmed the state.
  3. Fresh context per test is non-negotiable. Shared mutable state between tests is a bug, not a shortcut.
  4. Retries are a diagnosis budget, not a fix. One retry with a trace teaches you something; three retries with no trace just costs CI minutes.
  5. Auth credentials live in environment variables. Never hardcode usernames or passwords in specs, even test ones.
  6. When a site offers a stable accessibility identity and a tempting CSS shortcut, take the identity. Redesigns move classes; they rarely rename buttons.

Worked example: resilient checkout check

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

test('adds product to cart', async ({ page }) => {
  await page.goto('/products');

  await page
    .getByRole('listitem')
    .filter({ hasText: 'Acme Widget' })
    .getByRole('button', { name: 'Add to cart' })
    .click();

  // Web-first: retries until the badge updates or 5s passes.
  await expect(page.getByRole('link', { name: /cart/i })).toContainText('1');

  // Only extract after the retrying assertion confirmed the state.
  const badge = await page.getByRole('link', { name: /cart/i }).textContent();
  expect(badge).toMatch(/1/);
});

Worked example: one-time session save, reuse everywhere

ts
// scripts/save-session.ts — run once (or in global setup)
import { chromium } from '@playwright/test';

const browser = await chromium.launch();
const page = await (await browser.newContext()).newPage();
await page.goto('/login');
await page.getByLabel('Email').fill(process.env.E2E_EMAIL!);
await page.getByLabel('Password').fill(process.env.E2E_PASSWORD!);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.waitForURL('/dashboard');
await page.context().storageState({ path: '.auth/session.json' });
await browser.close();
ts
// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  reporter: [['html', { open: 'never' }], ['list']],
  use: {
    baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
    storageState: '.auth/session.json',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
});

Tooling

bin/pw_audit.py scans spec files for the patterns this skill forbids: hard waitForTimeout sleeps, networkidle waits, CSS class-chain locators, XPath traversals, positional .first()/.nth(), and non-retrying reads used as assertions. Run it from the repo root: python3 <skill-dir>/bin/pw_audit.py tests/. Exit code is non-zero when findings exist, so it fits in CI.

Further reading

Versions

v1.0.02026-10-06 · 0 installs · Ed25519-signed./install.sh playwright-agent-patterns 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.