@ralphralphai/playwright 0.1.0 → 0.2.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 CHANGED
@@ -1,134 +1,211 @@
1
1
  # @ralphralphai/playwright
2
2
 
3
- Opt-in Playwright fixtures for recording the page states a test visits. Ralph
4
- automatically captures URL changes, and explicit captures handle dialogs and
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
- The fixture saves local artifacts and can upload a gzip-compressed tar bundle per
9
- test attempt. Graph construction runs on the server; ingestion currently returns
10
- an attempt receipt, with graph processing and the dashboard URL still pending.
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 carrying a
8
+ `data-track-id`. Ralph builds a navigation graph from those recordings and
9
+ overlays real user interactions on the screenshots as heatmaps.
11
10
 
12
- ## Install and enable
11
+ Your tests keep driving the app exactly as they do today. Untagged tests are
12
+ untouched, and recording is off unless you turn it on.
13
13
 
14
- Requires Node.js 20.19+ and Playwright Test 1.62.1+ within major version 1.
14
+ ## Install
15
+
16
+ Requires Node.js 20.19+ and Playwright Test 1.62.1+ (major version 1).
15
17
 
16
18
  ```sh
17
19
  pnpm add -D @ralphralphai/playwright @playwright/test
18
20
  pnpm exec playwright install chromium
19
21
  ```
20
22
 
21
- Put a `ralph.jsonc` next to `playwright.config.ts`:
23
+ ## Quick start
24
+
25
+ **1. Add a `ralph.jsonc`** next to `playwright.config.ts`, listing the screen
26
+ sizes to record at:
22
27
 
23
- ```json
28
+ ```jsonc
24
29
  {
25
- "screenSizes": []
30
+ "$schema": "./node_modules/@ralphralphai/config/schema/ralph.schema.json",
31
+ "screenSizes": [{ "width": 1280, "height": 720 }]
26
32
  }
27
33
  ```
28
34
 
29
- The actual viewport is controlled by Playwright; use `ralphProjects` to run
30
- tests at the config's `screenSizes` (see [Screen sizes](#screen-sizes)). The
31
- fixture preserves the Ralph config; it does not run its simple URL crawls.
35
+ See [`@ralphralphai/config`](https://www.npmjs.com/package/@ralphralphai/config)
36
+ for everything the file can declare.
32
37
 
33
- Use Ralph's extended `test` and tag the tests that should participate:
38
+ **2. Import `test` from this package and tag the tests to record:**
34
39
 
35
40
  ```ts
36
41
  import { test, expect } from '@ralphralphai/playwright';
37
42
 
