@ralphralphai/playwright 1.9.0 → 2.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 +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts +19 -2
- package/dist/index.mjs +1 -1
- package/dist/reporter.cjs +37 -11
- package/dist/reporter.cjs.map +1 -1
- package/dist/reporter.d.mts +2 -2
- package/dist/reporter.mjs +37 -11
- package/dist/reporter.mjs.map +1 -1
- package/dist/{upload-BQSCs8By.cjs → upload-BJ-OMhZ8.cjs} +281 -12
- package/dist/upload-BJ-OMhZ8.cjs.map +1 -0
- package/dist/{upload-BZs6Vqix.mjs → upload-BhHRgN8t.mjs} +277 -14
- package/dist/upload-BhHRgN8t.mjs.map +1 -0
- package/package.json +3 -3
- package/dist/upload-BQSCs8By.cjs.map +0 -1
- package/dist/upload-BZs6Vqix.mjs.map +0 -1
|
@@ -5,7 +5,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
5
5
|
import configPackage from "@ralphralphai/config/package.json" with { type: "json" };
|
|
6
6
|
import { WebRalphConfigSchema } from "@ralphralphai/config/zod";
|
|
7
7
|
import { parse, printParseErrorCode } from "jsonc-parser";
|
|
8
|
-
import { randomUUID } from "node:crypto";
|
|
8
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
9
9
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
10
10
|
import playwrightPackage from "@playwright/test/package.json" with { type: "json" };
|
|
11
11
|
import { Readable } from "node:stream";
|
|
@@ -45,7 +45,7 @@ function snapshot(value) {
|
|
|
45
45
|
}
|
|
46
46
|
//#endregion
|
|
47
47
|
//#region package.json
|
|
48
|
-
var version = "1.
|
|
48
|
+
var version = "2.1.0";
|
|
49
49
|
//#endregion
|
|
50
50
|
//#region src/actions.ts
|
|
51
51
|
const BINDING = "__ralphAction";
|
|
@@ -522,6 +522,230 @@ async function waitForAnimations(page, timeoutMs) {
|
|
|
522
522
|
if (!(error instanceof errors.TimeoutError)) throw error;
|
|
523
523
|
}
|
|
524
524
|
}
|
|
525
|
+
const MAX_ARIA_DEPTH = Math.floor(128 / 2) - 2;
|
|
526
|
+
const STYLE_PROPERTIES = [
|
|
527
|
+
"font-family",
|
|
528
|
+
"font-size",
|
|
529
|
+
"font-weight",
|
|
530
|
+
"font-style",
|
|
531
|
+
"line-height",
|
|
532
|
+
"letter-spacing",
|
|
533
|
+
"color",
|
|
534
|
+
"background-color",
|
|
535
|
+
"text-align",
|
|
536
|
+
"text-transform"
|
|
537
|
+
];
|
|
538
|
+
const UNINHERITED_PROPERTIES = {
|
|
539
|
+
"padding-top": "0px",
|
|
540
|
+
"padding-right": "0px",
|
|
541
|
+
"padding-bottom": "0px",
|
|
542
|
+
"padding-left": "0px",
|
|
543
|
+
"margin-top": "0px",
|
|
544
|
+
"margin-right": "0px",
|
|
545
|
+
"margin-bottom": "0px",
|
|
546
|
+
"margin-left": "0px",
|
|
547
|
+
"row-gap": "normal",
|
|
548
|
+
"column-gap": "normal",
|
|
549
|
+
"justify-content": "normal",
|
|
550
|
+
"align-items": "normal",
|
|
551
|
+
"flex-direction": "row",
|
|
552
|
+
"grid-template-columns": "none",
|
|
553
|
+
order: "0",
|
|
554
|
+
"border-width": "0px",
|
|
555
|
+
"border-color": "currentcolor",
|
|
556
|
+
"border-radius": "0px",
|
|
557
|
+
"box-shadow": "none",
|
|
558
|
+
opacity: "1",
|
|
559
|
+
"text-decoration-line": "none"
|
|
560
|
+
};
|
|
561
|
+
const IMAGE_PROPERTIES = {
|
|
562
|
+
width: "auto",
|
|
563
|
+
height: "auto"
|
|
564
|
+
};
|
|
565
|
+
const TRANSPARENT = "rgba(0, 0, 0, 0)";
|
|
566
|
+
const ELEMENT_LOOKUP_TIMEOUT_MS = 2e3;
|
|
567
|
+
/**
|
|
568
|
+
* The page's aria tree as JSON, with each element's box in document
|
|
569
|
+
* coordinates, its distinctive styles, and a hash of its image. Undefined when
|
|
570
|
+
* the snapshot is past the server's limits.
|
|
571
|
+
*
|
|
572
|
+
* The page is not scrolled first, because scrolling changes the page the
|
|
573
|
+
* screenshot just showed: sticky headers, lazy loading, scroll-triggered
|
|
574
|
+
* animation.
|
|
575
|
+
*/
|
|
576
|
+
async function captureAriaSnapshot(page, { viewport, scroll }) {
|
|
577
|
+
const snapshot = await page.locator("body").ariaSnapshotJSON({
|
|
578
|
+
mode: "ai",
|
|
579
|
+
boxes: true,
|
|
580
|
+
depth: MAX_ARIA_DEPTH
|
|
581
|
+
});
|
|
582
|
+
const visible = (await Promise.all(snapshot.map((node) => toDocument(page, node, scroll)))).flatMap((node) => {
|
|
583
|
+
const kept = keepShown(node, viewport.width);
|
|
584
|
+
return kept === void 0 ? [] : [kept];
|
|
585
|
+
});
|
|
586
|
+
const described = await Promise.all(visible.map((node) => describe(page, node, {})));
|
|
587
|
+
const json = Buffer.from(JSON.stringify(fillMissingRefs(described)));
|
|
588
|
+
return json.byteLength > 4194304 ? void 0 : json;
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Moves the viewport boxes Playwright reports into the full-page screenshot's
|
|
592
|
+
* space. This has to happen before `keepShown`, which in viewport space would
|
|
593
|
+
* drop everything scrolled above the viewport, and no later move brings those
|
|
594
|
+
* back.
|
|
595
|
+
*
|
|
596
|
+
* An iframe's descendants are boxed in its own viewport, so they move by
|
|
597
|
+
* where that viewport sits: the iframe's box plus its border and padding.
|
|
598
|
+
*/
|
|
599
|
+
async function toDocument(page, node, offset) {
|
|
600
|
+
const box = node.box && {
|
|
601
|
+
...node.box,
|
|
602
|
+
x: node.box.x + offset.x,
|
|
603
|
+
y: node.box.y + offset.y
|
|
604
|
+
};
|
|
605
|
+
let childOffset = offset;
|
|
606
|
+
if (node.role === "iframe" && box !== void 0) {
|
|
607
|
+
const inset = (node.ref === void 0 ? void 0 : await readContentInset(page, node.ref)) ?? {
|
|
608
|
+
x: 0,
|
|
609
|
+
y: 0
|
|
610
|
+
};
|
|
611
|
+
childOffset = {
|
|
612
|
+
x: box.x + inset.x,
|
|
613
|
+
y: box.y + inset.y
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
return {
|
|
617
|
+
...node,
|
|
618
|
+
...box && { box },
|
|
619
|
+
...node.children && { children: await Promise.all(node.children.map((child) => typeof child === "string" ? child : toDocument(page, child, childOffset))) }
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
async function readContentInset(page, ref) {
|
|
623
|
+
try {
|
|
624
|
+
return await page.locator(`aria-ref=${ref}`).evaluate((element) => {
|
|
625
|
+
const computed = getComputedStyle(element);
|
|
626
|
+
return {
|
|
627
|
+
x: element.clientLeft + parseFloat(computed.paddingLeft),
|
|
628
|
+
y: element.clientTop + parseFloat(computed.paddingTop)
|
|
629
|
+
};
|
|
630
|
+
}, void 0, { timeout: ELEMENT_LOOKUP_TIMEOUT_MS });
|
|
631
|
+
} catch {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* In document space, the only boxes no scroll reveals are those past the left
|
|
637
|
+
* or right of the viewport's width (a collapsed sidebar, an off-canvas menu)
|
|
638
|
+
* or above the top of the document.
|
|
639
|
+
*/
|
|
640
|
+
function isReachable(box, viewportWidth) {
|
|
641
|
+
if (box === void 0) return false;
|
|
642
|
+
return box.x < viewportWidth && box.x + box.width > 0 && box.y + box.height > 0;
|
|
643
|
+
}
|
|
644
|
+
function isShown(node, viewportWidth) {
|
|
645
|
+
return node.ref !== void 0 && isReachable(node.box, viewportWidth);
|
|
646
|
+
}
|
|
647
|
+
function keepShown(node, viewportWidth) {
|
|
648
|
+
const shown = isShown(node, viewportWidth);
|
|
649
|
+
const children = (node.children ?? []).flatMap((child) => {
|
|
650
|
+
if (typeof child === "string") return shown ? [child] : [];
|
|
651
|
+
const kept = keepShown(child, viewportWidth);
|
|
652
|
+
return kept === void 0 ? [] : [kept];
|
|
653
|
+
});
|
|
654
|
+
if (!shown && children.length === 0) return;
|
|
655
|
+
return node.children === void 0 ? node : {
|
|
656
|
+
...node,
|
|
657
|
+
children
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function fillMissingRefs(nodes) {
|
|
661
|
+
let generated = 0;
|
|
662
|
+
const fill = (child) => {
|
|
663
|
+
if (typeof child === "string") return child;
|
|
664
|
+
generated += 1;
|
|
665
|
+
const ref = child.ref ?? `n${generated}`;
|
|
666
|
+
return child.children === void 0 ? {
|
|
667
|
+
...child,
|
|
668
|
+
ref
|
|
669
|
+
} : {
|
|
670
|
+
...child,
|
|
671
|
+
ref,
|
|
672
|
+
children: child.children.map(fill)
|
|
673
|
+
};
|
|
674
|
+
};
|
|
675
|
+
return nodes.map(fill);
|
|
676
|
+
}
|
|
677
|
+
async function readElement(page, ref, isImage) {
|
|
678
|
+
try {
|
|
679
|
+
return await page.locator(`aria-ref=${ref}`).evaluate((element, { properties, isImage }) => {
|
|
680
|
+
const computed = getComputedStyle(element);
|
|
681
|
+
const style = Object.fromEntries(properties.map((property) => [property, computed.getPropertyValue(property)]));
|
|
682
|
+
if (!isImage) return { style };
|
|
683
|
+
if (element instanceof HTMLImageElement) return {
|
|
684
|
+
style,
|
|
685
|
+
image: { src: element.currentSrc || element.src }
|
|
686
|
+
};
|
|
687
|
+
if (element instanceof SVGElement) return {
|
|
688
|
+
style,
|
|
689
|
+
image: { markup: element.outerHTML }
|
|
690
|
+
};
|
|
691
|
+
const backgroundUrl = /url\("(.+)"\)/.exec(computed.backgroundImage)?.[1];
|
|
692
|
+
return backgroundUrl === void 0 ? { style } : {
|
|
693
|
+
style,
|
|
694
|
+
image: { src: new URL(backgroundUrl, document.baseURI).href }
|
|
695
|
+
};
|
|
696
|
+
}, {
|
|
697
|
+
properties: [
|
|
698
|
+
...STYLE_PROPERTIES,
|
|
699
|
+
...Object.keys(UNINHERITED_PROPERTIES),
|
|
700
|
+
...isImage ? Object.keys(IMAGE_PROPERTIES) : []
|
|
701
|
+
],
|
|
702
|
+
isImage
|
|
703
|
+
}, { timeout: ELEMENT_LOOKUP_TIMEOUT_MS });
|
|
704
|
+
} catch {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function sha256(data) {
|
|
709
|
+
return createHash("sha256").update(data).digest("hex");
|
|
710
|
+
}
|
|
711
|
+
async function hashImage(page, image) {
|
|
712
|
+
if ("markup" in image) return sha256(image.markup);
|
|
713
|
+
if (image.src === "") return;
|
|
714
|
+
if (image.src.startsWith("data:")) return sha256(image.src);
|
|
715
|
+
try {
|
|
716
|
+
const response = await page.request.get(image.src);
|
|
717
|
+
try {
|
|
718
|
+
return response.ok() ? sha256(await response.body()) : void 0;
|
|
719
|
+
} finally {
|
|
720
|
+
await response.dispose();
|
|
721
|
+
}
|
|
722
|
+
} catch {
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function distinctStyle(style, inherited) {
|
|
727
|
+
const distinct = Object.entries(style).filter(([property, value]) => {
|
|
728
|
+
if (property === "border-color") return style["border-width"] !== "0px";
|
|
729
|
+
const initial = UNINHERITED_PROPERTIES[property] ?? IMAGE_PROPERTIES[property];
|
|
730
|
+
if (initial !== void 0) return value !== initial;
|
|
731
|
+
if (property === "background-color" && value === TRANSPARENT) return false;
|
|
732
|
+
return inherited[property] !== value;
|
|
733
|
+
});
|
|
734
|
+
return distinct.length === 0 ? void 0 : Object.fromEntries(distinct);
|
|
735
|
+
}
|
|
736
|
+
async function describe(page, child, inherited) {
|
|
737
|
+
if (typeof child === "string") return child;
|
|
738
|
+
const facts = child.ref === void 0 ? void 0 : await readElement(page, child.ref, child.role === "img");
|
|
739
|
+
const style = facts === void 0 ? void 0 : distinctStyle(facts.style, inherited);
|
|
740
|
+
const imageHash = facts?.image === void 0 ? void 0 : await hashImage(page, facts.image);
|
|
741
|
+
const children = child.children === void 0 ? void 0 : await Promise.all(child.children.map((grandchild) => describe(page, grandchild, facts?.style ?? inherited)));
|
|
742
|
+
return {
|
|
743
|
+
...child,
|
|
744
|
+
...style && { style },
|
|
745
|
+
...imageHash && { imageHash },
|
|
746
|
+
...children && { children }
|
|
747
|
+
};
|
|
748
|
+
}
|
|
525
749
|
//#endregion
|
|
526
750
|
//#region src/conditions.ts
|
|
527
751
|
const declared = (declaration = {}) => {
|
|
@@ -701,6 +925,8 @@ const MANIFEST_ATTACHMENT = "ralph-raw-graph";
|
|
|
701
925
|
const REPORTER_ENV = "RALPH_PLAYWRIGHT_REPORTER";
|
|
702
926
|
/** Prefix of the attachment holding each recorded node's screenshot; the node id follows. */
|
|
703
927
|
const SCREENSHOT_ATTACHMENT_PREFIX = "ralph-screenshot-";
|
|
928
|
+
/** Prefix of the attachment holding each recorded node's aria snapshot; the node id follows. */
|
|
929
|
+
const ARIA_SNAPSHOT_ATTACHMENT_PREFIX = "ralph-aria-snapshot-";
|
|
704
930
|
/** Once per worker: a run without the tracker says so once, not per node. */
|
|
705
931
|
let warnedNoTracker = false;
|
|
706
932
|
const DEFAULT_SETTLE_MS = 150;
|
|
@@ -918,11 +1144,14 @@ var Recorder = class {
|
|
|
918
1144
|
const deadline = startDeadline(this.recordTimeoutMs, "Ralph recording timed out.");
|
|
919
1145
|
const { wait } = deadline;
|
|
920
1146
|
const navigatedAway = () => state.generation !== generation || page.url() !== request.actualUrl;
|
|
921
|
-
const
|
|
922
|
-
deadline.signal.throwIfAborted();
|
|
1147
|
+
const checkPage = () => {
|
|
923
1148
|
if (navigatedAway()) throw new Error("The page navigated before its recording completed.");
|
|
924
1149
|
if (page.isClosed()) throw new Error("The page closed before its recording completed.");
|
|
925
1150
|
};
|
|
1151
|
+
const check = () => {
|
|
1152
|
+
deadline.signal.throwIfAborted();
|
|
1153
|
+
checkPage();
|
|
1154
|
+
};
|
|
926
1155
|
try {
|
|
927
1156
|
check();
|
|
928
1157
|
if (request.trigger !== "explicit") await this.waitForPageToSettle(page, deadline, check);
|
|
@@ -948,6 +1177,8 @@ var Recorder = class {
|
|
|
948
1177
|
check();
|
|
949
1178
|
const after = await wait(readLayout(page));
|
|
950
1179
|
check();
|
|
1180
|
+
const snapshot = await Promise.race([captureAriaSnapshot(page, after).catch(() => void 0), setTimeout$1(Math.max(0, deadline.endsAt - Date.now()), void 0, { ref: false })]);
|
|
1181
|
+
checkPage();
|
|
951
1182
|
const recorded = {
|
|
952
1183
|
...request,
|
|
953
1184
|
...withConditions(conditions),
|
|
@@ -957,7 +1188,8 @@ var Recorder = class {
|
|
|
957
1188
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
958
1189
|
layout: before,
|
|
959
1190
|
geometryStable: JSON.stringify(before) === JSON.stringify(after),
|
|
960
|
-
screenshot: await this.saveScreenshot(request.nodeId, image)
|
|
1191
|
+
screenshot: await this.saveScreenshot(request.nodeId, image),
|
|
1192
|
+
...snapshot && { ariaSnapshot: await this.saveAriaSnapshot(request.nodeId, snapshot) }
|
|
961
1193
|
};
|
|
962
1194
|
const retaken = sameNode ? this.nodes.findIndex(({ nodeId }) => nodeId === state.lastRecorded?.nodeId) : -1;
|
|
963
1195
|
if (retaken === -1) {
|
|
@@ -1017,6 +1249,21 @@ var Recorder = class {
|
|
|
1017
1249
|
contentType: image.contentType
|
|
1018
1250
|
};
|
|
1019
1251
|
}
|
|
1252
|
+
/** Writes the snapshot to the test output and attaches it to the report. */
|
|
1253
|
+
async saveAriaSnapshot(nodeId, snapshot) {
|
|
1254
|
+
const path = "ralph/" + nodeId + ".aria.json";
|
|
1255
|
+
const attachmentName = ARIA_SNAPSHOT_ATTACHMENT_PREFIX + nodeId;
|
|
1256
|
+
await mkdir(this.testInfo.outputPath(OUTPUT_DIR), { recursive: true });
|
|
1257
|
+
await writeFile(this.testInfo.outputPath(path), snapshot);
|
|
1258
|
+
await this.testInfo.attach(attachmentName, {
|
|
1259
|
+
...this.testInfo.config.preserveOutput === "always" ? { path: this.testInfo.outputPath(path) } : { body: snapshot },
|
|
1260
|
+
contentType: "application/json"
|
|
1261
|
+
});
|
|
1262
|
+
return {
|
|
1263
|
+
path,
|
|
1264
|
+
attachmentName
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1020
1267
|
/** Gives each node the actions taken since the node it was reached from was requested. */
|
|
1021
1268
|
attachActions(nodes) {
|
|
1022
1269
|
const byId = new Map(nodes.map((item) => [item.nodeId, item]));
|
|
@@ -1151,11 +1398,17 @@ function resolveUploadOptions(options) {
|
|
|
1151
1398
|
}
|
|
1152
1399
|
const RAW_GRAPH_BUNDLE_INDEX = "raw_graph.json";
|
|
1153
1400
|
const RAW_GRAPH_BUNDLE_IMAGE_DIR = "assets/images";
|
|
1401
|
+
const RAW_GRAPH_BUNDLE_SNAPSHOT_DIR = "assets/snapshots";
|
|
1154
1402
|
/** Where a node's screenshot sits inside the bundle. */
|
|
1155
1403
|
function bundleImagePath(nodeId, contentType) {
|
|
1156
1404
|
if (!/^[A-Za-z0-9-]{1,64}$/.test(nodeId)) throw new Error(`Ralph node id is not path-safe: ${nodeId}`);
|
|
1157
1405
|
return `${RAW_GRAPH_BUNDLE_IMAGE_DIR}/${nodeId}${imageExtension(contentType)}`;
|
|
1158
1406
|
}
|
|
1407
|
+
/** Where a node's aria snapshot sits inside the bundle. */
|
|
1408
|
+
function bundleSnapshotPath(nodeId) {
|
|
1409
|
+
if (!/^[A-Za-z0-9-]{1,64}$/.test(nodeId)) throw new Error(`Ralph node id is not path-safe: ${nodeId}`);
|
|
1410
|
+
return `${RAW_GRAPH_BUNDLE_SNAPSHOT_DIR}/${nodeId}.json`;
|
|
1411
|
+
}
|
|
1159
1412
|
const TOO_BIG = "This run is too big to upload to Ralph. Record fewer pages or screen sizes, or split the tests across runs.";
|
|
1160
1413
|
var TooBigError = class extends Error {
|
|
1161
1414
|
constructor() {
|
|
@@ -1166,23 +1419,33 @@ var TooBigError = class extends Error {
|
|
|
1166
1419
|
* Packs the manifest and screenshots into a gzipped tar archive, produced as it
|
|
1167
1420
|
* is read so the compressed bundle is never held whole:
|
|
1168
1421
|
*
|
|
1169
|
-
* raw_graph.json
|
|
1422
|
+
* raw_graph.json { manifest, screenshots: [{ nodeId, path }], snapshots: [...] }
|
|
1170
1423
|
* assets/images/<id>.webp raw screenshot bytes (.png when too large for WebP)
|
|
1424
|
+
* assets/snapshots/<id>.json the node's aria snapshot, when it has one
|
|
1171
1425
|
*
|
|
1172
1426
|
* Fails with a `TooBigError` once the compressed bytes pass the limit.
|
|
1173
1427
|
*/
|
|
1174
|
-
function rawGraphBundleStream(manifest, screenshots) {
|
|
1428
|
+
function rawGraphBundleStream(manifest, screenshots, snapshots) {
|
|
1175
1429
|
if (Buffer.byteLength(JSON.stringify(manifest)) > 16777216 || screenshots.length > 5e3 || screenshots.reduce((total, screenshot) => total + screenshot.bytes.byteLength, 0) > 268435456) throw new TooBigError();
|
|
1176
1430
|
const images = screenshots.map(({ nodeId, contentType, bytes }) => ({
|
|
1177
1431
|
nodeId,
|
|
1178
1432
|
path: bundleImagePath(nodeId, contentType),
|
|
1179
1433
|
bytes
|
|
1180
1434
|
}));
|
|
1435
|
+
const files = [...images, ...snapshots.map(({ nodeId, bytes }) => ({
|
|
1436
|
+
nodeId,
|
|
1437
|
+
path: bundleSnapshotPath(nodeId),
|
|
1438
|
+
bytes
|
|
1439
|
+
}))];
|
|
1181
1440
|
const index = {
|
|
1182
1441
|
manifest,
|
|
1183
1442
|
screenshots: images.map(({ nodeId, path }) => ({
|
|
1184
1443
|
nodeId,
|
|
1185
1444
|
path
|
|
1445
|
+
})),
|
|
1446
|
+
snapshots: snapshots.map(({ nodeId }) => ({
|
|
1447
|
+
nodeId,
|
|
1448
|
+
path: bundleSnapshotPath(nodeId)
|
|
1186
1449
|
}))
|
|
1187
1450
|
};
|
|
1188
1451
|
const attrs = {
|
|
@@ -1196,7 +1459,7 @@ function rawGraphBundleStream(manifest, screenshots) {
|
|
|
1196
1459
|
name: RAW_GRAPH_BUNDLE_INDEX,
|
|
1197
1460
|
...attrs
|
|
1198
1461
|
}, JSON.stringify(index)).on("error", onEntryError);
|
|
1199
|
-
for (const { path, bytes } of
|
|
1462
|
+
for (const { path, bytes } of files) tar.entry({
|
|
1200
1463
|
name: path,
|
|
1201
1464
|
...attrs
|
|
1202
1465
|
}, Buffer.from(bytes)).on("error", onEntryError);
|
|
@@ -1213,9 +1476,9 @@ function rawGraphBundleStream(manifest, screenshots) {
|
|
|
1213
1476
|
})());
|
|
1214
1477
|
}
|
|
1215
1478
|
/** The bundle `uploadRawGraph` streams, whole, for saving or inspecting it. */
|
|
1216
|
-
async function createRawGraphBundle(manifest, screenshots) {
|
|
1479
|
+
async function createRawGraphBundle(manifest, screenshots, snapshots = []) {
|
|
1217
1480
|
const chunks = [];
|
|
1218
|
-
for await (const chunk of rawGraphBundleStream(manifest, screenshots)) chunks.push(chunk);
|
|
1481
|
+
for await (const chunk of rawGraphBundleStream(manifest, screenshots, snapshots)) chunks.push(chunk);
|
|
1219
1482
|
return Buffer.concat(chunks);
|
|
1220
1483
|
}
|
|
1221
1484
|
function isLoopback(url) {
|
|
@@ -1290,7 +1553,7 @@ async function reserve(options, route, body) {
|
|
|
1290
1553
|
* `prepareArtifactUpload` has to have reserved. Ralph processes it after this
|
|
1291
1554
|
* returns; the result page shows when it is ready.
|
|
1292
1555
|
*/
|
|
1293
|
-
async function uploadRawGraph(manifest, screenshots, options) {
|
|
1556
|
+
async function uploadRawGraph(manifest, screenshots, options, snapshots = []) {
|
|
1294
1557
|
const url = checkedApiUrl(options, `/api/upload/web/apps/${options.appId}/artifact/${encodeURIComponent(manifest.artifactId)}`);
|
|
1295
1558
|
const storage = process.env["RALPH_UPLOAD_STORAGE"] || "gcs";
|
|
1296
1559
|
if (storage !== "gcs" && storage !== "local") throw new Error(`RALPH_UPLOAD_STORAGE must be gcs or local, not ${storage}.`);
|
|
@@ -1299,7 +1562,7 @@ async function uploadRawGraph(manifest, screenshots, options) {
|
|
|
1299
1562
|
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("uploadTimeoutMs in the Ralph reporter options must be a positive number of milliseconds.");
|
|
1300
1563
|
const receipt = await request(url, {
|
|
1301
1564
|
headers: { "content-type": "application/gzip" },
|
|
1302
|
-
body: Readable.toWeb(rawGraphBundleStream(manifest, screenshots)),
|
|
1565
|
+
body: Readable.toWeb(rawGraphBundleStream(manifest, screenshots, snapshots)),
|
|
1303
1566
|
duplex: "half"
|
|
1304
1567
|
}, options, timeoutMs);
|
|
1305
1568
|
if (receipt["result"] !== "ok" || receipt["status"] !== "queued" || receipt["artifactId"] !== manifest.artifactId || receipt["resultUrl"] !== void 0 && typeof receipt["resultUrl"] !== "string" || typeof receipt["replayed"] !== "boolean") throw unreadableReply(url);
|
|
@@ -1343,6 +1606,6 @@ async function responseDetail(response) {
|
|
|
1343
1606
|
return text.length > 200 ? text.slice(0, 200) + "…" : text || void 0;
|
|
1344
1607
|
}
|
|
1345
1608
|
//#endregion
|
|
1346
|
-
export {
|
|
1609
|
+
export { prepareArtifactUpload as a, resultLink as c, REPORTER_ENV as d, Recorder as f, resolveMode as g, loadConfigSync as h, createRawGraphBundle as i, uploadRawGraph as l, loadConfig as m, bundleImagePath as n, registerDeployment as o, version as p, bundleSnapshotPath as r, resolveUploadOptions as s, RAW_GRAPH_BUNDLE_INDEX as t, MANIFEST_ATTACHMENT as u };
|
|
1347
1610
|
|
|
1348
|
-
//# sourceMappingURL=upload-
|
|
1611
|
+
//# sourceMappingURL=upload-BhHRgN8t.mjs.map
|