@empiricalrun/playwright-utils 0.40.2 → 0.40.3
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 +6 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +8 -0
- package/dist/reporter/empirical-reporter.d.ts.map +1 -1
- package/dist/reporter/empirical-reporter.js +8 -0
- package/dist/reporter/incremental-blob-reporter.d.ts +59 -0
- package/dist/reporter/incremental-blob-reporter.d.ts.map +1 -0
- package/dist/reporter/incremental-blob-reporter.js +379 -0
- package/package.json +3 -3
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAgBzD,wBAAgB,gBAAgB,IAAI,MAAM,CAazC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAE7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAgBzD,wBAAgB,gBAAgB,IAAI,MAAM,CAazC;AAqCD,eAAO,MAAM,UAAU,EAAE,oBAyBxB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,iBAarB,CAAC"}
|
package/dist/config/index.js
CHANGED
|
@@ -39,6 +39,7 @@ function getReporters() {
|
|
|
39
39
|
if (process.env.HAS_SHARDING && !process.env.SHARD_INDEX) {
|
|
40
40
|
throw new Error("Invalid env variables: HAS_SHARDING needs SHARD_INDEX");
|
|
41
41
|
}
|
|
42
|
+
const useIncrementalReporter = process.env.SPOT_INSTANCE === "true";
|
|
42
43
|
const middle = process.env.HAS_SHARDING
|
|
43
44
|
? [
|
|
44
45
|
[
|
|
@@ -48,6 +49,13 @@ function getReporters() {
|
|
|
48
49
|
fileName: `report-${process.env.SHARD_INDEX}.zip`,
|
|
49
50
|
},
|
|
50
51
|
],
|
|
52
|
+
...(useIncrementalReporter
|
|
53
|
+
? [
|
|
54
|
+
[
|
|
55
|
+
"./node_modules/@empiricalrun/playwright-utils/dist/reporter/incremental-blob-reporter.js",
|
|
56
|
+
],
|
|
57
|
+
]
|
|
58
|
+
: []),
|
|
51
59
|
]
|
|
52
60
|
: [
|
|
53
61
|
["json", { outputFile: "summary.json" }],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"empirical-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/empirical-reporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"empirical-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/empirical-reporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;AAqBnC,cAAM,iBAAkB,YAAW,QAAQ;IACzC,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,oBAAoB,CAG1B;IACF,OAAO,CAAC,sBAAsB,CAAuB;IACrD,OAAO,CAAC,YAAY,CAAgD;IACpE,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,gBAAgB,CAAkC;IAC1D,OAAO,CAAC,SAAS,CAAyB;;IAM1C,OAAO,CAAC,6BAA6B,CAkCnC;IAEF,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU;IAoGtC,KAAK,CAAC,MAAM,EAAE,UAAU;IA8G9B,OAAO,CAAC,gBAAgB;YAoBV,gBAAgB;YAOhB,iBAAiB;CAmChC;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -9,6 +9,7 @@ const logger_1 = require("../logger");
|
|
|
9
9
|
const telemetry_1 = require("../telemetry");
|
|
10
10
|
const blob_utils_1 = require("./blob-utils");
|
|
11
11
|
const failing_line_1 = require("./failing-line");
|
|
12
|
+
const incremental_blob_reporter_1 = require("./incremental-blob-reporter");
|
|
12
13
|
const uploader_1 = require("./uploader");
|
|
13
14
|
const util_1 = require("./util");
|
|
14
15
|
class EmpiricalReporter {
|
|
@@ -70,6 +71,13 @@ class EmpiricalReporter {
|
|
|
70
71
|
if (this._hasSharding) {
|
|
71
72
|
const key = (0, blob_utils_1.makeAttachmentKey)(test.id, retryIndex, attachment.name);
|
|
72
73
|
this._attachmentUrlMap.set(key, url);
|
|
74
|
+
const incrementalReporter = (0, incremental_blob_reporter_1.getIncrementalBlobReporter)();
|
|
75
|
+
if (incrementalReporter) {
|
|
76
|
+
incrementalReporter.addAttachmentUrl(attachment.path, url);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
logger_1.logger.error("[IncrementalBlobReporter] Not Found");
|
|
80
|
+
}
|
|
73
81
|
}
|
|
74
82
|
return {
|
|
75
83
|
name: attachment.name,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { FullConfig, FullResult, Reporter, Suite, TestCase, TestResult, TestStep } from "@playwright/test/reporter";
|
|
2
|
+
/**
|
|
3
|
+
* IncrementalBlobReporter
|
|
4
|
+
*
|
|
5
|
+
* This reporter builds a report.jsonl incrementally matching Playwright's blob format.
|
|
6
|
+
* The zip can be uploaded for merge at any point (e.g., on SIGTERM).
|
|
7
|
+
*
|
|
8
|
+
* Format matches Playwright's blob reporter exactly.
|
|
9
|
+
*/
|
|
10
|
+
export type AttachmentUrlMap = Map<string, string>;
|
|
11
|
+
declare class IncrementalBlobReporter implements Reporter {
|
|
12
|
+
private _currentWorkingDir;
|
|
13
|
+
private _outputDir;
|
|
14
|
+
private _shardIndex;
|
|
15
|
+
private _totalShards;
|
|
16
|
+
private _reportLines;
|
|
17
|
+
private _config;
|
|
18
|
+
private _attachmentUrlMap;
|
|
19
|
+
private _resultIdMap;
|
|
20
|
+
private _stepIdMap;
|
|
21
|
+
private _uploader;
|
|
22
|
+
private _interrupted;
|
|
23
|
+
private _lastTestEndIndex;
|
|
24
|
+
private _finalized;
|
|
25
|
+
private _startTime;
|
|
26
|
+
constructor();
|
|
27
|
+
private _sigintHandler;
|
|
28
|
+
private _setupSignalHandler;
|
|
29
|
+
private _removeSignalHandler;
|
|
30
|
+
private _flushAndUpload;
|
|
31
|
+
private _finalizeReportForInterrupt;
|
|
32
|
+
private get _zipPath();
|
|
33
|
+
private get _jsonlPath();
|
|
34
|
+
private get _urlsJsonPath();
|
|
35
|
+
private _ensureOutputDir;
|
|
36
|
+
private _generateId;
|
|
37
|
+
private _getResultId;
|
|
38
|
+
private _getStepId;
|
|
39
|
+
private _appendEvent;
|
|
40
|
+
private _serializeSuiteEntry;
|
|
41
|
+
private _serializeSuite;
|
|
42
|
+
private _buildUrlsJson;
|
|
43
|
+
private _writeZip;
|
|
44
|
+
onBegin(config: FullConfig, suite: Suite): void;
|
|
45
|
+
onTestBegin(test: TestCase, result: TestResult): void;
|
|
46
|
+
onTestEnd(test: TestCase, result: TestResult): void;
|
|
47
|
+
onStepBegin(test: TestCase, result: TestResult, step: TestStep): void;
|
|
48
|
+
onStepEnd(test: TestCase, result: TestResult, step: TestStep): void;
|
|
49
|
+
onEnd(result: FullResult): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* Add a single attachment URL mapping
|
|
52
|
+
*/
|
|
53
|
+
addAttachmentUrl(localPath: string, remoteUrl: string): void;
|
|
54
|
+
printsToStdio(): boolean;
|
|
55
|
+
}
|
|
56
|
+
export declare function getIncrementalBlobReporter(): IncrementalBlobReporter | null;
|
|
57
|
+
export declare function setIncrementalBlobReporterInstance(reporter: IncrementalBlobReporter): void;
|
|
58
|
+
export default IncrementalBlobReporter;
|
|
59
|
+
//# sourceMappingURL=incremental-blob-reporter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incremental-blob-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/incremental-blob-reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,UAAU,EACV,QAAQ,EACT,MAAM,2BAA2B,CAAC;AASnC;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEnD,cAAM,uBAAwB,YAAW,QAAQ;IAC/C,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,UAAU,CAGhB;IACF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,iBAAiB,CAAc;IACvC,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,UAAU,CAAsB;;IAexC,OAAO,CAAC,cAAc,CAA6B;IAEnD,OAAO,CAAC,mBAAmB;IAwB3B,OAAO,CAAC,oBAAoB;YAOd,eAAe;IA0B7B,OAAO,CAAC,2BAA2B;IA4BnC,OAAO,KAAK,QAAQ,GAKnB;IAED,OAAO,KAAK,UAAU,GAErB;IAED,OAAO,KAAK,aAAa,GAExB;IAED,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,cAAc;YAQR,SAAS;IAwBvB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IA6E/C,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAerD,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAwCnD,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAmBrE,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAsB7D,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB9C;;OAEG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAI5D,aAAa,IAAI,OAAO;CAGzB;AAID,wBAAgB,0BAA0B,IAAI,uBAAuB,GAAG,IAAI,CAE3E;AAED,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,uBAAuB,GAChC,IAAI,CAEN;AAED,eAAe,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,379 @@
|
|
|
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.getIncrementalBlobReporter = getIncrementalBlobReporter;
|
|
7
|
+
exports.setIncrementalBlobReporterInstance = setIncrementalBlobReporterInstance;
|
|
8
|
+
const zip_1 = require("@empiricalrun/r2-uploader/zip");
|
|
9
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const uploader_1 = require("./uploader");
|
|
14
|
+
const BLOB_REPORT_VERSION = 2;
|
|
15
|
+
class IncrementalBlobReporter {
|
|
16
|
+
_currentWorkingDir = process.cwd();
|
|
17
|
+
_outputDir = path_1.default.join(this._currentWorkingDir, "blob-report-incremental");
|
|
18
|
+
_shardIndex;
|
|
19
|
+
_totalShards;
|
|
20
|
+
_reportLines = [];
|
|
21
|
+
_config = null;
|
|
22
|
+
_attachmentUrlMap = new Map();
|
|
23
|
+
_resultIdMap = new Map(); // testId+retry -> resultId
|
|
24
|
+
_stepIdMap = new Map(); // step -> stepId
|
|
25
|
+
_uploader = null;
|
|
26
|
+
_interrupted = false;
|
|
27
|
+
_lastTestEndIndex = -1;
|
|
28
|
+
_finalized = false;
|
|
29
|
+
_startTime = Date.now();
|
|
30
|
+
constructor() {
|
|
31
|
+
if (!process.env.SHARD_INDEX || !process.env.TOTAL_SHARDS) {
|
|
32
|
+
throw new Error("[IncrementalBlobReporter] SHARD_INDEX and TOTAL_SHARDS environment variables are required");
|
|
33
|
+
}
|
|
34
|
+
this._shardIndex = Number.parseInt(process.env.SHARD_INDEX, 10);
|
|
35
|
+
this._totalShards = Number.parseInt(process.env.TOTAL_SHARDS, 10);
|
|
36
|
+
this._uploader = (0, uploader_1.createUploader)();
|
|
37
|
+
setIncrementalBlobReporterInstance(this);
|
|
38
|
+
this._setupSignalHandler();
|
|
39
|
+
}
|
|
40
|
+
_sigintHandler = null;
|
|
41
|
+
_setupSignalHandler() {
|
|
42
|
+
this._sigintHandler = () => {
|
|
43
|
+
console.log("[IncrementalBlobReporter] SIGINT received, flushing and uploading...");
|
|
44
|
+
this._flushAndUpload()
|
|
45
|
+
.then(() => {
|
|
46
|
+
console.log("[IncrementalBlobReporter] Flush and upload complete on SIGINT");
|
|
47
|
+
})
|
|
48
|
+
.catch((err) => {
|
|
49
|
+
console.error("[IncrementalBlobReporter] Flush/upload failed on SIGINT:", err);
|
|
50
|
+
})
|
|
51
|
+
.finally(() => {
|
|
52
|
+
this._removeSignalHandler();
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
process.on("SIGINT", this._sigintHandler);
|
|
56
|
+
}
|
|
57
|
+
_removeSignalHandler() {
|
|
58
|
+
if (this._sigintHandler) {
|
|
59
|
+
process.off("SIGINT", this._sigintHandler);
|
|
60
|
+
this._sigintHandler = null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async _flushAndUpload() {
|
|
64
|
+
this._interrupted = true;
|
|
65
|
+
// Wait for pending attachment uploads FIRST so URLs are available
|
|
66
|
+
if (this._uploader) {
|
|
67
|
+
await this._uploader.waitForUploads();
|
|
68
|
+
}
|
|
69
|
+
this._finalizeReportForInterrupt();
|
|
70
|
+
await this._writeZip();
|
|
71
|
+
if (!this._uploader) {
|
|
72
|
+
console.log("[IncrementalBlobReporter] No uploader available, skipping upload");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await this._uploader.uploadFile(this._zipPath, `blobs/incremental-report-${this._shardIndex}.zip`);
|
|
76
|
+
await this._uploader.waitForUploads();
|
|
77
|
+
console.log("[IncrementalBlobReporter] Blob uploaded on SIGINT");
|
|
78
|
+
}
|
|
79
|
+
_finalizeReportForInterrupt() {
|
|
80
|
+
if (this._finalized)
|
|
81
|
+
return;
|
|
82
|
+
if (this._lastTestEndIndex === -1) {
|
|
83
|
+
console.log("[IncrementalBlobReporter] No onTestEnd found, cannot finalize report. Merge may fail");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
// Truncate to last onTestEnd
|
|
87
|
+
this._reportLines = this._reportLines.slice(0, this._lastTestEndIndex + 1);
|
|
88
|
+
// Append onEnd with interrupted status
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
this._appendEvent("onEnd", {
|
|
91
|
+
result: {
|
|
92
|
+
status: "interrupted",
|
|
93
|
+
startTime: this._startTime,
|
|
94
|
+
duration: now - this._startTime,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
this._finalized = true;
|
|
98
|
+
console.log(`[IncrementalBlobReporter] Finalized report at line ${this._lastTestEndIndex + 1}, status: interrupted`);
|
|
99
|
+
}
|
|
100
|
+
get _zipPath() {
|
|
101
|
+
return path_1.default.join(this._outputDir, `incremental-report-${this._shardIndex}.zip`);
|
|
102
|
+
}
|
|
103
|
+
get _jsonlPath() {
|
|
104
|
+
return path_1.default.join(this._outputDir, "report.jsonl");
|
|
105
|
+
}
|
|
106
|
+
get _urlsJsonPath() {
|
|
107
|
+
return path_1.default.join(this._outputDir, "_empirical_urls.json");
|
|
108
|
+
}
|
|
109
|
+
_ensureOutputDir() {
|
|
110
|
+
fs_1.default.mkdirSync(this._outputDir, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
_generateId() {
|
|
113
|
+
return crypto_1.default.randomBytes(16).toString("hex");
|
|
114
|
+
}
|
|
115
|
+
_getResultId(testId, retry) {
|
|
116
|
+
const key = `${testId}-${retry}`;
|
|
117
|
+
let resultId = this._resultIdMap.get(key);
|
|
118
|
+
if (!resultId) {
|
|
119
|
+
resultId = this._generateId();
|
|
120
|
+
this._resultIdMap.set(key, resultId);
|
|
121
|
+
}
|
|
122
|
+
return resultId;
|
|
123
|
+
}
|
|
124
|
+
_getStepId(step) {
|
|
125
|
+
let stepId = this._stepIdMap.get(step);
|
|
126
|
+
if (!stepId) {
|
|
127
|
+
stepId = this._generateId();
|
|
128
|
+
this._stepIdMap.set(step, stepId);
|
|
129
|
+
}
|
|
130
|
+
return stepId;
|
|
131
|
+
}
|
|
132
|
+
_appendEvent(method, params) {
|
|
133
|
+
if (this._finalized)
|
|
134
|
+
return;
|
|
135
|
+
if (this._interrupted && method !== "onEnd")
|
|
136
|
+
return;
|
|
137
|
+
const event = params !== undefined ? { method, params } : { method };
|
|
138
|
+
this._reportLines.push(JSON.stringify(event));
|
|
139
|
+
}
|
|
140
|
+
_serializeSuiteEntry(test) {
|
|
141
|
+
return {
|
|
142
|
+
testId: test.id,
|
|
143
|
+
title: test.title,
|
|
144
|
+
location: {
|
|
145
|
+
file: path_1.default.relative(this._config?.rootDir || "", test.location.file),
|
|
146
|
+
line: test.location.line,
|
|
147
|
+
column: test.location.column,
|
|
148
|
+
},
|
|
149
|
+
retries: test.retries,
|
|
150
|
+
tags: test.tags,
|
|
151
|
+
repeatEachIndex: test.repeatEachIndex,
|
|
152
|
+
annotations: test.annotations,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
_serializeSuite(suite) {
|
|
156
|
+
const entries = [];
|
|
157
|
+
for (const test of suite.tests) {
|
|
158
|
+
entries.push(this._serializeSuiteEntry(test));
|
|
159
|
+
}
|
|
160
|
+
for (const childSuite of suite.suites) {
|
|
161
|
+
entries.push(this._serializeSuite(childSuite));
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
title: suite.title,
|
|
165
|
+
location: suite.location
|
|
166
|
+
? {
|
|
167
|
+
file: path_1.default.relative(this._config?.rootDir || "", suite.location.file),
|
|
168
|
+
line: suite.location.line,
|
|
169
|
+
column: suite.location.column,
|
|
170
|
+
}
|
|
171
|
+
: { file: "", line: 0, column: 0 },
|
|
172
|
+
entries,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
_buildUrlsJson() {
|
|
176
|
+
const urlMap = {};
|
|
177
|
+
for (const [localPath, url] of this._attachmentUrlMap) {
|
|
178
|
+
urlMap[localPath] = url;
|
|
179
|
+
}
|
|
180
|
+
return urlMap;
|
|
181
|
+
}
|
|
182
|
+
async _writeZip() {
|
|
183
|
+
this._ensureOutputDir();
|
|
184
|
+
// Write report.jsonl to the output directory
|
|
185
|
+
const jsonlContent = this._reportLines.join("\n") + "\n";
|
|
186
|
+
fs_1.default.writeFileSync(this._jsonlPath, jsonlContent, "utf8");
|
|
187
|
+
// Write _empirical_urls.json with attachment URL mappings
|
|
188
|
+
const urlsJson = this._buildUrlsJson();
|
|
189
|
+
fs_1.default.writeFileSync(this._urlsJsonPath, JSON.stringify(urlsJson, null, 2), "utf8");
|
|
190
|
+
// Create zip from the output directory
|
|
191
|
+
const zipBuffer = await (0, zip_1.createZipFromDirectory)(this._outputDir);
|
|
192
|
+
fs_1.default.writeFileSync(this._zipPath, zipBuffer);
|
|
193
|
+
console.log(`[IncrementalBlobReporter] Zip updated: ${this._zipPath} (${this._reportLines.length} events, ${this._attachmentUrlMap.size} attachments)`);
|
|
194
|
+
}
|
|
195
|
+
onBegin(config, suite) {
|
|
196
|
+
this._config = config;
|
|
197
|
+
this._ensureOutputDir();
|
|
198
|
+
// 1. Emit onBlobReportMetadata (first event)
|
|
199
|
+
const osName = process.platform === "darwin" ? "macOS" : process.platform;
|
|
200
|
+
const osVersion = os_1.default.release();
|
|
201
|
+
this._appendEvent("onBlobReportMetadata", {
|
|
202
|
+
version: BLOB_REPORT_VERSION,
|
|
203
|
+
userAgent: `Playwright/${config.version} (${process.arch}; ${osName} ${osVersion}) node/${process.versions.node}`,
|
|
204
|
+
shard: {
|
|
205
|
+
current: this._shardIndex,
|
|
206
|
+
total: this._totalShards,
|
|
207
|
+
},
|
|
208
|
+
pathSeparator: path_1.default.sep,
|
|
209
|
+
});
|
|
210
|
+
// 2. Emit onConfigure
|
|
211
|
+
this._appendEvent("onConfigure", {
|
|
212
|
+
config: {
|
|
213
|
+
configFile: config.configFile
|
|
214
|
+
? path_1.default.relative(this._currentWorkingDir, config.configFile)
|
|
215
|
+
: null,
|
|
216
|
+
globalTimeout: config.globalTimeout,
|
|
217
|
+
maxFailures: config.maxFailures,
|
|
218
|
+
metadata: config.metadata,
|
|
219
|
+
rootDir: config.rootDir,
|
|
220
|
+
version: config.version,
|
|
221
|
+
workers: config.workers,
|
|
222
|
+
globalSetup: config.globalSetup,
|
|
223
|
+
globalTeardown: config.globalTeardown,
|
|
224
|
+
tags: [],
|
|
225
|
+
webServer: config.webServer,
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
// 3. Emit onProject for each project
|
|
229
|
+
for (const projectSuite of suite.suites) {
|
|
230
|
+
const project = config.projects.find((p) => p.name === projectSuite.title);
|
|
231
|
+
if (!project)
|
|
232
|
+
continue;
|
|
233
|
+
const suites = [];
|
|
234
|
+
for (const fileSuite of projectSuite.suites) {
|
|
235
|
+
suites.push(this._serializeSuite(fileSuite));
|
|
236
|
+
}
|
|
237
|
+
this._appendEvent("onProject", {
|
|
238
|
+
project: {
|
|
239
|
+
metadata: project.metadata,
|
|
240
|
+
name: project.name,
|
|
241
|
+
outputDir: path_1.default.relative(this._currentWorkingDir, project.outputDir),
|
|
242
|
+
repeatEach: project.repeatEach,
|
|
243
|
+
retries: project.retries,
|
|
244
|
+
testDir: project.testDir,
|
|
245
|
+
testIgnore: project.testIgnore,
|
|
246
|
+
testMatch: project.testMatch,
|
|
247
|
+
timeout: project.timeout,
|
|
248
|
+
suites,
|
|
249
|
+
grep: project.grep,
|
|
250
|
+
grepInvert: project.grepInvert,
|
|
251
|
+
dependencies: project.dependencies,
|
|
252
|
+
snapshotDir: project.snapshotDir,
|
|
253
|
+
use: project.use,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
// 4. Emit onBegin (empty params per Playwright format)
|
|
258
|
+
this._appendEvent("onBegin");
|
|
259
|
+
console.log(`[IncrementalBlobReporter] Started with ${suite.allTests().length} tests`);
|
|
260
|
+
}
|
|
261
|
+
onTestBegin(test, result) {
|
|
262
|
+
const resultId = this._getResultId(test.id, result.retry);
|
|
263
|
+
this._appendEvent("onTestBegin", {
|
|
264
|
+
testId: test.id,
|
|
265
|
+
result: {
|
|
266
|
+
id: resultId,
|
|
267
|
+
retry: result.retry,
|
|
268
|
+
workerIndex: result.workerIndex,
|
|
269
|
+
parallelIndex: result.parallelIndex,
|
|
270
|
+
startTime: result.startTime.getTime(),
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
onTestEnd(test, result) {
|
|
275
|
+
const resultId = this._getResultId(test.id, result.retry);
|
|
276
|
+
// Emit separate onAttach for each attachment (matching Playwright's blob format)
|
|
277
|
+
for (const attachment of result.attachments) {
|
|
278
|
+
this._appendEvent("onAttach", {
|
|
279
|
+
testId: test.id,
|
|
280
|
+
resultId,
|
|
281
|
+
attachments: [
|
|
282
|
+
{
|
|
283
|
+
name: attachment.name,
|
|
284
|
+
contentType: attachment.contentType,
|
|
285
|
+
path: attachment.path,
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
this._lastTestEndIndex = this._reportLines.length;
|
|
291
|
+
this._appendEvent("onTestEnd", {
|
|
292
|
+
test: {
|
|
293
|
+
testId: test.id,
|
|
294
|
+
expectedStatus: test.expectedStatus,
|
|
295
|
+
timeout: test.timeout,
|
|
296
|
+
annotations: test.annotations,
|
|
297
|
+
},
|
|
298
|
+
result: {
|
|
299
|
+
id: resultId,
|
|
300
|
+
duration: result.duration,
|
|
301
|
+
status: result.status,
|
|
302
|
+
errors: result.errors.map((e) => ({
|
|
303
|
+
message: e.message,
|
|
304
|
+
stack: e.stack,
|
|
305
|
+
location: e.location,
|
|
306
|
+
snippet: e.snippet,
|
|
307
|
+
})),
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
onStepBegin(test, result, step) {
|
|
312
|
+
const resultId = this._getResultId(test.id, result.retry);
|
|
313
|
+
const stepId = this._getStepId(step);
|
|
314
|
+
const parentStepId = step.parent ? this._getStepId(step.parent) : undefined;
|
|
315
|
+
this._appendEvent("onStepBegin", {
|
|
316
|
+
testId: test.id,
|
|
317
|
+
resultId,
|
|
318
|
+
step: {
|
|
319
|
+
id: stepId,
|
|
320
|
+
parentStepId,
|
|
321
|
+
title: step.title,
|
|
322
|
+
category: step.category,
|
|
323
|
+
startTime: step.startTime.getTime(),
|
|
324
|
+
location: step.location,
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
onStepEnd(test, result, step) {
|
|
329
|
+
const resultId = this._getResultId(test.id, result.retry);
|
|
330
|
+
const stepId = this._getStepId(step);
|
|
331
|
+
this._appendEvent("onStepEnd", {
|
|
332
|
+
testId: test.id,
|
|
333
|
+
resultId,
|
|
334
|
+
step: {
|
|
335
|
+
id: stepId,
|
|
336
|
+
duration: step.duration,
|
|
337
|
+
error: step.error
|
|
338
|
+
? {
|
|
339
|
+
message: step.error.message,
|
|
340
|
+
stack: step.error.stack,
|
|
341
|
+
location: step.error.location,
|
|
342
|
+
snippet: step.error.snippet,
|
|
343
|
+
}
|
|
344
|
+
: undefined,
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
async onEnd(result) {
|
|
349
|
+
this._removeSignalHandler();
|
|
350
|
+
this._appendEvent("onEnd", {
|
|
351
|
+
result: {
|
|
352
|
+
status: result.status,
|
|
353
|
+
startTime: result.startTime.getTime(),
|
|
354
|
+
duration: result.duration,
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
// Final zip write
|
|
358
|
+
await this._uploader?.waitForUploads();
|
|
359
|
+
await this._writeZip();
|
|
360
|
+
console.log(`[IncrementalBlobReporter] Finished with status: ${result.status}`);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Add a single attachment URL mapping
|
|
364
|
+
*/
|
|
365
|
+
addAttachmentUrl(localPath, remoteUrl) {
|
|
366
|
+
this._attachmentUrlMap.set(localPath, remoteUrl);
|
|
367
|
+
}
|
|
368
|
+
printsToStdio() {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
let _instance = null;
|
|
373
|
+
function getIncrementalBlobReporter() {
|
|
374
|
+
return _instance;
|
|
375
|
+
}
|
|
376
|
+
function setIncrementalBlobReporterInstance(reporter) {
|
|
377
|
+
_instance = reporter;
|
|
378
|
+
}
|
|
379
|
+
exports.default = IncrementalBlobReporter;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empiricalrun/playwright-utils",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.3",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
|
|
44
44
|
"rimraf": "^6.0.1",
|
|
45
45
|
"ts-morph": "^23.0.0",
|
|
46
|
-
"@empiricalrun/cua": "^0.2.0",
|
|
47
46
|
"@empiricalrun/dashboard-client": "^0.2.0",
|
|
48
47
|
"@empiricalrun/llm": "^0.25.2",
|
|
49
|
-
"@empiricalrun/r2-uploader": "^0.9.1"
|
|
48
|
+
"@empiricalrun/r2-uploader": "^0.9.1",
|
|
49
|
+
"@empiricalrun/cua": "^0.2.0"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"dev": "tsc --build --watch",
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/email.ts","./src/index.ts","./src/kv.ts","./src/logger.ts","./src/mailosaur-client.ts","./src/playwright-extensions.ts","./src/postgres.ts","./src/telemetry.ts","./src/webhook.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/config/index.ts","./src/config/proxy.ts","./src/config/devices/types.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/reporter/blob-utils.ts","./src/reporter/empirical-reporter.ts","./src/reporter/failing-line.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/index.ts","./src/test/types.ts","./src/test/video-labels.ts","./src/test/expect/index.ts","./src/test/expect/types.ts","./src/test/expect/visual.ts","./src/test/expect/webhook.ts","./src/test/scripts/agent-capabilities.ts","./src/test/scripts/index.ts","./src/test/scripts/locator-highlights.ts","./src/test/scripts/locator-vision.ts","./src/test/scripts/mouse-pointer.ts","./src/test/scripts/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/cache.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/index.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/prompt.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts","./src/test/scripts/pw-locator-patch/highlight/click.ts","./src/test/scripts/pw-locator-patch/highlight/expect.ts","./src/test/scripts/pw-locator-patch/highlight/hover.ts","./src/test/scripts/pw-locator-patch/highlight/inner-text.ts","./src/test/scripts/pw-locator-patch/highlight/input-value.ts","./src/test/scripts/pw-locator-patch/highlight/is-checked.ts","./src/test/scripts/pw-locator-patch/highlight/is-disabled.ts","./src/test/scripts/pw-locator-patch/highlight/is-editable.ts","./src/test/scripts/pw-locator-patch/highlight/text-content.ts","./src/test/scripts/pw-locator-patch/utils/index.ts","./src/test/scripts/pw-locator-patch/vision/query.ts"],"version":"5.8.3"}
|
|
1
|
+
{"root":["./src/email.ts","./src/index.ts","./src/kv.ts","./src/logger.ts","./src/mailosaur-client.ts","./src/playwright-extensions.ts","./src/postgres.ts","./src/telemetry.ts","./src/webhook.ts","./src/auth/google.ts","./src/auth/index.ts","./src/auth/types.ts","./src/captcha/index.ts","./src/config/index.ts","./src/config/proxy.ts","./src/config/devices/types.ts","./src/overlay-tests/cache.spec.ts","./src/overlay-tests/click.spec.ts","./src/overlay-tests/fixtures.ts","./src/overlay-tests/patch.spec.ts","./src/reporter/blob-utils.ts","./src/reporter/empirical-reporter.ts","./src/reporter/failing-line.ts","./src/reporter/incremental-blob-reporter.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/index.ts","./src/test/types.ts","./src/test/video-labels.ts","./src/test/expect/index.ts","./src/test/expect/types.ts","./src/test/expect/visual.ts","./src/test/expect/webhook.ts","./src/test/scripts/agent-capabilities.ts","./src/test/scripts/index.ts","./src/test/scripts/locator-highlights.ts","./src/test/scripts/locator-vision.ts","./src/test/scripts/mouse-pointer.ts","./src/test/scripts/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/cache.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/index.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/prompt.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/types.ts","./src/test/scripts/pw-locator-patch/dismiss-overlays/utils.ts","./src/test/scripts/pw-locator-patch/highlight/click.ts","./src/test/scripts/pw-locator-patch/highlight/expect.ts","./src/test/scripts/pw-locator-patch/highlight/hover.ts","./src/test/scripts/pw-locator-patch/highlight/inner-text.ts","./src/test/scripts/pw-locator-patch/highlight/input-value.ts","./src/test/scripts/pw-locator-patch/highlight/is-checked.ts","./src/test/scripts/pw-locator-patch/highlight/is-disabled.ts","./src/test/scripts/pw-locator-patch/highlight/is-editable.ts","./src/test/scripts/pw-locator-patch/highlight/text-content.ts","./src/test/scripts/pw-locator-patch/utils/index.ts","./src/test/scripts/pw-locator-patch/vision/query.ts"],"version":"5.8.3"}
|