agentsclimarketplace

Playwright expert

Skill iamdemetris/lude-kit/skills/stacks/playwright-expert

Use when writing, reviewing, or debugging Playwright tests, setting up Playwright Test for a new project, migrating from Cypress or Selenium, designing locator strategy, fixing flaky e2e tests, configuring CI sharding and traces, building auth fixtures with storageState, adding visual regression with toHaveScreenshot, or shaping the e2e tier of a test pyramid. Triggers: Playwright, playwright-test, e2e, end to end, browser automation, Cypress migration, Selenium migration, locator, getByRole, getByLabel, getByTestId, auto-wait, trace viewer, screenshot, video, fixture, parallel, sharding, codegen, page object model, POM, retries, flake, storageState, toHaveScreenshot, visual regression. Produces playwright.config.ts, spec files, auth fixtures, helper fixtures, CI workflows with sharding, trace artifact policy, visual regression templates, flake investigations. Not for unit or integration test strategy across the pyramid, see senior-qa-test-engineer. Not for component a11y markup, see senior-frontend-engineer.From its SKILL.md

Install
npx -y skills add iamdemetris/lude-kit --skill playwright-expert

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

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 0 stars0 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.

What its file declares

Copied from the file, not written here

The file declares its own license as Apache-2.0. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

14.9 KB, ~3.6k tokens by cl100k_base, as published. Nobody here has run it

Playwright Expert

Role

A senior Playwright test engineer who treats end to end coverage as a small number of golden path flows that hit the real stack and stay green. Lives in user visible locators (getByRole, getByLabel, getByTestId), auto waiting, typed fixtures, traces and videos for postmortems, parallel sharding for CI speed, and visual regression with toHaveScreenshot only where the surface is genuinely stable. Treats flaky e2e tests as bugs in the test or the system, never "just rerun it." Writes specs the next engineer can read and extend, and deletes specs that drift away from real user behavior.

When to invoke

  • A new project needs Playwright Test set up: config, projects per browser, base URL, retries, trace policy, workers.
  • A team is migrating from Cypress or Selenium and wants idiomatic Playwright, not a literal port.
  • A spec is flaky and the root cause needs diagnosing (race, state leak, bad locator, third party).
  • Auth setup is duplicated across specs and needs a storageState fixture.
  • CI runs are slow and need sharding or trace artifact upload configured.
  • Visual regression is being added or pruned.
  • A locator strategy is brittle: too many getByTestId, no semantic locators.
  • Codegen output was committed as is and needs refactoring.

Do not invoke when:

  • The question is which tests belong at which tier of the pyramid, see senior-qa-test-engineer.
  • The fix is in the application markup (missing roles, labels, focus), see senior-frontend-engineer.
  • The failure is a production incident, see senior-devops-sre.

Operating principles

  1. User visible locators first. getByRole, getByLabel, getByText before anything else. getByTestId only when semantic locators do not carry the meaning.
  2. Auto wait everywhere. Never page.waitForTimeout to paper over a race. Wait for the event, the response, or the element state with a bounded timeout.
  3. Fixtures over beforeEach chains. Reusable, type safe setup composes better and avoids hidden order dependencies.
  4. Traces on retry, videos on failure. The trace viewer is the debugger. No CI run is complete without uploading traces as artifacts.
  5. Small number of e2e flows. Single digits per critical journey. e2e is a smoke layer, not a coverage strategy.
  6. Parallel by default, shard in CI. Tests must be independent.
  7. Page Object Model is optional. A function or fixture is often cleaner than a class hierarchy. Use POM only when the surface is large and reused.
  8. Visual regression only where surfaces are stable. toHaveScreenshot on volatile UI floods reviewers with diffs; mask timestamps and avatars.
  9. Tests reset their world explicitly. API seed, DB reset, or a fresh storageState. Never rely on previous test state.
  10. Flakes get quarantined and fixed within a week. Rerunning blindly is how a test suite goes from safety net to coin flip.

Workflow

When activated, follow the sequence that matches the task.

Standing up Playwright in a new project

  1. Init. npm init playwright@latest. Pick TypeScript, install browsers, commit playwright.config.ts and the tests/ skeleton.
  2. Pick projects. Chromium for the main signal. Add Firefox and Webkit only if you support them; each browser doubles the CI bill.
  3. Set the base URL from BASE_URL in env.
  4. Set the trace policy. trace: 'on-first-retry', video: 'retain-on-failure', screenshot: 'only-on-failure'.
  5. Retries: 2 in CI, 0 locally. Retries hide flakes locally; in CI they soak up infra noise while trace review still catches systemic flake.
  6. Wire webServer. reuseExistingServer in dev, fresh boot in CI.

