Use when reviewing UI for accessibility — WCAG 2.2 AA, keyboard nav, focus, ARIA, contrast, screen-reader semantics — even on 'is this a11y-OK?' or 'mach das barrierefrei'.
中文简介正在准备中。当前显示原文说明。
Use when writing Playwright E2E tests — browser automation, visual regression testing, Page Objects, fixtures, and reliable test patterns.
中文简介正在准备中。当前显示原文说明。
安装前可以查看交给 AI 代理的指令内容。
Design verification. When exercising a UI artifact, run the design-artifact verification checklist (open → console/load → viewport → text-fit → assets → interaction) and capture evidence; a design task with browser capability present is not "done" without it.
Use this skill when:
Guideline: ../../../docs/guidelines/e2e/playwright.md — full conventions, config templates, CI setup.
Mobile: for native iOS/Android or React Native E2E, do NOT reuse Playwright — see the mobile-e2e-strategy skill for framework selection.
../../../docs/guidelines/e2e/playwright.md for detailed conventions.playwright.config.ts for browsers, base URL, timeouts.tests/e2e/ or e2e/.test-case-discovery funnel per user flow before writing specs; cover the happy flow AND at least one boundary (empty state, max input) and one error path (failed request, validation rejection) per flow — never the happy flow alone.import { test, expect } from '@playwright/test'
test.describe('User Authentication', () => {
test('should login with valid credentials', async ({ page }) => {
await page.goto('/login')
await page.getByLabel('Email').fill('user@example.com')
await page.getByLabel('Password').fill('password123')
await page.getByRole('button', { name: 'Sign in' }).click()
await expect(page).toHaveURL('/dashboard')
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible()
})
test('should show error for invalid credentials', async ({ page }) => {
await page.goto('/login')
await page.getByLabel('Email').fill('wrong@example.com')
await page.getByLabel('Password').fill('wrong')
await page.getByRole('button', { name: 'Sign in' }).click()
await expect(page.getByText('Invalid credentials')).toBeVisible()
})
})
| Strategy | Example | When to use |
|---|---|---|
| Role | getByRole('button', { name: 'Submit' }) | Default — most accessible |
| Label | getByLabel('Email') | Form inputs |
| Text | getByText('Welcome') | Visible text content |
| Placeholder | getByPlaceholder('Search...') | Input placeholders |
| Test ID | getByTestId('submit-btn') | Last resort — when no semantic locator works |
| CSS | page.locator('.my-class') | Avoid — brittle |
Prefer semantic locators (getByRole, getByLabel) over CSS selectors.
// Wait for page to fully load
await page.goto('/dashboard', { waitUntil: 'networkidle' })
// Wait for specific API response
await page.waitForResponse(resp =>
resp.url().includes('/api/users') && resp.status() === 200
)
// ✅ Auto-retrying assertions (Playwright retries until timeout)
await expect(page.getByText('Success')).toBeVisible()
await expect(page.getByRole('list')).toHaveCount(5)
// ❌ Non-retrying — can be flaky
const text = await page.textContent('.message')
expect(text).toBe('Success')
// pages/LoginPage.ts
export class LoginPage {
constructor(private page: Page) {}
async goto() {
await this.page.goto('/login')
}
async login(email: string, password: string) {
await this.page.getByLabel('Email').fill(email)
await this.page.getByLabel('Password').fill(password)
await this.page.getByRole('button', { name: 'Sign in' }).click()
}
}
test('homepage visual regression', async ({ page }) => {
await page.goto('/')
await expect(page).toHaveScreenshot('homepage.png', {
maxDiffPixelRatio: 0.01,
})
})
tests/*.png (or configured path).npx playwright test --update-snapshots.A viewport loop whose only assertion is an image proves that the page rendered, not that the breakpoint did anything: the same picture comes back whether the media query fired or silently did not. Assert the layout property the breakpoint is supposed to change, then keep the capture and name it for the one thing it proves.
test.describe('Responsive design', () => {
for (const viewport of [
{ width: 1440, height: 900, name: 'desktop', columns: '1fr 1fr 1fr' },
{ width: 768, height: 1024, name: 'tablet', columns: '1fr 1fr' },
{ width: 375, height: 812, name: 'mobile', columns: '1fr' },
]) {
test(`lays out correctly on ${viewport.name}`, async ({ page }) => {
await page.setViewportSize({ width: viewport.width, height: viewport.height })
await page.goto('/')
// Behaviour: the declared breakpoint actually changed the layout.
await expect(page.locator('#grid')).toHaveCSS('grid-template-columns', viewport.columns)
// Appearance only: presence + sanity that nothing renders broken.
await expect(page).toHaveScreenshot(`appearance-${viewport.name}.png`)
})
}
})
Read the probe artifact rather than re-deriving the matrix by hand.
agents/runtime/state/ui-conformance.json — produced by
ui_conformance_probe --target <file> --reference <file> — carries a
viewport_matrix row per declared width, plus a not-applicable row with its
reason wherever the host could not cross the breakpoint. A width with no row is
missing evidence, not a pass, and no dimension reads zero findings because it did
not run.
The capture above is appearance-only, and it stays mandatory. It proves
presence and sanity — the surface rendered and nothing renders obviously broken —
and proves nothing about hover, focus, keyboard or a media-preference branch.
Demoting it from behavioral evidence does not narrow when it runs: it still runs
at every width in the matrix. Floor and division of labour:
design-review § Appearance verification.
# Run with headed browser (see what's happening)
npx playwright test --headed
# Run with Playwright Inspector (step through)
npx playwright test --debug
# View test report
npx playwright show-report
# Run specific test
npx playwright test -g "should login"
Use --grep, --reporter=json, plus jq/rg to keep diagnosis scoped:
# Targeted run — only matching specs
npx playwright test --grep '@smoke'
# JSON report, narrowed to failures via jq
npx playwright test --reporter=json > pw.json
jq '.suites[].specs[] | select(.tests[].results[].status=="failed")' pw.json
# Scan trace logs for one selector
rg --color=never 'getByRole.*Submit' test-results/
Run verification. The test run's exit code is the pass/fail signal — 0
means every spec passed, non-zero means at least one failed. Read the command
output (or the --reporter=json above) to diagnose the failing spec's
root cause; do not blindly re-run hoping it turns green — a retry-until-pass
is not a fix, and a flaky green hides the real defect.
| Problem | Solution |
|---|---|
| Element not ready | Use auto-retrying assertions (toBeVisible, toHaveText) |
| Animation interference | Use page.evaluate(() => document.body.style.setProperty('--transition-duration', '0s')) |
| Network timing | Wait for specific responses, not arbitrary timeouts |
| Test isolation | Use fresh browser context per test (Playwright default) |
| Shared state | Reset database/state before each test |
// Use storageState to avoid logging in via UI in every test
// auth.setup.ts
import { test as setup } from '@playwright/test'
setup('authenticate', async ({ page }) => {
await page.goto('/login')
await page.getByLabel('Email').fill(process.env.TEST_USER_EMAIL!)
await page.getByLabel('Password').fill(process.env.TEST_USER_PASSWORD!)
await page.getByRole('button', { name: 'Sign in' }).click()
await page.waitForURL('/dashboard')
await page.context().storageState({ path: '.auth/user.json' })
})
// playwright.config.ts — use storage state in projects
projects: [
{ name: 'setup', testMatch: /.*\.setup\.ts/ },
{
name: 'chromium',
use: { ...devices['Desktop Chrome'], storageState: '.auth/user.json' },
dependencies: ['setup'],
},
]
// Mock API responses for isolated testing
await page.route('**/api/users', route =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Test User' }]),
})
)
page.waitForTimeout() as a fix — it masks the real problem and makes tests flaky.getByRole, getByLabel.test.fixme() is for app bugs, test.skip() is for environment constraints — don't confuse them.test.fixme() and move on.baseURL from config..only — enforce via forbidOnly: !!process.env.CI.When a spec fails, do not retry blindly by re-running, swapping locators at random, or bumping timeouts until green. Diagnose the root cause first: open the trace viewer, inspect the failing locator's aria tree, identify the real reason (timing, locator, app state), then apply a targeted fix. Trial-and-error locator swaps mask flaky test design.
还没有评价。欢迎分享你的使用感受。
无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。
还没有评论。来写第一条吧。
Use when reviewing UI for accessibility — WCAG 2.2 AA, keyboard nav, focus, ARIA, contrast, screen-reader semantics — even on 'is this a11y-OK?' or 'mach das barrierefrei'.
中文简介正在准备中。当前显示原文说明。
Use when defining or auditing the activation event — aha-moment selection, retention correlation, falsifiable definition. Triggers on 'what is our aha moment', 'redefine activation'.
中文简介正在准备中。当前显示原文说明。
Use when capturing an architectural decision — file naming, next ADR number, Status / Context / Decision / Consequences, index regen; fires even without saying 'ADR'.
中文简介正在准备中。当前显示原文说明。
Adversarial critique — devil's advocate, stress-test, honest teardown ('poke holes', 'be brutal', 'was hältst du davon'); explicit request only. Routine code or design review → code-review.
中文简介正在准备中。当前显示原文说明。
Use when reading, creating, or updating agent documentation, module docs, roadmaps, or AGENTS.md. Understands the full .augment/, agents/, and copilot-instructions structure.
中文简介正在准备中。当前显示原文说明。
Use for an adversarial red-team / blue-team / auditor review of an AI agent's CONFIG + behaviour (rules, skills, MCP, hooks, permissions) — attack-chain → defensive-gap list, not a code audit.
中文简介正在准备中。当前显示原文说明。