@empiricalrun/playwright-utils 0.45.3 → 0.46.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 +12 -0
- package/dist/reporter/ibr-utils.d.ts +47 -0
- package/dist/reporter/ibr-utils.d.ts.map +1 -0
- package/dist/reporter/ibr-utils.js +139 -0
- package/dist/reporter/incremental-blob-reporter.d.ts +8 -11
- package/dist/reporter/incremental-blob-reporter.d.ts.map +1 -1
- package/dist/reporter/incremental-blob-reporter.js +81 -258
- package/dist/reporter/lifecycle-events.d.ts +13 -0
- package/dist/reporter/lifecycle-events.d.ts.map +1 -0
- package/dist/reporter/lifecycle-events.js +211 -0
- package/package.json +3 -3
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @empiricalrun/playwright-utils
|
|
2
2
|
|
|
3
|
+
## 0.46.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ed258cd: feat: remove (all retries not complete and no passed) runs from blob-zip
|
|
8
|
+
|
|
9
|
+
## 0.46.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 491e5eb: feat: filter unrun tests from suite so blob wont show unrun tests as skipped
|
|
14
|
+
|
|
3
15
|
## 0.45.3
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Suite, TestCase, TestStep } from "@playwright/test/reporter";
|
|
2
|
+
export type JsonPattern = {
|
|
3
|
+
s?: string;
|
|
4
|
+
r?: {
|
|
5
|
+
source: string;
|
|
6
|
+
flags: string;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
export declare function serializeRegexPatterns(patterns: (string | RegExp) | (string | RegExp)[]): JsonPattern[];
|
|
10
|
+
export declare function generateId(): string;
|
|
11
|
+
export declare function getOrCreateId(map: Map<string, string>, key: string): string;
|
|
12
|
+
export declare function getOrCreateStepId(map: Map<TestStep, string>, step: TestStep): string;
|
|
13
|
+
type JsonLocation = {
|
|
14
|
+
file: string;
|
|
15
|
+
line: number;
|
|
16
|
+
column: number;
|
|
17
|
+
};
|
|
18
|
+
export type JsonTestCase = {
|
|
19
|
+
testId: string;
|
|
20
|
+
title: string;
|
|
21
|
+
location: JsonLocation;
|
|
22
|
+
retries: number;
|
|
23
|
+
tags: string[];
|
|
24
|
+
repeatEachIndex: number;
|
|
25
|
+
annotations: {
|
|
26
|
+
type: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
}[];
|
|
29
|
+
};
|
|
30
|
+
export type JsonSuite = {
|
|
31
|
+
title: string;
|
|
32
|
+
location: JsonLocation;
|
|
33
|
+
entries: (JsonTestCase | JsonSuite)[];
|
|
34
|
+
};
|
|
35
|
+
export declare function serializeTest(test: TestCase, rootDir: string): JsonTestCase;
|
|
36
|
+
export declare function serializeSuite(suite: Suite, rootDir: string): JsonSuite;
|
|
37
|
+
/**
|
|
38
|
+
* Recursively filters a serialized suite tree to only include tests
|
|
39
|
+
* whose testId is in the completedTestIds set. Removes empty parent suites.
|
|
40
|
+
* Returns null if the suite has no completed test descendants.
|
|
41
|
+
*/
|
|
42
|
+
export declare function filterSuiteByCompletedTests(suite: JsonSuite, completedTestIds: Set<string>): JsonSuite | null;
|
|
43
|
+
export declare function buildUrlsJson(attachmentUrlMap: Map<string, string>): Record<string, string>;
|
|
44
|
+
export declare function isLocalTesting(): boolean;
|
|
45
|
+
export declare function embedAttachment(attachmentPath: string | undefined, resultId: string, stagingDir: string): string | null;
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=ibr-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ibr-utils.d.ts","sourceRoot":"","sources":["../../src/reporter/ibr-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAO3E,MAAM,MAAM,WAAW,GAAG;IACxB,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,CAAC,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC,CAAC;AAIF,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,GAChD,WAAW,EAAE,CAOf;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAO3E;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,EAC1B,IAAI,EAAE,QAAQ,GACb,MAAM,CAOR;AAID,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAIF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACvD,CAAC;AAIF,MAAM,MAAM,SAAS,GAAG;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,CAAC,YAAY,GAAG,SAAS,CAAC,EAAE,CAAC;CACvC,CAAC;AAIF,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,CAc3E;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,GAAG,SAAS,CAsBvE;AAMD;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,SAAS,EAChB,gBAAgB,EAAE,GAAG,CAAC,MAAM,CAAC,GAC5B,SAAS,GAAG,IAAI,CAmBlB;AAED,wBAAgB,aAAa,CAC3B,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACpC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB;AAED,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED,wBAAgB,eAAe,CAC7B,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,MAAM,GAAG,IAAI,CAkBf"}
|
|
@@ -0,0 +1,139 @@
|
|
|
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.serializeRegexPatterns = serializeRegexPatterns;
|
|
7
|
+
exports.generateId = generateId;
|
|
8
|
+
exports.getOrCreateId = getOrCreateId;
|
|
9
|
+
exports.getOrCreateStepId = getOrCreateStepId;
|
|
10
|
+
exports.serializeTest = serializeTest;
|
|
11
|
+
exports.serializeSuite = serializeSuite;
|
|
12
|
+
exports.filterSuiteByCompletedTests = filterSuiteByCompletedTests;
|
|
13
|
+
exports.buildUrlsJson = buildUrlsJson;
|
|
14
|
+
exports.isLocalTesting = isLocalTesting;
|
|
15
|
+
exports.embedAttachment = embedAttachment;
|
|
16
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
17
|
+
const fs_1 = __importDefault(require("fs"));
|
|
18
|
+
const path_1 = __importDefault(require("path"));
|
|
19
|
+
// Mirrors serializeRegexPatterns from Playwright's teleReceiver.ts
|
|
20
|
+
// See: packages/playwright/src/isomorphic/teleReceiver.ts
|
|
21
|
+
function serializeRegexPatterns(patterns) {
|
|
22
|
+
const arr = Array.isArray(patterns) ? patterns : [patterns];
|
|
23
|
+
return arr.map((p) => typeof p === "string"
|
|
24
|
+
? { s: p }
|
|
25
|
+
: { r: { source: p.source, flags: p.flags } });
|
|
26
|
+
}
|
|
27
|
+
function generateId() {
|
|
28
|
+
return crypto_1.default.randomBytes(16).toString("hex");
|
|
29
|
+
}
|
|
30
|
+
function getOrCreateId(map, key) {
|
|
31
|
+
let id = map.get(key);
|
|
32
|
+
if (!id) {
|
|
33
|
+
id = generateId();
|
|
34
|
+
map.set(key, id);
|
|
35
|
+
}
|
|
36
|
+
return id;
|
|
37
|
+
}
|
|
38
|
+
function getOrCreateStepId(map, step) {
|
|
39
|
+
let id = map.get(step);
|
|
40
|
+
if (!id) {
|
|
41
|
+
id = generateId();
|
|
42
|
+
map.set(step, id);
|
|
43
|
+
}
|
|
44
|
+
return id;
|
|
45
|
+
}
|
|
46
|
+
// Mirrors _serializeTest from Playwright's TeleReporterEmitter
|
|
47
|
+
// See: packages/playwright/src/reporters/teleEmitter.ts
|
|
48
|
+
function serializeTest(test, rootDir) {
|
|
49
|
+
return {
|
|
50
|
+
testId: test.id,
|
|
51
|
+
title: test.title,
|
|
52
|
+
location: {
|
|
53
|
+
file: path_1.default.relative(rootDir, test.location.file),
|
|
54
|
+
line: test.location.line,
|
|
55
|
+
column: test.location.column,
|
|
56
|
+
},
|
|
57
|
+
retries: test.retries,
|
|
58
|
+
tags: test.tags,
|
|
59
|
+
repeatEachIndex: test.repeatEachIndex,
|
|
60
|
+
annotations: test.annotations,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
// Mirrors _serializeSuite from Playwright's TeleReporterEmitter
|
|
64
|
+
// See: packages/playwright/src/reporters/teleEmitter.ts
|
|
65
|
+
function serializeSuite(suite, rootDir) {
|
|
66
|
+
const entries = [];
|
|
67
|
+
for (const test of suite.tests) {
|
|
68
|
+
entries.push(serializeTest(test, rootDir));
|
|
69
|
+
}
|
|
70
|
+
for (const childSuite of suite.suites) {
|
|
71
|
+
entries.push(serializeSuite(childSuite, rootDir));
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
title: suite.title,
|
|
75
|
+
location: suite.location
|
|
76
|
+
? {
|
|
77
|
+
file: path_1.default.relative(rootDir, suite.location.file),
|
|
78
|
+
line: suite.location.line,
|
|
79
|
+
column: suite.location.column,
|
|
80
|
+
}
|
|
81
|
+
: { file: "", line: 0, column: 0 },
|
|
82
|
+
entries,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function isTestEntry(entry) {
|
|
86
|
+
return "testId" in entry;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Recursively filters a serialized suite tree to only include tests
|
|
90
|
+
* whose testId is in the completedTestIds set. Removes empty parent suites.
|
|
91
|
+
* Returns null if the suite has no completed test descendants.
|
|
92
|
+
*/
|
|
93
|
+
function filterSuiteByCompletedTests(suite, completedTestIds) {
|
|
94
|
+
const filteredEntries = [];
|
|
95
|
+
for (const entry of suite.entries) {
|
|
96
|
+
if (isTestEntry(entry)) {
|
|
97
|
+
if (completedTestIds.has(entry.testId)) {
|
|
98
|
+
filteredEntries.push(entry);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const filtered = filterSuiteByCompletedTests(entry, completedTestIds);
|
|
103
|
+
if (filtered) {
|
|
104
|
+
filteredEntries.push(filtered);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (filteredEntries.length === 0)
|
|
109
|
+
return null;
|
|
110
|
+
return { ...suite, entries: filteredEntries };
|
|
111
|
+
}
|
|
112
|
+
function buildUrlsJson(attachmentUrlMap) {
|
|
113
|
+
const urlMap = {};
|
|
114
|
+
for (const [localPath, url] of attachmentUrlMap) {
|
|
115
|
+
urlMap[localPath] = url;
|
|
116
|
+
}
|
|
117
|
+
return urlMap;
|
|
118
|
+
}
|
|
119
|
+
function isLocalTesting() {
|
|
120
|
+
return process.env.LOCAL_TEST === "true";
|
|
121
|
+
}
|
|
122
|
+
function embedAttachment(attachmentPath, resultId, stagingDir) {
|
|
123
|
+
if (!attachmentPath || !fs_1.default.existsSync(attachmentPath))
|
|
124
|
+
return null;
|
|
125
|
+
if (!isLocalTesting()) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
const ext = path_1.default.extname(attachmentPath);
|
|
129
|
+
const uniqueName = `${resultId}-${generateId()}${ext}`;
|
|
130
|
+
const destPath = path_1.default.join(stagingDir, uniqueName);
|
|
131
|
+
try {
|
|
132
|
+
fs_1.default.copyFileSync(attachmentPath, destPath);
|
|
133
|
+
return uniqueName;
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
console.error(`[IncrementalBlobReporter] Failed to copy attachment ${attachmentPath}:`, err);
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -15,14 +15,15 @@ declare class IncrementalBlobReporter implements Reporter {
|
|
|
15
15
|
private _shardIndex;
|
|
16
16
|
private _totalShards;
|
|
17
17
|
private _reportLines;
|
|
18
|
-
private _config;
|
|
19
18
|
private _attachmentUrlMap;
|
|
20
19
|
private _resultIdMap;
|
|
21
20
|
private _stepIdMap;
|
|
22
21
|
private _uploader;
|
|
23
|
-
private _interrupted;
|
|
24
22
|
private _flushPromise;
|
|
25
|
-
private
|
|
23
|
+
private _completedTestIds;
|
|
24
|
+
private _completedResultIds;
|
|
25
|
+
private _testRetries;
|
|
26
|
+
private _testResults;
|
|
26
27
|
private _startTime;
|
|
27
28
|
constructor();
|
|
28
29
|
private _sigintHandler;
|
|
@@ -30,19 +31,15 @@ declare class IncrementalBlobReporter implements Reporter {
|
|
|
30
31
|
private _removeSignalHandler;
|
|
31
32
|
private _flushAndUpload;
|
|
32
33
|
private _finalizeReportForInterrupt;
|
|
34
|
+
private _computeCompletedSets;
|
|
35
|
+
private _removeIncompleteTestArtifacts;
|
|
36
|
+
private _patchProjectSuites;
|
|
33
37
|
private get _zipPath();
|
|
34
38
|
private get _jsonlPath();
|
|
35
39
|
private get _urlsJsonPath();
|
|
36
40
|
private _ensureDirs;
|
|
37
|
-
private _serializePatterns;
|
|
38
|
-
private _embedAttachment;
|
|
39
|
-
private _generateId;
|
|
40
|
-
private _getResultId;
|
|
41
|
-
private _getStepId;
|
|
42
41
|
private _appendEvent;
|
|
43
|
-
private
|
|
44
|
-
private _serializeSuite;
|
|
45
|
-
private _buildUrlsJson;
|
|
42
|
+
private _appendEvents;
|
|
46
43
|
private _writeZip;
|
|
47
44
|
onBegin(config: FullConfig, suite: Suite): void;
|
|
48
45
|
onTestBegin(test: TestCase, result: TestResult): void;
|
|
@@ -1 +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;
|
|
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;AAsBnC;;;;;;;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,WAAW,CAIjB;IACF,OAAO,CAAC,UAAU,CAGhB;IACF,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,mBAAmB,CAA0B;IACrD,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,YAAY,CAGN;IACd,OAAO,CAAC,UAAU,CAAsB;;IAexC,OAAO,CAAC,cAAc,CAA6B;IAEnD,OAAO,CAAC,mBAAmB;IA0B3B,OAAO,CAAC,oBAAoB;YAOd,eAAe;IAiC7B,OAAO,CAAC,2BAA2B;IAyBnC,OAAO,CAAC,qBAAqB;IAiB7B,OAAO,CAAC,8BAA8B;IAStC,OAAO,CAAC,mBAAmB;IAkB3B,OAAO,KAAK,QAAQ,GAKnB;IAED,OAAO,KAAK,UAAU,GAErB;IAED,OAAO,KAAK,aAAa,GAExB;IAED,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,YAAY;IAMpB,OAAO,CAAC,aAAa;YAUP,SAAS;IAwBvB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAgB/C,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAIrD,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAuBnD,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAYrE,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAY7D,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB9C;;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"}
|
|
@@ -6,17 +6,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.getIncrementalBlobReporter = getIncrementalBlobReporter;
|
|
7
7
|
exports.setIncrementalBlobReporterInstance = setIncrementalBlobReporterInstance;
|
|
8
8
|
const zip_1 = require("@empiricalrun/r2-uploader/zip");
|
|
9
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
10
9
|
const fs_1 = __importDefault(require("fs"));
|
|
11
|
-
const os_1 = __importDefault(require("os"));
|
|
12
10
|
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const ibr_utils_1 = require("./ibr-utils");
|
|
12
|
+
const lifecycle_events_1 = require("./lifecycle-events");
|
|
13
13
|
const local_test_1 = require("./local-test");
|
|
14
14
|
const reporter_state_1 = require("./reporter-state");
|
|
15
15
|
const uploader_1 = require("./uploader");
|
|
16
|
-
const BLOB_REPORT_VERSION = 2;
|
|
17
|
-
function isLocalTesting() {
|
|
18
|
-
return process.env.LOCAL_TEST === "true";
|
|
19
|
-
}
|
|
20
16
|
class IncrementalBlobReporter {
|
|
21
17
|
_currentWorkingDir = process.cwd();
|
|
22
18
|
_stagingDir = path_1.default.join(this._currentWorkingDir, "blob-report-incremental", "staging");
|
|
@@ -24,14 +20,15 @@ class IncrementalBlobReporter {
|
|
|
24
20
|
_shardIndex;
|
|
25
21
|
_totalShards;
|
|
26
22
|
_reportLines = [];
|
|
27
|
-
_config = null;
|
|
28
23
|
_attachmentUrlMap = new Map();
|
|
29
24
|
_resultIdMap = new Map(); // testId+retry -> resultId
|
|
30
25
|
_stepIdMap = new Map(); // step -> stepId
|
|
31
26
|
_uploader = null;
|
|
32
|
-
_interrupted = false;
|
|
33
27
|
_flushPromise = null;
|
|
34
|
-
|
|
28
|
+
_completedTestIds = new Set();
|
|
29
|
+
_completedResultIds = new Set();
|
|
30
|
+
_testRetries = new Map(); // testId → configured retries
|
|
31
|
+
_testResults = new Map(); // testId → retryIndex → {retryId, retryStatus}
|
|
35
32
|
_startTime = Date.now();
|
|
36
33
|
constructor() {
|
|
37
34
|
if (process.env.SHARD_INDEX && process.env.TOTAL_SHARDS) {
|
|
@@ -49,6 +46,8 @@ class IncrementalBlobReporter {
|
|
|
49
46
|
_sigintHandler = null;
|
|
50
47
|
_setupSignalHandler() {
|
|
51
48
|
this._sigintHandler = () => {
|
|
49
|
+
if ((0, reporter_state_1.isFinalized)())
|
|
50
|
+
return;
|
|
52
51
|
console.log("[IncrementalBlobReporter] SIGINT received, flushing and uploading...");
|
|
53
52
|
this._flushPromise = this._flushAndUpload()
|
|
54
53
|
.then(() => {
|
|
@@ -71,7 +70,6 @@ class IncrementalBlobReporter {
|
|
|
71
70
|
}
|
|
72
71
|
}
|
|
73
72
|
async _flushAndUpload() {
|
|
74
|
-
this._interrupted = true;
|
|
75
73
|
(0, reporter_state_1.setFinalized)();
|
|
76
74
|
// Wait for pending attachment uploads FIRST so URLs are available
|
|
77
75
|
if (this._uploader) {
|
|
@@ -86,17 +84,17 @@ class IncrementalBlobReporter {
|
|
|
86
84
|
await this._uploader.uploadFile(this._zipPath, `blobs/incremental-report-${this._shardIndex}.zip`);
|
|
87
85
|
await this._uploader.waitForUploads();
|
|
88
86
|
console.log("[IncrementalBlobReporter] Flush and upload complete on SIGINT");
|
|
89
|
-
if (isLocalTesting()) {
|
|
87
|
+
if ((0, ibr_utils_1.isLocalTesting)()) {
|
|
90
88
|
await (0, local_test_1.mergeForLocalTest)(this._currentWorkingDir, this._outputDir);
|
|
91
89
|
}
|
|
92
90
|
}
|
|
93
91
|
_finalizeReportForInterrupt() {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
//
|
|
99
|
-
this.
|
|
92
|
+
// Determine which tests are fully complete (passed or all retries finished)
|
|
93
|
+
this._computeCompletedSets();
|
|
94
|
+
// Remove unrun tests from onProject suites
|
|
95
|
+
this._patchProjectSuites();
|
|
96
|
+
// Remove events (onTestBegin, onStepBegin, etc.) for incomplete tests
|
|
97
|
+
this._removeIncompleteTestArtifacts();
|
|
100
98
|
// Push onEnd directly (bypass _appendEvent which checks isFinalized)
|
|
101
99
|
const now = Date.now();
|
|
102
100
|
const onEndEvent = {
|
|
@@ -110,7 +108,42 @@ class IncrementalBlobReporter {
|
|
|
110
108
|
},
|
|
111
109
|
};
|
|
112
110
|
this._reportLines.push(JSON.stringify(onEndEvent));
|
|
113
|
-
|
|
111
|
+
}
|
|
112
|
+
_computeCompletedSets() {
|
|
113
|
+
for (const [testId, results] of this._testResults) {
|
|
114
|
+
const configuredRetries = this._testRetries.get(testId);
|
|
115
|
+
const hasPassedRetry = [...results.values()].some((r) => r.status === "passed");
|
|
116
|
+
const allRetriesCompleted = results.size === configuredRetries + 1;
|
|
117
|
+
if (hasPassedRetry || allRetriesCompleted) {
|
|
118
|
+
this._completedTestIds.add(testId);
|
|
119
|
+
for (const { resultId } of results.values()) {
|
|
120
|
+
this._completedResultIds.add(resultId);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
_removeIncompleteTestArtifacts() {
|
|
126
|
+
this._reportLines = this._reportLines.filter((line) => {
|
|
127
|
+
const parsed = JSON.parse(line);
|
|
128
|
+
const resultId = parsed.params?.resultId ?? parsed.params?.result?.id;
|
|
129
|
+
if (!resultId)
|
|
130
|
+
return true;
|
|
131
|
+
return this._completedResultIds.has(resultId);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
_patchProjectSuites() {
|
|
135
|
+
for (let i = 0; i < this._reportLines.length; i++) {
|
|
136
|
+
const line = this._reportLines[i];
|
|
137
|
+
const parsed = JSON.parse(line);
|
|
138
|
+
if (parsed.method !== "onProject")
|
|
139
|
+
continue;
|
|
140
|
+
const project = parsed.params.project;
|
|
141
|
+
const filteredSuites = project.suites
|
|
142
|
+
.map((suite) => (0, ibr_utils_1.filterSuiteByCompletedTests)(suite, this._completedTestIds))
|
|
143
|
+
.filter(Boolean);
|
|
144
|
+
project.suites = filteredSuites;
|
|
145
|
+
this._reportLines[i] = JSON.stringify(parsed);
|
|
146
|
+
}
|
|
114
147
|
}
|
|
115
148
|
get _zipPath() {
|
|
116
149
|
return path_1.default.join(this._outputDir, `incremental-report-${this._shardIndex}.zip`);
|
|
@@ -125,99 +158,20 @@ class IncrementalBlobReporter {
|
|
|
125
158
|
fs_1.default.mkdirSync(this._stagingDir, { recursive: true });
|
|
126
159
|
fs_1.default.mkdirSync(this._outputDir, { recursive: true });
|
|
127
160
|
}
|
|
128
|
-
|
|
129
|
-
const arr = Array.isArray(patterns) ? patterns : [patterns];
|
|
130
|
-
return arr.map((p) => typeof p === "string"
|
|
131
|
-
? { s: p }
|
|
132
|
-
: { r: { source: p.source, flags: p.flags } });
|
|
133
|
-
}
|
|
134
|
-
_embedAttachment(attachmentPath, resultId) {
|
|
135
|
-
if (!attachmentPath || !fs_1.default.existsSync(attachmentPath))
|
|
136
|
-
return null;
|
|
137
|
-
if (!isLocalTesting()) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
const ext = path_1.default.extname(attachmentPath);
|
|
141
|
-
const uniqueName = `${resultId}-${this._generateId()}${ext}`;
|
|
142
|
-
const destPath = path_1.default.join(this._stagingDir, uniqueName);
|
|
143
|
-
try {
|
|
144
|
-
fs_1.default.copyFileSync(attachmentPath, destPath);
|
|
145
|
-
return uniqueName;
|
|
146
|
-
}
|
|
147
|
-
catch (err) {
|
|
148
|
-
console.error(`[IncrementalBlobReporter] Failed to copy attachment ${attachmentPath}:`, err);
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
_generateId() {
|
|
153
|
-
return crypto_1.default.randomBytes(16).toString("hex");
|
|
154
|
-
}
|
|
155
|
-
_getResultId(testId, retry) {
|
|
156
|
-
const key = `${testId}-${retry}`;
|
|
157
|
-
let resultId = this._resultIdMap.get(key);
|
|
158
|
-
if (!resultId) {
|
|
159
|
-
resultId = this._generateId();
|
|
160
|
-
this._resultIdMap.set(key, resultId);
|
|
161
|
-
}
|
|
162
|
-
return resultId;
|
|
163
|
-
}
|
|
164
|
-
_getStepId(step) {
|
|
165
|
-
let stepId = this._stepIdMap.get(step);
|
|
166
|
-
if (!stepId) {
|
|
167
|
-
stepId = this._generateId();
|
|
168
|
-
this._stepIdMap.set(step, stepId);
|
|
169
|
-
}
|
|
170
|
-
return stepId;
|
|
171
|
-
}
|
|
172
|
-
_appendEvent(method, params) {
|
|
161
|
+
_appendEvent(event) {
|
|
173
162
|
if ((0, reporter_state_1.isFinalized)())
|
|
174
|
-
return;
|
|
175
|
-
if (this._interrupted && method !== "onEnd")
|
|
176
|
-
return;
|
|
177
|
-
const event = params !== undefined ? { method, params } : { method };
|
|
163
|
+
return false;
|
|
178
164
|
this._reportLines.push(JSON.stringify(event));
|
|
165
|
+
return true;
|
|
179
166
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
line: test.location.line,
|
|
187
|
-
column: test.location.column,
|
|
188
|
-
},
|
|
189
|
-
retries: test.retries,
|
|
190
|
-
tags: test.tags,
|
|
191
|
-
repeatEachIndex: test.repeatEachIndex,
|
|
192
|
-
annotations: test.annotations,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
_serializeSuite(suite) {
|
|
196
|
-
const entries = [];
|
|
197
|
-
for (const test of suite.tests) {
|
|
198
|
-
entries.push(this._serializeSuiteEntry(test));
|
|
199
|
-
}
|
|
200
|
-
for (const childSuite of suite.suites) {
|
|
201
|
-
entries.push(this._serializeSuite(childSuite));
|
|
202
|
-
}
|
|
203
|
-
return {
|
|
204
|
-
title: suite.title,
|
|
205
|
-
location: suite.location
|
|
206
|
-
? {
|
|
207
|
-
file: path_1.default.relative(this._config?.rootDir || "", suite.location.file),
|
|
208
|
-
line: suite.location.line,
|
|
209
|
-
column: suite.location.column,
|
|
210
|
-
}
|
|
211
|
-
: { file: "", line: 0, column: 0 },
|
|
212
|
-
entries,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
_buildUrlsJson() {
|
|
216
|
-
const urlMap = {};
|
|
217
|
-
for (const [localPath, url] of this._attachmentUrlMap) {
|
|
218
|
-
urlMap[localPath] = url;
|
|
167
|
+
_appendEvents(events) {
|
|
168
|
+
let allAppended = true;
|
|
169
|
+
for (const event of events) {
|
|
170
|
+
if (!this._appendEvent(event)) {
|
|
171
|
+
allAppended = false;
|
|
172
|
+
}
|
|
219
173
|
}
|
|
220
|
-
return
|
|
174
|
+
return allAppended;
|
|
221
175
|
}
|
|
222
176
|
async _writeZip() {
|
|
223
177
|
this._ensureDirs();
|
|
@@ -225,7 +179,7 @@ class IncrementalBlobReporter {
|
|
|
225
179
|
const jsonlContent = this._reportLines.join("\n") + "\n";
|
|
226
180
|
fs_1.default.writeFileSync(this._jsonlPath, jsonlContent, "utf8");
|
|
227
181
|
// Write _empirical_urls.json with attachment URL mappings
|
|
228
|
-
const urlsJson = this.
|
|
182
|
+
const urlsJson = (0, ibr_utils_1.buildUrlsJson)(this._attachmentUrlMap);
|
|
229
183
|
fs_1.default.writeFileSync(this._urlsJsonPath, JSON.stringify(urlsJson, null, 2), "utf8");
|
|
230
184
|
// Create zip from the staging directory
|
|
231
185
|
const zipBuffer = await (0, zip_1.createZipFromDirectory)(this._stagingDir);
|
|
@@ -233,170 +187,39 @@ class IncrementalBlobReporter {
|
|
|
233
187
|
console.log(`[IncrementalBlobReporter] Zip updated: ${this._zipPath} (${this._reportLines.length} events, ${this._attachmentUrlMap.size} attachments)`);
|
|
234
188
|
}
|
|
235
189
|
onBegin(config, suite) {
|
|
236
|
-
this._config = config;
|
|
237
190
|
this._ensureDirs();
|
|
238
|
-
|
|
239
|
-
const osName = process.platform === "darwin" ? "macOS" : process.platform;
|
|
240
|
-
const osVersion = os_1.default.release();
|
|
241
|
-
this._appendEvent("onBlobReportMetadata", {
|
|
242
|
-
version: BLOB_REPORT_VERSION,
|
|
243
|
-
userAgent: `Playwright/${config.version} (${process.arch}; ${osName} ${osVersion}) node/${process.versions.node}`,
|
|
244
|
-
shard: {
|
|
245
|
-
current: this._shardIndex,
|
|
246
|
-
total: this._totalShards,
|
|
247
|
-
},
|
|
248
|
-
pathSeparator: path_1.default.sep,
|
|
249
|
-
});
|
|
250
|
-
// 2. Emit onConfigure
|
|
251
|
-
this._appendEvent("onConfigure", {
|
|
252
|
-
config: {
|
|
253
|
-
configFile: config.configFile
|
|
254
|
-
? path_1.default.relative(this._currentWorkingDir, config.configFile)
|
|
255
|
-
: null,
|
|
256
|
-
globalTimeout: config.globalTimeout,
|
|
257
|
-
maxFailures: config.maxFailures,
|
|
258
|
-
metadata: config.metadata,
|
|
259
|
-
rootDir: config.rootDir,
|
|
260
|
-
version: config.version,
|
|
261
|
-
workers: config.workers,
|
|
262
|
-
globalSetup: config.globalSetup,
|
|
263
|
-
globalTeardown: config.globalTeardown,
|
|
264
|
-
tags: [],
|
|
265
|
-
webServer: config.webServer,
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
// 3. Emit onProject for each project
|
|
269
|
-
for (const projectSuite of suite.suites) {
|
|
270
|
-
const project = config.projects.find((p) => p.name === projectSuite.title);
|
|
271
|
-
if (!project)
|
|
272
|
-
continue;
|
|
273
|
-
const suites = [];
|
|
274
|
-
for (const fileSuite of projectSuite.suites) {
|
|
275
|
-
suites.push(this._serializeSuite(fileSuite));
|
|
276
|
-
}
|
|
277
|
-
this._appendEvent("onProject", {
|
|
278
|
-
project: {
|
|
279
|
-
metadata: project.metadata,
|
|
280
|
-
name: project.name,
|
|
281
|
-
outputDir: path_1.default.relative(this._currentWorkingDir, project.outputDir),
|
|
282
|
-
repeatEach: project.repeatEach,
|
|
283
|
-
retries: project.retries,
|
|
284
|
-
testDir: project.testDir,
|
|
285
|
-
testIgnore: this._serializePatterns(project.testIgnore),
|
|
286
|
-
testMatch: this._serializePatterns(project.testMatch),
|
|
287
|
-
timeout: project.timeout,
|
|
288
|
-
suites,
|
|
289
|
-
grep: this._serializePatterns(project.grep),
|
|
290
|
-
grepInvert: project.grepInvert
|
|
291
|
-
? this._serializePatterns(project.grepInvert)
|
|
292
|
-
: [],
|
|
293
|
-
dependencies: project.dependencies,
|
|
294
|
-
snapshotDir: project.snapshotDir,
|
|
295
|
-
use: project.use,
|
|
296
|
-
},
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
// 4. Emit onBegin (empty params per Playwright format)
|
|
300
|
-
this._appendEvent("onBegin");
|
|
191
|
+
this._appendEvents((0, lifecycle_events_1.buildOnBeginEvents)(config, suite, this._currentWorkingDir, this._shardIndex, this._totalShards));
|
|
301
192
|
console.log(`[IncrementalBlobReporter] Started with ${suite.allTests().length} tests`);
|
|
302
193
|
}
|
|
303
194
|
onTestBegin(test, result) {
|
|
304
|
-
|
|
305
|
-
this._appendEvent("onTestBegin", {
|
|
306
|
-
testId: test.id,
|
|
307
|
-
result: {
|
|
308
|
-
id: resultId,
|
|
309
|
-
retry: result.retry,
|
|
310
|
-
workerIndex: result.workerIndex,
|
|
311
|
-
parallelIndex: result.parallelIndex,
|
|
312
|
-
startTime: result.startTime.getTime(),
|
|
313
|
-
},
|
|
314
|
-
});
|
|
195
|
+
this._appendEvent((0, lifecycle_events_1.buildOnTestBeginEvent)(test, result, this._resultIdMap));
|
|
315
196
|
}
|
|
316
197
|
onTestEnd(test, result) {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
});
|
|
198
|
+
const events = (0, lifecycle_events_1.buildOnTestEndEvents)(test, result, this._resultIdMap, this._stagingDir);
|
|
199
|
+
if (this._appendEvents(events)) {
|
|
200
|
+
if (!this._testRetries.has(test.id)) {
|
|
201
|
+
this._testRetries.set(test.id, test.retries);
|
|
202
|
+
}
|
|
203
|
+
if (!this._testResults.has(test.id)) {
|
|
204
|
+
this._testResults.set(test.id, new Map());
|
|
205
|
+
}
|
|
206
|
+
const resultId = this._resultIdMap.get(`${test.id}-${result.retry}`);
|
|
207
|
+
if (resultId) {
|
|
208
|
+
this._testResults
|
|
209
|
+
.get(test.id)
|
|
210
|
+
.set(result.retry, { resultId, status: result.status });
|
|
211
|
+
}
|
|
332
212
|
}
|
|
333
|
-
this._lastTestEndIndex = this._reportLines.length;
|
|
334
|
-
this._appendEvent("onTestEnd", {
|
|
335
|
-
test: {
|
|
336
|
-
testId: test.id,
|
|
337
|
-
expectedStatus: test.expectedStatus,
|
|
338
|
-
timeout: test.timeout,
|
|
339
|
-
annotations: test.annotations,
|
|
340
|
-
},
|
|
341
|
-
result: {
|
|
342
|
-
id: resultId,
|
|
343
|
-
duration: result.duration,
|
|
344
|
-
status: result.status,
|
|
345
|
-
errors: result.errors.map((e) => ({
|
|
346
|
-
message: e.message,
|
|
347
|
-
stack: e.stack,
|
|
348
|
-
location: e.location,
|
|
349
|
-
snippet: e.snippet,
|
|
350
|
-
})),
|
|
351
|
-
},
|
|
352
|
-
});
|
|
353
213
|
}
|
|
354
214
|
onStepBegin(test, result, step) {
|
|
355
|
-
|
|
356
|
-
const stepId = this._getStepId(step);
|
|
357
|
-
const parentStepId = step.parent ? this._getStepId(step.parent) : undefined;
|
|
358
|
-
this._appendEvent("onStepBegin", {
|
|
359
|
-
testId: test.id,
|
|
360
|
-
resultId,
|
|
361
|
-
step: {
|
|
362
|
-
id: stepId,
|
|
363
|
-
parentStepId,
|
|
364
|
-
title: step.title,
|
|
365
|
-
category: step.category,
|
|
366
|
-
startTime: step.startTime.getTime(),
|
|
367
|
-
location: step.location,
|
|
368
|
-
},
|
|
369
|
-
});
|
|
215
|
+
this._appendEvent((0, lifecycle_events_1.buildOnStepBeginEvent)(test, result, step, this._resultIdMap, this._stepIdMap));
|
|
370
216
|
}
|
|
371
217
|
onStepEnd(test, result, step) {
|
|
372
|
-
|
|
373
|
-
const stepId = this._getStepId(step);
|
|
374
|
-
this._appendEvent("onStepEnd", {
|
|
375
|
-
testId: test.id,
|
|
376
|
-
resultId,
|
|
377
|
-
step: {
|
|
378
|
-
id: stepId,
|
|
379
|
-
duration: step.duration,
|
|
380
|
-
error: step.error
|
|
381
|
-
? {
|
|
382
|
-
message: step.error.message,
|
|
383
|
-
stack: step.error.stack,
|
|
384
|
-
location: step.error.location,
|
|
385
|
-
snippet: step.error.snippet,
|
|
386
|
-
}
|
|
387
|
-
: undefined,
|
|
388
|
-
},
|
|
389
|
-
});
|
|
218
|
+
this._appendEvent((0, lifecycle_events_1.buildOnStepEndEvent)(test, result, step, this._resultIdMap, this._stepIdMap));
|
|
390
219
|
}
|
|
391
220
|
async onEnd(result) {
|
|
392
221
|
this._removeSignalHandler();
|
|
393
|
-
this._appendEvent(
|
|
394
|
-
result: {
|
|
395
|
-
status: result.status,
|
|
396
|
-
startTime: result.startTime.getTime(),
|
|
397
|
-
duration: result.duration,
|
|
398
|
-
},
|
|
399
|
-
});
|
|
222
|
+
this._appendEvent((0, lifecycle_events_1.buildOnEndEvent)(result));
|
|
400
223
|
// Wait for SIGINT flush to complete if it was started
|
|
401
224
|
if (this._flushPromise) {
|
|
402
225
|
await this._flushPromise;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FullConfig, FullProject, FullResult, Suite, TestCase, TestResult, TestStep } from "@playwright/test/reporter";
|
|
2
|
+
export type JsonEvent = {
|
|
3
|
+
method: string;
|
|
4
|
+
params?: object;
|
|
5
|
+
};
|
|
6
|
+
export declare function buildOnBeginEvents(config: FullConfig, suite: Suite, currentWorkingDir: string, shardIndex: number, totalShards: number): JsonEvent[];
|
|
7
|
+
export declare function buildOnProjectEvent(config: FullConfig, project: FullProject, projectSuite: Suite, currentWorkingDir: string): JsonEvent;
|
|
8
|
+
export declare function buildOnTestBeginEvent(test: TestCase, result: TestResult, resultIdMap: Map<string, string>): JsonEvent;
|
|
9
|
+
export declare function buildOnTestEndEvents(test: TestCase, result: TestResult, resultIdMap: Map<string, string>, stagingDir: string): JsonEvent[];
|
|
10
|
+
export declare function buildOnStepBeginEvent(test: TestCase, result: TestResult, step: TestStep, resultIdMap: Map<string, string>, stepIdMap: Map<TestStep, string>): JsonEvent;
|
|
11
|
+
export declare function buildOnStepEndEvent(test: TestCase, result: TestResult, step: TestStep, resultIdMap: Map<string, string>, stepIdMap: Map<TestStep, string>): JsonEvent;
|
|
12
|
+
export declare function buildOnEndEvent(result: FullResult): JsonEvent;
|
|
13
|
+
//# sourceMappingURL=lifecycle-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle-events.d.ts","sourceRoot":"","sources":["../../src/reporter/lifecycle-events.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,UAAU,EACV,KAAK,EACL,QAAQ,EACR,UAAU,EACV,QAAQ,EACT,MAAM,2BAA2B,CAAC;AAenC,MAAM,MAAM,SAAS,GAAG;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,KAAK,EACZ,iBAAiB,EAAE,MAAM,EACzB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,GAClB,SAAS,EAAE,CAmDb;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,EAClB,OAAO,EAAE,WAAW,EACpB,YAAY,EAAE,KAAK,EACnB,iBAAiB,EAAE,MAAM,GACxB,SAAS,CA+BX;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,SAAS,CAeX;AAED,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,UAAU,EAClB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,UAAU,EAAE,MAAM,GACjB,SAAS,EAAE,CA+Cb;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,GAC/B,SAAS,CAsBX;AAED,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,EACd,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,QAAQ,EACd,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,SAAS,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,GAC/B,SAAS,CAuBX;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,GAAG,SAAS,CAW7D"}
|
|
@@ -0,0 +1,211 @@
|
|
|
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.buildOnBeginEvents = buildOnBeginEvents;
|
|
7
|
+
exports.buildOnProjectEvent = buildOnProjectEvent;
|
|
8
|
+
exports.buildOnTestBeginEvent = buildOnTestBeginEvent;
|
|
9
|
+
exports.buildOnTestEndEvents = buildOnTestEndEvents;
|
|
10
|
+
exports.buildOnStepBeginEvent = buildOnStepBeginEvent;
|
|
11
|
+
exports.buildOnStepEndEvent = buildOnStepEndEvent;
|
|
12
|
+
exports.buildOnEndEvent = buildOnEndEvent;
|
|
13
|
+
const os_1 = __importDefault(require("os"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
const ibr_utils_1 = require("./ibr-utils");
|
|
16
|
+
const BLOB_REPORT_VERSION = 2;
|
|
17
|
+
function buildOnBeginEvents(config, suite, currentWorkingDir, shardIndex, totalShards) {
|
|
18
|
+
const events = [];
|
|
19
|
+
// 1. onBlobReportMetadata
|
|
20
|
+
const osName = process.platform === "darwin" ? "macOS" : process.platform;
|
|
21
|
+
const osVersion = os_1.default.release();
|
|
22
|
+
events.push({
|
|
23
|
+
method: "onBlobReportMetadata",
|
|
24
|
+
params: {
|
|
25
|
+
version: BLOB_REPORT_VERSION,
|
|
26
|
+
userAgent: `Playwright/${config.version} (${process.arch}; ${osName} ${osVersion}) node/${process.versions.node}`,
|
|
27
|
+
shard: { current: shardIndex, total: totalShards },
|
|
28
|
+
pathSeparator: path_1.default.sep,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
// 2. onConfigure
|
|
32
|
+
events.push({
|
|
33
|
+
method: "onConfigure",
|
|
34
|
+
params: {
|
|
35
|
+
config: {
|
|
36
|
+
configFile: config.configFile
|
|
37
|
+
? path_1.default.relative(currentWorkingDir, config.configFile)
|
|
38
|
+
: null,
|
|
39
|
+
globalTimeout: config.globalTimeout,
|
|
40
|
+
maxFailures: config.maxFailures,
|
|
41
|
+
metadata: config.metadata,
|
|
42
|
+
rootDir: config.rootDir,
|
|
43
|
+
version: config.version,
|
|
44
|
+
workers: config.workers,
|
|
45
|
+
globalSetup: config.globalSetup,
|
|
46
|
+
globalTeardown: config.globalTeardown,
|
|
47
|
+
tags: [],
|
|
48
|
+
webServer: config.webServer,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
// 3. onProject for each project
|
|
53
|
+
for (const projectSuite of suite.suites) {
|
|
54
|
+
const project = config.projects.find((p) => p.name === projectSuite.title);
|
|
55
|
+
if (!project)
|
|
56
|
+
continue;
|
|
57
|
+
events.push(buildOnProjectEvent(config, project, projectSuite, currentWorkingDir));
|
|
58
|
+
}
|
|
59
|
+
// 4. onBegin (empty params per Playwright format)
|
|
60
|
+
events.push({ method: "onBegin" });
|
|
61
|
+
return events;
|
|
62
|
+
}
|
|
63
|
+
function buildOnProjectEvent(config, project, projectSuite, currentWorkingDir) {
|
|
64
|
+
const rootDir = config.rootDir || "";
|
|
65
|
+
const suites = [];
|
|
66
|
+
for (const fileSuite of projectSuite.suites) {
|
|
67
|
+
suites.push((0, ibr_utils_1.serializeSuite)(fileSuite, rootDir));
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
method: "onProject",
|
|
71
|
+
params: {
|
|
72
|
+
project: {
|
|
73
|
+
metadata: project.metadata,
|
|
74
|
+
name: project.name,
|
|
75
|
+
outputDir: path_1.default.relative(currentWorkingDir, project.outputDir),
|
|
76
|
+
repeatEach: project.repeatEach,
|
|
77
|
+
retries: project.retries,
|
|
78
|
+
testDir: project.testDir,
|
|
79
|
+
testIgnore: (0, ibr_utils_1.serializeRegexPatterns)(project.testIgnore),
|
|
80
|
+
testMatch: (0, ibr_utils_1.serializeRegexPatterns)(project.testMatch),
|
|
81
|
+
timeout: project.timeout,
|
|
82
|
+
suites,
|
|
83
|
+
grep: (0, ibr_utils_1.serializeRegexPatterns)(project.grep),
|
|
84
|
+
grepInvert: project.grepInvert
|
|
85
|
+
? (0, ibr_utils_1.serializeRegexPatterns)(project.grepInvert)
|
|
86
|
+
: [],
|
|
87
|
+
dependencies: project.dependencies,
|
|
88
|
+
snapshotDir: project.snapshotDir,
|
|
89
|
+
use: project.use,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function buildOnTestBeginEvent(test, result, resultIdMap) {
|
|
95
|
+
const resultId = (0, ibr_utils_1.getOrCreateId)(resultIdMap, `${test.id}-${result.retry}`);
|
|
96
|
+
return {
|
|
97
|
+
method: "onTestBegin",
|
|
98
|
+
params: {
|
|
99
|
+
testId: test.id,
|
|
100
|
+
result: {
|
|
101
|
+
id: resultId,
|
|
102
|
+
retry: result.retry,
|
|
103
|
+
workerIndex: result.workerIndex,
|
|
104
|
+
parallelIndex: result.parallelIndex,
|
|
105
|
+
startTime: result.startTime.getTime(),
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function buildOnTestEndEvents(test, result, resultIdMap, stagingDir) {
|
|
111
|
+
const resultId = (0, ibr_utils_1.getOrCreateId)(resultIdMap, `${test.id}-${result.retry}`);
|
|
112
|
+
const events = [];
|
|
113
|
+
// onAttach for each attachment (matching Playwright's blob format)
|
|
114
|
+
for (const attachment of result.attachments) {
|
|
115
|
+
const embeddedPath = (0, ibr_utils_1.embedAttachment)(attachment.path, resultId, stagingDir);
|
|
116
|
+
events.push({
|
|
117
|
+
method: "onAttach",
|
|
118
|
+
params: {
|
|
119
|
+
testId: test.id,
|
|
120
|
+
resultId,
|
|
121
|
+
attachments: [
|
|
122
|
+
{
|
|
123
|
+
name: attachment.name,
|
|
124
|
+
contentType: attachment.contentType,
|
|
125
|
+
path: embeddedPath ?? attachment.path,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
events.push({
|
|
132
|
+
method: "onTestEnd",
|
|
133
|
+
params: {
|
|
134
|
+
test: {
|
|
135
|
+
testId: test.id,
|
|
136
|
+
expectedStatus: test.expectedStatus,
|
|
137
|
+
timeout: test.timeout,
|
|
138
|
+
annotations: test.annotations,
|
|
139
|
+
},
|
|
140
|
+
result: {
|
|
141
|
+
id: resultId,
|
|
142
|
+
duration: result.duration,
|
|
143
|
+
status: result.status,
|
|
144
|
+
errors: result.errors.map((e) => ({
|
|
145
|
+
message: e.message,
|
|
146
|
+
stack: e.stack,
|
|
147
|
+
location: e.location,
|
|
148
|
+
snippet: e.snippet,
|
|
149
|
+
})),
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
return events;
|
|
154
|
+
}
|
|
155
|
+
function buildOnStepBeginEvent(test, result, step, resultIdMap, stepIdMap) {
|
|
156
|
+
const resultId = (0, ibr_utils_1.getOrCreateId)(resultIdMap, `${test.id}-${result.retry}`);
|
|
157
|
+
const stepId = (0, ibr_utils_1.getOrCreateStepId)(stepIdMap, step);
|
|
158
|
+
const parentStepId = step.parent
|
|
159
|
+
? (0, ibr_utils_1.getOrCreateStepId)(stepIdMap, step.parent)
|
|
160
|
+
: undefined;
|
|
161
|
+
return {
|
|
162
|
+
method: "onStepBegin",
|
|
163
|
+
params: {
|
|
164
|
+
testId: test.id,
|
|
165
|
+
resultId,
|
|
166
|
+
step: {
|
|
167
|
+
id: stepId,
|
|
168
|
+
parentStepId,
|
|
169
|
+
title: step.title,
|
|
170
|
+
category: step.category,
|
|
171
|
+
startTime: step.startTime.getTime(),
|
|
172
|
+
location: step.location,
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function buildOnStepEndEvent(test, result, step, resultIdMap, stepIdMap) {
|
|
178
|
+
const resultId = (0, ibr_utils_1.getOrCreateId)(resultIdMap, `${test.id}-${result.retry}`);
|
|
179
|
+
const stepId = (0, ibr_utils_1.getOrCreateStepId)(stepIdMap, step);
|
|
180
|
+
return {
|
|
181
|
+
method: "onStepEnd",
|
|
182
|
+
params: {
|
|
183
|
+
testId: test.id,
|
|
184
|
+
resultId,
|
|
185
|
+
step: {
|
|
186
|
+
id: stepId,
|
|
187
|
+
duration: step.duration,
|
|
188
|
+
error: step.error
|
|
189
|
+
? {
|
|
190
|
+
message: step.error.message,
|
|
191
|
+
stack: step.error.stack,
|
|
192
|
+
location: step.error.location,
|
|
193
|
+
snippet: step.error.snippet,
|
|
194
|
+
}
|
|
195
|
+
: undefined,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function buildOnEndEvent(result) {
|
|
201
|
+
return {
|
|
202
|
+
method: "onEnd",
|
|
203
|
+
params: {
|
|
204
|
+
result: {
|
|
205
|
+
status: result.status,
|
|
206
|
+
startTime: result.startTime.getTime(),
|
|
207
|
+
duration: result.duration,
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empiricalrun/playwright-utils",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -43,11 +43,11 @@
|
|
|
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.3.0",
|
|
47
46
|
"@empiricalrun/dashboard-client": "^0.2.0",
|
|
48
47
|
"@empiricalrun/llm": "^0.26.0",
|
|
49
48
|
"@empiricalrun/r2-uploader": "^0.9.1",
|
|
50
|
-
"@empiricalrun/reporter": "^0.28.0"
|
|
49
|
+
"@empiricalrun/reporter": "^0.28.0",
|
|
50
|
+
"@empiricalrun/cua": "^0.3.0"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"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/attachment-cleanup.ts","./src/reporter/blob-utils.ts","./src/reporter/empirical-reporter.ts","./src/reporter/failing-line.ts","./src/reporter/harness.ts","./src/reporter/incremental-blob-reporter.ts","./src/reporter/local-test.ts","./src/reporter/reporter-state.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/coverage.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/attachment-cleanup.ts","./src/reporter/blob-utils.ts","./src/reporter/empirical-reporter.ts","./src/reporter/failing-line.ts","./src/reporter/harness.ts","./src/reporter/ibr-utils.ts","./src/reporter/incremental-blob-reporter.ts","./src/reporter/lifecycle-events.ts","./src/reporter/local-test.ts","./src/reporter/reporter-state.ts","./src/reporter/uploader.ts","./src/reporter/util.ts","./src/test/constants.ts","./src/test/coverage.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"}
|