venetian 0.2.0-aarch64-linux → 0.2.3-aarch64-linux

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/exe/aarch64-linux/LICENSE +204 -204
  4. data/exe/aarch64-linux/node +2 -2
  5. data/exe/aarch64-linux/package/browsers.json +10 -39
  6. data/exe/aarch64-linux/package/lib/bootstrap.js +1 -1
  7. data/exe/aarch64-linux/package/lib/coreBundle.js +14708 -12281
  8. data/exe/aarch64-linux/package/lib/server/electron/loader.js +5 -4
  9. data/exe/aarch64-linux/package/lib/serverRegistry.js +1674 -7078
  10. data/exe/aarch64-linux/package/lib/serverRegistry.js.LICENSE +8 -297
  11. data/exe/aarch64-linux/package/lib/tools/cli-client/help.json +39 -11
  12. data/exe/aarch64-linux/package/lib/tools/cli-client/minimist.js +1 -1
  13. data/exe/aarch64-linux/package/lib/tools/cli-client/output.js +9 -1
  14. data/exe/aarch64-linux/package/lib/tools/cli-client/program.js +18 -4
  15. data/exe/aarch64-linux/package/lib/tools/cli-client/registry.js +9 -5
  16. data/exe/aarch64-linux/package/lib/tools/cli-client/session.js +6 -1
  17. data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/SKILL.md +26 -5
  18. data/exe/aarch64-linux/package/lib/tools/{cli-client/skill/references/spec-driven-testing.md → skills/playwright-cli/references/test-generation.md} +135 -7
  19. data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/tracing.md +1 -1
  20. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/SKILL.md +136 -0
  21. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  22. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/references/migration.md +89 -0
  23. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/references/react.md +69 -0
  24. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/references/typing.md +173 -0
  25. data/exe/aarch64-linux/package/lib/tools/skills/playwright-component-testing/references/vue.md +77 -0
  26. data/exe/aarch64-linux/package/lib/tools/{trace → skills/playwright-trace}/SKILL.md +6 -3
  27. data/exe/aarch64-linux/package/lib/tools/utils/extension.js +44 -10
  28. data/exe/aarch64-linux/package/lib/utilsBundle.js +48939 -50318
  29. data/exe/aarch64-linux/package/lib/utilsBundle.js.LICENSE +359 -525
  30. data/exe/aarch64-linux/package/lib/vite/dashboard/assets/codeMirrorModule--QdMvsKi.css +1 -0
  31. data/exe/aarch64-linux/package/lib/vite/dashboard/assets/codeMirrorModule-CQ8RZGBm.js +32 -0
  32. data/exe/aarch64-linux/package/lib/vite/dashboard/assets/index-AwgwcYie.css +1 -0
  33. data/exe/aarch64-linux/package/lib/vite/dashboard/assets/index-hRvO4yU2.js +12 -0
  34. data/exe/aarch64-linux/package/lib/vite/dashboard/index.html +2 -2
  35. data/exe/aarch64-linux/package/lib/vite/htmlReport/report.css +2 -1
  36. data/exe/aarch64-linux/package/lib/vite/htmlReport/report.js +15 -55
  37. data/exe/aarch64-linux/package/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  38. data/exe/aarch64-linux/package/lib/vite/recorder/assets/codeMirrorModule-CVQWJZAA.js +32 -0
  39. data/exe/aarch64-linux/package/lib/vite/recorder/assets/index-BZpYJZ-P.css +1 -0
  40. data/exe/aarch64-linux/package/lib/vite/recorder/assets/index-hrlLqLtq.js +129 -0
  41. data/exe/aarch64-linux/package/lib/vite/recorder/index.html +2 -2
  42. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/codeMirrorModule-BbkfBe3n.js +32 -0
  43. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/defaultSettingsView-Ds6CBOo0.js +182 -0
  44. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  45. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/xtermModule-DywYcAf8.js +7 -0
  46. data/exe/aarch64-linux/package/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  47. data/exe/aarch64-linux/package/lib/vite/traceViewer/defaultSettingsView.Bqk9acqE.css +1 -0
  48. data/exe/aarch64-linux/package/lib/vite/traceViewer/index.B4cLoZK3.js +1 -0
  49. data/exe/aarch64-linux/package/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  50. data/exe/aarch64-linux/package/lib/vite/traceViewer/index.html +5 -5
  51. data/exe/aarch64-linux/package/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  52. data/exe/aarch64-linux/package/lib/vite/traceViewer/snapshot.html +2 -2
  53. data/exe/aarch64-linux/package/lib/vite/traceViewer/sw.bundle.js +3 -4
  54. data/exe/aarch64-linux/package/lib/vite/traceViewer/uiMode.CU5KtEkS.js +5 -0
  55. data/exe/aarch64-linux/package/lib/vite/traceViewer/uiMode.CyMfwkXJ.css +1 -0
  56. data/exe/aarch64-linux/package/lib/vite/traceViewer/uiMode.html +5 -5
  57. data/exe/aarch64-linux/package/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  58. data/exe/aarch64-linux/package/lib/webp_codec.LICENSE +173 -0
  59. data/exe/aarch64-linux/package/lib/webp_codec.wasm +0 -0
  60. data/exe/aarch64-linux/package/lib/xdg-open +338 -137
  61. data/exe/aarch64-linux/package/package.json +2 -2
  62. data/exe/aarch64-linux/package/types/protocol.d.ts +446 -430
  63. data/exe/aarch64-linux/package/types/structs.d.ts +8 -1
  64. data/exe/aarch64-linux/package/types/types.d.ts +2627 -440
  65. data/lib/venetian/executable.rb +6 -2
  66. data/lib/venetian/upstream.rb +35 -18
  67. data/lib/venetian/version.rb +2 -2
  68. metadata +45 -39
  69. data/exe/aarch64-linux/package/api.json +0 -1
  70. data/exe/aarch64-linux/package/lib/server/deviceDescriptorsSource.json +0 -2739
  71. data/exe/aarch64-linux/package/lib/tools/cli-client/skill/references/test-generation.md +0 -134
  72. data/exe/aarch64-linux/package/lib/vite/dashboard/assets/index-BY2S1tHT.css +0 -1
  73. data/exe/aarch64-linux/package/lib/vite/dashboard/assets/index-C_5TMfeg.js +0 -52
  74. data/exe/aarch64-linux/package/lib/vite/recorder/assets/codeMirrorModule-DYBRYzYX.css +0 -1
  75. data/exe/aarch64-linux/package/lib/vite/recorder/assets/codeMirrorModule-DeBYQozu.js +0 -32
  76. data/exe/aarch64-linux/package/lib/vite/recorder/assets/index-4ZiSSCmn.css +0 -1
  77. data/exe/aarch64-linux/package/lib/vite/recorder/assets/index-Bq-mQf8S.js +0 -193
  78. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/codeMirrorModule-LEHpjmcn.js +0 -32
  79. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/defaultSettingsView-BNmKHKpQ.js +0 -264
  80. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/urlMatch-BYQrIQwR.js +0 -1
  81. data/exe/aarch64-linux/package/lib/vite/traceViewer/assets/xtermModule-CsJ4vdCR.js +0 -9
  82. data/exe/aarch64-linux/package/lib/vite/traceViewer/codeMirrorModule.DYBRYzYX.css +0 -1
  83. data/exe/aarch64-linux/package/lib/vite/traceViewer/defaultSettingsView.CjdS-WJx.css +0 -1
  84. data/exe/aarch64-linux/package/lib/vite/traceViewer/index.CzXZzn5A.css +0 -1
  85. data/exe/aarch64-linux/package/lib/vite/traceViewer/index.DMMX1gXU.js +0 -2
  86. data/exe/aarch64-linux/package/lib/vite/traceViewer/snapshot.v8KI4P3m.js +0 -2
  87. data/exe/aarch64-linux/package/lib/vite/traceViewer/uiMode.BZQ54Kgt.css +0 -1
  88. data/exe/aarch64-linux/package/lib/vite/traceViewer/uiMode.Ut8wwJNp.js +0 -6
  89. data/exe/aarch64-linux/package/lib/vite/traceViewer/xtermModule.DYP7pi_n.css +0 -32
  90. data/exe/aarch64-linux/package/protocol.yml +0 -4991
  91. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/element-attributes.md +0 -0
  92. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/playwright-tests.md +0 -0
  93. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/request-mocking.md +0 -0
  94. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/running-code.md +0 -0
  95. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/session-management.md +0 -0
  96. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/storage-state.md +0 -0
  97. /data/exe/aarch64-linux/package/lib/tools/{cli-client/skill → skills/playwright-cli}/references/video-recording.md +0 -0