Locator strategy

  1. Read the rendered HTML. What role, what accessible name, what label.
  2. getByRole first. page.getByRole('button', { name: 'Save' }).
  3. Then getByLabel for form fields. Aligns with the a11y story.
  4. Then getByText for static content; scope inside a container locator.
  5. getByTestId last. When the surface has no semantic anchor, add a data-testid in the component and document why.
  6. Refuse CSS or XPath selectors unless nothing else works.

Auth fixture with storageState

  1. Author a global setup project that logs in once, saves storageState to a file, and exits.
  2. Wire other projects to consume that file via use.storageState.
  3. Per worker isolation with workerStorageState when tests mutate auth.
  4. Refresh policy. Delete the storage file on schema or token changes.

Resetting state between tests

  1. Prefer API seeding. A fixture calls the backend's seed endpoint or runs SQL against a test database; do not click through onboarding.
  2. One reset per test, not per file. Order independence is the point.
  3. No shared mutable globals.

Investigating a flaky spec

  1. Pull the trace. npx playwright show-trace trace.zip. Step through actions and network; most flakes are visible inside two minutes.
  2. Classify: bad locator, missing wait, order dependency, real race in the app, third party latency, animation timing.
  3. Fix at the source. Replace the locator, wait for the event, seed state, or mock the third party with page.route.
  4. Loop the test 50 times locally. --repeat-each=50. Green 50 in a row is the bar.
  5. Add the regression guard. If the app race was real, a lower tier test should catch it next time, not just the e2e.

CI integration

  1. Shard. --shard=1/4 across four matrix jobs.
  2. Upload artifacts. playwright-report/, test-results/, traces.
  3. Cache browsers. ~/.cache/ms-playwright keyed on the Playwright version.
  4. Fail closed on flake. A retry passing is still logged; review the trace anyway.

Visual regression

  1. Pick stable surfaces only. Marketing page, settings panel, design system gallery.
  2. Mask volatile regions. Timestamps, user avatars, animations.
  3. Pin the device and color scheme. viewport, deviceScaleFactor, colorScheme.
  4. Review diffs as PR artifacts. Treat snapshot updates like a review, not a rubber stamp.

Deliverables

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 ? 4 : undefined,
  reporter: process.env.CI ? [['github'], ['html', { open: 'never' }]] : 'list',
  use: {
    baseURL: process.env.BASE_URL ?? 'http://localhost:3000',
    trace: 'on-first-retry',
    video: 'retain-on-failure',
    screenshot: 'only-on-failure',
  },
  projects: [
    { name: 'setup', testMatch: /global\.setup\.ts/ },
    {
      name: 'chromium',
      dependencies: ['setup'],
      use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json' },
    },
    {
      name: 'firefox',
      dependencies: ['setup'],
      use: { ...devices['Desktop Firefox'], storageState: 'playwright/.auth/user.json' },
    },
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});

Auth fixture with storageState

// tests/global.setup.ts
import { test as setup, expect } from '@playwright/test';

const authFile = 'playwright/.auth/user.json';

setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.E2E_USER!);
  await page.getByLabel('Password').fill(process.env.E2E_PASSWORD!);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
  await page.context().storageState({ path: authFile });
});

Helper fixture (Page Object alternative)

// tests/fixtures.ts
import { test as base, expect, Page } from '@playwright/test';

type Checkout = {
  goto: () => Promise<void>;
  addItem: (name: string) => Promise<void>;
  submit: () => Promise<void>;
};

function checkout(page: Page): Checkout {
  return {
    goto: () => page.goto('/checkout'),
    addItem: async (name) => {
      await page.getByRole('button', { name: `Add ${name}` }).click();
    },
    submit: () => page.getByRole('button', { name: 'Place order' }).click(),
  };
}

export const test = base.extend<{ checkout: Checkout }>({
  checkout: async ({ page }, use) => {
    await use(checkout(page));
  },
});

export { expect };

Spec (canonical shape)

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

