@cloud411716/fancy-webnovel 0.1.78 → 0.1.80
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.
- package/package.json +4 -1
- package/plugins/fancy-scan/scripts/scan-output-format.md +1 -1
- package/plugins/fancy-scan/scripts/scraper-registry.md +12 -12
- package/plugins/fancy-scan/scripts/scrapers/cdp-utils.cjs +11 -186
- package/plugins/fancy-scan/scripts/scrapers/node_modules/.package-lock.json +0 -20
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/LICENSE +0 -202
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/NOTICE +0 -5
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/README.md +0 -3
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/ThirdPartyNotices.txt +0 -13
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/install_media_pack.ps1 +0 -5
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/install_webkit_wsl.ps1 +0 -33
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_beta_linux.sh +0 -42
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_beta_mac.sh +0 -13
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_beta_win.ps1 +0 -24
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_stable_linux.sh +0 -42
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_stable_mac.sh +0 -12
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_chrome_stable_win.ps1 +0 -24
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_beta_linux.sh +0 -48
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_beta_mac.sh +0 -11
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_beta_win.ps1 +0 -23
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_dev_linux.sh +0 -48
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_dev_mac.sh +0 -11
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_dev_win.ps1 +0 -23
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_stable_linux.sh +0 -48
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_stable_mac.sh +0 -11
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/bin/reinstall_msedge_stable_win.ps1 +0 -24
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/browsers.json +0 -75
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/cli.js +0 -21
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/index.d.ts +0 -17
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/index.js +0 -17
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/index.mjs +0 -28
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/bootstrap.js +0 -88
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/coreBundle.js +0 -74830
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/entry/cliDaemon.js +0 -5
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/entry/dashboardApp.js +0 -3
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/entry/mcp.js +0 -10
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/entry/oopBrowserDownload.js +0 -3
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/package.js +0 -50
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/server/chromium/appIcon.png +0 -0
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/server/electron/loader.js +0 -118
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/serverRegistry.js +0 -7347
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/serverRegistry.js.LICENSE +0 -354
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/channelSessions.js +0 -141
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/cli.js +0 -6
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/help.json +0 -708
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/minimist.js +0 -128
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/output.js +0 -343
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/program.js +0 -404
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/registry.js +0 -176
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/cli-client/session.js +0 -258
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/dashboard/appIcon.png +0 -0
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/SKILL.md +0 -420
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/element-attributes.md +0 -23
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/playwright-tests.md +0 -39
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/request-mocking.md +0 -87
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/running-code.md +0 -241
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/session-management.md +0 -225
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/storage-state.md +0 -275
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/test-generation.md +0 -433
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/tracing.md +0 -139
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-cli/references/video-recording.md +0 -143
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +0 -143
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +0 -144
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +0 -85
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +0 -67
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +0 -75
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/skills/playwright-trace/SKILL.md +0 -171
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/utils/extension.js +0 -101
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/tools/utils/socketConnection.js +0 -108
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/utilsBundle.js +0 -90764
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +0 -2179
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/codicon-DCmgc-ay.ttf +0 -0
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-1bWoP6pv.svg +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-beta-k3eOH_eK.svg +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/firefox-nightly-Cp5nfeDT.svg +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/index-CyWAfh-p.js +0 -11
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/index-DhC616m4.css +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/assets/safari-na3_-uQk.svg +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/index.html +0 -29
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/dashboard/playwright-logo.svg +0 -24
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/htmlReport/index.html +0 -16
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/htmlReport/report.css +0 -2
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/htmlReport/report.js +0 -32
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +0 -32
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/assets/codicon-DCmgc-ay.ttf +0 -0
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/assets/index-DYjdXIbE.js +0 -129
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/assets/index-l_lX622x.css +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/index.html +0 -29
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/recorder/playwright-logo.svg +0 -9
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-rXmQmLUY.js +0 -32
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B-dXF5JN.js +0 -181
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-BuZfJS5v.js +0 -7
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/codicon.DCmgc-ay.ttf +0 -0
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/index.KZ4wOW1K.js +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/index.html +0 -44
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/manifest.webmanifest +0 -16
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/playwright-logo.svg +0 -9
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +0 -10
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +0 -4
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/uiMode.C7UW1sC9.css +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/uiMode.Dzuouizj.js +0 -5
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +0 -18
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +0 -1
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/webp_codec.LICENSE +0 -173
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/lib/xdg-open +0 -1267
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/package.json +0 -34
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/types/protocol.d.ts +0 -24842
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/types/structs.d.ts +0 -52
- package/plugins/fancy-scan/scripts/scrapers/node_modules/playwright-core/types/types.d.ts +0 -26188
- package/plugins/fancy-scan/scripts/scrapers/package.json +0 -9
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
# Tracing
|
|
2
|
-
|
|
3
|
-
Capture detailed execution traces for debugging and analysis. Traces include DOM snapshots, screenshots, network activity, and console logs.
|
|
4
|
-
|
|
5
|
-
## Basic Usage
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# Start trace recording
|
|
9
|
-
playwright-cli tracing-start
|
|
10
|
-
|
|
11
|
-
# Perform actions
|
|
12
|
-
playwright-cli open https://example.com
|
|
13
|
-
playwright-cli click e1
|
|
14
|
-
playwright-cli fill e2 "test"
|
|
15
|
-
|
|
16
|
-
# Stop trace recording
|
|
17
|
-
playwright-cli tracing-stop
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Trace Output Files
|
|
21
|
-
|
|
22
|
-
When you start tracing, Playwright creates a `traces/` directory with several files:
|
|
23
|
-
|
|
24
|
-
### `trace-{timestamp}.trace`
|
|
25
|
-
|
|
26
|
-
**Action log** - The main trace file containing:
|
|
27
|
-
- Every action performed (clicks, fills, navigations)
|
|
28
|
-
- DOM snapshots before and after each action
|
|
29
|
-
- Screenshots at each step
|
|
30
|
-
- Timing information
|
|
31
|
-
- Console messages
|
|
32
|
-
- Source locations
|
|
33
|
-
|
|
34
|
-
### `trace-{timestamp}.network`
|
|
35
|
-
|
|
36
|
-
**Network log** - Complete network activity:
|
|
37
|
-
- All HTTP requests and responses
|
|
38
|
-
- Request headers and bodies
|
|
39
|
-
- Response headers and bodies
|
|
40
|
-
- Timing (DNS, connect, TLS, TTFB, download)
|
|
41
|
-
- Resource sizes
|
|
42
|
-
- Failed requests and errors
|
|
43
|
-
|
|
44
|
-
### `resources/`
|
|
45
|
-
|
|
46
|
-
**Resources directory** - Cached resources:
|
|
47
|
-
- Images, fonts, stylesheets, scripts
|
|
48
|
-
- Response bodies for replay
|
|
49
|
-
- Assets needed to reconstruct page state
|
|
50
|
-
|
|
51
|
-
## What Traces Capture
|
|
52
|
-
|
|
53
|
-
| Category | Details |
|
|
54
|
-
|----------|---------|
|
|
55
|
-
| **Actions** | Clicks, fills, hovers, keyboard input, navigations |
|
|
56
|
-
| **DOM** | Full DOM snapshot before/after each action |
|
|
57
|
-
| **Screenshots** | Visual state at each step |
|
|
58
|
-
| **Network** | All requests, responses, headers, bodies, timing |
|
|
59
|
-
| **Console** | All console.log, warn, error messages |
|
|
60
|
-
| **Timing** | Precise timing for each operation |
|
|
61
|
-
|
|
62
|
-
## Use Cases
|
|
63
|
-
|
|
64
|
-
### Debugging Failed Actions
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
playwright-cli tracing-start
|
|
68
|
-
playwright-cli open https://app.example.com
|
|
69
|
-
|
|
70
|
-
# This click fails - why?
|
|
71
|
-
playwright-cli click e5
|
|
72
|
-
|
|
73
|
-
playwright-cli tracing-stop
|
|
74
|
-
# Open trace to see DOM state when click was attempted
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Analyzing Performance
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
playwright-cli tracing-start
|
|
81
|
-
playwright-cli open https://slow-site.com
|
|
82
|
-
playwright-cli tracing-stop
|
|
83
|
-
|
|
84
|
-
# View network waterfall to identify slow resources
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Capturing Evidence
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
# Record a complete user flow for documentation
|
|
91
|
-
playwright-cli tracing-start
|
|
92
|
-
|
|
93
|
-
playwright-cli open https://app.example.com/checkout
|
|
94
|
-
playwright-cli fill e1 "4111111111111111"
|
|
95
|
-
playwright-cli fill e2 "12/25"
|
|
96
|
-
playwright-cli fill e3 "123"
|
|
97
|
-
playwright-cli click e4
|
|
98
|
-
|
|
99
|
-
playwright-cli tracing-stop
|
|
100
|
-
# Trace shows exact sequence of events
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Trace vs Video vs Screenshot
|
|
104
|
-
|
|
105
|
-
| Feature | Trace | Video | Screenshot |
|
|
106
|
-
|---------|-------|-------|------------|
|
|
107
|
-
| **Format** | .trace file | .webm video | .png/.jpeg image |
|
|
108
|
-
| **DOM inspection** | Yes | No | No |
|
|
109
|
-
| **Network details** | Yes | No | No |
|
|
110
|
-
| **Step-by-step replay** | Yes | Continuous | Single frame |
|
|
111
|
-
| **File size** | Medium | Large | Small |
|
|
112
|
-
| **Best for** | Debugging | Demos | Quick capture |
|
|
113
|
-
|
|
114
|
-
## Best Practices
|
|
115
|
-
|
|
116
|
-
### 1. Start Tracing Before the Problem
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
# Trace the entire flow, not just the failing step
|
|
120
|
-
playwright-cli tracing-start
|
|
121
|
-
playwright-cli open https://example.com
|
|
122
|
-
# ... all steps leading to the issue ...
|
|
123
|
-
playwright-cli tracing-stop
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### 2. Clean Up Old Traces
|
|
127
|
-
|
|
128
|
-
Traces can consume significant disk space:
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
# Remove traces older than 7 days
|
|
132
|
-
find .playwright-cli/traces -mtime +7 -delete
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Limitations
|
|
136
|
-
|
|
137
|
-
- Traces add overhead to automation
|
|
138
|
-
- Large traces can consume significant disk space
|
|
139
|
-
- Some dynamic content may not replay perfectly
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# Video Recording
|
|
2
|
-
|
|
3
|
-
Capture browser automation sessions as video for debugging, documentation, or verification. Produces WebM (VP8/VP9 codec).
|
|
4
|
-
|
|
5
|
-
## Basic Recording
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# Open browser first
|
|
9
|
-
playwright-cli open
|
|
10
|
-
|
|
11
|
-
# Start recording
|
|
12
|
-
playwright-cli video-start demo.webm
|
|
13
|
-
|
|
14
|
-
# Add a chapter marker for section transitions
|
|
15
|
-
playwright-cli video-chapter "Getting Started" --description="Opening the homepage" --duration=2000
|
|
16
|
-
|
|
17
|
-
# Navigate and perform actions
|
|
18
|
-
playwright-cli goto https://example.com
|
|
19
|
-
playwright-cli snapshot
|
|
20
|
-
playwright-cli click e1
|
|
21
|
-
|
|
22
|
-
# Add another chapter
|
|
23
|
-
playwright-cli video-chapter "Filling Form" --description="Entering test data" --duration=2000
|
|
24
|
-
playwright-cli fill e2 "test input"
|
|
25
|
-
|
|
26
|
-
# Stop and save
|
|
27
|
-
playwright-cli video-stop
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Best Practices
|
|
31
|
-
|
|
32
|
-
### 1. Use Descriptive Filenames
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
# Include context in filename
|
|
36
|
-
playwright-cli video-start recordings/login-flow-2024-01-15.webm
|
|
37
|
-
playwright-cli video-start recordings/checkout-test-run-42.webm
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### 2. Record entire hero scripts.
|
|
41
|
-
|
|
42
|
-
When recording a video for the user or as a proof of work, it is best to create a code snippet and execute it with run-code.
|
|
43
|
-
It allows inserting appropriate pauses between the actions and annotating the video. There are new Playwright APIs for that.
|
|
44
|
-
|
|
45
|
-
1) Perform scenario using CLI and take note of all locators and actions. You'll need those locators to request their bounding boxes for highlight.
|
|
46
|
-
2) Create a file with the intended script for video (below). Use pressSequentially w/ delay for nice typing, make reasonable pauses.
|
|
47
|
-
3) Use playwright-cli run-code --filename your-script.js
|
|
48
|
-
|
|
49
|
-
**Important**: Overlays are `pointer-events: none` — they do not interfere with page interactions. You can safely keep sticky overlays visible while clicking, filling, or performing any actions on the page.
|
|
50
|
-
|
|
51
|
-
```js
|
|
52
|
-
async page => {
|
|
53
|
-
await page.screencast.start({ path: 'video.webm', size: { width: 1280, height: 800 } });
|
|
54
|
-
await page.goto('https://demo.playwright.dev/todomvc');
|
|
55
|
-
|
|
56
|
-
// Show a chapter card — blurs the page and shows a dialog.
|
|
57
|
-
// Blocks until duration expires, then auto-removes.
|
|
58
|
-
// Use this for simple use cases, but always feel free to hand-craft your own beautiful
|
|
59
|
-
// overlay via await page.screencast.showOverlay().
|
|
60
|
-
await page.screencast.showChapter('Adding Todo Items', {
|
|
61
|
-
description: 'We will add several items to the todo list.',
|
|
62
|
-
duration: 2000,
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// Perform action
|
|
66
|
-
await page.getByRole('textbox', { name: 'What needs to be done?' }).pressSequentially('Walk the dog', { delay: 60 });
|
|
67
|
-
await page.getByRole('textbox', { name: 'What needs to be done?' }).press('Enter');
|
|
68
|
-
await page.waitForTimeout(1000);
|
|
69
|
-
|
|
70
|
-
// Show next chapter
|
|
71
|
-
await page.screencast.showChapter('Verifying Results', {
|
|
72
|
-
description: 'Checking the item appeared in the list.',
|
|
73
|
-
duration: 2000,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Add a sticky annotation that stays while you perform actions.
|
|
77
|
-
// Overlays are pointer-events: none, so they won't block clicks.
|
|
78
|
-
const annotation = await page.screencast.showOverlay(`
|
|
79
|
-
<div style="position: absolute; top: 8px; right: 8px;
|
|
80
|
-
padding: 6px 12px; background: rgba(0,0,0,0.7);
|
|
81
|
-
border-radius: 8px; font-size: 13px; color: white;">
|
|
82
|
-
✓ Item added successfully
|
|
83
|
-
</div>
|
|
84
|
-
`);
|
|
85
|
-
|
|
86
|
-
// Perform more actions while the annotation is visible
|
|
87
|
-
await page.getByRole('textbox', { name: 'What needs to be done?' }).pressSequentially('Buy groceries', { delay: 60 });
|
|
88
|
-
await page.getByRole('textbox', { name: 'What needs to be done?' }).press('Enter');
|
|
89
|
-
await page.waitForTimeout(1500);
|
|
90
|
-
|
|
91
|
-
// Remove the annotation when done
|
|
92
|
-
await annotation.dispose();
|
|
93
|
-
|
|
94
|
-
// You can also highlight relevant locators and provide contextual annotations.
|
|
95
|
-
const bounds = await page.getByText('Walk the dog').boundingBox();
|
|
96
|
-
await page.screencast.showOverlay(`
|
|
97
|
-
<div style="position: absolute;
|
|
98
|
-
top: ${bounds.y}px;
|
|
99
|
-
left: ${bounds.x}px;
|
|
100
|
-
width: ${bounds.width}px;
|
|
101
|
-
height: ${bounds.height}px;
|
|
102
|
-
border: 1px solid red;">
|
|
103
|
-
</div>
|
|
104
|
-
<div style="position: absolute;
|
|
105
|
-
top: ${bounds.y + bounds.height + 5}px;
|
|
106
|
-
left: ${bounds.x + bounds.width / 2}px;
|
|
107
|
-
transform: translateX(-50%);
|
|
108
|
-
padding: 6px;
|
|
109
|
-
background: #808080;
|
|
110
|
-
border-radius: 10px;
|
|
111
|
-
font-size: 14px;
|
|
112
|
-
color: white;">Check it out, it is right above this text
|
|
113
|
-
</div>
|
|
114
|
-
`, { duration: 2000 });
|
|
115
|
-
|
|
116
|
-
await page.screencast.stop();
|
|
117
|
-
}
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
Embrace creativity, overlays are powerful.
|
|
121
|
-
|
|
122
|
-
### Overlay API Summary
|
|
123
|
-
|
|
124
|
-
| Method | Use Case |
|
|
125
|
-
|--------|----------|
|
|
126
|
-
| `page.screencast.showChapter(title, { description?, duration?, styleSheet? })` | Full-screen chapter card with blurred backdrop — ideal for section transitions |
|
|
127
|
-
| `page.screencast.showOverlay(html, { duration? })` | Custom HTML overlay — use for callouts, labels, highlights |
|
|
128
|
-
| `disposable.dispose()` | Remove a sticky overlay added without duration |
|
|
129
|
-
| `page.screencast.hideOverlays()` / `page.screencast.showOverlays()` | Temporarily hide/show all overlays |
|
|
130
|
-
|
|
131
|
-
## Tracing vs Video
|
|
132
|
-
|
|
133
|
-
| Feature | Video | Tracing |
|
|
134
|
-
|---------|-------|---------|
|
|
135
|
-
| Output | WebM file | Trace file (viewable in Trace Viewer) |
|
|
136
|
-
| Shows | Visual recording | DOM snapshots, network, console, actions |
|
|
137
|
-
| Use case | Demos, documentation | Debugging, analysis |
|
|
138
|
-
| Size | Larger | Smaller |
|
|
139
|
-
|
|
140
|
-
## Limitations
|
|
141
|
-
|
|
142
|
-
- Recording adds slight overhead to automation
|
|
143
|
-
- Large recordings can consume significant disk space
|
|
@@ -1,143 +0,0 @@
|
|
|
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).
|
|
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
|
-
`mount` is generic over the story: pass the story type as a template argument to type-check the props (and `update()`):
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
import type { WithTitle } from './Button.story';
|
|
96
|
-
|
|
97
|
-
const component = await mount<typeof WithTitle>('components/Button/WithTitle', { title: 'Hello' });
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
This works for React and Vue stories alike; Vue stories must additionally declare the props at runtime — see the `Typed props` sections in `references/react.md` / `references/vue.md`.
|
|
101
|
-
|
|
102
|
-
### Prop transitions with `update()`
|
|
103
|
-
|
|
104
|
-
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:
|
|
105
|
-
|
|
106
|
-
```ts
|
|
107
|
-
const component = await mount('components/Counter/Default', { value: 1 });
|
|
108
|
-
await expect(component.getByTestId('value')).toHaveText('1');
|
|
109
|
-
await component.update({ value: 2 });
|
|
110
|
-
await expect(component.getByTestId('value')).toHaveText('2');
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
This requires the gallery to reuse its root/instance (`references/gallery-spec.md`); state survives as long as the story stays the same.
|
|
114
|
-
|
|
115
|
-
### Multiple states in one test
|
|
116
|
-
|
|
117
|
-
Each `mount()` navigates fresh, so tests are fully isolated and mounting several stories in one test is cheap:
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
await expect(await mount('Button/Primary')).toHaveScreenshot('primary.png');
|
|
121
|
-
await expect(await mount('Button/Disabled')).toHaveScreenshot('disabled.png');
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
For visual comparison, screenshot the returned root locator (as above), not the page, to avoid asserting on browser chrome.
|
|
125
|
-
|
|
126
|
-
### Network mocking
|
|
127
|
-
|
|
128
|
-
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.
|
|
129
|
-
|
|
130
|
-
### Debugging stories
|
|
131
|
-
|
|
132
|
-
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.
|
|
133
|
-
|
|
134
|
-
## Decision points
|
|
135
|
-
|
|
136
|
-
- **Monorepos / non-`src` layouts**: change the glob and the id derivation in your gallery (`references/gallery-spec.md`) to match.
|
|
137
|
-
- **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`.
|
|
138
|
-
## References
|
|
139
|
-
|
|
140
|
-
- `references/gallery-spec.md` — the gallery endpoint contract to implement (**start here**).
|
|
141
|
-
- `references/react.md` — React walkthrough: providers, StrictMode, CSS.
|
|
142
|
-
- `references/vue.md` — Vue walkthrough: `.story.ts` and `.story.vue` stories, plugins.
|
|
143
|
-
- `references/migration.md` — migrating off `@playwright/experimental-ct-react` / `-vue`.
|
|
@@ -1,144 +0,0 @@
|
|
|
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.
|
|
@@ -1,85 +0,0 @@
|
|
|
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
|
-
## Steps
|
|
26
|
-
|
|
27
|
-
1. Set up the gallery and config per `SKILL.md`. Keep the old CT project running until the last
|
|
28
|
-
spec is migrated.
|
|
29
|
-
2. For each CT spec, split every `mount(<…/>)` call: JSX structure becomes a story export next to
|
|
30
|
-
the component; plain data props stay in the test as `mount`'s second argument. Callback spies
|
|
31
|
-
become story state recorded into a hidden form. A call site that only varies data props usually
|
|
32
|
-
needs just one generic story that spreads them:
|
|
33
|
-
`export const Default = (props: ButtonProps) => <Button title="Submit" {...props} />`.
|
|
34
|
-
3. Rewrite the spec: import `test`/`expect` from `@playwright/test`; `mount(<X a={1}/>)`
|
|
35
|
-
→ `mount('X/Default', { a: 1 })`; `update(<X a={2}/>)` → `update({ a: 2 })`;
|
|
36
|
-
`unmount()` unchanged. `mount` returns a locator for the gallery root — scope the queries:
|
|
37
|
-
`component.getByRole('button').click()`. Spy assertions become `toHaveValue()` on the story's
|
|
38
|
-
recorded state.
|
|
39
|
-
4. Port `beforeMount` hooks: app-wide setup into the gallery's `window.mount`; per-test
|
|
40
|
-
`hooksConfig` branches into props interpreted by a story or decorator.
|
|
41
|
-
5. When all specs are green, delete the CT project from the config, drop the
|
|
42
|
-
`@playwright/experimental-ct-*` dependency, and remove `playwright/index.html`,
|
|
43
|
-
`playwright/index.ts*` and `playwright/.cache`.
|
|
44
|
-
|
|
45
|
-
## Gotchas
|
|
46
|
-
|
|
47
|
-
- **Story ids are strings.** Renaming or moving a story breaks specs at runtime, not compile time
|
|
48
|
-
— and the suffix-matching resolution can silently match a different story after a rename.
|
|
49
|
-
- **Per-test JSX is gone.** Any test that built a different JSX tree per test (children matrices,
|
|
50
|
-
inline wrappers) becomes one story export per composition.
|
|
51
|
-
|
|
52
|
-
## Before / after
|
|
53
|
-
|
|
54
|
-
```tsx
|
|
55
|
-
// Before (CT)
|
|
56
|
-
import { test, expect } from '@playwright/experimental-ct-react';
|
|
57
|
-
import Button from '../src/components/Button';
|
|
58
|
-
|
|
59
|
-
test('click', async ({ mount }) => {
|
|
60
|
-
const messages: string[] = [];
|
|
61
|
-
const component = await mount(<Button title="Submit" onClick={data => messages.push(data)} />);
|
|
62
|
-
await component.click();
|
|
63
|
-
expect(messages).toEqual(['hello']);
|
|
64
|
-
});
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
```tsx
|
|
68
|
-
// After: src/components/Button.story.tsx
|
|
69
|
-
import Button from './Button';
|
|
70
|
-
|
|
71
|
-
export const Default = (props: { onClick?: (data: string) => void }) =>
|
|
72
|
-
<Button title="Submit" {...props} />;
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
```ts
|
|
76
|
-
// After: src/components/Button.spec.ts
|
|
77
|
-
import { test, expect } from '@playwright/test';
|
|
78
|
-
|
|
79
|
-
test('click', async ({ mount }) => {
|
|
80
|
-
const messages: string[] = [];
|
|
81
|
-
const component = await mount('components/Button/Default', { onClick: (data: string) => messages.push(data) });
|
|
82
|
-
await component.click();
|
|
83
|
-
expect(messages).toEqual(['hello']);
|
|
84
|
-
});
|
|
85
|
-
```
|