@@ -130,6 +130,10 @@ to start the browser session.`);
130
130
  ];
131
131
  if (cliArgs.headed)
132
132
  args.push("--headed");
133
+ if (cliArgs.mobile)
134
+ args.push("--mobile");
135
+ if (cliArgs.device)
136
+ args.push(`--device=${cliArgs.device}`);
133
137
  if (cliArgs.browser)
134
138
  args.push(`--browser=${cliArgs.browser}`);
135
139
  if (cliArgs.persistent)
@@ -147,8 +151,9 @@ to start the browser session.`);
147
151
  const child = (0, import_child_process.spawn)(process.execPath, args, {
148
152
  detached: true,
149
153
  stdio: ["ignore", "pipe", err],
150
- cwd: process.cwd()
154
+ cwd: process.cwd(),
151
155
  // Will be used as root.
156
+ windowsHide: true
152
157
  });
153
158
  let signalled = false;
154
159
  const sigintHandler = () => {
@@ -47,6 +47,11 @@ playwright-cli upload ./document.pdf
47
47
  playwright-cli check e12
48
48
  playwright-cli uncheck e12
49
49
  playwright-cli snapshot
50
+ # search the snapshot for text or a regexp, returns matching nodes with surrounding context
51
+ playwright-cli find "Sign in"
52
+ playwright-cli find --regex "Sign (in|up)"
53
+ # wrap the regexp in slashes to add flags, e.g. /i for case-insensitive
54
+ playwright-cli find --regex "/sign (in|up)/i"
50
55
  playwright-cli eval "document.title"
51
56
  playwright-cli eval "el => el.textContent" e5
52
57
  # get element id, class, or any attribute not visible in the snapshot
@@ -93,6 +98,7 @@ playwright-cli mousewheel 0 100
93
98
  playwright-cli screenshot
94
99
  playwright-cli screenshot e5
95
100
  playwright-cli screenshot --filename=page.png
101
+ playwright-cli screenshot --hires
96
102
  playwright-cli pdf --filename=page.pdf
97
103
  ```
98
104
 
@@ -159,6 +165,11 @@ playwright-cli run-code "async page => await page.context().grantPermissions(['g
159
165
  playwright-cli run-code --filename=script.js
160
166
  playwright-cli tracing-start
161
167
  playwright-cli tracing-stop
168
+
169
+ # record user actions in the browser, print them as Playwright code on stop
170
+ playwright-cli recording-start
171
+ playwright-cli recording-stop
172
+
162
173
  playwright-cli video-start video.webm
163
174
  playwright-cli video-chapter "Chapter Title" --description="Details" --duration=2000
164
175
  playwright-cli video-stop
@@ -209,6 +220,12 @@ playwright-cli open --browser=firefox
209
220
  playwright-cli open --browser=webkit
210
221
  playwright-cli open --browser=msedge
211
222
 
223
+ # Emulate a generic mobile device (Pixel 10 for Chromium, iPhone 17 for WebKit).
224
+ # Prefer this when a mobile layout is acceptable: mobile pages are usually
225
+ # lighter, so snapshots are smaller and cheaper.
226
+ playwright-cli open --mobile
227
+ playwright-cli open --device="iPhone 15"
228
+
212
229
  # Use persistent profile (by default profile is in-memory)
213
230
  playwright-cli open --persistent
214
231
  # Use persistent profile with custom directory
@@ -278,6 +295,11 @@ playwright-cli snapshot e34
278
295
 
279
296
  # include each element's bounding box as [box=x,y,width,height]
280
297
  playwright-cli snapshot --boxes
298
+
299
+ # search a large snapshot instead of capturing it all — returns matching nodes
300
+ # with 3 lines of context around each match (like grep -C)
301
+ playwright-cli find "Add to cart"
302
+ playwright-cli find --regex "\\$[0-9]+\\.[0-9]{2}"
281
303
  ```
282
304
 
283
305
  ## Targeting elements
@@ -325,13 +347,13 @@ playwright-cli kill-all
325
347
 
326
348
  ## Installation
327
349
 
328
- If global `playwright-cli` command is not available, try a local version via `npx playwright-cli`:
350
+ If global `playwright-cli` command is not available, try a local version via `npx playwright cli`:
329
351
 
330
352
  ```bash
331
- npx --no-install playwright-cli --version
353
+ npx --no-install playwright --version
332
354
  ```
333
355
 
334
- When local version is available, use `npx playwright-cli` in all commands. Otherwise, install `playwright-cli` as a global command:
356
+ When local version is available, use `npx playwright cli` in all commands. Otherwise, install `playwright-cli` as a global command:
335
357
 
336
358
  ```bash
337
359
  npm install -g @playwright/cli@latest
@@ -396,9 +418,8 @@ playwright-cli show --annotate
396
418
  * **Request mocking** [references/request-mocking.md](references/request-mocking.md)
397
419
  * **Running Playwright code** [references/running-code.md](references/running-code.md)
398
420
  * **Browser session management** [references/session-management.md](references/session-management.md)
399
- * **Spec-driven testing (plan / generate / heal)** [references/spec-driven-testing.md](references/spec-driven-testing.md)
400
421
  * **Storage state (cookies, localStorage)** [references/storage-state.md](references/storage-state.md)
401
- * **Test generation** [references/test-generation.md](references/test-generation.md)
422
+ * **Test generation (plan / generate / heal)** [references/test-generation.md](references/test-generation.md)
402
423
  * **Tracing** [references/tracing.md](references/tracing.md)
403
424
  * **Video recording** [references/video-recording.md](references/video-recording.md)
404
425
  * **Inspecting element attributes** [references/element-attributes.md](references/element-attributes.md)
@@ -1,12 +1,141 @@
1
- # Spec-driven testing (plan → generate → heal)
1
+ # Test generation (plan → generate → heal)
2
2
 
3
- End-to-end workflow for authoring and maintaining Playwright tests using `playwright-cli`. The three sections below can be used independently:
3
+ End-to-end workflow for authoring and maintaining Playwright tests with `playwright-cli`. Every `playwright-cli` action emits the equivalent Playwright TypeScript, and that generated code is the raw material for every test. The sections below can be used independently:
4
4
 
5
- - **Planning** — explore the app, produce a spec file describing what to test.
5
+ - **How generation works** — the core mechanic everything else relies on: actions become TypeScript, plus how to add assertions.
6
+ - **Plan** — explore the app, produce a spec file describing what to test.
6
7
  - **Generate** — turn a spec into Playwright test files. Update the spec if it's vague or stale.
7
8
  - **Heal** — diagnose failing tests, fix the code, reconcile the spec with reality.
8
9
 
9
- All three lean on the same mechanic: run `npx playwright test --debug=cli` in the background, then `playwright-cli attach tw-XXXX` to drive the paused page interactively. See [playwright-tests.md](playwright-tests.md) for the debug/attach mechanics and [test-generation.md](test-generation.md) for how every `playwright-cli` action emits Playwright TypeScript.
10
+ Plan / generate / heal lean on the same mechanic: run `npx playwright test --debug=cli` in the background, then `playwright-cli attach tw-XXXX` to drive the paused page interactively. See [playwright-tests.md](playwright-tests.md) for the debug/attach mechanics.
11
+
12
+ ---
13
+
14
+ ## 0. How generation works
15
+
16
+ Every action you perform with `playwright-cli` generates corresponding Playwright TypeScript code. This code appears in the output and can be copied directly into your test files.
17
+
18
+ ```bash
19
+ # Start a session
20
+ playwright-cli open https://example.com/login
21
+
22
+ # Take a snapshot to see elements
23
+ playwright-cli snapshot
24
+ # Output shows: e1 [textbox "Email"], e2 [textbox "Password"], e3 [button "Sign In"]
25
+
26
+ # Fill form fields - generates code automatically
27
+ playwright-cli fill e1 "user@example.com"
28
+ # Ran Playwright code:
29
+ # await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
30
+
31
+ playwright-cli fill e2 "password123"
32
+ # Ran Playwright code:
33
+ # await page.getByRole('textbox', { name: 'Password' }).fill('password123');
34
+
35
+ playwright-cli click e3
36
+ # Ran Playwright code:
37
+ # await page.getByRole('button', { name: 'Sign In' }).click();
38
+ ```
39
+
40
+ ### Building a test file
41
+
42
+ Collect the generated code into a Playwright test:
43
+
44
+ ```typescript
45
+ import { test, expect } from '@playwright/test';
46
+
47
+ test('login flow', async ({ page }) => {
48
+ // Generated code from playwright-cli session:
49
+ await page.goto('https://example.com/login');
50
+ await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
51
+ await page.getByRole('textbox', { name: 'Password' }).fill('password123');
52
+ await page.getByRole('button', { name: 'Sign In' }).click();
53
+
54
+ // Add assertions
55
+ await expect(page).toHaveURL(/.*dashboard/);
56
+ });
57
+ ```
58
+
59
+ ### Use semantic locators
60
+
61
+ The generated code uses role-based locators when possible, which are more resilient:
62
+
63
+ ```typescript
64
+ // Generated (good - semantic)
65
+ await page.getByRole('button', { name: 'Submit' }).click();
66
+
67
+ // Avoid (fragile - CSS selectors)
68
+ await page.locator('#submit-btn').click();
69
+ ```
70
+
71
+ ### Explore before recording
72
+
73
+ Take snapshots to understand the page structure before recording actions:
74
+
75
+ ```bash
76
+ playwright-cli open https://example.com
77
+ playwright-cli snapshot
78
+ # Review the element structure
79
+ playwright-cli click e5
80
+ ```
81
+
82
+ ### Add assertions manually
83
+
84
+ Generated code captures actions but not assertions. Add expectations in your test using one of the recommended matchers:
85
+
86
+ - `toBeVisible()` — element is rendered and visible
87
+ - `toHaveText(text)` — element text content matches
88
+ - `toHaveValue(value) / toBeEmpty()` — input/select value matches
89
+ - `toBeChecked() / toBeUnchecked()` — checkbox state matches
90
+ - `toMatchAriaSnapshot(snapshot)` — page (or locator) matches a partial accessibility snapshot
91
+
92
+ Use `playwright-cli generate-locator <target>` to produce the locator expression for the assertion, and the snapshot/eval commands to capture the expected value.
93
+
94
+ When asserting text content, make sure that generated locator does not contain text from the element itself. `getByTestId()` or `getByLabel()` usually work well with asserting text. When locator is text-based, prefer `toBeVisible()` instead.
95
+
96
+ Snapshot to be matched does not have to contain all the information - only capture what's necessary for the assertion. You can use regular expressions for unstable values.
97
+
98
+ ```bash
99
+ # Get a stable locator for an element ref to use in the assertion
100
+ playwright-cli --raw generate-locator e5
101
+ # getByRole('button', { name: 'Submit' })
102
+
103
+ # Capture expected text content for toHaveText
104
+ playwright-cli --raw eval "el => el.textContent" e5
105
+
106
+ # Capture expected input value for toHaveValue/toBeEmpty
107
+ playwright-cli --raw eval "el => el.value" e5
108
+
109
+ # Capture expected aria snapshot for toMatchAriaSnapshot/toBeChecked
110
+ # (whole page, or use a ref to scope to a region)
111
+ playwright-cli --raw snapshot
112
+ playwright-cli --raw snapshot e5
113
+ ```
114
+
115
+ ```typescript
116
+ // Generated action
117
+ await page.getByRole('button', { name: 'Submit' }).click();
118
+
119
+ // Manual assertions using the outputs above:
120
+ await expect(page.getByRole('alert', { name: 'Success' })).toBeVisible();
121
+ await expect(page.getByTestId('main-header')).toHaveText('Welcome, user');
122
+ await expect(page.getByRole('textbox', { name: 'Email' })).toHaveValue('user@example.com');
123
+ await expect(page.getByRole('checkbox', { name: 'Enable notifications' })).toBeChecked();
124
+
125
+ // toMatchAriaSnapshot on the whole page, finds a matching region
126
+ await expect(page).toMatchAriaSnapshot(`
127
+ - heading "Welcome, user"
128
+ - link /\\d+ new messages?/
129
+ - button "Sign out"
130
+ `);
131
+
132
+ // toMatchAriaSnapshot scoped to a region
133
+ await expect(page.getByRole('navigation')).toMatchAriaSnapshot(`
134
+ - link "Home"
135
+ - link /\\d+ new messages?/
136
+ - link "Profile"
137
+ `);
138
+ ```
10
139
 
11
140
  ---
12
141
 
@@ -173,7 +302,7 @@ playwright-cli attach tw-XXXX
173
302
 
174
303
  Walk the scenario's `Steps:` one by one with `playwright-cli`, treating the spec as the plan and the live app as the source of truth. If a step is vague ("click the button" — which button?), references an element that no longer exists, or contradicts the app's actual behaviour, use your judgement: update the spec to match what the app really does, then keep going. Editing the spec mid-generation is expected.
175
304
 
176
- Every action prints the equivalent Playwright TypeScript (see [test-generation.md](test-generation.md)):
305
+ Every action prints the equivalent Playwright TypeScript (see [How generation works](#0-how-generation-works)):
177
306
 
178
307
  ```bash
179
308
  playwright-cli snapshot # find refs
@@ -182,7 +311,7 @@ playwright-cli press Enter
182
311
  playwright-cli click e7
183
312
  ```
184
313
 
185
- For each `- expect:` bullet, add an explicit assertion. See [test-generation.md](test-generation.md) for details.
314
+ For each `- expect:` bullet, add an explicit assertion. See [How generation works](#0-how-generation-works) for details.
186
315
 
187
316
  Collect the generated code and write the test file at the path given in the spec:
188
317
 
@@ -300,6 +429,5 @@ Only after the user answers, either update the spec (intentional change) or file
300
429
  | For... | See |
301
430
  |---|---|
302
431
  | `--debug=cli` / attach mechanics | [playwright-tests.md](playwright-tests.md) |
303
- | How `playwright-cli` actions become TS | [test-generation.md](test-generation.md) |
304
432
  | Mocking requests during exploration/generation | [request-mocking.md](request-mocking.md) |
305
433
  | Managing the CLI browser session | [session-management.md](session-management.md) |
@@ -19,7 +19,7 @@ playwright-cli tracing-stop
19
19
 
20
20
  ## Trace Output Files
21
21
 
22
- When you start tracing, Playwright creates a `traces/` directory with several files:
22
+ When you start tracing, Playwright creates a `.playwright-cli/traces/` directory with several files:
23
23
 
24
24
  ### `trace-{timestamp}.trace`
25
25
 
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: playwright-component-testing
3
+ description: Set up component testing with Playwright using a story gallery — scaffold stories and a gallery dev page driven by the built-in mount fixture, no dedicated component-testing runtime. Use when asked to test React or Vue components in isolation with Playwright, or to migrate off @playwright/experimental-ct-react / -vue.
4
+ ---
5
+
6
+ # Component Testing with Playwright
7
+
8
+ Test components with regular Playwright e2e tests against a small **story gallery** page hosted by the app's own dev server. No extra test runner, bundler integration or npm packages are required.
9
+
10
+ ## Concept
11
+
12
+ - A **story** is a tiny wrapper component that embeds the component under test in one specific scenario: hard-coded props, mock data, providers, recorded callbacks. Stories live next to the component in `*.story.tsx` (or `.ts`/`.jsx`/`.js`/`.vue`) files; each named export is one story.
13
+ - The **gallery** is a single page you implement to `references/gallery-spec.md`: it exposes `window.mount(params)` / `window.unmount()` that render a story — resolved from your story files (e.g. with `import.meta.glob`) — into `#root`. It is framework-specific and yours to own — there is no template to copy for it.
14
+ - Tests are plain Playwright tests. The built-in **`mount(storyId, props?)` fixture** (from `@playwright/test`) drives the gallery's `window.mount` and returns a `Locator` for the gallery root (`#root`). Scope the queries from there — `component.getByRole('button').click()`, not `component.click()`. Nothing to scaffold for it.
15
+
16
+ Everything the component needs must be set up *inside the story* (it runs in the browser); everything the test asserts must be observable *through the page* (DOM, URL, network). Where the component takes callbacks, the story creates the state, provides the callbacks and records the state into a hidden form for the test to assert on. `mount(id, props)` passes plain serializable `props` to the story.
17
+
18
+ ## Setup workflow
19
+
20
+ 1. **Detect the framework and bundler.** React vs Vue decides the framework notes and example story to follow. Then:
21
+ - **App runs on Vite** (has `vite.config.*`): the gallery is served by the existing dev server at `/playwright/gallery/index.html` — Vite serves any `.html` file under the project root, the app's plugins/aliases/CSS apply automatically, and `vite build` ignores it. No extra server needed.
22
+ - **Anything else** (Next.js, webpack, no dev server): run a small standalone dev server (e.g. Vite) that serves the gallery page, and point `baseURL` at it. Requires `vite` and the framework plugin as devDependencies.
23
+ 2. **Implement the gallery** to `references/gallery-spec.md`: a page at `<project>/playwright/gallery/` that renders the requested story into `#root`. Start from the worked example in the spec and the framework notes in `references/react.md` / `references/vue.md`. Keep story discovery (`import.meta.glob`) and the framework mount here — this is the only framework-specific glue, so keep it small. Import the app's global CSS the same way the app's own entry does.
24
+ 3. **Configure Playwright** — add to `playwright.config.ts`:
25
+
26
+ ```ts
27
+ projects: [
28
+ {
29
+ name: 'components',
30
+ testDir: './tests/components',
31
+ use: { ...devices['Desktop Chrome'], baseURL: 'http://localhost:5173/playwright/gallery/index.html', serviceWorkers: 'block', reuseContext: true },
32
+ },
33
+ ],
34
+ webServer: {
35
+ command: 'npm run dev', // or: npx vite --config playwright/vite.config.ts
36
+ url: 'http://localhost:5173/playwright/gallery/index.html', // standalone server: http://localhost:3100/playwright/gallery/index.html
37
+ reuseExistingServer: !process.env.CI,
38
+ },
39
+ ```
40
+
41
+ Match the port to the dev server. `mount` navigates to `baseURL`, so set `baseURL` to the gallery's URL. `serviceWorkers: 'block'` keeps the app's own service worker from serving cached responses that would shadow your `page.route()` mocks. `reuseContext: true` reuses the browser context across tests in a worker (as the old component-testing runtime did) — a large speedup for component suites. If the config already has projects/webServer, merge instead of replacing.
42
+ 4. **Write a first story** next to an existing component, modeled on `templates/<react|vue>/Button.story.*`.
43
+ 5. **Write a first spec**, modeled on `templates/react/button.spec.ts`, importing `test`/`expect` from `@playwright/test`.
44
+ 6. **Run**: `npx playwright test --project=components`. Open `http://localhost:5173/playwright/gallery/index.html` in a browser to eyeball all stories.
45
+
46
+ ## Conventions
47
+
48
+ - Story id: path under `src/` without the `.story.*` extension, plus the export name — `src/components/Button.story.tsx` export `Primary` → `components/Button/Primary`. Any unique suffix works too: `mount('Button/Primary')`. A `.story.vue` single-file component is one story, addressable by its path alone (its `default` export). With gallery types (`references/typing.md`) ids are prefixed with the package name: `acme-ui/components/Button/Primary`.
49
+ - One export per scenario. Prefer a new story export over parameterizing an existing one — stories are greppable, reviewable documentation of component states.
50
+
51
+ ## Testing patterns
52
+
53
+ Examples are React; the Vue equivalents differ only in story syntax.
54
+
55
+ ### Callbacks and events
56
+
57
+ **The story owns the state and provides the callbacks.** Where the component takes callbacks, create the state inside the story, wire the callbacks to it, and record the state into a hidden form next to the component. Tests perform operations and assert on the recorded values:
58
+
59
+ ```tsx
60
+ export const Stateful = () => {
61
+ const [expanded, setExpanded] = useState(false);
62
+ return <>
63
+ <Expandable expanded={expanded} setExpanded={setExpanded} title="Title">Details</Expandable>
64
+ <form hidden><input data-testid="expanded" readOnly value={String(expanded)} /></form>
65
+ </>;
66
+ };
67
+ ```
68
+
69
+ ```ts
70
+ test('click should expand', async ({ mount }) => {
71
+ const component = await mount('components/Expandable/Stateful');
72
+ await component.locator('.codicon-chevron-right').click();
73
+ await expect(component.getByTestId('expanded')).toHaveValue('true');
74
+ });
75
+ ```
76
+
77
+ This keeps the whole scenario in the browser: no callback marshalling, the story doubles as documentation, and the recorded state is visible when eyeballing the gallery. Record each observed value in its own `data-testid` input (`String(...)` or `JSON.stringify(...)` for payloads) and assert with `toHaveValue()` — a web-first assertion that retries until the state lands. The negative direction works the same way: perform the operation, then assert the value did **not** change.
78
+
79
+ ### Per-test props
80
+
81
+ When a scenario is genuinely parametric (e.g. a boundary-value sweep), pass props as the second argument to `mount`; the gallery hands them to the story as its props. Keep props to plain serializable data — callbacks belong inside the story.
82
+
83
+ ```tsx
84
+ export const WithTitle = ({ title = 'Default' }: { title?: string }) =>
85
+ <Button title={title} />;
86
+ ```
87
+
88
+ ```ts
89
+ const component = await mount('components/Button/WithTitle', { title: 'Hello' });
90
+ ```
91
+
92
+ Props are type-checked in two optional ways, see `references/typing.md`: pass the story type as a template argument (`mount<typeof WithTitle>('components/Button/WithTitle', { title: 'Hello' })`, no setup), or generate gallery types with a small Vite plugin so the id itself is typed (`mount('acme-ui/components/Button/WithTitle', { title: 'Hello' })`, with autocomplete and rename safety). Vue stories must additionally declare the props at runtime — see the `Typed props` sections in `references/react.md` / `references/vue.md`.
93
+
94
+ ### Prop transitions with `update()`
95
+
96
+ To test how a component reacts to a prop change **without remounting** (state preserved), call `component.update(newProps)` — it re-renders the same story with new props on the existing root:
97
+
98
+ ```ts
99
+ const component = await mount('components/Counter/Default', { value: 1 });
100
+ await expect(component.getByTestId('value')).toHaveText('1');
101
+ await component.update({ value: 2 });
102
+ await expect(component.getByTestId('value')).toHaveText('2');
103
+ ```
104
+
105
+ This requires the gallery to reuse its root/instance (`references/gallery-spec.md`); state survives as long as the story stays the same.
106
+
107
+ ### Multiple states in one test
108
+
109
+ Each `mount()` navigates fresh, so tests are fully isolated and mounting several stories in one test is cheap:
110
+
111
+ ```ts
112
+ await expect(await mount('Button/Primary')).toHaveScreenshot('primary.png');
113
+ await expect(await mount('Button/Disabled')).toHaveScreenshot('disabled.png');
114
+ ```
115
+
116
+ For visual comparison, screenshot the returned root locator (as above), not the page, to avoid asserting on browser chrome.
117
+
118
+ ### Network mocking
119
+
120
+ Use `page.route()` as usual — register routes before `mount()`, since mounting navigates. `serviceWorkers: 'block'` (set in the config above) keeps the app's own service worker from serving cached responses that shadow the routes. Teams with MSW handler libraries can start the worker inside a story or decorator instead.
121
+
122
+ ### Debugging stories
123
+
124
+ Open your gallery URL (`baseURL`) in a browser and call `await window.mount({ story: 'components/Button/Primary' })` from the devtools console — that is exactly what the `mount` fixture does. An unknown story rejects `window.mount`, which surfaces as the test's `mount()` throwing with a real stack. To browse without the console, give your gallery an optional index page.
125
+
126
+ ## Decision points
127
+
128
+ - **Monorepos / non-`src` layouts**: change the glob and the id derivation in your gallery (`references/gallery-spec.md`) to match, and prefix ids with the package name (`references/typing.md`).
129
+ - **Global providers** (theme, i18n, store, router): create a shared `decorator` helper next to the gallery and wrap components in stories; see `references/react.md` / `references/vue.md`.
130
+ ## References
131
+
132
+ - `references/gallery-spec.md` — the gallery endpoint contract to implement (**start here**).
133
+ - `references/typing.md` — optional typing for `mount`: explicit story types vs generated gallery types, with the Vite plugin.
134
+ - `references/react.md` — React walkthrough: providers, StrictMode, CSS.
135
+ - `references/vue.md` — Vue walkthrough: `.story.ts` and `.story.vue` stories, plugins.
136
+ - `references/migration.md` — migrating off `@playwright/experimental-ct-react` / `-vue`.
@@ -0,0 +1,144 @@
1
+ # Gallery contract
2
+
3
+ The **gallery** is a single page, served by your dev server at the URL you set as `baseURL` in your
4
+ Playwright config, that exposes two methods on `window` for Playwright to drive:
5
+
6
+ - `window.mount(params)` — render a story.
7
+ - `window.unmount()` — unmount the current story.
8
+
9
+ The built-in `mount` fixture navigates to the gallery, then calls `window.mount` via
10
+ `page.evaluate()`. Keep props to plain serializable data — where the component takes callbacks,
11
+ the story creates the state, provides the callbacks and records the state into a hidden form for
12
+ the test to assert on.
13
+
14
+ ## `window.mount(params)`
15
+
16
+ `params` is `{ story, props }`, straight from the test's `mount(story, props)` call:
17
+
18
+ - `story` — the story id (string). Resolve it (see id grammar) to a component.
19
+ - `props` — the plain serializable props object passed to the component.
20
+
21
+ Render the resolved component with `props` into `#root`. Return a `Promise` that resolves once the
22
+ component is mounted and **rejects on failure** (unknown story, render throw). The rejection
23
+ surfaces as the test's `await mount(...)` throwing, with a real stack — there is no HTTP-status or
24
+ DOM-attribute signalling.
25
+
26
+ **Reuse the root across calls.** The test's `component.update(props)` calls `window.mount` again
27
+ with the same story and new props, **without navigating**. If you render into the same root /
28
+ instance rather than recreating it, the framework reconciles and component-internal state is
29
+ preserved — that is CT's `update()`. Recreating the root each call (or navigating) resets state, so
30
+ reuse it: create it on first mount and render into it on every call. The framework reconciles,
31
+ remounting on its own only when the story (component type) changes.
32
+
33
+ **`window.mount` is your setup/teardown hook.** It is the browser-side equivalent of CT's
34
+ `beforeMount` / `afterMount`: install providers or plugins, seed a store, start an in-browser mock
35
+ server *before* you render, and run post-render work *after* — all inside this one function,
36
+ branched on the `story` / `props` the test passed. There is no separate hook registry; the function
37
+ you own is the hook.
38
+
39
+ ## `window.unmount()`
40
+
41
+ Unmount the current story from `#root` and return a `Promise`. The test calls it via
42
+ `component.unmount()`. Needed only to assert teardown/cleanup effects — each `mount` navigates
43
+ fresh, so tests are already isolated.
44
+
45
+ ## `#root`
46
+
47
+ Render the component into an element with `id="root"`. `mount` returns a `Locator` for `#root`
48
+ itself, so tests scope their queries from there — `component.getByRole('button').click()`, not
49
+ `component.click()`. Stories are free to render fragments, e.g. the component plus a hidden form
50
+ recording its state.
51
+
52
+ ## Story id grammar (recommended)
53
+
54
+ The gallery owns resolution; `mount` passes the id through untouched. Recommended scheme:
55
+
56
+ - `<path under src, without the .story.* extension>/<ExportName>` — e.g.
57
+ `src/components/Button.story.tsx` export `Primary` → `components/Button/Primary`.
58
+ - Any unique trailing suffix resolves too: `Button/Primary`.
59
+ - A single-file-component story (`Button.story.vue`) is one story, addressed by its path alone
60
+ (its default export): `components/Button`.
61
+
62
+ ## Worked example (React + Vite SPA)
63
+
64
+ An illustration of the contract, **not** a file to copy — implement the equivalent for your stack.
65
+ `import.meta.glob` stays inline here: Vite analyzes it statically, relative to this file, so it
66
+ cannot be moved into shared/shipped code. That is exactly why the gallery is yours to own.
67
+
68
+ ```tsx
69
+ // playwright/gallery/main.tsx
70
+ import { flushSync } from 'react-dom';
71
+ import { createRoot, type Root } from 'react-dom/client';
72
+
73
+ const stories = import.meta.glob('../../src/**/*.story.{tsx,jsx}');
74
+ const id = (f: string) => f.replace(/^(\.\.\/)+src\//, '').replace(/\.story\.\w+$/, '');
75
+
76
+ async function resolve(storyId: string) {
77
+ const sep = storyId.lastIndexOf('/');
78
+ const [path, name] = [storyId.slice(0, sep), storyId.slice(sep + 1)];
79
+ const file = Object.keys(stories).find(f => id(f) === path || id(f).endsWith('/' + path));
80
+ const mod = (file && await stories[file]()) as Record<string, any> | undefined;
81
+ return mod?.[name] ?? mod?.default;
82
+ }
83
+
84
+ const rootEl = document.getElementById('root')!;
85
+ let root: Root | undefined;
86
+
87
+ (window as any).mount = async ({ story, props }: { story: string, props?: Record<string, any> }) => {
88
+ const Story = await resolve(story);
89
+ if (!Story)
90
+ throw new Error(`Unknown story: ${story}`);
91
+ root ??= createRoot(rootEl); // reuse the root so update() reconciles and preserves state
92
+ // flushSync so a render error rejects the promise instead of being swallowed.
93
+ flushSync(() => root!.render(<Story {...props} />));
94
+ };
95
+
96
+ (window as any).unmount = async () => {
97
+ root?.unmount();
98
+ root = undefined;
99
+ };
100
+ ```
101
+
102
+ ```html
103
+ <!-- playwright/gallery/index.html -->
104
+ <!DOCTYPE html>
105
+ <div id="root"></div>
106
+ <script type="module" src="./main.tsx"></script>
107
+ ```
108
+
109
+ ## Vue variant (state-preserving)
110
+
111
+ Vue's `createApp(...).mount()` builds a fresh instance each call, so mount a small **reactive host**
112
+ once and update its refs — updating them re-renders in place, which is what preserves state across
113
+ `update()`:
114
+
115
+ ```ts
116
+ // playwright/gallery/main.ts
117
+ import { createApp, h, shallowRef, type App, type Component } from 'vue';
118
+
119
+ // resolve() and the import.meta.glob are the same as the React example.
120
+ const story = shallowRef<Component | null>(null);
121
+ const props = shallowRef<Record<string, any>>({});
122
+ const host = { render: () => (story.value ? h(story.value, props.value) : null) };
123
+ let app: App | undefined;
124
+
125
+ (window as any).mount = async ({ story: id, props: next }: { story: string, props?: Record<string, any> }) => {
126
+ const resolved = await resolve(id);
127
+ if (!resolved)
128
+ throw new Error(`Unknown story: ${id}`);
129
+ story.value = resolved;
130
+ props.value = next ?? {};
131
+ if (!app) { // mount once; the ref updates above re-render in place
132
+ app = createApp(host);
133
+ app.mount('#root');
134
+ }
135
+ };
136
+
137
+ (window as any).unmount = async () => {
138
+ app?.unmount();
139
+ app = undefined;
140
+ };
141
+ ```
142
+
143
+ Keep the story-resolution glob and the framework mount in this file; everything
144
+ else lives in your stories and tests.
@@ -0,0 +1,89 @@
1
+ # Migrating from @playwright/experimental-ct-react / -vue
2
+
3
+ The CT packages compiled JSX in the test file and marshalled it into the browser. The gallery
4
+ pattern moves the scenario into a story export that runs natively in the browser: structure (which
5
+ component, its children, providers) plus behavior (state and callbacks, recorded into a hidden
6
+ form for the test to assert on). Plain data props travel through `mount(storyId, props)`;
7
+ `update()` and `unmount()` work as before.
8
+
9
+ ## Concept mapping
10
+
11
+ | `@playwright/experimental-ct-*` | Gallery pattern |
12
+ |---|---|
13
+ | `mount(<Button title="…" onClick={spy} />)` | Stateful story: the story provides `onClick`, records the effect into a hidden form input; the test asserts `toHaveValue()` |
14
+ | Plain data props from the test | Unchanged in spirit: `mount(id, props)` |
15
+ | JSX children / slots from the test | Cannot cross — bake each composition into its own story export (Vue: `.story.vue` for slot-heavy scenarios) |
16
+ | `component.update(<Button count={2} />)` | `component.update({ count: 2 })` — state-preserving, needs the gallery to reuse its root (`gallery-spec.md`) |
17
+ | `component.unmount()` | `component.unmount()` — backed by the gallery's `window.unmount()` |
18
+ | `beforeMount`/`afterMount` in `playwright/index.ts` | The body of the gallery's `window.mount` (global), or story decorators (per-story) |
19
+ | `hooksConfig` per-test variation | Props: `mount('App/Routing', { route: '/dashboard' })` — the story/decorator interprets them |
20
+ | `router` fixture / MSW handlers in Node | `page.route()` in the test, or MSW `setupWorker` inside a story/decorator |
21
+ | `playwright/index.html` (styles, fonts, theme) | The gallery's `index.html` / entry module imports |
22
+ | `ctViteConfig`, `ctPort`, `ctTemplateDir`, `ctCacheDir` | Gone — the gallery runs through the app's own dev server; port lives in `webServer` + `baseURL`; location is `playwright/gallery/` |
23
+ | `defineConfig` from `@playwright/experimental-ct-react` | Plain `defineConfig` from `@playwright/test`, with `baseURL` = gallery URL, `serviceWorkers: 'block'`, `reuseContext: true` (see `SKILL.md`) |
24
+
25
+ The CT packages were removed in Playwright 1.63 and are no longer published, so migrate while
26
+ pinned to Playwright 1.62 and upgrade once the last spec is ported.
27
+
28
+ ## Steps
29
+
30
+ 1. Set up the gallery and config per `SKILL.md`. Keep the old CT project running until the last
31
+ spec is migrated.
32
+ 2. For each CT spec, split every `mount(<…/>)` call: JSX structure becomes a story export next to
33
+ the component; plain data props stay in the test as `mount`'s second argument. Callback spies
34
+ become story state recorded into a hidden form. A call site that only varies data props usually
35
+ needs just one generic story that spreads them:
36
+ `export const Default = (props: ButtonProps) => <Button title="Submit" {...props} />`.
37
+ 3. Rewrite the spec: import `test`/`expect` from `@playwright/test`; `mount(<X a={1}/>)`
38
+ → `mount('X/Default', { a: 1 })`; `update(<X a={2}/>)` → `update({ a: 2 })`;
39
+ `unmount()` unchanged. `mount` returns a locator for the gallery root — scope the queries:
40
+ `component.getByRole('button').click()`. Spy assertions become `toHaveValue()` on the story's
41
+ recorded state.
42
+ 4. Port `beforeMount` hooks: app-wide setup into the gallery's `window.mount`; per-test
43
+ `hooksConfig` branches into props interpreted by a story or decorator.
44
+ 5. When all specs are green, delete the CT project from the config, drop the
45
+ `@playwright/experimental-ct-*` dependency, remove `playwright/index.html`,
46
+ `playwright/index.ts*` and `playwright/.cache`, and lift the Playwright version pin.
47
+
48
+ ## Gotchas
49
+
50
+ - **Story ids are strings.** Renaming or moving a story breaks specs at runtime, not compile time
51
+ — and the suffix-matching resolution can silently match a different story after a rename.
52
+ Gallery types (`typing.md`) turn registered ids into a compile-time check.
53
+ - **Per-test JSX is gone.** Any test that built a different JSX tree per test (children matrices,
54
+ inline wrappers) becomes one story export per composition.
55
+
56
+ ## Before / after
57
+
58
+ ```tsx
59
+ // Before (CT)
60
+ import { test, expect } from '@playwright/experimental-ct-react';
61
+ import Button from '../src/components/Button';
62
+
63
+ test('click', async ({ mount }) => {
64
+ const messages: string[] = [];
65
+ const component = await mount(<Button title="Submit" onClick={data => messages.push(data)} />);
66
+ await component.click();
67
+ expect(messages).toEqual(['hello']);
68
+ });
69
+ ```
70
+
71
+ ```tsx
72
+ // After: src/components/Button.story.tsx
73
+ import Button from './Button';
74
+
75
+ export const Default = (props: { onClick?: (data: string) => void }) =>
76
+ <Button title="Submit" {...props} />;
77
+ ```
78
+
79
+ ```ts
80
+ // After: src/components/Button.spec.ts
81
+ import { test, expect } from '@playwright/test';
82
+
83
+ test('click', async ({ mount }) => {
84
+ const messages: string[] = [];
85
+ const component = await mount('components/Button/Default', { onClick: (data: string) => messages.push(data) });
86
+ await component.click();
87
+ expect(messages).toEqual(['hello']);
88
+ });
89
+ ```