playwright-e2e-mcp 0.3.2 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +125 -41
  2. package/dist/cli/init.d.ts +1 -1
  3. package/dist/cli/init.js.map +1 -1
  4. package/dist/http.d.ts +1 -1
  5. package/dist/http.js +1 -1
  6. package/dist/http.js.map +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/server.d.ts +2 -2
  9. package/dist/server.js +16 -3
  10. package/dist/server.js.map +1 -1
  11. package/dist/tools/analyze-history.d.ts +3 -3
  12. package/dist/tools/audit-accessibility.d.ts +184 -0
  13. package/dist/tools/audit-accessibility.js +162 -0
  14. package/dist/tools/audit-accessibility.js.map +1 -0
  15. package/dist/tools/compare-visual-state.d.ts +39 -39
  16. package/dist/tools/diagnose-flaky.d.ts +38 -0
  17. package/dist/tools/diagnose-flaky.js +196 -1
  18. package/dist/tools/diagnose-flaky.js.map +1 -1
  19. package/dist/tools/inspect-page.d.ts +38 -38
  20. package/dist/tools/probe-script.d.ts +2 -0
  21. package/dist/tools/probe-script.js +50 -0
  22. package/dist/tools/probe-script.js.map +1 -1
  23. package/dist/tools/run-test.d.ts +14 -1
  24. package/dist/tools/run-test.js +51 -4
  25. package/dist/tools/run-test.js.map +1 -1
  26. package/dist/tools/shared.d.ts +38 -11
  27. package/dist/tools/shared.js.map +1 -1
  28. package/dist/tools/suggest-fix.d.ts +13 -13
  29. package/dist/tools/validate-selector.d.ts +21 -21
  30. package/dist/types/index.d.ts +5 -0
  31. package/dist/types/index.js.map +1 -1
  32. package/dist/utils/change-analyzer.js.map +1 -1
  33. package/dist/utils/image-diff.js.map +1 -1
  34. package/dist/utils/locator-expr.d.ts +1 -1
  35. package/dist/utils/locator-expr.js.map +1 -1
  36. package/dist/utils/logger.js.map +1 -1
  37. package/dist/utils/playwright-runner.js +6 -0
  38. package/dist/utils/playwright-runner.js.map +1 -1
  39. package/dist/utils/project-detector.js.map +1 -1
  40. package/dist/utils/trace-reader.js +6 -1
  41. package/dist/utils/trace-reader.js.map +1 -1
  42. package/package.json +13 -6
  43. package/skill/playwright-e2e-mcp/SKILL.md +41 -7
  44. package/skill/playwright-e2e-mcp/references/setup.md +5 -3
  45. package/skill/playwright-e2e-mcp/references/tools.md +26 -1
  46. package/skill/playwright-e2e-mcp/references/writing-tests.md +170 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: playwright-e2e-mcp
3
- description: Runs, debugs and fixes Playwright end-to-end tests through the playwright-e2e-mcp server, using structured run results, trace-based failure diagnostics, verified locators, self-healing fixes that re-run and revert if still red, per-test flaky verdicts that separate parallelism bugs from races, signed-in page inspection and local run history. Use when the user asks to run Playwright tests, fix a failing or broken e2e test, find out why a test is flaky or which tests are unstable, repair a selector that no longer matches, write a new Playwright test, or check a page for visual regressions.
3
+ description: Runs, debugs and fixes Playwright end-to-end tests through the playwright-e2e-mcp server, using structured run results grouped by root cause, trace-based failure diagnostics, verified locators, self-healing fixes that re-run and revert if still red, per-test flaky verdicts that separate parallelism, test-order and timing bugs, signed-in page inspection, WCAG accessibility audits and local run history, plus a guide to writing tests that stay green. Use when the user asks to run Playwright tests, fix a failing or broken e2e test, find out why a test is flaky or which tests are unstable, repair a selector that no longer matches, write or review a Playwright test, check a page for accessibility or visual regressions.
4
4
  ---
5
5
 
6
6
  # Playwright E2E with playwright-e2e-mcp
@@ -13,22 +13,26 @@ and concrete next steps. It can also propose and apply a verified fix for a brok
13
13
  locator, inspect pages behind a login, run long suites in the background and keep
14
14
  a local history of every run.
15
15
 
