@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 +217 -250
- package/dist/index.cjs +54 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +18 -17
- package/dist/index.mjs +53 -53
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -11,15 +11,15 @@ type RalphOptions = {
|
|
|
11
11
|
runId?: string;
|
|
12
12
|
buildId?: string;
|
|
13
13
|
settleMs?: number;
|
|
14
|
-
|
|
14
|
+
recordTimeoutMs?: number;
|
|
15
15
|
ready?: (page: Page, signal: AbortSignal) => Promise<void>;
|
|
16
16
|
};
|
|
17
|
-
type
|
|
17
|
+
type RecordNodeOptions = {
|
|
18
18
|
state?: string;
|
|
19
19
|
url?: string;
|
|
20
20
|
};
|
|
21
21
|
type Ralph = {
|
|
22
|
-
|
|
22
|
+
recordNode(page: Page, options?: RecordNodeOptions): Promise<void>;
|
|
23
23
|
flush(): Promise<void>;
|
|
24
24
|
observe(context: BrowserContext): void;
|
|
25
25
|
};
|
|
@@ -56,14 +56,14 @@ type RawNode = {
|
|
|
56
56
|
trigger: 'url-change' | 'explicit';
|
|
57
57
|
state?: string;
|
|
58
58
|
/**
|
|
59
|
-
* The
|
|
60
|
-
* page, or for a popup's first
|
|
61
|
-
*
|
|
59
|
+
* The node this page state was reached from: the previous one on the same
|
|
60
|
+
* page, or for a popup's first node, its opener's latest. Superseded nodes
|
|
61
|
+
* are skipped.
|
|
62
62
|
*/
|
|
63
63
|
previousNodeId?: string;
|
|
64
64
|
} & ({
|
|
65
|
-
status: '
|
|
66
|
-
|
|
65
|
+
status: 'recorded';
|
|
66
|
+
recordedAt: string;
|
|
67
67
|
layout: PageLayout;
|
|
68
68
|
geometryStable: boolean;
|
|
69
69
|
screenshot: {
|
|
@@ -82,7 +82,7 @@ type ConfigSnapshot = {
|
|
|
82
82
|
config: WebRalphConfig$1;
|
|
83
83
|
packageVersion: string;
|
|
84
84
|
};
|
|
85
|
-
type
|
|
85
|
+
type RawGraphManifest = {
|
|
86
86
|
formatVersion: 1;
|
|
87
87
|
producer: {
|
|
88
88
|
name: '@ralphralphai/playwright';
|
|
@@ -135,7 +135,7 @@ type UploadOptions = {
|
|
|
135
135
|
uploadKey: string;
|
|
136
136
|
timeoutMs?: number;
|
|
137
137
|
};
|
|
138
|
-
type
|
|
138
|
+
type RawGraphScreenshot = {
|
|
139
139
|
nodeId: string;
|
|
140
140
|
contentType: Screenshot['contentType'];
|
|
141
141
|
bytes: Uint8Array;
|
|
@@ -144,10 +144,11 @@ type UploadReceipt = {
|
|
|
144
144
|
result: 'ok';
|
|
145
145
|
status: 'received';
|
|
146
146
|
attemptId: string;
|
|
147
|
+
resultId: string;
|
|
147
148
|
replayed: boolean;
|
|
148
149
|
};
|
|
149
|
-
type
|
|
150
|
-
manifest:
|
|
150
|
+
type RawGraphBundleIndex = {
|
|
151
|
+
manifest: RawGraphManifest;
|
|
151
152
|
screenshots: {
|
|
152
153
|
nodeId: string;
|
|
153
154
|
path: string;
|
|
@@ -156,14 +157,14 @@ type CaptureBundleIndex = {
|
|
|
156
157
|
/**
|
|
157
158
|
* Packs the manifest and screenshots into a gzipped tar archive:
|
|
158
159
|
*
|
|
159
|
-
*
|
|
160
|
+
* raw_graph.json { manifest, screenshots: [{ nodeId, path }] }
|
|
160
161
|
* assets/images/<id>.webp raw screenshot bytes (.png when too large for WebP)
|
|
161
162
|
*/
|
|
162
|
-
declare function
|
|
163
|
-
declare function
|
|
163
|
+
declare function createRawGraphBundle(manifest: RawGraphManifest, screenshots: readonly RawGraphScreenshot[]): Promise<Uint8Array>;
|
|
164
|
+
declare function uploadRawGraph(manifest: RawGraphManifest, screenshots: readonly RawGraphScreenshot[], options: UploadOptions): Promise<UploadReceipt>;
|
|
164
165
|
//#endregion
|
|
165
166
|
//#region src/recorder.d.ts
|
|
166
|
-
declare const MANIFEST_ATTACHMENT = "ralph-
|
|
167
|
+
declare const MANIFEST_ATTACHMENT = "ralph-raw-graph";
|
|
167
168
|
//#endregion
|
|
168
169
|
//#region src/screens.d.ts
|
|
169
170
|
type RalphProject = Project<PlaywrightTestOptions & {
|
|
@@ -187,5 +188,5 @@ type RalphFixtures = {
|
|
|
187
188
|
};
|
|
188
189
|
declare const test: TestType<PlaywrightTestArgs & PlaywrightTestOptions & RalphFixtures, PlaywrightWorkerArgs & PlaywrightWorkerOptions>;
|
|
189
190
|
//#endregion
|
|
190
|
-
export { type
|
|
191
|
+
export { type ConfigSnapshot, MANIFEST_ATTACHMENT, type PageLayout, type Ralph, RalphFixtures, type RalphOptions, type RalphProjectOptions, type RawGraphBundleIndex, type RawGraphManifest, type RawGraphScreenshot, type RawNode, type RecordNodeOptions, SCREEN_TAG_PREFIX, type TrackedElement, type UploadOptions, type UploadReceipt, type WebRalphConfig, createRawGraphBundle, expect, mergeTests, ralphProjects, test, uploadRawGraph };
|
|
191
192
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
CHANGED
|
@@ -44,11 +44,11 @@ function snapshot(value) {
|
|
|
44
44
|
}
|
|
45
45
|
//#endregion
|
|
46
46
|
//#region package.json
|
|
47
|
-
var version = "0.
|
|
47
|
+
var version = "0.2.0";
|
|
48
48
|
//#endregion
|
|
49
49
|
//#region src/image.ts
|
|
50
50
|
/**
|
|
51
|
-
* Matches the crawler's full-page
|
|
51
|
+
* Matches the crawler's full-page screenshots: lossless measured larger than this
|
|
52
52
|
* on real sites, which are mostly photographic content.
|
|
53
53
|
*/
|
|
54
54
|
const WEBP_QUALITY = 80;
|
|
@@ -148,21 +148,21 @@ function resolveUploadOptions(options) {
|
|
|
148
148
|
timeoutMs: options.uploadTimeoutMs
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
|
-
const
|
|
152
|
-
const
|
|
151
|
+
const RAW_GRAPH_BUNDLE_INDEX = "raw_graph.json";
|
|
152
|
+
const RAW_GRAPH_BUNDLE_IMAGE_DIR = "assets/images";
|
|
153
153
|
/**
|
|
154
154
|
* Packs the manifest and screenshots into a gzipped tar archive:
|
|
155
155
|
*
|
|
156
|
-
*
|
|
156
|
+
* raw_graph.json { manifest, screenshots: [{ nodeId, path }] }
|
|
157
157
|
* assets/images/<id>.webp raw screenshot bytes (.png when too large for WebP)
|
|
158
158
|
*/
|
|
159
|
-
async function
|
|
160
|
-
if (Buffer.byteLength(JSON.stringify(manifest)) > 2097152 || screenshots.length > 500 || screenshots.reduce((total, screenshot) => total + screenshot.bytes.byteLength, 0) > 67108864) throw new Error("Ralph
|
|
159
|
+
async function createRawGraphBundle(manifest, screenshots) {
|
|
160
|
+
if (Buffer.byteLength(JSON.stringify(manifest)) > 2097152 || screenshots.length > 500 || screenshots.reduce((total, screenshot) => total + screenshot.bytes.byteLength, 0) > 67108864) throw new Error("Ralph raw graph exceeds upload limits.");
|
|
161
161
|
const images = screenshots.map(({ nodeId, contentType, bytes }) => {
|
|
162
162
|
if (!/^[A-Za-z0-9-]{1,64}$/.test(nodeId)) throw new Error(`Ralph node id is not path-safe: ${nodeId}`);
|
|
163
163
|
return {
|
|
164
164
|
nodeId,
|
|
165
|
-
path: `${
|
|
165
|
+
path: `${RAW_GRAPH_BUNDLE_IMAGE_DIR}/${nodeId}${imageExtension(contentType)}`,
|
|
166
166
|
bytes
|
|
167
167
|
};
|
|
168
168
|
});
|
|
@@ -180,7 +180,7 @@ async function createCaptureBundle(manifest, screenshots) {
|
|
|
180
180
|
};
|
|
181
181
|
const tar = pack();
|
|
182
182
|
tar.entry({
|
|
183
|
-
name:
|
|
183
|
+
name: RAW_GRAPH_BUNDLE_INDEX,
|
|
184
184
|
...attrs
|
|
185
185
|
}, JSON.stringify(index));
|
|
186
186
|
for (const { path, bytes } of images) tar.entry({
|
|
@@ -191,19 +191,19 @@ async function createCaptureBundle(manifest, screenshots) {
|
|
|
191
191
|
const chunks = [];
|
|
192
192
|
for await (const chunk of tar) chunks.push(chunk);
|
|
193
193
|
const compressed = await promisify(gzip)(Buffer.concat(chunks));
|
|
194
|
-
if (compressed.byteLength > 67108864) throw new Error("Ralph compressed
|
|
194
|
+
if (compressed.byteLength > 67108864) throw new Error("Ralph compressed raw graph bundle exceeds upload limits.");
|
|
195
195
|
return compressed;
|
|
196
196
|
}
|
|
197
197
|
function isLoopback(url) {
|
|
198
198
|
return url.hostname === "localhost" || url.hostname === "[::1]" || /^127(\.\d{1,3}){3}$/.test(url.hostname);
|
|
199
199
|
}
|
|
200
|
-
async function
|
|
200
|
+
async function uploadRawGraph(manifest, screenshots, options) {
|
|
201
201
|
if (!/^[A-Za-z0-9_-]{1,64}$/.test(options.appId)) throw new Error(`Ralph app id is not valid: ${options.appId}`);
|
|
202
202
|
const url = new URL(`/api/upload/playwright/apps/${options.appId}/attempts`, options.apiUrl);
|
|
203
203
|
if (!(url.protocol === "https:" || url.protocol === "http:" && isLoopback(url)) || !options.uploadKey) throw new Error("Ralph upload requires an HTTPS API URL (HTTP only for loopback hosts) and an upload key.");
|
|
204
204
|
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
205
205
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("Ralph uploadTimeoutMs must be positive and finite.");
|
|
206
|
-
const blob = new Blob([new Uint8Array(await
|
|
206
|
+
const blob = new Blob([new Uint8Array(await createRawGraphBundle(manifest, screenshots))], { type: "application/gzip" });
|
|
207
207
|
const response = await fetch(url, {
|
|
208
208
|
method: "POST",
|
|
209
209
|
headers: { Authorization: `Bearer ${options.uploadKey}` },
|
|
@@ -211,17 +211,17 @@ async function uploadCapture(manifest, screenshots, options) {
|
|
|
211
211
|
redirect: "error",
|
|
212
212
|
signal: AbortSignal.timeout(timeoutMs)
|
|
213
213
|
});
|
|
214
|
-
if (!response.ok) throw new Error(`Ralph upload failed (HTTP ${response.status}). Local
|
|
214
|
+
if (!response.ok) throw new Error(`Ralph upload failed (HTTP ${response.status}). Local Ralph artifacts are retained for retry.`);
|
|
215
215
|
const receipt = await response.json();
|
|
216
|
-
if (receipt.result !== "ok" || receipt.status !== "received" || receipt.attemptId !== manifest.attemptId || typeof receipt.replayed !== "boolean") throw new Error("Ralph server returned an invalid upload receipt.");
|
|
216
|
+
if (receipt.result !== "ok" || receipt.status !== "received" || receipt.attemptId !== manifest.attemptId || typeof receipt.resultId !== "string" || typeof receipt.replayed !== "boolean") throw new Error("Ralph server returned an invalid upload receipt.");
|
|
217
217
|
return receipt;
|
|
218
218
|
}
|
|
219
219
|
//#endregion
|
|
220
220
|
//#region src/recorder.ts
|
|
221
|
-
const MANIFEST_ATTACHMENT = "ralph-
|
|
221
|
+
const MANIFEST_ATTACHMENT = "ralph-raw-graph";
|
|
222
222
|
const DEFAULT_SETTLE_MS = 150;
|
|
223
|
-
const
|
|
224
|
-
/** Where
|
|
223
|
+
const DEFAULT_RECORD_TIMEOUT_MS = 5e3;
|
|
224
|
+
/** Where the raw graph and screenshots are written, inside Playwright's per-test output directory. */
|
|
225
225
|
const OUTPUT_DIR = "ralph";
|
|
226
226
|
var Recorder = class {
|
|
227
227
|
options;
|
|
@@ -234,13 +234,13 @@ var Recorder = class {
|
|
|
234
234
|
*/
|
|
235
235
|
attemptId = randomUUID();
|
|
236
236
|
startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
237
|
-
/** How long to wait after a navigation's DOM loads before
|
|
237
|
+
/** How long to wait after a navigation's DOM loads before recording. */
|
|
238
238
|
settleMs;
|
|
239
239
|
/**
|
|
240
|
-
* The budget for one
|
|
241
|
-
* the
|
|
240
|
+
* The budget for recording one node, from settling through the screenshot.
|
|
241
|
+
* Past it, the node is recorded as failed.
|
|
242
242
|
*/
|
|
243
|
-
|
|
243
|
+
recordTimeoutMs;
|
|
244
244
|
/**
|
|
245
245
|
* Every context being watched. `onPage` is kept so `stopContext` can remove
|
|
246
246
|
* the exact listener `observe` added. The id is stored in the manifest's
|
|
@@ -249,17 +249,17 @@ var Recorder = class {
|
|
|
249
249
|
contexts = /* @__PURE__ */ new Map();
|
|
250
250
|
/**
|
|
251
251
|
* Every tab seen in a watched context, including closed ones, so their
|
|
252
|
-
*
|
|
252
|
+
* nodes and opener links still reach the manifest.
|
|
253
253
|
*/
|
|
254
254
|
pages = /* @__PURE__ */ new Map();
|
|
255
255
|
/**
|
|
256
|
-
* Each
|
|
256
|
+
* Each node's outcome, in completion order. Nodes in one tab complete
|
|
257
257
|
* in sequence, but tabs interleave; `finish` sorts them.
|
|
258
258
|
*/
|
|
259
259
|
nodes = [];
|
|
260
260
|
/**
|
|
261
|
-
* When each
|
|
262
|
-
* popup's first
|
|
261
|
+
* When each node was requested, keyed by node id, for linking a
|
|
262
|
+
* popup's first node to its opener's latest. Numbers, so they compare
|
|
263
263
|
* without parsing each node's `requestedAt` string.
|
|
264
264
|
*/
|
|
265
265
|
requestedAtMs = /* @__PURE__ */ new Map();
|
|
@@ -271,10 +271,10 @@ var Recorder = class {
|
|
|
271
271
|
this.testInfo = testInfo;
|
|
272
272
|
this.upload = upload;
|
|
273
273
|
this.settleMs = options.settleMs ?? DEFAULT_SETTLE_MS;
|
|
274
|
-
this.
|
|
275
|
-
if (!(Number.isFinite(this.settleMs) && this.settleMs >= 0 && Number.isFinite(this.
|
|
274
|
+
this.recordTimeoutMs = options.recordTimeoutMs ?? DEFAULT_RECORD_TIMEOUT_MS;
|
|
275
|
+
if (!(Number.isFinite(this.settleMs) && this.settleMs >= 0 && Number.isFinite(this.recordTimeoutMs) && this.recordTimeoutMs > this.settleMs)) throw new Error("Ralph recordTimeoutMs must be finite and greater than settleMs, which must be non-negative.");
|
|
276
276
|
}
|
|
277
|
-
/** Starts
|
|
277
|
+
/** Starts recording URL changes in every current and future tab of `context`. */
|
|
278
278
|
observe(context) {
|
|
279
279
|
if (this.closed) throw new Error("The Ralph recorder has finished.");
|
|
280
280
|
if (this.contexts.has(context)) return;
|
|
@@ -287,18 +287,18 @@ var Recorder = class {
|
|
|
287
287
|
context.on("page", onPage);
|
|
288
288
|
for (const page of context.pages()) onPage(page);
|
|
289
289
|
}
|
|
290
|
-
/**
|
|
291
|
-
async
|
|
290
|
+
/** Records the page as it is now, e.g. an open dialog at the same URL. */
|
|
291
|
+
async recordNode(page, options = {}) {
|
|
292
292
|
this.observe(page.context());
|
|
293
293
|
const state = this.pages.get(page);
|
|
294
|
-
if (!state) throw new Error("Cannot
|
|
294
|
+
if (!state) throw new Error("Cannot record a closed browser page.");
|
|
295
295
|
await this.enqueue(state, "explicit", options);
|
|
296
296
|
}
|
|
297
|
-
/** Waits for every queued
|
|
297
|
+
/** Waits for every queued recording to finish. */
|
|
298
298
|
async flush() {
|
|
299
299
|
await Promise.all([...this.pages.values()].map((state) => state.tail));
|
|
300
300
|
}
|
|
301
|
-
/** Stops watching `context` and waits for its queued
|
|
301
|
+
/** Stops watching `context` and waits for its queued recordings. */
|
|
302
302
|
async stopContext(context) {
|
|
303
303
|
const entry = this.contexts.get(context);
|
|
304
304
|
if (entry) context.off("page", entry.onPage);
|
|
@@ -308,7 +308,7 @@ var Recorder = class {
|
|
|
308
308
|
}
|
|
309
309
|
/**
|
|
310
310
|
* Stops recording, then writes the manifest, uploads it if configured, and
|
|
311
|
-
* fails a passing test whose
|
|
311
|
+
* fails a passing test whose nodes failed to record.
|
|
312
312
|
*/
|
|
313
313
|
async finish() {
|
|
314
314
|
this.closed = true;
|
|
@@ -320,7 +320,7 @@ var Recorder = class {
|
|
|
320
320
|
const manifest = this.buildManifest(nodes, failures.length);
|
|
321
321
|
await this.writeManifest(manifest);
|
|
322
322
|
if (this.upload) await this.uploadManifest(manifest, this.upload);
|
|
323
|
-
if (failures.length && this.testInfo.status === "passed") throw new Error("Ralph could not
|
|
323
|
+
if (failures.length && this.testInfo.status === "passed") throw new Error("Ralph could not record " + failures.length + " page state(s). See the ralph-raw-graph attachment.");
|
|
324
324
|
}
|
|
325
325
|
watchPage(page, contextId) {
|
|
326
326
|
if (this.pages.has(page)) return;
|
|
@@ -354,11 +354,11 @@ var Recorder = class {
|
|
|
354
354
|
state.lastUrl = url;
|
|
355
355
|
if (isHttpUrl(url)) this.enqueue(state, "url-change", {}).catch(() => void 0);
|
|
356
356
|
}
|
|
357
|
-
/** Queues a
|
|
357
|
+
/** Queues a recording behind the tab's earlier ones. */
|
|
358
358
|
enqueue(state, trigger, options) {
|
|
359
359
|
const actualUrl = state.page.url();
|
|
360
360
|
const url = options.url === void 0 ? actualUrl : new URL(options.url, actualUrl).href;
|
|
361
|
-
if (!isHttpUrl(actualUrl) || !isHttpUrl(url)) throw new Error("Ralph
|
|
361
|
+
if (!isHttpUrl(actualUrl) || !isHttpUrl(url)) throw new Error("Ralph recordings require HTTP or HTTPS page and recorded URLs.");
|
|
362
362
|
const request = {
|
|
363
363
|
nodeId: randomUUID(),
|
|
364
364
|
pageId: state.info.pageId,
|
|
@@ -376,18 +376,18 @@ var Recorder = class {
|
|
|
376
376
|
return task;
|
|
377
377
|
}
|
|
378
378
|
/**
|
|
379
|
-
*
|
|
380
|
-
* closes before it finishes, or if it runs past the
|
|
379
|
+
* Records one node and its outcome. Fails if the tab navigates or
|
|
380
|
+
* closes before it finishes, or if it runs past the record timeout.
|
|
381
381
|
*/
|
|
382
382
|
async record(state, generation, request) {
|
|
383
383
|
const { page } = state;
|
|
384
|
-
const deadline = startDeadline(this.
|
|
384
|
+
const deadline = startDeadline(this.recordTimeoutMs, "Ralph recording timed out.");
|
|
385
385
|
const { wait } = deadline;
|
|
386
386
|
const navigatedAway = () => state.generation !== generation || page.url() !== request.actualUrl;
|
|
387
387
|
const check = () => {
|
|
388
388
|
deadline.signal.throwIfAborted();
|
|
389
|
-
if (navigatedAway()) throw new Error("The page navigated before its
|
|
390
|
-
if (page.isClosed()) throw new Error("The page closed before its
|
|
389
|
+
if (navigatedAway()) throw new Error("The page navigated before its recording completed.");
|
|
390
|
+
if (page.isClosed()) throw new Error("The page closed before its recording completed.");
|
|
391
391
|
};
|
|
392
392
|
try {
|
|
393
393
|
check();
|
|
@@ -401,8 +401,8 @@ var Recorder = class {
|
|
|
401
401
|
check();
|
|
402
402
|
this.nodes.push({
|
|
403
403
|
...request,
|
|
404
|
-
status: "
|
|
405
|
-
|
|
404
|
+
status: "recorded",
|
|
405
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
406
406
|
layout: before,
|
|
407
407
|
geometryStable: JSON.stringify(before) === JSON.stringify(after),
|
|
408
408
|
screenshot: await this.saveScreenshot(request.nodeId, image)
|
|
@@ -420,7 +420,7 @@ var Recorder = class {
|
|
|
420
420
|
}
|
|
421
421
|
/** After a navigation: the DOM, the settle delay, then the `ready` hook. */
|
|
422
422
|
async waitForPageToSettle(page, { wait, signal }, check) {
|
|
423
|
-
await wait(page.waitForLoadState("domcontentloaded", { timeout: this.
|
|
423
|
+
await wait(page.waitForLoadState("domcontentloaded", { timeout: this.recordTimeoutMs }));
|
|
424
424
|
await wait(setTimeout$1(this.settleMs, void 0, { signal }));
|
|
425
425
|
check();
|
|
426
426
|
if (this.options.ready) await wait(this.options.ready(page, signal));
|
|
@@ -477,11 +477,11 @@ var Recorder = class {
|
|
|
477
477
|
config: this.config,
|
|
478
478
|
pages: [...this.pages.values()].map((state) => state.info),
|
|
479
479
|
nodes,
|
|
480
|
-
complete: testPassed && nodes.length > 0 && nodes.every((item) => item.status === "
|
|
480
|
+
complete: testPassed && nodes.length > 0 && nodes.every((item) => item.status === "recorded" && item.geometryStable)
|
|
481
481
|
};
|
|
482
482
|
}
|
|
483
483
|
async writeManifest(manifest) {
|
|
484
|
-
const file = this.testInfo.outputPath(OUTPUT_DIR, "
|
|
484
|
+
const file = this.testInfo.outputPath(OUTPUT_DIR, "raw_graph.json");
|
|
485
485
|
await mkdir(this.testInfo.outputPath(OUTPUT_DIR), { recursive: true });
|
|
486
486
|
await writeFile(file, JSON.stringify(manifest, null, 2) + "\n");
|
|
487
487
|
await this.testInfo.attach(MANIFEST_ATTACHMENT, {
|
|
@@ -490,14 +490,14 @@ var Recorder = class {
|
|
|
490
490
|
});
|
|
491
491
|
}
|
|
492
492
|
async uploadManifest(manifest, upload) {
|
|
493
|
-
const
|
|
493
|
+
const recorded = manifest.nodes.filter((item) => item.status === "recorded");
|
|
494
494
|
const screenshots = [];
|
|
495
|
-
for (const { nodeId, screenshot } of
|
|
495
|
+
for (const { nodeId, screenshot } of recorded) screenshots.push({
|
|
496
496
|
nodeId,
|
|
497
497
|
contentType: screenshot.contentType,
|
|
498
498
|
bytes: await readFile(this.testInfo.outputPath(screenshot.path))
|
|
499
499
|
});
|
|
500
|
-
const receipt = await
|
|
500
|
+
const receipt = await uploadRawGraph(manifest, screenshots, upload);
|
|
501
501
|
await this.testInfo.attach("ralph-upload-receipt", {
|
|
502
502
|
body: JSON.stringify(receipt),
|
|
503
503
|
contentType: "application/json"
|
|
@@ -524,7 +524,7 @@ function startDeadline(timeoutMs, message) {
|
|
|
524
524
|
}
|
|
525
525
|
/**
|
|
526
526
|
* Sets `previousNodeId` on each node: the one before it in the same
|
|
527
|
-
* tab, or for a popup's first
|
|
527
|
+
* tab, or for a popup's first node, its opener's latest. Superseded nodes
|
|
528
528
|
* are skipped, since their page state was never shown.
|
|
529
529
|
*
|
|
530
530
|
* `nodes` must be sorted by page, then sequence.
|
|
@@ -641,7 +641,7 @@ const test = test$1.extend({
|
|
|
641
641
|
if (resolveMode(ralphOptions) !== "off") throw new Error("Opt this test into Ralph with the @ralph tag.");
|
|
642
642
|
};
|
|
643
643
|
await use(_ralphRecorder ?? {
|
|
644
|
-
|
|
644
|
+
recordNode: async () => {
|
|
645
645
|
checkOptIn();
|
|
646
646
|
},
|
|
647
647
|
flush: async () => {},
|
|
@@ -660,6 +660,6 @@ const test = test$1.extend({
|
|
|
660
660
|
}
|
|
661
661
|
});
|
|
662
662
|
//#endregion
|
|
663
|
-
export { MANIFEST_ATTACHMENT, SCREEN_TAG_PREFIX,
|
|
663
|
+
export { MANIFEST_ATTACHMENT, SCREEN_TAG_PREFIX, createRawGraphBundle, expect, mergeTests, ralphProjects, test, uploadRawGraph };
|
|
664
664
|
|
|
665
665
|
//# sourceMappingURL=index.mjs.map
|