@ralphralphai/playwright 2.8.0 → 2.9.0
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/README.md +262 -222
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +6 -3
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/reporter.cjs +9 -6
- package/dist/reporter.cjs.map +1 -1
- package/dist/reporter.d.mts +6 -10
- package/dist/reporter.mjs +9 -6
- package/dist/reporter.mjs.map +1 -1
- package/dist/{upload-KEwXpYxG.cjs → upload-8iYQZaCa.cjs} +5 -4
- package/dist/upload-8iYQZaCa.cjs.map +1 -0
- package/dist/{upload-CcdDM33Z.mjs → upload-DKeVoDQ2.mjs} +5 -4
- package/dist/upload-DKeVoDQ2.mjs.map +1 -0
- package/package.json +1 -1
- package/dist/upload-CcdDM33Z.mjs.map +0 -1
- package/dist/upload-KEwXpYxG.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -3,19 +3,26 @@
|
|
|
3
3
|
Turn your Playwright tests into a map of your app for
|
|
4
4
|
[Ralph](https://ralphralph.ai).
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
screenshot, the URL, and the position of every element with a
|
|
6
|
+
Every page an `@ralph`-tagged test visits becomes a **node** in a graph: a
|
|
7
|
+
full-page screenshot, the URL, and the position of every element with a
|
|
8
|
+
`data-track-id`. The actions the test takes between two pages (clicks, fills,
|
|
9
|
+
navigation) become the **edge** between them. Ralph matches your production
|
|
10
|
+
tracker events against this graph.
|
|
11
|
+
|
|
8
12
|
Untagged tests are untouched, and recording is off unless you turn it on.
|
|
9
13
|
|
|
10
14
|
## Install
|
|
11
15
|
|
|
12
|
-
Requires Node.js 20.19+ and Playwright Test 1.63
|
|
16
|
+
Requires Node.js 20.19+ and Playwright Test 1.63+.
|
|
13
17
|
|
|
14
18
|
```sh
|
|
15
19
|
pnpm add -D @ralphralphai/playwright @playwright/test
|
|
16
20
|
pnpm exec playwright install chromium
|
|
17
21
|
```
|
|
18
22
|
|
|
23
|
+
**IMPORTANT** It is currently only tested on chromium. The Firefox support will
|
|
24
|
+
be added soon.
|
|
25
|
+
|
|
19
26
|
## Quick start
|
|
20
27
|
|
|
21
28
|
**1. Add a `ralph.jsonc`** next to `playwright.config.ts`:
|
|
@@ -30,7 +37,15 @@ pnpm exec playwright install chromium
|
|
|
30
37
|
See [`@ralphralphai/config`](https://www.npmjs.com/package/@ralphralphai/config)
|
|
31
38
|
for all fields.
|
|
32
39
|
|
|
33
|
-
**2.
|
|
40
|
+
**2. Tag your elements** with `data-track-id`, using the same ids as your
|
|
41
|
+
[`@ralphralphai/tracker`](https://www.npmjs.com/package/@ralphralphai/tracker)
|
|
42
|
+
setup:
|
|
43
|
+
|
|
44
|
+
```html
|
|
45
|
+
<button data-track-id="add-to-cart">Add to cart</button>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**3. Import `test` from this package and tag tests with `@ralph`:**
|
|
34
49
|
|
|
35
50
|
```ts
|
|
36
51
|
import { test, expect } from "@ralphralphai/playwright";
|
|
@@ -42,7 +57,7 @@ test("checkout", { tag: "@ralph" }, async ({ page }) => {
|
|
|
42
57
|
});
|
|
43
58
|
```
|
|
44
59
|
|
|
45
|
-
**
|
|
60
|
+
**4. Add the Ralph reporter** in `playwright.config.ts`:
|
|
46
61
|
|
|
47
62
|
```ts
|
|
48
63
|
export default defineConfig({
|
|
@@ -50,25 +65,92 @@ export default defineConfig({
|
|
|
50
65
|
});
|
|
51
66
|
```
|
|
52
67
|
|
|
53
|
-
**
|
|
68
|
+
**5. Run with an upload key.** Create one in the project dashboard, then:
|
|
54
69
|
|
|
55
70
|
```sh
|
|
56
71
|
RALPH_UPLOAD_KEY=… RALPH_APP_ID=… pnpm exec playwright test
|
|
57
72
|
```
|
|
58
73
|
|
|
59
|
-
|
|
74
|
+
This runs in `upload` mode, the default whenever `RALPH_UPLOAD_KEY` is set. The
|
|
75
|
+
reporter sends the recorded run to Ralph, which builds the graph from it. An
|
|
76
|
+
upload is a preview: it isn't tied to any release, so production tracker
|
|
77
|
+
events aren't matched against it. Use it to check the graph while you write
|
|
78
|
+
tests, then [deploy](#deploying-a-release) once it looks right.
|
|
60
79
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
setup:
|
|
80
|
+
The reporter prints a link before the upload starts. Open it to watch the run
|
|
81
|
+
upload, get processed, and then browse the graph:
|
|
64
82
|
|
|
65
|
-
```html
|
|
66
|
-
<button data-track-id="add-to-cart">Add to cart</button>
|
|
67
83
|
```
|
|
84
|
+
Ralph: uploading the run. Follow the upload and processing at https://dash.ralphralph.ai/uploads/0f6c6a2e-…
|
|
85
|
+
Ralph: uploaded the run. Ralph is processing it at https://dash.ralphralph.ai/uploads/0f6c6a2e-…
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`RALPH_UPLOAD_KEY` must be an environment variable. `RALPH_APP_ID` can be
|
|
89
|
+
set as the reporter's `appId` option.
|
|
90
|
+
|
|
91
|
+
## Deploying a release
|
|
92
|
+
|
|
93
|
+
Once the previewed graph looks right, upload it as the graph of the build you
|
|
94
|
+
deploy. Ralph then matches that build's tracker events to it. Set
|
|
95
|
+
`RALPH_MODE=deploy` and `RALPH_BUILD_ID`, usually the commit SHA:
|
|
96
|
+
|
|
97
|
+
```sh
|
|
98
|
+
RALPH_MODE=deploy RALPH_BUILD_ID="$COMMIT_SHA" \
|
|
99
|
+
RALPH_UPLOAD_KEY=… RALPH_APP_ID=… pnpm exec playwright test
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
If the build goes live before the test run finishes, register it first so the
|
|
103
|
+
tracker picks up its config right away:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
curl --fail-with-body -X POST https://api.ralphralph.ai/api/upload/deployment/register \
|
|
107
|
+
-H "Authorization: Bearer $RALPH_UPLOAD_KEY" -H 'content-type: application/json' \
|
|
108
|
+
-d "{\"externalAppId\":\"$RALPH_APP_ID\",\"buildId\":\"$COMMIT_SHA\",
|
|
109
|
+
\"reserveArtifact\":false,\"config\":$(cat ralph.json)}"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`ralph.json` is your `ralph.jsonc` as plain JSON, without comments or trailing
|
|
113
|
+
commas. The test run that follows registers the same build again and uploads
|
|
114
|
+
into it.
|
|
115
|
+
|
|
116
|
+
## Diffing deployments
|
|
117
|
+
|
|
118
|
+
Along with the screenshot, each node records an ARIA snapshot of the page: the
|
|
119
|
+
accessibility tree, each element's box, its distinctive styles (fonts, colors,
|
|
120
|
+
spacing, borders), and a hash of each image. Ralph diffs a node's snapshot
|
|
121
|
+
against the same node in the previous deployment to show what changed.
|
|
122
|
+
|
|
123
|
+
Hidden elements, elements clipped by an ancestor's overflow, and tiny iframes
|
|
124
|
+
such as tracking pixels are left out, so they never show up as changes.
|
|
125
|
+
|
|
126
|
+
Two tools keep content that changes on its own from showing up as a change:
|
|
68
127
|
|
|
69
|
-
|
|
128
|
+
- **`data-ralph-dynamic`** marks a subtree whose content the server generates
|
|
129
|
+
on every request, such as a blog listing or a price quote. The diff compares
|
|
130
|
+
only its structure and styles, not its text, links, or images:
|
|
70
131
|
|
|
71
|
-
|
|
132
|
+
```html
|
|
133
|
+
<div>
|
|
134
|
+
<article>
|
|
135
|
+
<p data-ralph-dynamic>Posted 3 days ago</p>
|
|
136
|
+
</article>
|
|
137
|
+
</div>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- **`ignoreInDiff`** removes a subtree from the snapshot entirely, for parts
|
|
141
|
+
that aren't yours to diff, such as ads, chat widgets, or live counters.
|
|
142
|
+
Matched elements still appear in the screenshot:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
ralphOptions: {
|
|
146
|
+
ignoreInDiff: ['.ad-slot', '#intercom-container', 'live-clock'],
|
|
147
|
+
},
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Use `data-ralph-dynamic` when you still want layout and style changes in that
|
|
151
|
+
area reported, and `ignoreInDiff` when you don't.
|
|
152
|
+
|
|
153
|
+
## How recording works
|
|
72
154
|
|
|
73
155
|
A page is recorded whenever the URL changes in any tab of the test: page loads,
|
|
74
156
|
redirects (the final page), SPA route changes, query and hash changes, and new
|
|
@@ -86,56 +168,25 @@ ralphOptions: {
|
|
|
86
168
|
},
|
|
87
169
|
```
|
|
88
170
|
|
|
89
|
-
|
|
90
|
-
|
|
171
|
+
Recordings run in the background. A page the test leaves before its screenshot
|
|
172
|
+
is done stays in the graph as `uncaptured`, with no screenshot. See
|
|
173
|
+
[`waitForCapture`](#waiting-for-capture).
|
|
91
174
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`
|
|
175
|
+
Each action between two nodes is stored on the edge with the element it landed
|
|
176
|
+
on (role, name, `data-track-id`) and where that element was on the earlier
|
|
177
|
+
screenshot. Ralph shows it as, e.g., `Click button "Checkout"`. Recorded
|
|
178
|
+
actions are clicks, hovers, fills, key presses, selects, uploads, drags, and
|
|
179
|
+
`goto` / back / forward / reload. Text typed into password or one-time-code
|
|
180
|
+
fields is never recorded.
|
|
95
181
|
|
|
96
|
-
|
|
97
|
-
tracked-element geometry, and ARIA snapshot, enable:
|
|
182
|
+
## Test API
|
|
98
183
|
|
|
99
|
-
|
|
100
|
-
ralphOptions: {
|
|
101
|
-
freezeDuringCapture: true,
|
|
102
|
-
},
|
|
103
|
-
```
|
|
184
|
+
The `ralph` fixture controls recording from inside a test.
|
|
104
185
|
|
|
105
|
-
|
|
106
|
-
finishes running finite CSS transitions, CSS animations, and Web Animations,
|
|
107
|
-
and watches DOM changes in the page, its frames, and open shadow roots. Timers
|
|
108
|
-
and animation frames keep running while it waits for 150 ms without changes,
|
|
109
|
-
so JavaScript-driven animations can finish without adding callbacks or markers
|
|
110
|
-
to your app. Animations started by completion callbacks are checked again.
|
|
186
|
+
### Recording once the page is ready
|
|
111
187
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
temporarily paused while settling. Then timers and all remaining browser
|
|
115
|
-
animations pause during capture and resume afterward, including on failure.
|
|
116
|
-
Animations already paused by the app stay paused; finished animations stay
|
|
117
|
-
finished.
|
|
118
|
-
|
|
119
|
-
The DOM quiet check is a heuristic, not a guarantee that every animation has
|
|
120
|
-
ended. Long-running JavaScript animations can be captured midway when the
|
|
121
|
-
budget expires. Future delayed work and drawing inside canvas or WebGL aren't
|
|
122
|
-
detected by this check.
|
|
123
|
-
|
|
124
|
-
This option defaults to `false`. The clock replaces the native timer APIs for
|
|
125
|
-
the lifetime of the context, so don't combine it with manual `page.clock` or
|
|
126
|
-
`context.clock` control. Playwright requires a future pause time: Ralph uses
|
|
127
|
-
the page's current time plus 100 ms, which advances the clock and can fire due
|
|
128
|
-
callbacks once before freezing. Time resumes from there without catching up
|
|
129
|
-
through the capture duration. All tabs in a context share the clock, so their
|
|
130
|
-
timers pause together and their captures run one at a time. Network responses,
|
|
131
|
-
workers, and media playback are not frozen, so unrelated updates can still
|
|
132
|
-
change the page.
|
|
133
|
-
|
|
134
|
-
### Explicitly, once the page is ready
|
|
135
|
-
|
|
136
|
-
A capture taken when the URL or the declared conditions change can catch the
|
|
137
|
-
page mid-transition. Call `ralph.recordNode` after your assertions to retake
|
|
138
|
-
the screenshot of the node just recorded:
|
|
188
|
+
An automatic capture can catch the page mid-transition. Call `ralph.recordNode`
|
|
189
|
+
after your assertions to retake the screenshot of the current node:
|
|
139
190
|
|
|
140
191
|
```ts
|
|
141
192
|
test("cart", { tag: "@ralph" }, async ({ page, ralph }) => {
|
|
@@ -146,19 +197,18 @@ test("cart", { tag: "@ralph" }, async ({ page, ralph }) => {
|
|
|
146
197
|
});
|
|
147
198
|
```
|
|
148
199
|
|
|
149
|
-
|
|
150
|
-
`ralph.recordNode(page, { name: 'Cart' })`. A later call without one keeps it.
|
|
200
|
+
Each call replaces the screenshot, so the last one wins. Options:
|
|
151
201
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
202
|
+
- `name` labels the node in the graph: `ralph.recordNode(page, { name: 'Cart' })`.
|
|
203
|
+
- `edgeLabel` describes the step into it in your own words:
|
|
204
|
+
`ralph.recordNode(page, { name: 'Done', edgeLabel: 'Sign in' })`.
|
|
205
|
+
|
|
206
|
+
A later call without either one keeps the earlier value.
|
|
156
207
|
|
|
157
|
-
###
|
|
208
|
+
### One URL, several renderings
|
|
158
209
|
|
|
159
210
|
When one URL renders differently by visitor (a plan, an experiment group) or by
|
|
160
|
-
page state (an open dialog), declare what each screenshot stands for
|
|
161
|
-
the only source of a node's conditions, including sets of values:
|
|
211
|
+
page state (an open dialog), declare what each screenshot stands for:
|
|
162
212
|
|
|
163
213
|
```ts
|
|
164
214
|
await page.goto("https://example.com/checkout");
|
|
@@ -169,18 +219,13 @@ const undo = await ralph.setNodeConditions(page, {
|
|
|
169
219
|
await undo();
|
|
170
220
|
```
|
|
171
221
|
|
|
172
|
-
|
|
173
|
-
records it again
|
|
174
|
-
|
|
175
|
-
declaration against those tags: where the app reports a value outside it, the node
|
|
176
|
-
lists it in `tagMismatches` and the upload page shows it, because those nodes would
|
|
177
|
-
match no production traffic. `strictNodeTags` fails the test instead.
|
|
222
|
+
This changes no app state. A declaration that moves the page to a different
|
|
223
|
+
node records it again, and declarations merge until the URL changes. A dialog
|
|
224
|
+
at the same URL is the same node unless you give it a page tag like this.
|
|
178
225
|
|
|
179
226
|
### Waiting for capture
|
|
180
227
|
|
|
181
|
-
|
|
182
|
-
screenshot is done, the page is `uncaptured`: it's in the graph without a
|
|
183
|
-
screenshot. To make sure a page is captured before leaving it:
|
|
228
|
+
To make sure a page is captured before leaving it:
|
|
184
229
|
|
|
185
230
|
```ts
|
|
186
231
|
await page.goto("https://example.com/pricing");
|
|
@@ -204,9 +249,7 @@ await ralph.visitAllLinks(page.locator("a.article:visible"), {
|
|
|
204
249
|
```
|
|
205
250
|
|
|
206
251
|
Each link opens in its own tab, is recorded, and closes before the next one
|
|
207
|
-
opens.
|
|
208
|
-
(`<a href>`): a button that navigates by script can't open in a new tab, so the
|
|
209
|
-
call throws before opening any.
|
|
252
|
+
opens. Every match must be an `<a href>`, or the call throws before opening any.
|
|
210
253
|
|
|
211
254
|
### Pausing capture
|
|
212
255
|
|
|
@@ -221,8 +264,8 @@ ralph.resume();
|
|
|
221
264
|
await page.goto("https://example.com/d"); // recorded, linked from /a
|
|
222
265
|
```
|
|
223
266
|
|
|
224
|
-
This works across tabs. While paused, `recordNode` does nothing
|
|
225
|
-
not record the current page.
|
|
267
|
+
This works across tabs. While paused, `recordNode` does nothing and actions are
|
|
268
|
+
left off the edge. `resume` does not record the current page.
|
|
226
269
|
|
|
227
270
|
### Browser contexts you create yourself
|
|
228
271
|
|
|
@@ -234,131 +277,36 @@ await ralph.waitForCapture();
|
|
|
234
277
|
await context.close();
|
|
235
278
|
```
|
|
236
279
|
|
|
237
|
-
|
|
238
|
-
navigating any pages, so no app scripts schedule native timers first. A context
|
|
239
|
-
with an already navigated page cannot be enrolled with this option enabled.
|
|
240
|
-
|
|
241
|
-
### What happened between two nodes
|
|
242
|
-
|
|
243
|
-
The Playwright actions a test takes on the way from one node to the next are
|
|
244
|
-
recorded on the edge between them: clicks, hovers, fills, key presses,
|
|
245
|
-
selects, uploads, drags, and `goto` / back / forward / reload. Each one keeps
|
|
246
|
-
the element it landed on (its role, name, and `data-track-id`) and where that
|
|
247
|
-
element was on the earlier node's screenshot, which Ralph shows as, e.g.,
|
|
248
|
-
`Click button "Checkout"`.
|
|
249
|
-
|
|
250
|
-
What was typed into a password or one-time-code field is never recorded.
|
|
251
|
-
Actions taken while recording is paused are left out.
|
|
252
|
-
|
|
253
|
-
Pass an `edgeLabel` to describe the step in your own words, e.g.
|
|
254
|
-
`ralph.recordNode(page, { name: 'Done', edgeLabel: 'Sign in' })`. A later call
|
|
255
|
-
without one keeps it.
|
|
280
|
+
## Stable captures
|
|
256
281
|
|
|
257
|
-
|
|
282
|
+
For apps with animations or live content:
|
|
258
283
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
Matched elements and their descendants, including content in open shadow roots
|
|
269
|
-
and slots, are left out of the ARIA snapshot used for diffing. They still appear
|
|
270
|
-
in the screenshot, and the space they take up still moves the elements around
|
|
271
|
-
them. Text that sits inline in a larger element, like a bare `<span>` in a
|
|
272
|
-
paragraph, belongs to that element's text and is not removed. Wrap it in an
|
|
273
|
-
element with a role, or match the whole paragraph.
|
|
274
|
-
|
|
275
|
-
### When recording fails
|
|
276
|
-
|
|
277
|
-
- A failed `recordNode` call rejects.
|
|
278
|
-
- A failed automatic recording fails the test at the end.
|
|
279
|
-
- Pages left before capture, and pages whose layout moved during the
|
|
280
|
-
screenshot, don't fail the test.
|
|
284
|
+
- `reduceMotion: true` emulates `prefers-reduced-motion: reduce`. CSS
|
|
285
|
+
animations and transitions are always frozen for the screenshot.
|
|
286
|
+
- `freezeDuringCapture: true` lets the page settle, then freezes timers and
|
|
287
|
+
animations so the screenshot, element positions, and ARIA snapshot match.
|
|
288
|
+
See [Troubleshooting](#freezeduringcapture-in-detail) before enabling it.
|
|
289
|
+
- `data-ralph-dynamic` and `ignoreInDiff` keep content that changes on its own
|
|
290
|
+
out of the diff. See [Diffing deployments](#diffing-deployments).
|
|
281
291
|
|
|
282
292
|
## Modes
|
|
283
293
|
|
|
284
294
|
Set with `RALPH_MODE` or the `mode` option:
|
|
285
295
|
|
|
286
|
-
| Mode | What happens
|
|
287
|
-
| -------- |
|
|
288
|
-
| `off` | Default. Nothing is recorded, and no config or credentials are needed.
|
|
289
|
-
| `upload` | `@ralph` tests are recorded, and the reporter uploads the run. Default when `RALPH_UPLOAD_KEY` is set. |
|
|
296
|
+
| Mode | What happens |
|
|
297
|
+
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
298
|
+
| `off` | Default. Nothing is recorded, and no config or credentials are needed. |
|
|
299
|
+
| `upload` | `@ralph` tests are recorded, and the reporter uploads the run as a preview graph not tied to a release. Default when `RALPH_UPLOAD_KEY` is set. |
|
|
300
|
+
| `deploy` | Like `upload`, but the run is uploaded as its build's graph. See [Deploying a release](#deploying-a-release). |
|
|
290
301
|
|
|
291
|
-
|
|
302
|
+
The reporter uploads the run when any test ran in `upload` or `deploy` mode,
|
|
303
|
+
and as its build's graph when any ran in `deploy` mode.
|
|
304
|
+
|
|
305
|
+
Untagged tests are never recorded. In `upload` and `deploy` modes, calling
|
|
292
306
|
`ralph.recordNode`, `ralph.setNodeConditions`, or `ralph.observe` in an
|
|
293
307
|
untagged test throws. Set `ignoreUntaggedCalls` to make them do nothing
|
|
294
308
|
instead, for helpers shared between tagged and untagged tests.
|
|
295
309
|
|
|
296
|
-
## Uploading to Ralph
|
|
297
|
-
|
|
298
|
-
1. Create an upload key for your project in the project dashboard.
|
|
299
|
-
2. Set these as environment variables, or `appId` as
|
|
300
|
-
[reporter options](#reporter-options):
|
|
301
|
-
|
|
302
|
-
| Variable | Value |
|
|
303
|
-
| ------------------ | --------------------------------- |
|
|
304
|
-
| `RALPH_UPLOAD_KEY` | The upload key. Environment only. |
|
|
305
|
-
| `RALPH_APP_ID` | The app the recordings belong to. |
|
|
306
|
-
|
|
307
|
-
3. Run:
|
|
308
|
-
|
|
309
|
-
```sh
|
|
310
|
-
RALPH_MODE=upload pnpm exec playwright test
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
The reporter uploads the whole run once. It prints the result link before the
|
|
314
|
-
upload starts, so you can open it straight away and watch the run upload and
|
|
315
|
-
then be processed:
|
|
316
|
-
|
|
317
|
-
```
|
|
318
|
-
Ralph: uploading the run. Follow the upload and processing at https://dash.ralphralph.ai/uploads/0f6c6a2e-…
|
|
319
|
-
Ralph: uploaded the run. Ralph is processing it at https://dash.ralphralph.ai/uploads/0f6c6a2e-…
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
### Sharded runs
|
|
323
|
-
|
|
324
|
-
Use the `blob` reporter on each shard, then merge with the Ralph reporter:
|
|
325
|
-
|
|
326
|
-
```sh
|
|
327
|
-
# Each shard (no upload credentials needed):
|
|
328
|
-
RALPH_MODE=upload pnpm exec playwright test --shard=1/4 --reporter blob
|
|
329
|
-
|
|
330
|
-
# Once, after collecting every shard's blob-report/ into all-blob-reports/:
|
|
331
|
-
RALPH_MODE=upload RALPH_UPLOAD_KEY=… RALPH_APP_ID=… \
|
|
332
|
-
pnpm exec playwright merge-reports --reporter @ralphralphai/playwright/reporter ./all-blob-reports
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### Deployments
|
|
336
|
-
|
|
337
|
-
When you deploy, upload the run as the graph of the deployed build. Ralph then
|
|
338
|
-
matches tracker events from that build to it.
|
|
339
|
-
|
|
340
|
-
Set `RALPH_DEPLOY=1` and `RALPH_BUILD_ID` to the build's id, usually the commit
|
|
341
|
-
SHA:
|
|
342
|
-
|
|
343
|
-
```sh
|
|
344
|
-
RALPH_MODE=upload RALPH_DEPLOY=1 RALPH_BUILD_ID="$COMMIT_SHA" \
|
|
345
|
-
RALPH_UPLOAD_KEY=… RALPH_APP_ID=… pnpm exec playwright test
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
If the build goes live before the test run finishes, register it first so the
|
|
349
|
-
tracker picks up its config right away:
|
|
350
|
-
|
|
351
|
-
```sh
|
|
352
|
-
curl --fail-with-body -X POST https://api.ralphralph.ai/api/upload/deployment/register \
|
|
353
|
-
-H "Authorization: Bearer $RALPH_UPLOAD_KEY" -H 'content-type: application/json' \
|
|
354
|
-
-d "{\"externalAppId\":\"$RALPH_APP_ID\",\"buildId\":\"$COMMIT_SHA\",
|
|
355
|
-
\"reserveArtifact\":false,\"config\":$(cat ralph.json)}"
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
`ralph.json` here is your `ralph.jsonc` as plain JSON, without comments or
|
|
359
|
-
trailing commas. The test run that follows registers the same build again and
|
|
360
|
-
uploads into it.
|
|
361
|
-
|
|
362
310
|
## Screen sizes
|
|
363
311
|
|
|
364
312
|
Use `ralphProjects` to create one project per entry in `screenSizes`:
|
|
@@ -404,9 +352,21 @@ test.describe("desktop layouts", { tag: "@ralph-screen:1280x720" }, () => {
|
|
|
404
352
|
});
|
|
405
353
|
```
|
|
406
354
|
|
|
407
|
-
A size missing from `screenSizes` fails the test.
|
|
355
|
+
A size missing from `screenSizes` fails the test. To run one size:
|
|
356
|
+
`playwright test --project "chromium 375x812"`.
|
|
357
|
+
|
|
358
|
+
## Sharded runs
|
|
408
359
|
|
|
409
|
-
|
|
360
|
+
Use the `blob` reporter on each shard, then merge with the Ralph reporter:
|
|
361
|
+
|
|
362
|
+
```sh
|
|
363
|
+
# Each shard (no upload credentials needed):
|
|
364
|
+
RALPH_MODE=upload pnpm exec playwright test --shard=1/4 --reporter blob
|
|
365
|
+
|
|
366
|
+
# Once, after collecting every shard's blob-report/ into all-blob-reports/:
|
|
367
|
+
RALPH_MODE=upload RALPH_UPLOAD_KEY=… RALPH_APP_ID=… \
|
|
368
|
+
pnpm exec playwright merge-reports --reporter @ralphralphai/playwright/reporter ./all-blob-reports
|
|
369
|
+
```
|
|
410
370
|
|
|
411
371
|
## Options
|
|
412
372
|
|
|
@@ -420,37 +380,114 @@ export default defineConfig<RalphFixtures>({
|
|
|
420
380
|
});
|
|
421
381
|
```
|
|
422
382
|
|
|
423
|
-
| Option | Default | What it does
|
|
424
|
-
| --------------------- | ------------------------ |
|
|
425
|
-
| `mode` | `RALPH_MODE`, else `off` | `off` or `
|
|
426
|
-
| `configPath` | `ralph.jsonc` | Path to the config, relative to the Playwright config.
|
|
427
|
-
| `config` | | An inline config instead of a file (type: `WebRalphConfig`).
|
|
428
|
-
| `buildId` | `RALPH_BUILD_ID` | Your app's build, to line up recordings with its analytics.
|
|
429
|
-
| `runId` | `RALPH_RUN_ID` | Identifies the CI run.
|
|
430
|
-
| `settleMs` | 150 | Wait after the DOM loads before an automatic recording.
|
|
431
|
-
| `recordTimeoutMs` | 5000 | Budget for one recording, including `settleMs` and `ready`. Must exceed `settleMs`.
|
|
432
|
-
| `ready` | | `(page, signal) => Promise<void>`. Waits for your app before an automatic recording. Honor `signal`.
|
|
433
|
-
| `reduceMotion` | `false` | Emulates `prefers-reduced-motion: reduce` and waits for finite animations before recording.
|
|
434
|
-
| `freezeDuringCapture` | `false` |
|
|
435
|
-
| `ignoreInDiff` | | CSS selectors for subtrees to omit from the ARIA snapshot.
|
|
436
|
-
| `strictNodeTags` | `false` | Fails the test when the app reports a tag outside what `ralph.setNodeConditions` declared.
|
|
437
|
-
| `ignoreUntaggedCalls` | `false` | Makes `recordNode`, `setNodeConditions`, and `observe` no-ops in untagged tests instead of throwing.
|
|
438
|
-
|
|
439
|
-
|
|
383
|
+
| Option | Default | What it does |
|
|
384
|
+
| --------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------- |
|
|
385
|
+
| `mode` | `RALPH_MODE`, else `off` | `off`, `upload`, or `deploy`. See [Modes](#modes). |
|
|
386
|
+
| `configPath` | `ralph.jsonc` | Path to the config, relative to the Playwright config. |
|
|
387
|
+
| `config` | | An inline config instead of a file (type: `WebRalphConfig`). |
|
|
388
|
+
| `buildId` | `RALPH_BUILD_ID` | Your app's build, to line up recordings with its analytics. |
|
|
389
|
+
| `runId` | `RALPH_RUN_ID` | Identifies the CI run. |
|
|
390
|
+
| `settleMs` | 150 | Wait after the DOM loads before an automatic recording. |
|
|
391
|
+
| `recordTimeoutMs` | 5000 | Budget for one recording, including `settleMs` and `ready`. Must exceed `settleMs`. |
|
|
392
|
+
| `ready` | | `(page, signal) => Promise<void>`. Waits for your app before an automatic recording. Honor `signal`. |
|
|
393
|
+
| `reduceMotion` | `false` | Emulates `prefers-reduced-motion: reduce` and waits for finite animations before recording. |
|
|
394
|
+
| `freezeDuringCapture` | `false` | Lets the page settle, then freezes timers and animations across capture. See [Troubleshooting](#freezeduringcapture-in-detail). |
|
|
395
|
+
| `ignoreInDiff` | | CSS selectors for subtrees to omit from the ARIA snapshot. |
|
|
396
|
+
| `strictNodeTags` | `false` | Fails the test when the app reports a tag outside what `ralph.setNodeConditions` declared. |
|
|
397
|
+
| `ignoreUntaggedCalls` | `false` | Makes `recordNode`, `setNodeConditions`, and `observe` no-ops in untagged tests instead of throwing. |
|
|
398
|
+
|
|
399
|
+
### Reporter options
|
|
440
400
|
|
|
441
401
|
```ts
|
|
442
402
|
reporter: [['list'], ['@ralphralphai/playwright/reporter', { appId: 'my-app' }]],
|
|
443
403
|
```
|
|
444
404
|
|
|
445
|
-
| Option | Default | What it does
|
|
446
|
-
| ----------------- | ------------------------------------------------- |
|
|
447
|
-
| `mode` | `RALPH_MODE`, else `upload` if `RALPH_UPLOAD_KEY` | `upload` uploads the run.
|
|
448
|
-
| `appId` | `RALPH_APP_ID` | The app the recordings belong to.
|
|
449
|
-
| `uploadTimeoutMs` | 120000 | Upload timeout.
|
|
450
|
-
| `
|
|
451
|
-
|
|
405
|
+
| Option | Default | What it does |
|
|
406
|
+
| ----------------- | ------------------------------------------------- | ------------------------------------------------------------- |
|
|
407
|
+
| `mode` | `RALPH_MODE`, else `upload` if `RALPH_UPLOAD_KEY` | `upload` uploads the run. `deploy` uploads it as its build's. |
|
|
408
|
+
| `appId` | `RALPH_APP_ID` | The app the recordings belong to. |
|
|
409
|
+
| `uploadTimeoutMs` | 120000 | Upload timeout. |
|
|
410
|
+
| `outputDir` | `ralph/` in the first project's output directory | Where the merged graph is written. |
|
|
411
|
+
|
|
412
|
+
## Troubleshooting
|
|
413
|
+
|
|
414
|
+
### A test failed because of recording
|
|
415
|
+
|
|
416
|
+
- A failed `recordNode` call rejects.
|
|
417
|
+
- A failed automatic recording fails the test at the end.
|
|
418
|
+
- Pages left before capture, and pages whose layout moved during the
|
|
419
|
+
screenshot, don't fail the test.
|
|
420
|
+
|
|
421
|
+
### A page has no screenshot
|
|
422
|
+
|
|
423
|
+
The test left the page before capture finished, so it's `uncaptured`. Call
|
|
424
|
+
`ralph.waitForCapture()` before navigating away.
|
|
452
425
|
|
|
453
|
-
|
|
426
|
+
### Part of a very large page is missing
|
|
427
|
+
|
|
428
|
+
Screenshots are WebP, which holds at most 16,383 px per side. A taller or wider
|
|
429
|
+
page is recorded up to that size, and what lies beyond it has no screenshot.
|
|
430
|
+
|
|
431
|
+
### A dialog replaced the page's screenshot
|
|
432
|
+
|
|
433
|
+
A dialog at the same URL is the same node. Give it its own node with a page tag
|
|
434
|
+
via `ralph.setNodeConditions` (see
|
|
435
|
+
[One URL, several renderings](#one-url-several-renderings)), since that is what
|
|
436
|
+
production events carry.
|
|
437
|
+
|
|
438
|
+
### The upload page shows `tagMismatches`
|
|
439
|
+
|
|
440
|
+
If your app tags visitors and pages with `@ralphralphai/tracker`, each
|
|
441
|
+
recording checks the declared node conditions against those tags. Where the app
|
|
442
|
+
reports a value outside the declaration, the node lists it in `tagMismatches`,
|
|
443
|
+
because that node would match no production traffic. Fix the declaration, or set
|
|
444
|
+
`strictNodeTags` to fail the test instead.
|
|
445
|
+
|
|
446
|
+
### `visitAllLinks` throws
|
|
447
|
+
|
|
448
|
+
Every match must be a link (`<a href>`). A button that navigates by script
|
|
449
|
+
can't open in a new tab, so the call throws before opening any.
|
|
450
|
+
|
|
451
|
+
### An ignored element's text still shows in the diff
|
|
452
|
+
|
|
453
|
+
`ignoreInDiff` removes matched elements and their descendants, including
|
|
454
|
+
content in open shadow roots and slots. Text that sits inline in a larger
|
|
455
|
+
element, like a bare `<span>` in a paragraph, belongs to that element's text
|
|
456
|
+
and is not removed. Wrap it in an element with a role, or match the whole
|
|
457
|
+
paragraph. The space ignored elements take up still moves the elements around
|
|
458
|
+
them.
|
|
459
|
+
|
|
460
|
+
### `freezeDuringCapture` in detail
|
|
461
|
+
|
|
462
|
+
Ralph installs Playwright's clock before the app loads. Before capture it
|
|
463
|
+
finishes running finite CSS transitions, CSS animations, and Web Animations,
|
|
464
|
+
and watches DOM changes in the page, its frames, and open shadow roots. Timers
|
|
465
|
+
and animation frames keep running until the DOM has been quiet for 150 ms, so
|
|
466
|
+
JavaScript-driven animations can finish without changes to your app.
|
|
467
|
+
Animations started by completion callbacks are checked again.
|
|
468
|
+
|
|
469
|
+
This settling wait is capped at 1.5 seconds or half the remaining
|
|
470
|
+
`recordTimeoutMs` budget, whichever is smaller. Looping browser animations are
|
|
471
|
+
paused while settling. Then timers and all remaining browser animations pause
|
|
472
|
+
during capture and resume afterward, including on failure. Animations the app
|
|
473
|
+
already paused stay paused, and finished animations stay finished.
|
|
474
|
+
|
|
475
|
+
Limits:
|
|
476
|
+
|
|
477
|
+
- The quiet check is a heuristic. Long JavaScript animations can be captured
|
|
478
|
+
midway when the budget runs out, and future delayed work and canvas or WebGL
|
|
479
|
+
drawing aren't detected.
|
|
480
|
+
- The clock replaces native timer APIs for the lifetime of the context, so
|
|
481
|
+
don't combine it with manual `page.clock` or `context.clock` control.
|
|
482
|
+
- Playwright requires a future pause time. Ralph uses the page's current time
|
|
483
|
+
plus 100 ms, which advances the clock and can fire due callbacks once before
|
|
484
|
+
freezing. Time resumes from there without catching up through the capture.
|
|
485
|
+
- All tabs in a context share the clock, so their timers pause together and
|
|
486
|
+
their captures run one at a time.
|
|
487
|
+
- Network responses, workers, and media playback are not frozen.
|
|
488
|
+
- With contexts you create yourself, await `ralph.observe(context)` before
|
|
489
|
+
creating or navigating any pages. A context with an already navigated page
|
|
490
|
+
can't be enrolled with this option on.
|
|
454
491
|
|
|
455
492
|
## Limits
|
|
456
493
|
|
|
@@ -458,6 +495,9 @@ Per run: 5,000 screenshots, 256 MiB of screenshots, 16 MiB of other recorded
|
|
|
458
495
|
data, 256 MiB compressed, 20,000 recorded pages, and 5,000 recorded pages per
|
|
459
496
|
test.
|
|
460
497
|
|
|
498
|
+
Per page: an ARIA snapshot over 4 MiB or nested deeper than the server
|
|
499
|
+
allows is left out. The page still uploads, but it can't be diffed.
|
|
500
|
+
|
|
461
501
|
## License
|
|
462
502
|
|
|
463
503
|
Apache-2.0
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_upload = require("./upload-
|
|
2
|
+
const require_upload = require("./upload-8iYQZaCa.cjs");
|
|
3
3
|
let node_path = require("node:path");
|
|
4
4
|
node_path = require_upload.__toESM(node_path, 1);
|
|
5
5
|
let _playwright_test = require("@playwright/test");
|
|
@@ -70,7 +70,7 @@ const test = _playwright_test.test.extend({
|
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
const reported = process.env["RALPH_PLAYWRIGHT_REPORTER"] || testInfo.config.reporter.some(([name]) => name === "blob");
|
|
73
|
-
if (mode === "upload" && !reported) throw new Error("Ralph uploads one merged graph per run from its reporter. Add ['@ralphralphai/playwright/reporter'] to `reporter` in playwright.config, or use the blob reporter and merge-reports when sharding.");
|
|
73
|
+
if ((mode === "upload" || mode === "deploy") && !reported) throw new Error("Ralph uploads one merged graph per run from its reporter. Add ['@ralphralphai/playwright/reporter'] to `reporter` in playwright.config, or use the blob reporter and merge-reports when sharding.");
|
|
74
74
|
const root = testInfo.config.configFile ? node_path.default.dirname(testInfo.config.configFile) : process.cwd();
|
|
75
75
|
const key = JSON.stringify({
|
|
76
76
|
root,
|