16
- This skill is written for playwright-e2e-mcp 0.3.0 and works with 0.2.0 or
16
+ This skill is written for playwright-e2e-mcp 0.4.1 and works with 0.2.0 or
17
17
  newer. If `suggest-fix`, `get-run-status` or `analyze-history` is missing, the
18
18
  server is older than 0.2.0: use the steps that do not need those tools and tell
19
19
  the user to update it. `diagnose-flaky`'s per-test table and `isolate` option
20
- arrived in 0.3.0.
20
+ arrived in 0.3.0; `audit-accessibility`, `diagnose-flaky`'s `order` option and
21
+ root-cause grouping in `run-test` arrived in 0.4.0.
21
22
 
22
23
  ## Before you start
23
24
 
24
25
  1. Check that the server's tools are available. They are named `run-test`,
25
26
  `get-run-status`, `get-failure`, `suggest-fix`, `inspect-page`,
26
27
  `validate-selector`, `list-tests`, `generate-e2e-test`,
27
- `compare-visual-state`, `diagnose-flaky` and `analyze-history` (your client
28
+ `compare-visual-state`, `audit-accessibility`, `diagnose-flaky` and
29
+ `analyze-history` (your client
28
30
  may prefix them, e.g. `mcp__playwright-e2e__run-test`).
29
31
  2. If they are all missing, stop and tell the user the MCP server is not
30
32
  connected. Point them to `references/setup.md` for the one-line install for
31
- their client. Do not fall back to shelling out to `npx playwright test` unless
33
+ their client (`npx -y playwright-e2e-mcp init` sets up every client the
34
+ project uses). Writing or reviewing tests by hand still works: follow
35
+ `references/writing-tests.md`. Do not fall back to shelling out to `npx playwright test` unless
32
36
  the user asks. If only some are missing, the server may run a smaller tool
33
37
  profile (`--tools=core` exposes `run-test`, `get-run-status`, `get-failure`,
34
38
  `suggest-fix`, `diagnose-flaky` and `list-tests`); work with what is there and
@@ -49,7 +53,10 @@ Follow these steps in order. Do not edit code before step 3.
49
53
  Omit `testFiles` only when the user asks for the whole suite. Failing tests are
50
54
  retried once by default, so a test that passes on retry is reported as
51
55
  **flaky**, not failed. For a long suite pass `background: true` and poll
52
- `get-run-status` with `waitSeconds: 30`.
56
+ `get-run-status` with `waitSeconds: 30`. When several tests fail the same
57
+ way, the result says how many distinct causes there are and shows each
58
+ cause once: fix the cause, not each test (a dead server or one changed
59
+ shared component often fails dozens of tests).
53
60
  2. **Read the failure.** For each failure, call `get-failure` with its 1-based
54
61
  `index`. Read the message and code frame, expected vs actual, the failure kind,
55
62
  the DOM snapshot at failure, the failed action and its selector, failed network
@@ -138,6 +145,20 @@ failing ones with one worker, and reads each one:
138
145
  - **flaky even alone**: the race is inside the test or the app.
139
146
  - **fails alone too**: a real bug, not parallelism.
140
147
 
148
+ When a test fails in the full suite but `diagnose-flaky` on that test alone says
149
+ **NOT REPRODUCING**, suspect test order: call it again with `order: true` and
150
+ the spec file(s) in `testFiles` (Playwright 1.64+). It runs the files in random
151
+ orders with one worker, replays a failing order, and reads each test:
152
+
153
+ - **order dependent**: fails after certain tests and fails again on replay. The
154
+ result names the test it **relies on** (state it needs another test to create)
155
+ or is **broken by** (state another test leaves behind), and the
156
+ `--shuffle=<seed>` command that reproduces it. Fix it by giving the test its
157
+ own setup (`beforeEach`, a fixture, its own data) and cleaning up in the test
158
+ that leaks; do not pin the file to serial mode.
159
+ - **flaky regardless of order**: the failing order passed on replay: timing.
160
+ - **fails in every order**: a real bug.
161
+
141
162
  Pass `separateProcesses: true` only when the flake seems tied to global setup or
142
163
  a cold start, since one process per run is slower. `get-failure` works on the
143
164
  last failing diagnose run straight after, and each repetition is recorded in the
@@ -158,9 +179,20 @@ pass/fail timeline.
158
179
  file it writes, then `run-test` it. Pass `write: false` to preview without
159
180
  writing.
160
181
  - **Set up CI:** when the user wants the same diagnosis on every pull request,
161
- point them to the GitHub Action (`uses: xsignon-secure/E2E@v0.3.0`) or the
182
+ point them to the GitHub Action (`uses: xsignon-secure/E2E@v0.4.1`) or the
162
183
  `npx -y playwright-e2e-mcp ci` command in `references/setup.md`. It writes the
163
184
  run, flaky verdicts and fix diffs to the job summary and one PR comment.
185
+ - **Accessibility:** `audit-accessibility` with `url` runs axe-core against
186
+ WCAG 2.2 A/AA (`standard` picks another level or `best-practice`). It takes
187
+ the same `storageState`, `headers` and `actions` as `inspect-page`, so audit
188
+ the signed-in pages and open dialogs, not only the landing page. Each
189
+ violation lists the offending elements as verified locators and the fix; fix
190
+ the worst (`critical`, `serious`) first, re-run the audit, then add the
191
+ `AxeBuilder` check it prints to the spec that reaches that page so it stays
192
+ fixed. Pass `minImpact: "serious"` for a short list.
193
+ - **Write or review a test:** follow `references/writing-tests.md` (locators,
194
+ web-first assertions, auth setup, test data, network mocking, flake
195
+ patterns), with or without the server.
164
196
  - **Visual regression:** `compare-visual-state` with `url` and a `name`. The first
165
197
  call saves a baseline under `.pw-mcp/visual/`; later calls report changed
166
198
  regions and color shifts and write a diff image. Use `action: "baseline"` only
@@ -194,3 +226,5 @@ When you finish, tell the user, in this order:
194
226
  - `references/failure-kinds.md`: failure kinds and error codes, and what to do.
195
227
  - `references/example-session.md`: a worked fix-a-failing-test session with
196
228
  `suggest-fix`.
229
+ - `references/writing-tests.md`: how to write Playwright tests that stay green,
230
+ and the review checklist.
@@ -5,10 +5,11 @@ This skill only gives the agent instructions. The tools come from the
5
5
 
6
6
  ## Requirements
7
7
 
8
- - playwright-e2e-mcp **0.2.0 or newer**, 0.3.0 recommended. `suggest-fix`,
8
+ - playwright-e2e-mcp **0.2.0 or newer**, 0.4.0 recommended. `suggest-fix`,
9
9
  `get-run-status`, `analyze-history`, verified locators and signed-in page
10
10
  inspection arrived in 0.2.0; per-test flaky verdicts, `isolate`, tool profiles
11
- and the `ci` command in 0.3.0. Check with
11
+ and the `ci` command in 0.3.0; `audit-accessibility`, the `order` check
12
+ (needs Playwright 1.64+) and root-cause grouping in 0.4.0. Check with
12
13
  `npx -y playwright-e2e-mcp --version`.
13
14
 
14
15
  - Node.js 20 or newer.
@@ -80,6 +81,7 @@ Set these in the server's `env` block. None of them are secrets except
80
81
  | `PW_MCP_ALLOWED_ROOTS` | none | Extra directories allowed as `projectRoot` (`:`-separated, `;` on Windows) |
81
82
  | `PW_MCP_BLOCK_PRIVATE_URLS` | off for stdio | `1` makes the URL tools refuse loopback, private-network and cloud-metadata addresses |
82
83
  | `PW_MCP_STORAGE_STATE` | none | Default Playwright storageState JSON (inside the project) for the page tools, so signed-in pages open logged in |
84
+ | `PW_MCP_SHUFFLE_SEEDS` | random | Seeds for `diagnose-flaky`'s `order` pass (comma separated), to replay reported orders |
83
85
  | `PW_MCP_HISTORY` | on | `0` stops recording runs to `<project>/.playwright-e2e-mcp/history.jsonl` (this turns off `analyze-history`) |
84
86
  | `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error` or `silent` |
85
87
  | `PW_MCP_HTTP_TOKEN` | none | HTTP bridge only: bearer token that unlocks all tools |
@@ -110,7 +112,7 @@ Exit code 0 means passed, 1 tests failed, 2 the run itself failed. On GitHub
110
112
  Actions it also writes to the job summary. As an Action:
111
113
 
112
114
  ```yaml
113
- - uses: xsignon-secure/E2E@v0.3.0
115
+ - uses: xsignon-secure/E2E@v0.4.1
114
116
  with:
115
117
  flaky-runs: 3
116
118
  suggest: 2
@@ -24,6 +24,9 @@ Runs Playwright and returns stats, failures, failure kinds and fix hints.
24
24
 
25
25
  Traces are captured on failure automatically so `get-failure` can show the DOM.
26
26
  Clients that send a progress token get live progress while the run goes.
27
+ Failures that share a kind and first error line are grouped (0.4.0+): each
28
+ cause is shown once in full, the other tests under it as one line each, and
29
+ their `index` numbers stay the ones `get-failure` takes.
27
30
 
28
31
  ## get-failure
29
32
 
@@ -113,6 +116,7 @@ Baselines live in `.pw-mcp/visual/`.
113
116
  | `testFiles` | string[] | Default: tests that failed in the last run |
114
117
  | `runs` | number | 2 to 10 (default 3) |
115
118
  | `isolate` | boolean | Repeat fully parallel (`workers` default 4), then repeat the failing tests with one worker (0.3.0+) |
119
+ | `order` | boolean | Run the target files in `runs` random orders with one worker (`--shuffle`, Playwright 1.64+), replay a failing order, and name the test it relies on or is broken by (0.4.0+) |
116
120
  | `separateProcesses` | boolean | One Playwright process per run instead of one `--repeat-each` process; slower (0.3.0+) |
117
121
  | `browser`, `headed`, `workers`, `config` | | As `run-test` |
118
122
  | `timeoutMs` | number | Per run (default 120000); the single `--repeat-each` process gets this × `runs` |
@@ -121,9 +125,30 @@ Runs with retries disabled and returns, per test, passes, failures, duration
121
125
  spread, first error and `CONSISTENTLY FAILING`, `FLAKY` or `NOT REPRODUCING`,
122
126
  plus the verdict block for the worst test. With `isolate`, a second table reads
123
127
  each failing test as "fails only in parallel", "flaky even alone" or "fails
124
- alone too". Versions before 0.3.0 return one run-level verdict and a per-run
128
+ alone too". With `order`, a third section reads each test as "order
129
+ dependent", "flaky regardless of order" or "fails in every order", with the
130
+ `--shuffle=<seed>` command that reproduces it. `PW_MCP_SHUFFLE_SEEDS=1,2,3`
131
+ on the server replays given seeds instead of random ones. Versions before 0.3.0 return one run-level verdict and a per-run
125
132
  table instead.
126
133
 
134
+ ## audit-accessibility
135
+
136
+ Runs axe-core (0.4.0+) on a live page and returns WCAG violations, worst first.
137
+
138
+ | Argument | Type | Notes |
139
+ | --- | --- | --- |
140
+ | `url` | string | Required |
141
+ | `standard` | `wcag2a` \| `wcag2aa` \| `wcag21aa` \| `wcag22aa` \| `best-practice` \| `all` | Default `wcag22aa` (WCAG 2.0 to 2.2, A and AA) |
142
+ | `minImpact` | `critical` \| `serious` \| `moderate` \| `minor` | Hide violations below this impact |
143
+ | `selector` | string | Audit only this part of the page |
144
+ | `storageState`, `headers`, `actions`, `viewport` | | As `inspect-page` |
145
+ | `waitFor`, `waitUntil`, `timeoutMs` | | `waitUntil` default `load`; `timeoutMs` default 60000 |
146
+
147
+ Each violation has its rule id, impact, WCAG criteria, a help link, up to three
148
+ offending elements as verified locators with what to change, and the count of
149
+ the rest. The result ends with an `@axe-core/playwright` check to paste into
150
+ the spec.
151
+
127
152
  ## suggest-fix
128
153
 
129
154
  | Argument | Type | Notes |
@@ -0,0 +1,170 @@
1
+ # Writing Playwright tests that stay green
2
+
3
+ Use this when you write a new test, extend one, or review one. It applies with or
4
+ without the MCP server; where a tool helps, it is named.
5
+
6
+ ## 1. Locators
7
+
8
+ Pick the first that works, and check it with `validate-selector` (exactly 1 match)
9
+ or take it from `inspect-page` with `view: "locators"`:
10
+
11
+ 1. `page.getByRole('button', { name: 'Place order' })`: what a user and a screen
12
+ reader see. Add `exact: true` when the name is a prefix of another.
13
+ 2. `page.getByLabel('Email')`, `page.getByPlaceholder('Search')` for form fields.
14
+ 3. `page.getByText('Order confirmed')` for non-interactive content.
15
+ 4. `page.getByTestId('checkout')` when the app has test ids (set a custom
16
+ attribute with `testIdAttribute` in the config).
17
+ 5. A short CSS selector on a stable attribute, as a last resort.
18
+
19
+ Narrow instead of indexing: `page.getByRole('listitem').filter({ hasText: 'Milk' })`,
20
+ `row.getByRole('button', { name: 'Delete' })`. Avoid `nth()`, `:nth-child`,
21
+ XPath, generated class names (`.css-1x2y3z`) and long descendant chains. Locators
22
+ are strict: an action on a locator that matches several elements throws, which
23
+ is a feature. Fix the locator rather than adding `.first()`.
24
+
25
+ ## 2. Waiting and assertions
26
+
27
+ - Use web-first assertions, which retry until the timeout:
28
+ `await expect(page.getByRole('alert')).toHaveText('Saved')`, `toBeVisible()`,
29
+ `toHaveURL(/\/orders\/\d+/)`, `toHaveCount(3)`, `toBeEnabled()`.
30
+ - Never `expect(await locator.isVisible()).toBe(true)` or
31
+ `expect(await locator.textContent())`: they read once and do not wait.
32
+ - Never `page.waitForTimeout(…)`. Wait for the thing you need: an assertion, a
33
+ response, or a URL.
34
+ - Actions already wait for the element to be visible, enabled and stable; do
35
+ not add `waitForSelector` before `click`.
36
+ - For a value that is not in the DOM, poll: `await expect.poll(() => api.count()).toBe(3)`.
37
+ - Wait for a request triggered by an action by starting the wait first:
38
+
39
+ ```ts
40
+ const saved = page.waitForResponse((r) => r.url().includes('/api/orders') && r.ok());
41
+ await page.getByRole('button', { name: 'Save' }).click();
42
+ await saved;
43
+ ```
44
+ - `waitForLoadState('networkidle')` is discouraged: apps with polling or
45
+ analytics never go idle. Assert on the content you need instead.
46
+ - Use `expect.soft` only when later checks still make sense after a failure.
47
+
48
+ ## 3. Signing in
49
+
50
+ Log in once, not in every test. Use a setup project that saves the session and
51
+ make the other projects depend on it:
52
+
53
+ ```ts
54
+ // tests/auth.setup.ts
55
+ import { test as setup, expect } from '@playwright/test';
56
+ setup('sign in', async ({ page }) => {
57
+ await page.goto('/login');
58
+ await page.getByLabel('Email').fill(process.env.E2E_USER!);
59
+ await page.getByLabel('Password').fill(process.env.E2E_PASSWORD!);
60
+ await page.getByRole('button', { name: 'Sign in' }).click();
61
+ await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
62
+ await page.context().storageState({ path: 'playwright/.auth/user.json' });
63
+ });
64
+ ```
65
+
66
+ ```ts
67
+ // playwright.config.ts
68
+ projects: [
69
+ { name: 'setup', testMatch: /.*\.setup\.ts/ },
70
+ { name: 'chromium', use: { ...devices['Desktop Chrome'], storageState: 'playwright/.auth/user.json' }, dependencies: ['setup'] },
71
+ ],
72
+ ```
73
+
74
+ Keep `playwright/.auth/` in `.gitignore`. Use one file per role (admin, member).
75
+ Credentials come from environment variables, never from the spec. Pass the same
76
+ file as `storageState` to `inspect-page`, `validate-selector` and
77
+ `audit-accessibility` to look at signed-in pages.
78
+
79
+ ## 4. Test data and isolation
80
+
81
+ Every test must pass alone, in any order, and in parallel:
82
+
83
+ - Create the data a test needs in the test or a fixture, ideally through the API
84
+ (`request.post('/api/products', { data })`), not through the UI and not by
85
+ relying on a test that ran earlier.
86
+ - Make it unique per worker and run:
87
+ `` `user-${testInfo.workerIndex}-${Date.now()}@example.com` ``.
88
+ - Clean up what you created, or create it in a namespace that is reset.
89
+ - Do not share mutable module or `globalThis` state between tests.
90
+
91
+ Check it: `diagnose-flaky` with `isolate: true` finds tests that only fail in
92
+ parallel; `order: true` finds tests that depend on another test running first.
93
+
94
+ ## 5. Network and time
95
+
96
+ - Mock third parties (payments, maps, analytics) with `page.route`, and mock your
97
+ own API only for states that are hard to reach (errors, empty lists):
98
+
99
+ ```ts
100
+ await page.route('**/api/orders', (route) => route.fulfill({ status: 500, json: { error: 'down' } }));
101
+ ```
102
+ - Replay a recorded session with `page.routeFromHAR` when the backend is slow or
103
+ unstable.
104
+ - Control time with `page.clock` (`install`, `setFixedTime`, `fastForward`)
105
+ instead of waiting for timers, and pin `timezoneId` and `locale` in the config
106
+ when dates are on screen.
107
+
108
+ ## 6. Structure
109
+
110
+ - One user journey per test, named for the behavior: `places an order with a saved card`.
111
+ - Group steps with `await test.step('pay', async () => { … })` so traces and
112
+ reports read like the journey.
113
+ - Prefer fixtures for setup and shared helpers; use a page object only for a
114
+ page several specs drive. Keep assertions in the test, not in page objects.
115
+ - Tag suites (`test('…', { tag: '@smoke' }, …)`) and run them with `--grep @smoke`.
116
+
117
+ ## 7. Config that prevents trouble
118
+
119
+ ```ts
120
+ export default defineConfig({
121
+ fullyParallel: true,
122
+ forbidOnly: !!process.env.CI,
123
+ retries: process.env.CI ? 2 : 0,
124
+ use: { baseURL: 'http://localhost:3000', trace: 'on-first-retry' },
125
+ webServer: { command: 'npm run start', url: 'http://localhost:3000', reuseExistingServer: !process.env.CI },
126
+ });
127
+ ```
128
+
129
+ `webServer` starts the app so runs never fail with "connection refused". Traces
130
+ on retry are what `get-failure` reads.
131
+
132
+ ## 8. Flake patterns
133
+
134
+ | Symptom | Usual cause | Fix |
135
+ | --- | --- | --- |
136
+ | Passes locally, times out in CI | Fixed sleeps, slow CI machine, no `webServer` | Web-first assertions; `webServer`; wait for the response |
137
+ | "strict mode violation" | Locator matches several elements | Filter or scope the locator |
138
+ | Element "detached from the DOM" | List re-renders after data loads | Assert the loaded state first, then act |
139
+ | Fails only with other tests | Shared user or data | Unique data per test or worker (`isolate: true` confirms) |
140
+ | Fails only after certain tests | Order dependence | Own setup per test (`order: true` names the test) |
141
+ | Wrong date or number format | Time zone or locale | Set `timezoneId`, `locale`, `page.clock` |
142
+ | Click does nothing | Overlay, animation, or element not ready | Assert the overlay is gone; assert enabled |
143
+
144
+ Never fix flakiness with retries, longer timeouts or `waitForTimeout` alone.
145
+
146
+ ## 9. Accessibility and structure checks
147
+
148
+ - Run `audit-accessibility` on new pages, then keep them clean with
149
+ `@axe-core/playwright`:
150
+
151
+ ```ts
152
+ import AxeBuilder from '@axe-core/playwright';
153
+ const { violations } = await new AxeBuilder({ page }).withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa']).analyze();
154
+ expect(violations.map((v) => v.id)).toEqual([]);
155
+ ```
156
+ - Lock the structure of a component with
157
+ `await expect(page.getByRole('navigation')).toMatchAriaSnapshot(…)`.
158
+ - For visual checks, `compare-visual-state` locally or `toHaveScreenshot()` in
159
+ the suite, with animations disabled and dynamic content masked.
160
+
161
+ ## 10. Review checklist
162
+
163
+ - [ ] Every locator is role, label, text or test id based, and unique.
164
+ - [ ] No `waitForTimeout`, no assertions on one-off reads.
165
+ - [ ] The test creates its own data and passes alone and in any order.
166
+ - [ ] Sign-in comes from a saved session, credentials from the environment.
167
+ - [ ] Third-party calls are mocked; time-dependent output is pinned.
168
+ - [ ] The test name describes the behavior; steps are grouped.
169
+ - [ ] It ran green with `run-test`, and three times in a row with
170
+ `diagnose-flaky` (`runs: 3`) before you call it done.