Playwright E2E Test Writer
Writes Playwright end-to-end tests from a described user flow using role-based selectors and web-first assertions, with no arbitrary waits.
What this skill does
Key features
- Turns a described user flow into a runnable Playwright spec
- Uses role and label selectors instead of brittle CSS or XPath
- Relies on web-first assertions that auto-wait for state
- Avoids fixed timeouts and manual sleeps that cause flake
Use cases
- Cover a critical signup or checkout flow before shipping
- Add a regression test that reproduces a reported UI bug
SKILL.md
The skill definition — its metadata and the exact instructions an agent follows. Copy it into a SKILL.md file in your agent's skills folder.
| name | e2e-playwright |
|---|---|
| description | Write a Playwright end-to-end test for a described user flow using stable selectors and web-first assertions. |
| allowed-tools | Read, Grep, Write, Bash(npx playwright test:*) |
| output | playwright spec file |
| runner | playwright |
Musts
- Restate the user flow as ordered steps before writing the spec, so each assertion maps to a user-visible outcome.
- Select elements by role, label, or accessible text using
getByRole,getByLabel, orgetByText. - Use web-first assertions such as
expect(locator).toBeVisible()that auto-wait for the expected state. - Assert the outcome of each step, not just that navigation happened.
- Keep tests independent so any one can run alone without relying on another’s leftover state.
Guidelines
- Never use
page.waitForTimeoutor fixed sleeps; let assertions and auto-waiting handle timing. - Prefer a stable
data-testidonly when no accessible role or label fits. - Reset or isolate test data per test so reruns are deterministic.
- Group steps with
test.stepwhen a flow has several stages, to make failures readable. - Keep one user journey per test file section rather than one giant test.
Output format
import { test, expect } from '@playwright/test';
test('user can sign in and reach the dashboard', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill('correct-horse');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
await expect(page.getByText('Welcome back')).toBeVisible();
});
FAQ
Why not use CSS selectors for elements?
CSS classes change with styling and break tests. Role and label selectors track what the user sees and stay stable across refactors.
How does it avoid flaky waits?
It uses web-first assertions like expect(locator).toBeVisible() that retry until the condition holds, instead of page.waitForTimeout.
Before running any skill, read its instructions and the tools it's allowed to use, and test it on a safe target first. See LLM security best practices.