@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/dist/index.d.mts CHANGED
@@ -11,15 +11,15 @@ type RalphOptions = {
11
11
  runId?: string;
12
12
  buildId?: string;
13
13
  settleMs?: number;
14
- captureTimeoutMs?: number;
14
+ recordTimeoutMs?: number;
15
15
  ready?: (page: Page, signal: AbortSignal) => Promise<void>;
16
16
  };
17
- type CaptureOptions = {
17
+ type RecordNodeOptions = {
18
18
  state?: string;
19
19
  url?: string;
20
20
  };
21
21
  type Ralph = {
22
- captureNode(page: Page, options?: CaptureOptions): Promise<void>;
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 capture this page state was reached from: the previous one on the same
60
- * page, or for a popup's first capture, its opener's latest. Superseded
61
- * captures are skipped.
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: 'captured';
66
- capturedAt: string;
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 CaptureManifest = {
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 CaptureScreenshot = {
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 CaptureBundleIndex = {
150
- manifest: CaptureManifest;
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
- * capture.json { manifest, screenshots: [{ nodeId, path }] }
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 createCaptureBundle(manifest: CaptureManifest, screenshots: readonly CaptureScreenshot[]): Promise<Uint8Array>;
163
- declare function uploadCapture(manifest: CaptureManifest, screenshots: readonly CaptureScreenshot[], options: UploadOptions): Promise<UploadReceipt>;
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-captures";
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 CaptureBundleIndex, type CaptureManifest, type CaptureOptions, type CaptureScreenshot, type ConfigSnapshot, MANIFEST_ATTACHMENT, type PageLayout, type Ralph, RalphFixtures, type RalphOptions, type RalphProjectOptions, type RawNode, SCREEN_TAG_PREFIX, type TrackedElement, type UploadOptions, type UploadReceipt, type WebRalphConfig, createCaptureBundle, expect, mergeTests, ralphProjects, test, uploadCapture };
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.1.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 captures: lossless measured larger than this
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 CAPTURE_BUNDLE_INDEX = "capture.json";
152
- const CAPTURE_BUNDLE_IMAGE_DIR = "assets/images";
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
- * capture.json { manifest, screenshots: [{ nodeId, path }] }
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 createCaptureBundle(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 capture attempt exceeds upload limits.");
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: `${CAPTURE_BUNDLE_IMAGE_DIR}/${nodeId}${imageExtension(contentType)}`,
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: CAPTURE_BUNDLE_INDEX,
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 capture bundle exceeds upload limits.");
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 uploadCapture(manifest, screenshots, options) {
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 createCaptureBundle(manifest, screenshots))], { type: "application/gzip" });
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 capture artifacts are retained for retry.`);
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-captures";
221
+ const MANIFEST_ATTACHMENT = "ralph-raw-graph";
222
222
  const DEFAULT_SETTLE_MS = 150;
223
- const DEFAULT_CAPTURE_TIMEOUT_MS = 5e3;
224
- /** Where captures are written, inside Playwright's per-test output directory. */
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 capturing. */
237
+ /** How long to wait after a navigation's DOM loads before recording. */
238
238
  settleMs;
239
239
  /**
240
- * The budget for one capture, from settling through the screenshot. Past it,
241
- * the capture is recorded as failed.
240
+ * The budget for recording one node, from settling through the screenshot.
241
+ * Past it, the node is recorded as failed.
242
242
  */
243
- captureTimeoutMs;
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
- * captures and opener links still reach the manifest.
252
+ * nodes and opener links still reach the manifest.
253
253
  */
254
254
  pages = /* @__PURE__ */ new Map();
255
255
  /**
256
- * Each capture's outcome, in completion order. Captures in one tab complete
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 capture was requested, keyed by capture id, for linking a
262
- * popup's first capture to its opener's latest. Numbers, so they compare
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.captureTimeoutMs = options.captureTimeoutMs ?? DEFAULT_CAPTURE_TIMEOUT_MS;
275
- if (!(Number.isFinite(this.settleMs) && this.settleMs >= 0 && Number.isFinite(this.captureTimeoutMs) && this.captureTimeoutMs > this.settleMs)) throw new Error("Ralph captureTimeoutMs must be finite and greater than settleMs, which must be non-negative.");
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 capturing URL changes in every current and future tab of `context`. */
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
- /** Captures the page as it is now, e.g. an open dialog at the same URL. */
291
- async captureNode(page, options = {}) {
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 capture a closed browser page.");
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 capture to finish. */
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 captures. */
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 captures failed.
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 capture " + failures.length + " page state(s). See the ralph-captures attachment.");
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 capture behind the tab's earlier ones. */
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 captures require HTTP or HTTPS page and recorded URLs.");
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
- * Takes one capture and records its outcome. Fails if the tab navigates or
380
- * closes before it finishes, or if it runs past the capture timeout.
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.captureTimeoutMs, "Ralph capture timed out.");
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 capture completed.");
390
- if (page.isClosed()) throw new Error("The page closed before its capture completed.");
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: "captured",
405
- capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
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.captureTimeoutMs }));
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 === "captured" && item.geometryStable)
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, "captures.json");
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 captured = manifest.nodes.filter((item) => item.status === "captured");
493
+ const recorded = manifest.nodes.filter((item) => item.status === "recorded");
494
494
  const screenshots = [];
495
- for (const { nodeId, screenshot } of captured) screenshots.push({
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 uploadCapture(manifest, screenshots, upload);
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 capture, its opener's latest. Superseded captures
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
- captureNode: async () => {
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, createCaptureBundle, expect, mergeTests, ralphProjects, test, uploadCapture };
663
+ export { MANIFEST_ATTACHMENT, SCREEN_TAG_PREFIX, createRawGraphBundle, expect, mergeTests, ralphProjects, test, uploadRawGraph };
664
664
 
665
665
  //# sourceMappingURL=index.mjs.map