@ralphralphai/playwright 0.1.0 → 1.1.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/LICENSE +201 -0
- package/README.md +222 -268
- package/dist/index.cjs +23 -585
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +91 -52
- package/dist/index.mjs +17 -555
- package/dist/index.mjs.map +1 -1
- package/dist/reporter.cjs +169 -0
- package/dist/reporter.cjs.map +1 -0
- package/dist/reporter.d.cts +1 -0
- package/dist/reporter.d.mts +54 -0
- package/dist/reporter.mjs +167 -0
- package/dist/reporter.mjs.map +1 -0
- package/dist/upload-QZreR5i6.cjs +694 -0
- package/dist/upload-QZreR5i6.cjs.map +1 -0
- package/dist/upload-giSVxpun.mjs +592 -0
- package/dist/upload-giSVxpun.mjs.map +1 -0
- package/package.json +26 -16
package/README.md
CHANGED
|
@@ -1,134 +1,220 @@
|
|
|
1
1
|
# @ralphralphai/playwright
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
other states at the same URL. Each capture contains a screenshot, the recorded
|
|
6
|
-
and actual URLs, and `data-track-id` element positions.
|
|
3
|
+
Turn your Playwright tests into a map of your app for
|
|
4
|
+
[Ralph](https://ralphralph.ai).
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
Tag a test with `@ralph` and every page it visits is recorded: a full-page
|
|
7
|
+
screenshot, the URL, and the position of every element with a `data-track-id`.
|
|
8
|
+
Untagged tests are untouched, and recording is off unless you turn it on.
|
|
11
9
|
|
|
12
|
-
## Install
|
|
10
|
+
## Install
|
|
13
11
|
|
|
14
|
-
Requires Node.js 20.19+ and Playwright Test 1.62.1+
|
|
12
|
+
Requires Node.js 20.19+ and Playwright Test 1.62.1+ (major version 1).
|
|
15
13
|
|
|
16
14
|
```sh
|
|
17
15
|
pnpm add -D @ralphralphai/playwright @playwright/test
|
|
18
16
|
pnpm exec playwright install chromium
|
|
19
17
|
```
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
## Quick start
|
|
22
20
|
|
|
23
|
-
|
|
21
|
+
**1. Add a `ralph.jsonc`** next to `playwright.config.ts`:
|
|
22
|
+
|
|
23
|
+
```jsonc
|
|
24
24
|
{
|
|
25
|
-
"
|
|
25
|
+
"$schema": "./node_modules/@ralphralphai/config/schema/ralph.schema.json",
|
|
26
|
+
"screenSizes": [{ "width": 1280, "height": 720 }]
|
|
26
27
|
}
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
fixture preserves the Ralph config; it does not run its simple URL crawls.
|
|
30
|
+
See [`@ralphralphai/config`](https://www.npmjs.com/package/@ralphralphai/config)
|
|
31
|
+
for all fields.
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
**2. Import `test` from this package and tag tests with `@ralph`:**
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
36
|
import { test, expect } from '@ralphralphai/playwright';
|
|
37
37
|
|
|
38
|
-
test('checkout', { tag: '@ralph' }, async ({ page
|
|
38
|
+
test('checkout', { tag: '@ralph' }, async ({ page }) => {
|
|
39
39
|
await page.goto('https://example.com/cart');
|
|
40
|
-
await
|
|
40
|
+
await page.getByRole('link', { name: 'Checkout' }).click();
|
|
41
|
+
await expect(page).toHaveURL(/checkout/);
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**3. Add the Ralph reporter** in `playwright.config.ts`:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
export default defineConfig({
|
|
49
|
+
reporter: [['list'], ['@ralphralphai/playwright/reporter']],
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**4. Run:**
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
RALPH_MODE=local pnpm exec playwright test
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The merged graph for the run is written to `test-results/ralph/`. To send it to
|
|
60
|
+
Ralph, see [Uploading to Ralph](#uploading-to-ralph).
|
|
61
|
+
|
|
62
|
+
## Tag your elements
|
|
63
|
+
|
|
64
|
+
Add `data-track-id` to elements, using the same ids as your
|
|
65
|
+
[`@ralphralphai/tracker`](https://www.npmjs.com/package/@ralphralphai/tracker)
|
|
66
|
+
setup:
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<button data-track-id="add-to-cart">Add to cart</button>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Recording pages
|
|
73
|
+
|
|
74
|
+
### Automatically
|
|
75
|
+
|
|
76
|
+
A page is recorded whenever the URL changes in any tab of the test: page loads,
|
|
77
|
+
redirects (the final page), SPA route changes, query and hash changes, and new
|
|
78
|
+
tabs and popups. Reloads, iframes, `about:blank`, and changes that keep the URL
|
|
79
|
+
are not recorded.
|
|
41
80
|
|
|
81
|
+
Each recording waits for the DOM to load plus `settleMs` (150 ms). If your app
|
|
82
|
+
needs longer, use `ready`:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
ralphOptions: {
|
|
86
|
+
ready: async (page, signal) => {
|
|
87
|
+
await page.getByTestId('app-ready').waitFor({ timeout: 3000 });
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
CSS animations and transitions are frozen for the screenshot. For animations
|
|
93
|
+
driven from JavaScript, set `reduceMotion` if your app honors
|
|
94
|
+
`prefers-reduced-motion`.
|
|
95
|
+
|
|
96
|
+
### Explicitly, for states at the same URL
|
|
97
|
+
|
|
98
|
+
For dialogs, menus, and tabs, call `ralph.recordNode` after your assertions:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
test('shipping options', { tag: '@ralph' }, async ({ page, ralph }) => {
|
|
102
|
+
await page.goto('https://example.com/cart');
|
|
42
103
|
await page.getByRole('button', { name: 'Shipping options' }).click();
|
|
43
104
|
await expect(page.getByRole('dialog')).toBeVisible();
|
|
44
105
|
|
|
45
|
-
await ralph.
|
|
46
|
-
state: 'shipping-options-open',
|
|
47
|
-
url: '/checkout/shipping-options',
|
|
106
|
+
await ralph.recordNode(page, {
|
|
107
|
+
state: 'shipping-options-open', // a label for this state
|
|
108
|
+
url: '/checkout/shipping-options', // optional: the URL to record it under
|
|
48
109
|
});
|
|
49
110
|
});
|
|
111
|
+
```
|
|
50
112
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
113
|
+
`url` only changes what is recorded; the browser doesn't navigate.
|
|
114
|
+
|
|
115
|
+
### Waiting for capture
|
|
116
|
+
|
|
117
|
+
Recordings run in the background. If the test leaves a page before its
|
|
118
|
+
screenshot is done, the page is `uncaptured`: it's in the graph without a
|
|
119
|
+
screenshot. To make sure a page is captured before leaving it:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
await page.goto('https://example.com/pricing');
|
|
123
|
+
await ralph.waitForCapture();
|
|
124
|
+
await page.goto('https://example.com/signup');
|
|
55
125
|
```
|
|
56
126
|
|
|
57
|
-
|
|
58
|
-
|
|
127
|
+
### Pausing capture
|
|
128
|
+
|
|
129
|
+
To leave steps out of the graph:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
await page.goto('https://example.com/a');
|
|
133
|
+
ralph.pause();
|
|
134
|
+
await page.goto('https://example.com/b'); // not recorded
|
|
135
|
+
await page.goto('https://example.com/c'); // not recorded
|
|
136
|
+
ralph.resume();
|
|
137
|
+
await page.goto('https://example.com/d'); // recorded, linked from /a
|
|
59
138
|
```
|
|
60
139
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
Calling `ralph.capture` in an unmarked test while recording is enabled reports
|
|
64
|
-
the missing `@ralph` tag.
|
|
65
|
-
|
|
66
|
-
`RALPH_MODE=off` disables recording and makes explicit capture calls no-ops.
|
|
67
|
-
Tests that import directly from `@playwright/test` continue to work alongside
|
|
68
|
-
Ralph tests. Importing the integration does not launch a browser for API-only
|
|
69
|
-
tests.
|
|
70
|
-
|
|
71
|
-
`RALPH_MODE=upload`, or setting `RALPH_UPLOAD_KEY` without an explicit mode,
|
|
72
|
-
enables uploading. Set `RALPH_API_URL` to your Ralph server origin (or set
|
|
73
|
-
`ralphOptions.apiUrl`). The origin must use HTTPS; plain HTTP is accepted only
|
|
74
|
-
for loopback hosts (`localhost`, `127.x.x.x`, `[::1]`) during local development.
|
|
75
|
-
Issue a private upload key for your Ralph project with the authenticated
|
|
76
|
-
`/api/project/playwright/key/create` endpoint and keep it in CI secrets. A key belongs to
|
|
77
|
-
the project, so one key serves every app in it. Set `RALPH_APP_ID` (or
|
|
78
|
-
`ralphOptions.appId`) to the app the captures belong to.
|
|
140
|
+
This works across tabs. While paused, `recordNode` does nothing. `resume` does
|
|
141
|
+
not record the current page.
|
|
79
142
|
|
|
80
|
-
|
|
81
|
-
|
|
143
|
+
### Browser contexts you create yourself
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const context = await browser.newContext();
|
|
147
|
+
ralph.observe(context);
|
|
148
|
+
// ...
|
|
149
|
+
await ralph.waitForCapture();
|
|
150
|
+
await context.close();
|
|
82
151
|
```
|
|
83
152
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
`
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
153
|
+
### When recording fails
|
|
154
|
+
|
|
155
|
+
- A failed `recordNode` call rejects.
|
|
156
|
+
- A failed automatic recording fails the test at the end.
|
|
157
|
+
- Pages left before capture, and pages whose layout moved during the
|
|
158
|
+
screenshot, don't fail the test.
|
|
159
|
+
|
|
160
|
+
## Modes
|
|
161
|
+
|
|
162
|
+
Set with `RALPH_MODE` or the `mode` option:
|
|
163
|
+
|
|
164
|
+
| Mode | What happens |
|
|
165
|
+
| -------- | --------------------------------------------------------------------------------------------------- |
|
|
166
|
+
| `off` | Default. Nothing is recorded, and no config or credentials are needed. |
|
|
167
|
+
| `local` | `@ralph` tests are recorded, and the reporter writes the merged graph locally. |
|
|
168
|
+
| `upload` | As `local`, and the reporter uploads the graph once the run ends. Default when `RALPH_UPLOAD_KEY` is set. |
|
|
169
|
+
|
|
170
|
+
Untagged tests are never recorded. In `local` or `upload` mode, calling
|
|
171
|
+
`ralph.recordNode` in an untagged test throws.
|
|
172
|
+
|
|
173
|
+
## Uploading to Ralph
|
|
174
|
+
|
|
175
|
+
1. Create an upload key for your project with `/api/project/key/create`, and
|
|
176
|
+
keep it in your CI secrets.
|
|
177
|
+
2. Set these as environment variables, or `apiUrl` and `appId` as
|
|
178
|
+
[reporter options](#reporter-options):
|
|
179
|
+
|
|
180
|
+
| Variable | Value |
|
|
181
|
+
| ------------------ | ----------------------------------------------- |
|
|
182
|
+
| `RALPH_UPLOAD_KEY` | The upload key. Environment only. |
|
|
183
|
+
| `RALPH_API_URL` | Your Ralph server. HTTPS, or HTTP on localhost. |
|
|
184
|
+
| `RALPH_APP_ID` | The app the recordings belong to. |
|
|
185
|
+
|
|
186
|
+
3. Run:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
RALPH_MODE=upload pnpm exec playwright test
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The reporter uploads the whole run once and prints the link:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
Ralph: uploaded the run's raw graph. https://dash.ralphralph.ai/uploads/0f6c6a2e-…
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- Retried tests contribute only their final attempt.
|
|
199
|
+
- Every test in a run must use the same Ralph config, `runId`, and `buildId`.
|
|
200
|
+
- If the upload fails, the run fails.
|
|
201
|
+
|
|
202
|
+
### Sharded runs
|
|
203
|
+
|
|
204
|
+
Use the `blob` reporter on each shard, then merge with the Ralph reporter:
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
# Each shard (no upload credentials needed):
|
|
208
|
+
RALPH_MODE=upload pnpm exec playwright test --shard=1/4 --reporter blob
|
|
209
|
+
|
|
210
|
+
# Once, after collecting every shard's blob-report/ into all-blob-reports/:
|
|
211
|
+
RALPH_MODE=upload RALPH_UPLOAD_KEY=… RALPH_API_URL=… RALPH_APP_ID=… \
|
|
212
|
+
pnpm exec playwright merge-reports --reporter @ralphralphai/playwright/reporter ./all-blob-reports
|
|
213
|
+
```
|
|
124
214
|
|
|
125
215
|
## Screen sizes
|
|
126
216
|
|
|
127
|
-
|
|
128
|
-
different test from its desktop flow. So each `screenSizes` entry becomes its
|
|
129
|
-
own Playwright project. `ralphProjects` reads the Ralph config synchronously
|
|
130
|
-
and expands a base project into one project per size. Each project's viewport
|
|
131
|
-
is that size:
|
|
217
|
+
Use `ralphProjects` to create one project per entry in `screenSizes`:
|
|
132
218
|
|
|
133
219
|
```ts
|
|
134
220
|
import { defineConfig, devices } from '@playwright/test';
|
|
@@ -136,10 +222,8 @@ import { ralphProjects, type RalphFixtures } from '@ralphralphai/playwright';
|
|
|
136
222
|
|
|
137
223
|
export default defineConfig<RalphFixtures>({
|
|
138
224
|
projects: [
|
|
139
|
-
// Ordinary tests keep their single project.
|
|
140
225
|
{ name: 'chromium', grepInvert: /@ralph\b/, use: devices['Desktop Chrome'] },
|
|
141
|
-
//
|
|
142
|
-
// "chromium 375x812", "chromium 768x1024" and "chromium 1280x720".
|
|
226
|
+
// Creates "chromium 375x812", "chromium 1280x720", …
|
|
143
227
|
...ralphProjects({
|
|
144
228
|
name: 'chromium',
|
|
145
229
|
grep: /@ralph\b/,
|
|
@@ -149,202 +233,72 @@ export default defineConfig<RalphFixtures>({
|
|
|
149
233
|
});
|
|
150
234
|
```
|
|
151
235
|
|
|
152
|
-
|
|
153
|
-
`{ root: import.meta.dirname }`
|
|
154
|
-
second argument when Playwright runs from elsewhere. Keep it pointing at the
|
|
155
|
-
same config as `ralphOptions`.
|
|
236
|
+
`ralphProjects` reads `ralph.jsonc` from the current directory. Otherwise pass
|
|
237
|
+
`{ root: import.meta.dirname }` or an inline `config` as the second argument.
|
|
156
238
|
|
|
157
|
-
|
|
158
|
-
with `@ralph-screen:WIDTHxHEIGHT`, once per size
|
|
159
|
-
sizes. Tags on a `describe` block and its tests combine. The other
|
|
160
|
-
screen projects skip it:
|
|
239
|
+
A test runs at every size. To run it only at some sizes, tag it or its
|
|
240
|
+
`describe` block with `@ralph-screen:WIDTHxHEIGHT`, once per size:
|
|
161
241
|
|
|
162
242
|
```ts
|
|
163
|
-
test(
|
|
164
|
-
'
|
|
165
|
-
|
|
166
|
-
async ({ page }) => {
|
|
167
|
-
await page.getByRole('button', { name: 'Menu' }).click();
|
|
168
|
-
},
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
// One tag per size: runs at 768x1024 and 1280x720, skipped at 375x812.
|
|
172
|
-
test(
|
|
173
|
-
'data table',
|
|
174
|
-
{ tag: ['@ralph', '@ralph-screen:768x1024', '@ralph-screen:1280x720'] },
|
|
175
|
-
async ({ page }) => {},
|
|
176
|
-
);
|
|
243
|
+
test('mobile navigation', { tag: ['@ralph', '@ralph-screen:375x812'] }, async ({ page }) => {
|
|
244
|
+
await page.getByRole('button', { name: 'Menu' }).click();
|
|
245
|
+
});
|
|
177
246
|
|
|
178
247
|
test.describe('desktop layouts', { tag: '@ralph-screen:1280x720' }, () => {
|
|
179
248
|
test('sidebar', { tag: '@ralph' }, async ({ page }) => {});
|
|
180
249
|
});
|
|
181
250
|
```
|
|
182
251
|
|
|
183
|
-
|
|
184
|
-
they choose which flows run. A size the config does not declare fails the test,
|
|
185
|
-
so typos don't silently skip it. Projects not created by `ralphProjects` ignore
|
|
186
|
-
screen tags. To run one size from the command line, use Playwright's project
|
|
187
|
-
filter: `playwright test --project "chromium 375x812"`.
|
|
188
|
-
|
|
189
|
-
## Options
|
|
252
|
+
A size missing from `screenSizes` fails the test.
|
|
190
253
|
|
|
191
|
-
|
|
192
|
-
annotations. Its definition remains in `@ralphralphai/config`:
|
|
193
|
-
|
|
194
|
-
```ts
|
|
195
|
-
import type { WebRalphConfig } from '@ralphralphai/playwright';
|
|
254
|
+
To run one size: `playwright test --project "chromium 375x812"`.
|
|
196
255
|
|
|
197
|
-
|
|
198
|
-
```
|
|
256
|
+
## Options
|
|
199
257
|
|
|
200
|
-
Set `ralphOptions`
|
|
258
|
+
Set `ralphOptions` in `playwright.config.ts`, or per file with `test.use`:
|
|
201
259
|
|
|
202
260
|
```ts
|
|
203
|
-
import { defineConfig } from '@playwright/test';
|
|
204
|
-
import type { RalphFixtures } from '@ralphralphai/playwright';
|
|
205
|
-
|
|
206
261
|
export default defineConfig<RalphFixtures>({
|
|
207
262
|
use: {
|
|
208
|
-
|
|
209
|
-
ralphOptions: {
|
|
210
|
-
mode: 'local',
|
|
211
|
-
configPath: './ralph.jsonc',
|
|
212
|
-
buildId: 'application-build-id',
|
|
213
|
-
runId: 'ci-run-id',
|
|
214
|
-
settleMs: 150,
|
|
215
|
-
captureTimeoutMs: 5000,
|
|
216
|
-
ready: async (page, signal) => {
|
|
217
|
-
signal.throwIfAborted();
|
|
218
|
-
await page.getByTestId('app-ready').waitFor({ timeout: 3000 });
|
|
219
|
-
},
|
|
220
|
-
},
|
|
263
|
+
ralphOptions: { mode: 'local', buildId: process.env.GIT_SHA, settleMs: 300 },
|
|
221
264
|
},
|
|
222
265
|
});
|
|
223
266
|
```
|
|
224
267
|
|
|
225
|
-
| Option |
|
|
226
|
-
|
|
|
227
|
-
| `mode` | `off`, `local`, or `upload
|
|
228
|
-
| `
|
|
229
|
-
| `
|
|
230
|
-
| `
|
|
231
|
-
| `
|
|
232
|
-
| `
|
|
233
|
-
| `
|
|
234
|
-
| `
|
|
235
|
-
| `
|
|
236
|
-
| `ready` | Optional automatic-capture readiness callback with an abort signal |
|
|
237
|
-
|
|
238
|
-
The deadline includes settling and readiness. Custom readiness work should
|
|
239
|
-
honor the signal or use its own bounded Playwright operations. Ralph can stop
|
|
240
|
-
waiting for a callback but cannot cancel arbitrary user code.
|
|
241
|
-
|
|
242
|
-
Config files accept JSONC comments and trailing commas and are validated against
|
|
243
|
-
the published config schema. The complete parsed values are snapshotted without
|
|
244
|
-
injecting schema defaults, along with the config package version.
|
|
245
|
-
Snapshots are cached per config source within a worker. Keep the config unchanged
|
|
246
|
-
during a run; the server compares configs across workers before combining
|
|
247
|
-
attempts. Run/build IDs are optional for local exploration, and unique
|
|
248
|
-
attempt IDs are always generated.
|
|
249
|
-
|
|
250
|
-
## Existing fixtures
|
|
251
|
-
|
|
252
|
-
Compose Ralph with the test object your project already uses:
|
|
268
|
+
| Option | Default | What it does |
|
|
269
|
+
| ----------------- | ------------------------ | ----------------------------------------------------------------------------------------------- |
|
|
270
|
+
| `mode` | `RALPH_MODE`, else `off` | `off`, `local`, or `upload`. See [Modes](#modes). |
|
|
271
|
+
| `configPath` | `ralph.jsonc` | Path to the config, relative to the Playwright config. |
|
|
272
|
+
| `config` | | An inline config instead of a file (type: `WebRalphConfig`). |
|
|
273
|
+
| `buildId` | `RALPH_BUILD_ID` | Your app's build, to line up recordings with its analytics. |
|
|
274
|
+
| `runId` | `RALPH_RUN_ID` | Identifies the CI run. |
|
|
275
|
+
| `settleMs` | 150 | Wait after the DOM loads before an automatic recording. |
|
|
276
|
+
| `recordTimeoutMs` | 5000 | Budget for one recording, including `settleMs` and `ready`. Must exceed `settleMs`. |
|
|
277
|
+
| `ready` | | `(page, signal) => Promise<void>`. Waits for your app before an automatic recording. Honor `signal`. |
|
|
278
|
+
| `reduceMotion` | `false` | Emulates `prefers-reduced-motion: reduce` and waits for finite animations before recording. |
|
|
253
279
|
|
|
254
|
-
|
|
255
|
-
import { mergeTests } from '@playwright/test';
|
|
256
|
-
import { test as ralphTest } from '@ralphralphai/playwright';
|
|
257
|
-
import { test as applicationTest } from './application-fixtures';
|
|
280
|
+
## Reporter options
|
|
258
281
|
|
|
259
|
-
|
|
282
|
+
```ts
|
|
283
|
+
reporter: [['list'], ['@ralphralphai/playwright/reporter', { appId: 'my-app' }]],
|
|
260
284
|
```
|
|
261
285
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
`ralph-captures`; screenshots are attached with unique names referenced by the
|
|
270
|
-
manifest. Copy the full test output or retain the Playwright attachments.
|
|
271
|
-
Configure Playwright to preserve successful test output when collecting captures.
|
|
272
|
-
|
|
273
|
-
The exported `CaptureManifest` and `RawNode` types describe format
|
|
274
|
-
version 1. The manifest includes the config snapshot, producer versions, optional run and
|
|
275
|
-
build IDs, test/project/retry/repeat/shard identity, separate browser-page streams,
|
|
276
|
-
opener relationships, and the `nodes` the test produced.
|
|
277
|
-
|
|
278
|
-
Each entry in `nodes` is a raw node: one page state as the test saw it, with a
|
|
279
|
-
sequence within its page. The same page visited twice produces two raw nodes,
|
|
280
|
-
and superseded or failed captures are kept too. The server merges raw nodes
|
|
281
|
-
into the graph's nodes; this package does no normalization or merging.
|
|
282
|
-
|
|
283
|
-
Each node records `previousNodeId`, the node its page state was reached from:
|
|
284
|
-
the previous one on the same page, or for a popup's first node, its opener's
|
|
285
|
-
latest. Superseded nodes are skipped. No code is injected into
|
|
286
|
-
the page; the flow comes from the order of URL changes.
|
|
287
|
-
|
|
288
|
-
Screenshots are WebP at quality 80, falling back to PNG for pages over 16,383
|
|
289
|
-
pixels in either dimension, which WebP cannot encode. They use one image pixel
|
|
290
|
-
per CSS pixel, including on high-DPI devices.
|
|
291
|
-
Tracked-element boxes use document CSS coordinates and preserve duplicate track
|
|
292
|
-
IDs as separate boxes. Collection includes visible elements in the main frame,
|
|
293
|
-
including open shadow roots; it does not collect child-frame geometry. Elements
|
|
294
|
-
with zero width or height, or entirely off-screen horizontally, are dropped;
|
|
295
|
-
elements below the fold are kept.
|
|
296
|
-
The manifest records viewport, document extent, scroll position, screenshot size,
|
|
297
|
-
and device pixel ratio.
|
|
298
|
-
|
|
299
|
-
Geometry is measured before and after the screenshot. Differences set
|
|
300
|
-
`geometryStable: false`. This detects layout drift, not every visual change;
|
|
301
|
-
animations, sticky elements, and virtualized content can still need explicit
|
|
302
|
-
application preparation. The package does not freeze or alter the application.
|
|
303
|
-
|
|
304
|
-
An attempt is `complete` only when the test has passed at fixture finalization,
|
|
305
|
-
there is at least one node, and every node has a screenshot with
|
|
306
|
-
stable measured geometry. A future reporter must reconcile this with the final
|
|
307
|
-
Playwright result if another fixture fails later. Retries have distinct manifests
|
|
308
|
-
and attempt IDs; partial nodes are preserved.
|
|
309
|
-
|
|
310
|
-
## Upload format and retries
|
|
311
|
-
|
|
312
|
-
The HTTP body is one `application/gzip` tar archive. `capture.json` holds UTF-8
|
|
313
|
-
JSON `{ manifest, screenshots: [{ nodeId, path }] }`, and each `path` points to
|
|
314
|
-
a raw WebP (or PNG fallback) under `assets/images/`. The manifest includes the exact config
|
|
315
|
-
snapshot.
|
|
316
|
-
The package emits only its current `formatVersion: 1`; the server independently
|
|
317
|
-
keeps versioned Zod schemas and selects one by `formatVersion`. No crawler API
|
|
318
|
-
package or server schema is imported by this integration.
|
|
319
|
-
|
|
320
|
-
The limits per attempt are 64 MiB compressed, 2 MiB manifest JSON, 64 MiB total
|
|
321
|
-
screenshot bytes, and 500 screenshots. Server validation
|
|
322
|
-
also limits nodes to 5,000. Split larger scenarios into smaller tests.
|
|
323
|
-
Screenshots are already compressed; gzip mainly compresses the manifest.
|
|
324
|
-
|
|
325
|
-
`uploadCapture(manifest, screenshots, { apiUrl, uploadKey, timeoutMs? })` can retry
|
|
326
|
-
saved artifacts, where each screenshot is `{ nodeId, contentType, bytes: Uint8Array }`.
|
|
327
|
-
`createCaptureBundle(manifest, screenshots)` returns the gzip bytes when a caller
|
|
328
|
-
needs to handle transport itself. An identical retry preserves `attemptId` and
|
|
329
|
-
returns `replayed: true`; changed contents under the same ID are rejected. There
|
|
330
|
-
are no automatic HTTP retries. Playwright test retries create new attempt IDs.
|
|
331
|
-
The manifest's status reflects fixture finalization; a future reporter can
|
|
332
|
-
reconcile failures in fixtures that finish later.
|
|
333
|
-
|
|
334
|
-
## Development
|
|
335
|
-
|
|
336
|
-
From the repository root:
|
|
286
|
+
| Option | Default | What it does |
|
|
287
|
+
| ----------------- | ------------------------------------------------- | ----------------------------------------------- |
|
|
288
|
+
| `mode` | `RALPH_MODE`, else `upload` if `RALPH_UPLOAD_KEY` | `upload` uploads the run. |
|
|
289
|
+
| `apiUrl` | `RALPH_API_URL` | Your Ralph server. |
|
|
290
|
+
| `appId` | `RALPH_APP_ID` | The app the recordings belong to. |
|
|
291
|
+
| `uploadTimeoutMs` | 120000 | Upload timeout. |
|
|
292
|
+
| `outputDir` | `ralph/` in the first project's output directory | Where the merged graph is written. |
|
|
337
293
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
294
|
+
The run is also uploaded when any test ran in `upload` mode.
|
|
295
|
+
|
|
296
|
+
## Limits
|
|
297
|
+
|
|
298
|
+
Per run: 5,000 screenshots, 256 MiB of screenshots, 16 MiB of other recorded
|
|
299
|
+
data, 256 MiB compressed, 20,000 recorded pages, and 5,000 recorded pages per
|
|
300
|
+
test.
|
|
301
|
+
|
|
302
|
+
## License
|
|
347
303
|
|
|
348
|
-
|
|
349
|
-
then checks the emitted manifests and screenshot attachments. It includes an
|
|
350
|
-
intentional failed first attempt to verify retry isolation.
|
|
304
|
+
Apache-2.0
|