38
- test('checkout', { tag: '@ralph' }, async ({ page, ralph }) => {
43
+ test('checkout', { tag: '@ralph' }, async ({ page }) => {
39
44
  await page.goto('https://example.com/cart');
40
- await ralph.flush();
45
+ await page.getByRole('link', { name: 'Checkout' }).click();
46
+ await expect(page).toHaveURL(/checkout/);
47
+ });
48
+ ```
49
+
50
+ Every URL the test reaches is recorded automatically. You don't need to call
51
+ anything.
52
+
53
+ **3. Run with recording on:**
54
+
55
+ ```sh
56
+ RALPH_MODE=local pnpm exec playwright test
57
+ ```
58
+
59
+ Recordings are saved into Playwright's test output directory. To send them to
60
+ Ralph instead, see [Uploading to Ralph](#uploading-to-ralph).
61
+
62
+ ## Tag your elements
63
+
64
+ Ralph records the position of every visible element with a `data-track-id`, so
65
+ it can place heatmap data on the screenshot. Use the same ids your
66
+ [`@ralphralphai/tracker`](https://www.npmjs.com/package/@ralphralphai/tracker)
67
+ setup uses:
68
+
69
+ ```html
70
+ <button data-track-id="add-to-cart">Add to cart</button>
71
+ ```
72
+
73
+ ## Recording pages
74
+
75
+ ### Automatically, on every URL change
41
76
 
77
+ In a `@ralph` test, Ralph records a page whenever the URL changes in any tab of
78
+ the test's browser context:
79
+
80
+ - full page loads, redirects (the final page), and SPA route changes
81
+ - query-string and hash changes
82
+ - new tabs and popups
83
+
84
+ It does not record `about:blank`, iframes, reloads of the same URL, or changes
85
+ to the page that don't change the URL.
86
+
87
+ Each recording waits for the DOM to load, then another 150 ms (`settleMs`) for
88
+ the page to render. If your app needs longer, use the `ready` option to say when
89
+ a page is ready:
90
+
91
+ ```ts
92
+ ralphOptions: {
93
+ ready: async (page, signal) => {
94
+ await page.getByTestId('app-ready').waitFor({ timeout: 3000 });
95
+ },
96
+ },
97
+ ```
98
+
99
+ ### Explicitly, for states at the same URL
100
+
101
+ Dialogs, menus, and tabs often change the page without changing the URL. Record
102
+ them with `ralph.recordNode`:
103
+
104
+ ```ts
105
+ test('shipping options', { tag: '@ralph' }, async ({ page, ralph }) => {
106
+ await page.goto('https://example.com/cart');
42
107
  await page.getByRole('button', { name: 'Shipping options' }).click();
43
108
  await expect(page.getByRole('dialog')).toBeVisible();
44
109
 
45
- await ralph.captureNode(page, {
46
- state: 'shipping-options-open',
47
- url: '/checkout/shipping-options',
110
+ await ralph.recordNode(page, {
111
+ state: 'shipping-options-open', // a label for this state
112
+ url: '/checkout/shipping-options', // optional: the URL to record it under
48
113
  });
49
114
  });
115
+ ```
50
116
 
51
- test('ordinary validation', async ({ page }) => {
52
- await page.goto('https://example.com/login');
53
- await expect(page.getByRole('button', { name: 'Sign in' })).toBeVisible();
54
- });
117
+ `recordNode` records the page as it is right now, without waiting for it to
118
+ settle, so call it after your assertions. The `url` option only changes what is
119
+ recorded; the browser doesn't navigate. Relative URLs resolve against the
120
+ current page.
121
+
122
+ ### Waiting for recordings
123
+
124
+ Recording runs in the background, so your test isn't slowed down. If the test
125
+ moves on before a screenshot finishes, that recording is marked `superseded`
126
+ rather than attributed to the wrong page. When you need a page recorded before
127
+ leaving it, wait for pending recordings first:
128
+
129
+ ```ts
130
+ await page.goto('https://example.com/pricing');
131
+ await ralph.flush(); // make sure /pricing is recorded
132
+ await page.goto('https://example.com/signup');
55
133
  ```
56
134
 
57
- ```sh
58
- RALPH_MODE=local pnpm exec playwright test
135
+ ### Browser contexts you create yourself
136
+
137
+ The standard `context` and `page` fixtures are recorded for you. For a context
138
+ you create yourself, enroll it, and flush before closing it:
139
+
140
+ ```ts
141
+ const context = await browser.newContext();
142
+ ralph.observe(context);
143
+ // ...
144
+ await ralph.flush();
145
+ await context.close();
59
146
  ```
60
147
 
61
- Recording defaults to off. Normal `pnpm exec playwright test` runs need no
62
- Ralph config or credentials. Unmarked tests stay unrecorded in every mode.
63
- Calling `ralph.capture` in an unmarked test while recording is enabled reports
64
- the missing `@ralph` tag.
148
+ ### When recording fails
149
+
150
+ - An explicit `recordNode` call that fails rejects, like any other failing
151
+ step.
152
+ - A failed automatic recording fails an otherwise passing test at the end, so
153
+ missing pages don't go unnoticed. The reason is recorded with the results.
154
+ - A superseded recording, or a page whose layout moved during its screenshot,
155
+ doesn't fail the test.
156
+
157
+ ## Modes
65
158
 
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
159
+ Set the mode with `RALPH_MODE` or the `mode` option:
160
+
161
+ | Mode | What happens |
162
+ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
163
+ | `off` | Default. Nothing is recorded, no Ralph config or credentials are needed, and `ralph.recordNode` does nothing. |
164
+ | `local` | `@ralph` tests are recorded into Playwright's output directory. |
165
+ | `upload` | As `local`, and each test's recordings are uploaded to Ralph when it finishes. Chosen automatically when `RALPH_UPLOAD_KEY` is set and no mode is. |
166
+
167
+ Tests without the `@ralph` tag are never recorded, in any mode. In `local` or
168
+ `upload` mode, calling `ralph.recordNode` in an untagged test throws a reminder
169
+ to add the tag.
170
+
171
+ Tests that import from `@playwright/test` directly keep working alongside Ralph
69
172
  tests.
70
173
 
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.
174
+ ## Uploading to Ralph
79
175
 
80
- ```sh
81
- RALPH_MODE=upload RALPH_API_URL=https://your-ralph-server.example RALPH_APP_ID=<app id> pnpm exec playwright test
82
- ```
176
+ 1. Create an upload key for your Ralph project with the authenticated
177
+ `/api/project/key/create` endpoint. One key covers every app in the project.
178
+ Keep it in your CI secrets.
179
+ 2. Set these, as environment variables or options:
180
+
181
+ | Variable | Option | Value |
182
+ | ------------------ | -------- | -------------------------------------- |
183
+ | `RALPH_UPLOAD_KEY` | | The upload key. Environment only. |
184
+ | `RALPH_API_URL` | `apiUrl` | Your Ralph server, e.g. `https://…` |
185
+ | `RALPH_APP_ID` | `appId` | The app these recordings belong to |
186
+
187
+ 3. Run:
188
+
189
+ ```sh
190
+ RALPH_MODE=upload RALPH_API_URL=https://your-ralph-server.example RALPH_APP_ID=<app id> \
191
+ pnpm exec playwright test
192
+ ```
83
193
 
84
- This uses `RALPH_UPLOAD_KEY` from the environment. Each opted-in attempt uploads
85
- once during fixture teardown, after its local manifest and screenshots are saved.
86
- No reporter configuration is required. `off`, `local`, and unmarked tests do not
87
- upload, even if CI provides a key. Upload failures fail fixture teardown and leave
88
- local artifacts available according to Playwright's `preserveOutput` setting.
89
- Successful uploads attach a `ralph-upload-receipt`; credentials are never written
90
- to the manifest or receipt.
91
-
92
- ## Capture behavior
93
-
94
- Automatic capture watches main-frame HTTP(S) URLs in the standard `context`
95
- fixture, including new tabs and popups. It includes the first application URL,
96
- full-document navigation, SPA history changes, query changes, and hash changes.
97
- It skips `about:blank`, subframe navigation, same-URL reloads, and DOM-only changes.
98
-
99
- Automatic captures wait for DOM content loaded and then settle for 150 ms by
100
- default. A URL change does not prove application readiness. Supply `ready` for
101
- an application-specific condition, or explicitly capture after your assertions.
102
-
103
- Capture runs in the background and is serialized per browser page. A test can
104
- navigate again before a screenshot completes. Such nodes are retained as
105
- `superseded`, without a screenshot attributed to the wrong URL. Use
106
- `await ralph.flush()` to wait for currently queued captures before deliberately
107
- leaving a state whose screenshot you need. This is also useful before manually
108
- closing a page or context.
109
-
110
- The standard context fixture drains captures before closing its pages. Manually
111
- created contexts can be enrolled with `ralph.observe(context)`; their owner must
112
- flush before closing them. An explicit capture enrolls its page's context too.
113
-
114
- `await ralph.captureNode(page, { state?, url? })` always requests a node,
115
- even at the same URL. It uses the current state without the automatic settling
116
- delay or readiness hook. A relative URL override resolves against the actual
117
- browser URL. It does not navigate, change history, or affect subsequent automatic
118
- captures. Only HTTP(S) recorded URLs are accepted.
119
-
120
- Explicit capture errors reject the call. Automatic capture errors are written
121
- to the manifest and fail an otherwise passing opted-in test at fixture teardown.
122
- Superseded captures and detected geometry changes mark the manifest incomplete,
123
- without independently failing the test.
194
+ Each attempt of a `@ralph` test uploads once when it finishes. The upload
195
+ receipt is attached to the test as `ralph-upload-receipt`, and its `resultId`
196
+ opens the upload in the Ralph dashboard at `/uploads/<resultId>`. The upload key
197
+ is never written to the results or the receipt.
198
+
199
+ `RALPH_API_URL` must be HTTPS. Plain HTTP is accepted only for `localhost`,
200
+ `127.x.x.x`, and `[::1]`, for local development.
201
+
202
+ If an upload fails, the test fails.
124
203
 
125
204
  ## Screen sizes
126
205
 
127
- A viewport is fixed for a browser context, and a mobile flow is usually a
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:
206
+ A browser context's viewport is fixed, so each entry in `screenSizes` becomes
207
+ its own Playwright project. `ralphProjects` expands one project into one per
208
+ size:
132
209
 
133
210
  ```ts
134
211
  import { defineConfig, devices } from '@playwright/test';
@@ -138,8 +215,8 @@ export default defineConfig<RalphFixtures>({
138
215
  projects: [
139
216
  // Ordinary tests keep their single project.
140
217
  { name: 'chromium', grepInvert: /@ralph\b/, use: devices['Desktop Chrome'] },
141
- // With screenSizes 375x812, 768x1024 and 1280x720, this yields the projects
142
- // "chromium 375x812", "chromium 768x1024" and "chromium 1280x720".
218
+ // With screenSizes 375x812 and 1280x720, this creates the projects
219
+ // "chromium 375x812" and "chromium 1280x720".
143
220
  ...ralphProjects({
144
221
  name: 'chromium',
145
222
  grep: /@ralph\b/,
@@ -149,15 +226,14 @@ export default defineConfig<RalphFixtures>({
149
226
  });
150
227
  ```
151
228
 
152
- A relative `configPath` resolves against the current directory. Pass
153
- `{ root: import.meta.dirname }` (or `__dirname`) or an inline `config` as the
154
- second argument when Playwright runs from elsewhere. Keep it pointing at the
155
- same config as `ralphOptions`.
229
+ `ralphProjects` reads `ralph.jsonc` from the current directory. If Playwright
230
+ runs from elsewhere, pass `{ root: import.meta.dirname }` (or `__dirname`), or
231
+ an inline `config`, as the second argument. Keep it pointing at the same config
232
+ as `ralphOptions`.
156
233
 
157
- By default, a test runs at every screen size. Tag a test or a `describe` block
158
- with `@ralph-screen:WIDTHxHEIGHT`, once per size, to run it only at the listed
159
- sizes. Tags on a `describe` block and its tests combine. The other
160
- screen projects skip it:
234
+ A test runs at every size by default. A mobile flow is usually a different test
235
+ from its desktop flow, so tag a test or `describe` block with
236
+ `@ralph-screen:WIDTHxHEIGHT`, once per size, to run it only at those sizes:
161
237
 
162
238
  ```ts
163
239
  test(
@@ -168,36 +244,22 @@ test(
168
244
  },
169
245
  );
170
246
 
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
- );
177
-
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
- Screen tags apply in every mode, including `off`, and to untagged tests, because
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
190
-
191
- `WebRalphConfig` is re-exported as a type from this package for inline config
192
- annotations. Its definition remains in `@ralphralphai/config`:
252
+ - Tags on a `describe` block and its tests combine.
253
+ - Screen tags apply in every mode, because they choose which tests run.
254
+ - A size missing from `screenSizes` fails the test, so a typo can't silently
255
+ skip it.
256
+ - Projects not created by `ralphProjects` ignore screen tags.
193
257
 
194
- ```ts
195
- import type { WebRalphConfig } from '@ralphralphai/playwright';
258
+ To run one size: `playwright test --project "chromium 375x812"`.
196
259
 
197
- const config: WebRalphConfig = { screenSizes: [] };
198
- ```
260
+ ## Options
199
261
 
200
- Set `ralphOptions` with `test.use` or Playwright's configuration:
262
+ Set `ralphOptions` in `playwright.config.ts`, or per file with `test.use`:
201
263
 
202
264
  ```ts
203
265
  import { defineConfig } from '@playwright/test';
@@ -205,146 +267,51 @@ import type { RalphFixtures } from '@ralphralphai/playwright';
205
267
 
206
268
  export default defineConfig<RalphFixtures>({
207
269
  use: {
208
- viewport: { width: 1280, height: 720 },
209
270
  ralphOptions: {
210
271
  mode: 'local',
211
272
  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
- },
273
+ buildId: process.env.GIT_SHA,
274
+ settleMs: 300,
220
275
  },
221
276
  },
222
277
  });
223
278
  ```
224
279
 
225
- | Option | Behavior |
226
- | --- | --- |
227
- | `mode` | `off`, `local`, or `upload`; overrides `RALPH_MODE` |
228
- | `apiUrl` | Server origin for uploads; falls back to `RALPH_API_URL` |
229
- | `uploadTimeoutMs` | Upload request deadline; defaults to 15000 ms |
230
- | `configPath` | Relative to the Playwright config directory; defaults to `ralph.jsonc` |
231
- | `config` | Inline `WebRalphConfig` instead of a config file |
232
- | `buildId` | Optional application build ID; falls back to `RALPH_BUILD_ID` |
233
- | `runId` | Optional shared CI run ID; falls back to `RALPH_RUN_ID` |
234
- | `settleMs` | Automatic settling delay; defaults to 150 ms |
235
- | `captureTimeoutMs` | Per-capture browser-work deadline; defaults to 5000 ms |
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:
280
+ | Option | Default | What it does |
281
+ | ----------------- | --------------------- | ------------------------------------------------------------------------------------------------ |
282
+ | `mode` | `RALPH_MODE`, else `off` | `off`, `local`, or `upload`. See [Modes](#modes). |
283
+ | `configPath` | `ralph.jsonc` | Path to the config, relative to the Playwright config's directory. |
284
+ | `config` | | An inline config instead of a file. |
285
+ | `apiUrl` | `RALPH_API_URL` | Your Ralph server, for uploads. |
286
+ | `appId` | `RALPH_APP_ID` | The app recordings belong to, for uploads. |
287
+ | `uploadTimeoutMs` | 30000 | How long an upload may take. |
288
+ | `buildId` | `RALPH_BUILD_ID` | Your app's build, so recordings line up with the analytics from that build. |
289
+ | `runId` | `RALPH_RUN_ID` | Groups the tests of one CI run. |
290
+ | `settleMs` | 150 | How long to wait after the DOM loads before an automatic recording. |
291
+ | `recordTimeoutMs` | 5000 | How long one recording may take, including settling and `ready`. Must be more than `settleMs`. |
292
+ | `ready` | | `(page, signal) => Promise<void>`. Waits for your app to be ready before an automatic recording. |
293
+
294
+ `ready` is bounded by `recordTimeoutMs`. Ralph stops waiting when time runs out
295
+ but can't cancel your code, so honor `signal` or use Playwright operations with
296
+ their own timeouts.
297
+
298
+ Config files accept comments and trailing commas, and are validated when the
299
+ first `@ralph` test in a worker starts. Keep the config unchanged during a run.
300
+
301
+ For an inline config, the type is re-exported:
253
302
 
254
303
  ```ts
255
- import { mergeTests } from '@playwright/test';
256
- import { test as ralphTest } from '@ralphralphai/playwright';
257
- import { test as applicationTest } from './application-fixtures';
304
+ import type { WebRalphConfig } from '@ralphralphai/playwright';
258
305
 
259
- export const test = mergeTests(applicationTest, ralphTest);
306
+ const config: WebRalphConfig = { screenSizes: [{ width: 1280, height: 720 }] };
260
307
  ```
261
308
 
262
- If a custom fixture replaces the standard context entirely, enroll that context
263
- using `ralph.observe(context)`. Capture it while it is still alive.
264
-
265
- ## Artifacts
266
-
267
- Each opted-in attempt writes `ralph/captures.json` and full-page screenshots within
268
- Playwright's per-test output directory. The JSON is attached as
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:
309
+ ## Limits
337
310
 
338
- ```sh
339
- pnpm --filter @ralphralphai/config build
340
- pnpm --filter @ralphralphai/playwright build
341
- pnpm --filter @ralphralphai/playwright check
342
- pnpm --filter @ralphralphai/playwright lint
343
- pnpm --filter @ralphralphai/playwright test
344
- pnpm --filter @ralphralphai/playwright exec playwright install chromium
345
- pnpm --filter @ralphralphai/playwright test:browser
346
- ```
311
+ Per test attempt: 500 screenshots, 64 MiB of screenshots, 2 MiB of recorded
312
+ data besides the screenshots, and 64 MiB compressed. The server accepts up to
313
+ 5,000 recorded pages. Split larger flows into several tests.
314
+
315
+ ## License
347
316
 
348
- The browser suite runs real Playwright tests against locally supplied pages,
349
- then checks the emitted manifests and screenshot attachments. It includes an
350
- intentional failed first attempt to verify retry isolation.
317
+ Apache-2.0