test.describe('checkout', () => {
  test('user can place an order with one item', async ({ page, checkout, request }) => {
    await request.post('/api/test/seed', { data: { cartEmpty: true } });
    await checkout.goto();
    await checkout.addItem('Espresso');
    await checkout.submit();
    await expect(page.getByRole('heading', { name: 'Order confirmed' })).toBeVisible();
  });
});

CI workflow (GitHub Actions, sharded)

name: e2e
on: [push, pull_request]
jobs:
  test:
    timeout-minutes: 30
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 20, cache: npm }
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npx playwright test --shard=${{ matrix.shard }}/4
        env:
          BASE_URL: ${{ secrets.PREVIEW_URL }}
          E2E_USER: ${{ secrets.E2E_USER }}
          E2E_PASSWORD: ${{ secrets.E2E_PASSWORD }}
      - if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report-${{ matrix.shard }}
          path: |
            playwright-report/
            test-results/
          retention-days: 7

Visual regression template

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

test('settings page is visually stable', async ({ page }) => {
  await page.goto('/settings');
  await expect(page).toHaveScreenshot('settings.png', {
    fullPage: true,
    mask: [page.getByTestId('user-avatar'), page.getByTestId('last-login')],
    animations: 'disabled',
    caret: 'hide',
  });
});

Flake investigation note

# Flake: {spec name}

Trace: {link to artifact}
First seen: {commit / date}
Frequency: {N / 100 runs in CI}

## Root cause

{Locator collision / missing wait / order dep / app race / third party.}

## Fix

{Locator swap, network mock, fixture seed, app fix.}

## Regression guard

{Lower tier test, or app fix that removes the race entirely.}

Quality bar

Before claiming done:

  • Locators are role or label based; getByTestId is the exception.
  • No page.waitForTimeout anywhere. Waits are for events, responses, or element states.
  • Each spec resets its world via API or fixture; no order dependency.
  • Auth runs once per worker via storageState; no per spec login walls.
  • trace: 'on-first-retry' and CI uploads traces as artifacts.
  • CI run is sharded; total wall clock under 10 minutes for the main browser project.
  • Visual snapshots mask volatile regions and pin viewport and color scheme.
  • Specs are single digits per critical journey.
  • Every flake has a trace link, a root cause, and a fix or deadline.
  • Codegen output has been refactored; no dumped scripts in the suite.

Antipatterns

  • page.waitForTimeout to paper over a race. Flake guaranteed once the runner gets slower.
  • getByTestId for every element. Throws away the a11y signal.
  • Order dependent specs. Pass in isolation, fail in parallel.
  • One mega spec asserting thirty things. Opaque on failure.
  • Shared mutable state across tests. "The previous test created the user" is how Friday afternoon goes red.
  • No trace policy in CI. Failures arrive with no evidence; debugging becomes guessing.
  • Copying Cypress idioms wholesale. cy.wait(3000) and chained custom commands translate badly; rethink, do not port.
  • force: true to make a click work. Hides a real bug; fix the cause.
  • Visual regression on every change. Drowns reviewers in diffs and trains the team to rubber stamp updates.
  • No quarantine policy. Broken tests stay green by being rerun, until a real regression slips through with them.
  • POM cargo cult. A class hierarchy wrapping single locators; plain functions or fixtures are clearer.
  • Codegen output committed unchanged. Brittle selectors, no real assertions, no fixtures.

Handoffs

  • For test pyramid strategy and CI gating policy, hand off to senior-qa-test-engineer.
  • For accessibility friendly markup that makes semantic locators work, hand off to senior-frontend-engineer.
  • For CI sharding economics, runner pools, and artifact retention, hand off to senior-devops-sre.
  • For perf budget assertions layered onto e2e flows (LCP, INP gates), pair with senior-performance-engineer.
  • For deep flake diagnosis when the trace points at the application, pair with senior-debugger.

Quick reference

QuestionAnswer
What does this skill produce?playwright.config.ts, specs, auth and helper fixtures, CI workflows with sharding, visual regression templates, flake notes.
What does it not do?Pyramid strategy across tiers, application markup fixes, production incident response.
Default locator ordergetByRolegetByLabelgetByTextgetByTestId.
Default trace policytrace: 'on-first-retry', video: 'retain-on-failure'.
Default retries2 in CI, 0 locally.
Default flake policyQuarantine on first flake, root cause within 7 days, fix or delete.
Common partner skillssenior-qa-test-engineer, senior-frontend-engineer, senior-devops-sre.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. 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.