@ralphralphai/playwright 0.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/README.md +350 -0
- package/dist/index.cjs +708 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.mts +191 -0
- package/dist/index.mjs +665 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +74 -0
package/README.md
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# @ralphralphai/playwright
|
|
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.
|
|
7
|
+
|
|
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.
|
|
11
|
+
|
|
12
|
+
## Install and enable
|
|
13
|
+
|
|
14
|
+
Requires Node.js 20.19+ and Playwright Test 1.62.1+ within major version 1.
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
pnpm add -D @ralphralphai/playwright @playwright/test
|
|
18
|
+
pnpm exec playwright install chromium
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Put a `ralph.jsonc` next to `playwright.config.ts`:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"screenSizes": []
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
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.
|
|
32
|
+
|
|
33
|
+
Use Ralph's extended `test` and tag the tests that should participate:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { test, expect } from '@ralphralphai/playwright';
|
|
37
|
+
|
|
38
|
+
test('checkout', { tag: '@ralph' }, async ({ page, ralph }) => {
|
|
39
|
+
await page.goto('https://example.com/cart');
|
|
40
|
+
await ralph.flush();
|
|
41
|
+
|
|
42
|
+
await page.getByRole('button', { name: 'Shipping options' }).click();
|
|
43
|
+
await expect(page.getByRole('dialog')).toBeVisible();
|
|
44
|
+
|
|
45
|
+
await ralph.captureNode(page, {
|
|
46
|
+
state: 'shipping-options-open',
|
|
47
|
+
url: '/checkout/shipping-options',
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
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
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
RALPH_MODE=local pnpm exec playwright test
|
|
59
|
+
```
|
|
60
|
+
|
|
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.
|
|
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.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
RALPH_MODE=upload RALPH_API_URL=https://your-ralph-server.example RALPH_APP_ID=<app id> pnpm exec playwright test
|
|
82
|
+
```
|
|
83
|
+
|
|
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.
|
|
124
|
+
|
|
125
|
+
## Screen sizes
|
|
126
|
+
|
|
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:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
135
|
+
import { ralphProjects, type RalphFixtures } from '@ralphralphai/playwright';
|
|
136
|
+
|
|
137
|
+
export default defineConfig<RalphFixtures>({
|
|
138
|
+
projects: [
|
|
139
|
+
// Ordinary tests keep their single project.
|
|
140
|
+
{ 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".
|
|
143
|
+
...ralphProjects({
|
|
144
|
+
name: 'chromium',
|
|
145
|
+
grep: /@ralph\b/,
|
|
146
|
+
use: devices['Desktop Chrome'],
|
|
147
|
+
}),
|
|
148
|
+
],
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
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`.
|
|
156
|
+
|
|
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:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
test(
|
|
164
|
+
'mobile navigation',
|
|
165
|
+
{ tag: ['@ralph', '@ralph-screen:375x812'] },
|
|
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
|
+
);
|
|
177
|
+
|
|
178
|
+
test.describe('desktop layouts', { tag: '@ralph-screen:1280x720' }, () => {
|
|
179
|
+
test('sidebar', { tag: '@ralph' }, async ({ page }) => {});
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
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`:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import type { WebRalphConfig } from '@ralphralphai/playwright';
|
|
196
|
+
|
|
197
|
+
const config: WebRalphConfig = { screenSizes: [] };
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Set `ralphOptions` with `test.use` or Playwright's configuration:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import { defineConfig } from '@playwright/test';
|
|
204
|
+
import type { RalphFixtures } from '@ralphralphai/playwright';
|
|
205
|
+
|
|
206
|
+
export default defineConfig<RalphFixtures>({
|
|
207
|
+
use: {
|
|
208
|
+
viewport: { width: 1280, height: 720 },
|
|
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
|
+
},
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
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:
|
|
253
|
+
|
|
254
|
+
```ts
|
|
255
|
+
import { mergeTests } from '@playwright/test';
|
|
256
|
+
import { test as ralphTest } from '@ralphralphai/playwright';
|
|
257
|
+
import { test as applicationTest } from './application-fixtures';
|
|
258
|
+
|
|
259
|
+
export const test = mergeTests(applicationTest, ralphTest);
|
|
260
|
+
```
|
|
261
|
+
|
|
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:
|
|
337
|
+
|
|
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
|
+
```
|
|
347
|
+
|
|
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.
|