@empiricalrun/test-run 0.12.0 → 0.13.1
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/CHANGELOG.md +19 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/lib/cancellation-watcher.d.ts +5 -0
- package/dist/lib/cancellation-watcher.d.ts.map +1 -0
- package/dist/lib/cancellation-watcher.js +49 -0
- package/dist/lib/cmd.d.ts +3 -0
- package/dist/lib/cmd.d.ts.map +1 -1
- package/dist/lib/cmd.js +41 -3
- package/dist/lib/merge-reports/html.d.ts +2 -0
- package/dist/lib/merge-reports/html.d.ts.map +1 -0
- package/dist/lib/merge-reports/html.js +113 -0
- package/dist/lib/merge-reports/index.d.ts +16 -0
- package/dist/lib/merge-reports/index.d.ts.map +1 -0
- package/dist/lib/{merge-reports.js → merge-reports/index.js} +31 -102
- package/dist/lib/merge-reports/json.d.ts +2 -0
- package/dist/lib/merge-reports/json.d.ts.map +1 -0
- package/dist/lib/merge-reports/json.js +67 -0
- package/dist/lib/merge-reports/types.d.ts +13 -0
- package/dist/lib/merge-reports/types.d.ts.map +1 -0
- package/dist/lib/merge-reports/types.js +11 -0
- package/package.json +2 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/lib/merge-reports.d.ts +0 -26
- package/dist/lib/merge-reports.d.ts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @empiricalrun/test-run
|
|
2
2
|
|
|
3
|
+
## 0.13.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [3ee1aec]
|
|
8
|
+
- @empiricalrun/r2-uploader@0.8.0
|
|
9
|
+
|
|
10
|
+
## 0.13.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- d270c6d: feat: add cancellation watcher to self destruct
|
|
15
|
+
- 2d9919d: feat: consolidate zip utils and move to streaming
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [2d9919d]
|
|
20
|
+
- @empiricalrun/r2-uploader@0.7.0
|
|
21
|
+
|
|
3
22
|
## 0.12.0
|
|
4
23
|
|
|
5
24
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { Platform, TestCase } from "./types";
|
|
|
6
6
|
import { getProjectsFromPlaywrightConfig } from "./utils/config";
|
|
7
7
|
export { getProjectsFromPlaywrightConfig, parseTestListOutput, Platform, runSpecificTestsCmd, spawnCmd, };
|
|
8
8
|
export * from "./glob-matcher";
|
|
9
|
+
export { type CancellationWatcher, startCancellationWatcher, } from "./lib/cancellation-watcher";
|
|
9
10
|
export { filterArrayByGlobMatchersSet, generateProjectFilters } from "./utils";
|
|
10
11
|
export declare function runSingleTest({ testName, suites, filePath, projects, envOverrides, repoDir, stdout, stderr, }: {
|
|
11
12
|
testName: string;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAI/E,OAAO,EAAkB,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,EAAE,+BAA+B,EAAE,MAAM,gBAAgB,CAAC;AAMjE,OAAO,EACL,+BAA+B,EAC/B,mBAAmB,EACnB,QAAQ,EACR,mBAAmB,EACnB,QAAQ,GACT,CAAC;AACF,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAc/E,wBAAsB,aAAa,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC,GAAG,OAAO,CAAC;IACV,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,oBAAoB,CAAC;CACnC,CAAC,CAoBD;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IACnE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;CACjD,CAAC,CAeD"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,IAAI,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAI/E,OAAO,EAAkB,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE7C,OAAO,EAAE,+BAA+B,EAAE,MAAM,gBAAgB,CAAC;AAMjE,OAAO,EACL,+BAA+B,EAC/B,mBAAmB,EACnB,QAAQ,EACR,mBAAmB,EACnB,QAAQ,GACT,CAAC;AACF,cAAc,gBAAgB,CAAC;AAC/B,OAAO,EACL,KAAK,mBAAmB,EACxB,wBAAwB,GACzB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAc/E,wBAAsB,aAAa,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,YAAY,EACZ,OAAO,EACP,MAAM,EACN,MAAM,GACP,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC,GAAG,OAAO,CAAC;IACV,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,EAAE,oBAAoB,CAAC;CACnC,CAAC,CAoBD;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IACnE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;CACjD,CAAC,CAeD"}
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
17
17
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
18
18
|
};
|
|
19
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
-
exports.generateProjectFilters = exports.filterArrayByGlobMatchersSet = exports.spawnCmd = exports.runSpecificTestsCmd = exports.Platform = exports.parseTestListOutput = exports.getProjectsFromPlaywrightConfig = void 0;
|
|
20
|
+
exports.generateProjectFilters = exports.filterArrayByGlobMatchersSet = exports.startCancellationWatcher = exports.spawnCmd = exports.runSpecificTestsCmd = exports.Platform = exports.parseTestListOutput = exports.getProjectsFromPlaywrightConfig = void 0;
|
|
21
21
|
exports.runSingleTest = runSingleTest;
|
|
22
22
|
exports.listProjectsAndTests = listProjectsAndTests;
|
|
23
23
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -37,6 +37,8 @@ Object.defineProperty(exports, "getProjectsFromPlaywrightConfig", { enumerable:
|
|
|
37
37
|
// The bin entrypoint has support for mobile also
|
|
38
38
|
const supportedPlatform = types_1.Platform.WEB;
|
|
39
39
|
__exportStar(require("./glob-matcher"), exports);
|
|
40
|
+
var cancellation_watcher_1 = require("./lib/cancellation-watcher");
|
|
41
|
+
Object.defineProperty(exports, "startCancellationWatcher", { enumerable: true, get: function () { return cancellation_watcher_1.startCancellationWatcher; } });
|
|
40
42
|
var utils_2 = require("./utils");
|
|
41
43
|
Object.defineProperty(exports, "filterArrayByGlobMatchersSet", { enumerable: true, get: function () { return utils_2.filterArrayByGlobMatchersSet; } });
|
|
42
44
|
Object.defineProperty(exports, "generateProjectFilters", { enumerable: true, get: function () { return utils_2.generateProjectFilters; } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cancellation-watcher.d.ts","sourceRoot":"","sources":["../../src/lib/cancellation-watcher.ts"],"names":[],"mappings":"AAqCA,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,IAAI,EACpB,cAAc,SAA2B,GACxC,mBAAmB,CAgCrB"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startCancellationWatcher = startCancellationWatcher;
|
|
4
|
+
const DOMAIN = process.env.DASHBOARD_DOMAIN || "https://dash.empirical.run";
|
|
5
|
+
const DEFAULT_POLL_INTERVAL_MS = 5000;
|
|
6
|
+
async function checkTestRunStatus(testRunId, apiKey) {
|
|
7
|
+
const url = `${DOMAIN}/api/test-runs/${testRunId}/status`;
|
|
8
|
+
try {
|
|
9
|
+
const response = await fetch(url, {
|
|
10
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
11
|
+
});
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
console.log(`[CancellationWatcher] Failed to check status: HTTP ${response.status} from ${url}`);
|
|
14
|
+
return { isTerminal: false, status: null };
|
|
15
|
+
}
|
|
16
|
+
const result = (await response.json());
|
|
17
|
+
const status = result.data || { isTerminal: false, status: null };
|
|
18
|
+
return status;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22
|
+
console.log(`[CancellationWatcher] Failed to check status: ${message}`);
|
|
23
|
+
return { isTerminal: false, status: null };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function startCancellationWatcher(testRunId, apiKey, onCancel, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS) {
|
|
27
|
+
let stopped = false;
|
|
28
|
+
console.log(`[CancellationWatcher] Starting watcher for test run ${testRunId} (polling every ${pollIntervalMs}ms)`);
|
|
29
|
+
console.log(`[CancellationWatcher] Dashboard domain: ${DOMAIN}`);
|
|
30
|
+
const poll = async () => {
|
|
31
|
+
while (!stopped) {
|
|
32
|
+
const { status } = await checkTestRunStatus(testRunId, apiKey);
|
|
33
|
+
if (status === "cancelling" || status === "cancelled") {
|
|
34
|
+
console.log(`[CancellationWatcher] Test run ${testRunId} is ${status}, triggering cancellation`);
|
|
35
|
+
onCancel();
|
|
36
|
+
stopped = true;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
poll();
|
|
43
|
+
return {
|
|
44
|
+
stop: () => {
|
|
45
|
+
console.log(`[CancellationWatcher] Stopping watcher for ${testRunId}`);
|
|
46
|
+
stopped = true;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
package/dist/lib/cmd.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ChildProcess } from "child_process";
|
|
1
2
|
import { CommandToRun } from "../types";
|
|
2
3
|
export declare function getCommandFromString(command: string): {
|
|
3
4
|
command: string;
|
|
@@ -8,6 +9,7 @@ export declare function runTestsForCmd({ command, args, env }: CommandToRun, cwd
|
|
|
8
9
|
stderr?: NodeJS.WritableStream;
|
|
9
10
|
}): Promise<{
|
|
10
11
|
hasTestPassed: boolean;
|
|
12
|
+
wasCancelled: boolean;
|
|
11
13
|
}>;
|
|
12
14
|
export declare function spawnCmd(command: string, args: string[], options: {
|
|
13
15
|
cwd: string;
|
|
@@ -16,6 +18,7 @@ export declare function spawnCmd(command: string, args: string[], options: {
|
|
|
16
18
|
throwOnError: boolean;
|
|
17
19
|
stdout?: NodeJS.WritableStream;
|
|
18
20
|
stderr?: NodeJS.WritableStream;
|
|
21
|
+
onSpawn?: (proc: ChildProcess) => void;
|
|
19
22
|
}): Promise<{
|
|
20
23
|
code: number;
|
|
21
24
|
output?: string;
|
package/dist/lib/cmd.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cmd.d.ts","sourceRoot":"","sources":["../../src/lib/cmd.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cmd.d.ts","sourceRoot":"","sources":["../../src/lib/cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAS,MAAM,eAAe,CAAC;AAGzD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAMxC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG;IACrD,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAeA;AAED,wBAAsB,cAAc,CAClC,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,YAAY,EACpC,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;CAChC;;;GAqEF;AAED,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IACP,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,IAAI,CAAC;CACxC,GACA,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAiD5C"}
|
package/dist/lib/cmd.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.runTestsForCmd = runTestsForCmd;
|
|
|
5
5
|
exports.spawnCmd = spawnCmd;
|
|
6
6
|
const child_process_1 = require("child_process");
|
|
7
7
|
const logger_1 = require("../logger");
|
|
8
|
+
const cancellation_watcher_1 = require("./cancellation-watcher");
|
|
8
9
|
function getCommandFromString(command) {
|
|
9
10
|
const regex = /[^\s"']+|"([^"]*)"|'([^']*)'/g;
|
|
10
11
|
const matches = command.match(regex) || [];
|
|
@@ -21,7 +22,37 @@ function getCommandFromString(command) {
|
|
|
21
22
|
}
|
|
22
23
|
async function runTestsForCmd({ command, args, env }, cwd, options) {
|
|
23
24
|
logger_1.logger.debug(`Running cmd: ${command} with args: ${args}`);
|
|
25
|
+
const testRunId = process.env.TEST_RUN_GITHUB_ACTION_ID;
|
|
26
|
+
const apiKey = process.env.EMPIRICALRUN_API_KEY;
|
|
27
|
+
console.log(`[CancellationWatcher] Environment: TEST_RUN_GITHUB_ACTION_ID=${testRunId}, EMPIRICALRUN_API_KEY=${apiKey ? "***" : "undefined"}`);
|
|
24
28
|
let hasTestPassed = true;
|
|
29
|
+
let wasCancelled = false;
|
|
30
|
+
let cancellationWatcher;
|
|
31
|
+
let childProcess;
|
|
32
|
+
const cancelHandler = () => {
|
|
33
|
+
wasCancelled = true;
|
|
34
|
+
console.log(`[CancellationWatcher] Cancel handler invoked, killing child process (pid: ${childProcess?.pid})`);
|
|
35
|
+
if (childProcess && !childProcess.killed) {
|
|
36
|
+
// Use SIGTERM for more forceful termination that propagates to child processes
|
|
37
|
+
childProcess.kill("SIGTERM");
|
|
38
|
+
// Also try to kill the process group to ensure Playwright workers are stopped
|
|
39
|
+
if (childProcess.pid) {
|
|
40
|
+
try {
|
|
41
|
+
process.kill(-childProcess.pid, "SIGTERM");
|
|
42
|
+
console.log(`[CancellationWatcher] Sent SIGTERM to process group ${childProcess.pid}`);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Process group kill may fail if not a group leader
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
if (testRunId && apiKey) {
|
|
51
|
+
cancellationWatcher = (0, cancellation_watcher_1.startCancellationWatcher)(testRunId, apiKey, cancelHandler);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log("[CancellationWatcher] Not starting watcher - missing testRunId or apiKey");
|
|
55
|
+
}
|
|
25
56
|
try {
|
|
26
57
|
await spawnCmd(command, args, {
|
|
27
58
|
cwd,
|
|
@@ -30,12 +61,18 @@ async function runTestsForCmd({ command, args, env }, cwd, options) {
|
|
|
30
61
|
throwOnError: true,
|
|
31
62
|
stdout: options?.stdout,
|
|
32
63
|
stderr: options?.stderr,
|
|
64
|
+
onSpawn: (proc) => {
|
|
65
|
+
childProcess = proc;
|
|
66
|
+
},
|
|
33
67
|
});
|
|
34
68
|
}
|
|
35
69
|
catch {
|
|
36
70
|
hasTestPassed = false;
|
|
37
71
|
}
|
|
38
|
-
|
|
72
|
+
finally {
|
|
73
|
+
cancellationWatcher?.stop();
|
|
74
|
+
}
|
|
75
|
+
return { hasTestPassed, wasCancelled };
|
|
39
76
|
}
|
|
40
77
|
async function spawnCmd(command, args, options) {
|
|
41
78
|
let output = options.captureOutput ? "" : undefined;
|
|
@@ -44,9 +81,10 @@ async function spawnCmd(command, args, options) {
|
|
|
44
81
|
const p = (0, child_process_1.spawn)(command, args, {
|
|
45
82
|
env: { ...process.env, ...options.envOverrides },
|
|
46
83
|
cwd: options.cwd,
|
|
47
|
-
//
|
|
48
|
-
detached:
|
|
84
|
+
// Create new process group so we can kill all child processes together
|
|
85
|
+
detached: true,
|
|
49
86
|
});
|
|
87
|
+
options.onSpawn?.(p);
|
|
50
88
|
// Setup signal handlers and get cleanup function
|
|
51
89
|
const cleanupSignalHandlers = setupProcessSignalHandlers(p);
|
|
52
90
|
p.stdout.on("data", (x) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../../src/lib/merge-reports/html.ts"],"names":[],"mappings":"AAwEA,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CA8Gf"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.patchMergedHtmlReport = patchMergedHtmlReport;
|
|
7
|
+
const zip_1 = require("@empiricalrun/r2-uploader/zip");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const logger_1 = require("../../logger");
|
|
11
|
+
const types_1 = require("./types");
|
|
12
|
+
function patchHtmlReportAttachments(report, urlMap) {
|
|
13
|
+
let patchCount = 0;
|
|
14
|
+
// report.json has files[].tests[], individual test files have tests[] directly
|
|
15
|
+
const tests = report.files
|
|
16
|
+
? report.files.flatMap((f) => f.tests)
|
|
17
|
+
: report.tests || [];
|
|
18
|
+
for (const test of tests) {
|
|
19
|
+
for (const result of test.results || []) {
|
|
20
|
+
for (const attachment of result.attachments || []) {
|
|
21
|
+
if (attachment.path) {
|
|
22
|
+
for (const [fileName, url] of urlMap) {
|
|
23
|
+
if (attachment.path.endsWith(fileName)) {
|
|
24
|
+
attachment.path = url;
|
|
25
|
+
patchCount++;
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return patchCount;
|
|
34
|
+
}
|
|
35
|
+
async function patchMergedHtmlReport(htmlFilePath, urlMappings) {
|
|
36
|
+
if (Object.keys(urlMappings).length === 0) {
|
|
37
|
+
logger_1.logger.debug(`[Merge Reports] No URL mappings to apply`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let htmlContent;
|
|
41
|
+
const startTime = Date.now();
|
|
42
|
+
logger_1.logger.info(`[Merge Reports] Starting HTML patch...`);
|
|
43
|
+
try {
|
|
44
|
+
htmlContent = await fs_1.default.promises.readFile(htmlFilePath, "utf8");
|
|
45
|
+
logger_1.logger.info(`[Merge Reports] HTML file read: ${(htmlContent.length / 1024 / 1024).toFixed(2)} MB in ${Date.now() - startTime}ms`);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
logger_1.logger.error(`[Merge Reports] Failed to read HTML file:`, error);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Support both old format (1.53.x) and new format (1.57.0+)
|
|
52
|
+
const oldFormatMatch = htmlContent.match(/window\.playwrightReportBase64\s*=\s*"(?:data:application\/zip;base64,)?([^"]+)"/);
|
|
53
|
+
const newFormatMatch = htmlContent.match(/<script\s+id="playwrightReportBase64"[^>]*>(?:data:application\/zip;base64,)?([^<]+)<\/script>/);
|
|
54
|
+
const base64 = oldFormatMatch?.[1] || newFormatMatch?.[1];
|
|
55
|
+
if (!base64) {
|
|
56
|
+
logger_1.logger.error(`[Merge Reports] Base64 zip data not found in HTML`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const htmlDir = path_1.default.dirname(path_1.default.resolve(htmlFilePath));
|
|
60
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(htmlDir, "merge-patch-"));
|
|
61
|
+
const zipPath = path_1.default.join(tempDir, "archive.zip");
|
|
62
|
+
try {
|
|
63
|
+
let stepTime = Date.now();
|
|
64
|
+
await fs_1.default.promises.writeFile(zipPath, Buffer.from(base64, "base64"));
|
|
65
|
+
await (0, zip_1.extractZipToDirectory)(zipPath, tempDir);
|
|
66
|
+
await fs_1.default.promises.unlink(zipPath);
|
|
67
|
+
logger_1.logger.info(`[Merge Reports] Zip extracted in ${Date.now() - stepTime}ms`);
|
|
68
|
+
const jsonFiles = (await fs_1.default.promises.readdir(tempDir)).filter((f) => f.endsWith(".json"));
|
|
69
|
+
logger_1.logger.info(`[Merge Reports] Patching ${jsonFiles.length} JSON files with ${Object.keys(urlMappings).length} mappings`);
|
|
70
|
+
const urlMap = (0, types_1.buildUrlMap)(urlMappings);
|
|
71
|
+
stepTime = Date.now();
|
|
72
|
+
let totalPatchCount = 0;
|
|
73
|
+
const patchResults = await Promise.allSettled(jsonFiles.map(async (file) => {
|
|
74
|
+
const filePath = path_1.default.join(tempDir, file);
|
|
75
|
+
const content = await fs_1.default.promises.readFile(filePath, "utf8");
|
|
76
|
+
const report = JSON.parse(content);
|
|
77
|
+
const patchCount = patchHtmlReportAttachments(report, urlMap);
|
|
78
|
+
if (patchCount > 0) {
|
|
79
|
+
await fs_1.default.promises.writeFile(filePath, JSON.stringify(report), "utf8");
|
|
80
|
+
logger_1.logger.debug(`[Merge Reports] Patched ${file} (${patchCount} paths)`);
|
|
81
|
+
}
|
|
82
|
+
return patchCount;
|
|
83
|
+
}));
|
|
84
|
+
for (const result of patchResults) {
|
|
85
|
+
if (result.status === "rejected") {
|
|
86
|
+
logger_1.logger.error(`[Merge Reports] Failed to patch JSON file:`, result.reason);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
totalPatchCount += result.value;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
logger_1.logger.info(`[Merge Reports] JSON patching completed in ${Date.now() - stepTime}ms (${totalPatchCount} paths patched)`);
|
|
93
|
+
stepTime = Date.now();
|
|
94
|
+
const newBuffer = await (0, zip_1.createZipFromDirectory)(tempDir);
|
|
95
|
+
const newBase64 = newBuffer.toString("base64");
|
|
96
|
+
logger_1.logger.info(`[Merge Reports] New zip created in ${Date.now() - stepTime}ms`);
|
|
97
|
+
let updatedHtml;
|
|
98
|
+
if (oldFormatMatch) {
|
|
99
|
+
updatedHtml = htmlContent.replace(/(window\.playwrightReportBase64\s*=\s*")(?:data:application\/zip;base64,)?[^"]*(")/, `$1data:application/zip;base64,${newBase64}$2`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
updatedHtml = htmlContent.replace(/(<script\s+id="playwrightReportBase64"[^>]*>)(?:data:application\/zip;base64,)?[^<]*(<\/script>)/, `$1data:application/zip;base64,${newBase64}$2`);
|
|
103
|
+
}
|
|
104
|
+
await fs_1.default.promises.writeFile(htmlFilePath, updatedHtml, "utf8");
|
|
105
|
+
logger_1.logger.info(`[Merge Reports] HTML file patched successfully`);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
logger_1.logger.error(`[Merge Reports] Failed to patch HTML:`, error);
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
await fs_1.default.promises.rm(tempDir, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MergeReportsOptions, UploadOptions } from "./types";
|
|
2
|
+
export { patchMergedHtmlReport } from "./html";
|
|
3
|
+
export { patchSummaryJson } from "./json";
|
|
4
|
+
export type { MergeReportsOptions, UploadOptions } from "./types";
|
|
5
|
+
export declare function runPlaywrightMergeReports(options: MergeReportsOptions): Promise<{
|
|
6
|
+
success: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function extractUrlMappingsFromBlobs(blobDir: string): Promise<Record<string, string>>;
|
|
9
|
+
export declare function uploadMergedReports(cwd: string, outputDir: string, uploadOptions: UploadOptions): Promise<void>;
|
|
10
|
+
export declare function mergeReports(options: {
|
|
11
|
+
blobDir?: string;
|
|
12
|
+
cwd?: string;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
success: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/merge-reports/index.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAElE,OAAO,EAAE,qBAAqB,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAC;AAC1C,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAElE,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CA2B/B;AAED,wBAAsB,2BAA2B,CAC/C,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAoCjC;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,IAAI,CAAC,CAkDf;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAyDhC"}
|
|
@@ -3,18 +3,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.patchSummaryJson = exports.patchMergedHtmlReport = void 0;
|
|
6
7
|
exports.runPlaywrightMergeReports = runPlaywrightMergeReports;
|
|
7
8
|
exports.extractUrlMappingsFromBlobs = extractUrlMappingsFromBlobs;
|
|
8
|
-
exports.patchMergedHtmlReport = patchMergedHtmlReport;
|
|
9
|
-
exports.patchSummaryJson = patchSummaryJson;
|
|
10
9
|
exports.uploadMergedReports = uploadMergedReports;
|
|
11
10
|
exports.mergeReports = mergeReports;
|
|
12
11
|
const r2_uploader_1 = require("@empiricalrun/r2-uploader");
|
|
13
|
-
const
|
|
12
|
+
const zip_1 = require("@empiricalrun/r2-uploader/zip");
|
|
14
13
|
const fs_1 = __importDefault(require("fs"));
|
|
15
14
|
const path_1 = __importDefault(require("path"));
|
|
16
|
-
const logger_1 = require("
|
|
17
|
-
const cmd_1 = require("
|
|
15
|
+
const logger_1 = require("../../logger");
|
|
16
|
+
const cmd_1 = require("../cmd");
|
|
17
|
+
const html_1 = require("./html");
|
|
18
|
+
const json_1 = require("./json");
|
|
19
|
+
var html_2 = require("./html");
|
|
20
|
+
Object.defineProperty(exports, "patchMergedHtmlReport", { enumerable: true, get: function () { return html_2.patchMergedHtmlReport; } });
|
|
21
|
+
var json_2 = require("./json");
|
|
22
|
+
Object.defineProperty(exports, "patchSummaryJson", { enumerable: true, get: function () { return json_2.patchSummaryJson; } });
|
|
18
23
|
async function runPlaywrightMergeReports(options) {
|
|
19
24
|
const { blobDir, outputDir, cwd } = options;
|
|
20
25
|
logger_1.logger.debug(`[Merge Reports] Running playwright merge-reports`);
|
|
@@ -38,109 +43,31 @@ async function runPlaywrightMergeReports(options) {
|
|
|
38
43
|
return { success: false };
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
|
-
function extractUrlMappingsFromBlobs(blobDir) {
|
|
46
|
+
async function extractUrlMappingsFromBlobs(blobDir) {
|
|
42
47
|
const combinedMap = {};
|
|
43
48
|
const files = fs_1.default.readdirSync(blobDir);
|
|
44
|
-
|
|
49
|
+
const zipFiles = files.filter((f) => f.endsWith(".zip"));
|
|
50
|
+
const results = await Promise.allSettled(zipFiles.map(async (fileName) => {
|
|
45
51
|
const zipPath = path_1.default.join(blobDir, fileName);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Object.assign(combinedMap, content);
|
|
52
|
-
logger_1.logger.debug(`[Merge Reports] Extracted ${Object.keys(content).length} URL mappings from ${fileName}`);
|
|
53
|
-
}
|
|
52
|
+
const buffer = await (0, zip_1.readZipEntry)(zipPath, "_empirical_urls.json");
|
|
53
|
+
if (buffer) {
|
|
54
|
+
const content = JSON.parse(buffer.toString("utf8"));
|
|
55
|
+
logger_1.logger.debug(`[Merge Reports] Extracted ${Object.keys(content).length} URL mappings from ${fileName}`);
|
|
56
|
+
return content;
|
|
54
57
|
}
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
return null;
|
|
59
|
+
}));
|
|
60
|
+
for (const result of results) {
|
|
61
|
+
if (result.status === "fulfilled" && result.value) {
|
|
62
|
+
Object.assign(combinedMap, result.value);
|
|
63
|
+
}
|
|
64
|
+
else if (result.status === "rejected") {
|
|
65
|
+
logger_1.logger.error(`[Merge Reports] Failed to extract URL mappings:`, result.reason);
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
logger_1.logger.info(`[Merge Reports] Total URL mappings: ${Object.keys(combinedMap).length}`);
|
|
60
69
|
return combinedMap;
|
|
61
70
|
}
|
|
62
|
-
async function patchMergedHtmlReport(htmlFilePath, urlMappings) {
|
|
63
|
-
if (Object.keys(urlMappings).length === 0) {
|
|
64
|
-
logger_1.logger.debug(`[Merge Reports] No URL mappings to apply`);
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
let htmlContent;
|
|
68
|
-
try {
|
|
69
|
-
htmlContent = await fs_1.default.promises.readFile(htmlFilePath, "utf8");
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
logger_1.logger.error(`[Merge Reports] Failed to read HTML file:`, error);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const oldFormatMatch = htmlContent.match(/window\.playwrightReportBase64\s*=\s*"(?:data:application\/zip;base64,)?([^"]+)"/);
|
|
76
|
-
const newFormatMatch = htmlContent.match(/<script\s+id="playwrightReportBase64"[^>]*>(?:data:application\/zip;base64,)?([^<]+)<\/script>/);
|
|
77
|
-
const base64 = oldFormatMatch?.[1] || newFormatMatch?.[1];
|
|
78
|
-
if (!base64) {
|
|
79
|
-
logger_1.logger.error(`[Merge Reports] Base64 zip data not found in HTML`);
|
|
80
|
-
return;
|
|
81
|
-
}
|
|
82
|
-
const htmlDir = path_1.default.dirname(path_1.default.resolve(htmlFilePath));
|
|
83
|
-
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(htmlDir, "merge-patch-"));
|
|
84
|
-
const zipPath = path_1.default.join(tempDir, "archive.zip");
|
|
85
|
-
try {
|
|
86
|
-
await fs_1.default.promises.writeFile(zipPath, Buffer.from(base64, "base64"));
|
|
87
|
-
const zip = new adm_zip_1.default(zipPath);
|
|
88
|
-
zip.extractAllTo(tempDir, true);
|
|
89
|
-
await fs_1.default.promises.unlink(zipPath);
|
|
90
|
-
const jsonFiles = (await fs_1.default.promises.readdir(tempDir)).filter((f) => f.endsWith(".json"));
|
|
91
|
-
for (const file of jsonFiles) {
|
|
92
|
-
const filePath = path_1.default.join(tempDir, file);
|
|
93
|
-
const content = await fs_1.default.promises.readFile(filePath, "utf8");
|
|
94
|
-
let modified = content;
|
|
95
|
-
for (const [resourcePath, url] of Object.entries(urlMappings)) {
|
|
96
|
-
const resourceFileName = resourcePath.replace(/^resources\//, "");
|
|
97
|
-
const regex = new RegExp(`[^"]*${resourceFileName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g");
|
|
98
|
-
modified = modified.replace(regex, url);
|
|
99
|
-
}
|
|
100
|
-
if (modified !== content) {
|
|
101
|
-
await fs_1.default.promises.writeFile(filePath, modified, "utf8");
|
|
102
|
-
logger_1.logger.debug(`[Merge Reports] Patched ${file}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const newZip = new adm_zip_1.default();
|
|
106
|
-
newZip.addLocalFolder(tempDir);
|
|
107
|
-
const newBase64 = newZip.toBuffer().toString("base64");
|
|
108
|
-
let updatedHtml;
|
|
109
|
-
if (oldFormatMatch) {
|
|
110
|
-
updatedHtml = htmlContent.replace(/(window\.playwrightReportBase64\s*=\s*")(?:data:application\/zip;base64,)?[^"]*(")/, `$1data:application/zip;base64,${newBase64}$2`);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
updatedHtml = htmlContent.replace(/(<script\s+id="playwrightReportBase64"[^>]*>)(?:data:application\/zip;base64,)?[^<]*(<\/script>)/, `$1data:application/zip;base64,${newBase64}$2`);
|
|
114
|
-
}
|
|
115
|
-
await fs_1.default.promises.writeFile(htmlFilePath, updatedHtml, "utf8");
|
|
116
|
-
logger_1.logger.info(`[Merge Reports] HTML file patched successfully`);
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
logger_1.logger.error(`[Merge Reports] Failed to patch HTML:`, error);
|
|
120
|
-
}
|
|
121
|
-
finally {
|
|
122
|
-
await fs_1.default.promises.rm(tempDir, { recursive: true, force: true });
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
async function patchSummaryJson(jsonFilePath, urlMappings) {
|
|
126
|
-
if (Object.keys(urlMappings).length === 0) {
|
|
127
|
-
logger_1.logger.debug(`[Merge Reports] No URL mappings to apply to summary.json`);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
try {
|
|
131
|
-
let content = await fs_1.default.promises.readFile(jsonFilePath, "utf8");
|
|
132
|
-
for (const [resourcePath, url] of Object.entries(urlMappings)) {
|
|
133
|
-
const resourceFileName = resourcePath.replace(/^resources\//, "");
|
|
134
|
-
const regex = new RegExp(`[^"]*${resourceFileName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "g");
|
|
135
|
-
content = content.replace(regex, url);
|
|
136
|
-
}
|
|
137
|
-
await fs_1.default.promises.writeFile(jsonFilePath, content, "utf8");
|
|
138
|
-
logger_1.logger.info(`[Merge Reports] summary.json patched successfully`);
|
|
139
|
-
}
|
|
140
|
-
catch (error) {
|
|
141
|
-
logger_1.logger.error(`[Merge Reports] Failed to patch summary.json:`, error);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
71
|
async function uploadMergedReports(cwd, outputDir, uploadOptions) {
|
|
145
72
|
const { projectName, runId, baseUrl, uploadBucket } = uploadOptions;
|
|
146
73
|
const destinationDir = path_1.default.join(projectName, runId);
|
|
@@ -200,7 +127,7 @@ async function mergeReports(options) {
|
|
|
200
127
|
logger_1.logger.error(`[Merge Reports] Blob directory does not exist: ${blobDir}`);
|
|
201
128
|
return { success: false };
|
|
202
129
|
}
|
|
203
|
-
const urlMappings = extractUrlMappingsFromBlobs(blobDir);
|
|
130
|
+
const urlMappings = await extractUrlMappingsFromBlobs(blobDir);
|
|
204
131
|
const { success } = await runPlaywrightMergeReports({
|
|
205
132
|
blobDir,
|
|
206
133
|
outputDir,
|
|
@@ -211,8 +138,10 @@ async function mergeReports(options) {
|
|
|
211
138
|
}
|
|
212
139
|
const htmlFilePath = path_1.default.join(outputDir, "index.html");
|
|
213
140
|
const jsonFilePath = path_1.default.join(cwd, "summary.json");
|
|
214
|
-
await
|
|
215
|
-
|
|
141
|
+
await Promise.all([
|
|
142
|
+
(0, html_1.patchMergedHtmlReport)(htmlFilePath, urlMappings),
|
|
143
|
+
(0, json_1.patchSummaryJson)(jsonFilePath, urlMappings),
|
|
144
|
+
]);
|
|
216
145
|
const hasR2Creds = process.env.R2_ACCOUNT_ID &&
|
|
217
146
|
process.env.R2_ACCESS_KEY_ID &&
|
|
218
147
|
process.env.R2_SECRET_ACCESS_KEY;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../../src/lib/merge-reports/json.ts"],"names":[],"mappings":"AA8EA,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CAyCf"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.patchSummaryJson = patchSummaryJson;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const logger_1 = require("../../logger");
|
|
9
|
+
const types_1 = require("./types");
|
|
10
|
+
function patchAttachmentPaths(report, urlMap) {
|
|
11
|
+
let patchCount = 0;
|
|
12
|
+
function processSuite(suite) {
|
|
13
|
+
for (const spec of suite.specs) {
|
|
14
|
+
for (const test of spec.tests) {
|
|
15
|
+
for (const result of test.results) {
|
|
16
|
+
for (const attachment of result.attachments) {
|
|
17
|
+
if (attachment.path) {
|
|
18
|
+
for (const [fileName, url] of urlMap) {
|
|
19
|
+
if (attachment.path.endsWith(fileName)) {
|
|
20
|
+
attachment.path = url;
|
|
21
|
+
patchCount++;
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (suite.suites) {
|
|
31
|
+
for (const nested of suite.suites) {
|
|
32
|
+
processSuite(nested);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const suite of report.suites) {
|
|
37
|
+
processSuite(suite);
|
|
38
|
+
}
|
|
39
|
+
return patchCount;
|
|
40
|
+
}
|
|
41
|
+
async function patchSummaryJson(jsonFilePath, urlMappings) {
|
|
42
|
+
if (Object.keys(urlMappings).length === 0) {
|
|
43
|
+
logger_1.logger.debug(`[Merge Reports] No URL mappings to apply to summary.json`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
logger_1.logger.info(`[Merge Reports] Starting summary.json patch...`);
|
|
48
|
+
try {
|
|
49
|
+
let stepTime = Date.now();
|
|
50
|
+
const content = await fs_1.default.promises.readFile(jsonFilePath, "utf8");
|
|
51
|
+
logger_1.logger.info(`[Merge Reports] summary.json read: ${(content.length / 1024).toFixed(2)} KB in ${Date.now() - stepTime}ms`);
|
|
52
|
+
stepTime = Date.now();
|
|
53
|
+
const report = JSON.parse(content);
|
|
54
|
+
logger_1.logger.info(`[Merge Reports] summary.json parsed in ${Date.now() - stepTime}ms`);
|
|
55
|
+
stepTime = Date.now();
|
|
56
|
+
const urlMap = (0, types_1.buildUrlMap)(urlMappings);
|
|
57
|
+
const patchCount = patchAttachmentPaths(report, urlMap);
|
|
58
|
+
logger_1.logger.info(`[Merge Reports] summary.json patched ${patchCount} attachment paths in ${Date.now() - stepTime}ms`);
|
|
59
|
+
stepTime = Date.now();
|
|
60
|
+
await fs_1.default.promises.writeFile(jsonFilePath, JSON.stringify(report), "utf8");
|
|
61
|
+
logger_1.logger.info(`[Merge Reports] summary.json written in ${Date.now() - stepTime}ms`);
|
|
62
|
+
logger_1.logger.info(`[Merge Reports] summary.json patched successfully (total: ${Date.now() - startTime}ms)`);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
logger_1.logger.error(`[Merge Reports] Failed to patch summary.json:`, error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface MergeReportsOptions {
|
|
2
|
+
blobDir: string;
|
|
3
|
+
outputDir: string;
|
|
4
|
+
cwd: string;
|
|
5
|
+
}
|
|
6
|
+
export interface UploadOptions {
|
|
7
|
+
projectName: string;
|
|
8
|
+
runId: string;
|
|
9
|
+
baseUrl: string;
|
|
10
|
+
uploadBucket: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function buildUrlMap(urlMappings: Record<string, string>): Map<string, string>;
|
|
13
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/merge-reports/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,WAAW,CACzB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAOrB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildUrlMap = buildUrlMap;
|
|
4
|
+
function buildUrlMap(urlMappings) {
|
|
5
|
+
const map = new Map();
|
|
6
|
+
for (const [resourcePath, url] of Object.entries(urlMappings)) {
|
|
7
|
+
const fileName = resourcePath.replace(/^resources\//, "");
|
|
8
|
+
map.set(fileName, url);
|
|
9
|
+
}
|
|
10
|
+
return map;
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empiricalrun/test-run",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -26,18 +26,16 @@
|
|
|
26
26
|
},
|
|
27
27
|
"author": "Empirical Team <hey@empirical.run>",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"adm-zip": "^0.5.16",
|
|
30
29
|
"async-retry": "^1.3.3",
|
|
31
30
|
"commander": "^12.1.0",
|
|
32
31
|
"console-log-level": "^1.4.1",
|
|
33
32
|
"dotenv": "^16.4.5",
|
|
34
33
|
"minimatch": "^10.0.1",
|
|
35
34
|
"ts-morph": "^23.0.0",
|
|
36
|
-
"@empiricalrun/r2-uploader": "^0.
|
|
35
|
+
"@empiricalrun/r2-uploader": "^0.8.0"
|
|
37
36
|
},
|
|
38
37
|
"devDependencies": {
|
|
39
38
|
"@playwright/test": "1.53.2",
|
|
40
|
-
"@types/adm-zip": "^0.5.7",
|
|
41
39
|
"@types/async-retry": "^1.4.8",
|
|
42
40
|
"@types/console-log-level": "^1.4.5",
|
|
43
41
|
"@types/node": "^22.5.5",
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/dashboard.ts","./src/glob-matcher.ts","./src/index.ts","./src/logger.ts","./src/bin/index.ts","./src/bin/merge-reports.ts","./src/lib/
|
|
1
|
+
{"root":["./src/dashboard.ts","./src/glob-matcher.ts","./src/index.ts","./src/logger.ts","./src/bin/index.ts","./src/bin/merge-reports.ts","./src/lib/cancellation-watcher.ts","./src/lib/cmd.ts","./src/lib/run-all-tests.ts","./src/lib/run-specific-test.ts","./src/lib/memfs/read-hello-world.ts","./src/lib/merge-reports/html.ts","./src/lib/merge-reports/index.ts","./src/lib/merge-reports/json.ts","./src/lib/merge-reports/types.ts","./src/stdout-parser/index.ts","./src/types/index.ts","./src/utils/config-parser.ts","./src/utils/config.ts","./src/utils/index.ts"],"version":"5.8.3"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
interface MergeReportsOptions {
|
|
2
|
-
blobDir: string;
|
|
3
|
-
outputDir: string;
|
|
4
|
-
cwd: string;
|
|
5
|
-
}
|
|
6
|
-
interface UploadOptions {
|
|
7
|
-
projectName: string;
|
|
8
|
-
runId: string;
|
|
9
|
-
baseUrl: string;
|
|
10
|
-
uploadBucket: string;
|
|
11
|
-
}
|
|
12
|
-
export declare function runPlaywrightMergeReports(options: MergeReportsOptions): Promise<{
|
|
13
|
-
success: boolean;
|
|
14
|
-
}>;
|
|
15
|
-
export declare function extractUrlMappingsFromBlobs(blobDir: string): Record<string, string>;
|
|
16
|
-
export declare function patchMergedHtmlReport(htmlFilePath: string, urlMappings: Record<string, string>): Promise<void>;
|
|
17
|
-
export declare function patchSummaryJson(jsonFilePath: string, urlMappings: Record<string, string>): Promise<void>;
|
|
18
|
-
export declare function uploadMergedReports(cwd: string, outputDir: string, uploadOptions: UploadOptions): Promise<void>;
|
|
19
|
-
export declare function mergeReports(options: {
|
|
20
|
-
blobDir?: string;
|
|
21
|
-
cwd?: string;
|
|
22
|
-
}): Promise<{
|
|
23
|
-
success: boolean;
|
|
24
|
-
}>;
|
|
25
|
-
export {};
|
|
26
|
-
//# sourceMappingURL=merge-reports.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"merge-reports.d.ts","sourceRoot":"","sources":["../../src/lib/merge-reports.ts"],"names":[],"mappings":"AAYA,UAAU,mBAAmB;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,UAAU,aAAa;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CA2B/B;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA4BxB;AAED,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CAqFf;AAED,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CAuBf;AAED,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,IAAI,CAAC,CAkDf;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAuDhC"}
|