@empiricalrun/playwright-utils 0.45.2 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/reporter/attachment-cleanup.d.ts +31 -0
- package/dist/reporter/attachment-cleanup.d.ts.map +1 -0
- package/dist/reporter/attachment-cleanup.js +65 -0
- package/dist/reporter/empirical-reporter.d.ts +1 -0
- package/dist/reporter/empirical-reporter.d.ts.map +1 -1
- package/dist/reporter/empirical-reporter.js +3 -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 +5 -10
- package/dist/reporter/incremental-blob-reporter.d.ts.map +1 -1
- package/dist/reporter/incremental-blob-reporter.js +57 -250
- 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 +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @empiricalrun/playwright-utils
|
|
2
2
|
|
|
3
|
+
## 0.46.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 491e5eb: feat: filter unrun tests from suite so blob wont show unrun tests as skipped
|
|
8
|
+
|
|
9
|
+
## 0.45.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 7078a69: fix: attachment clean up from disk using lru
|
|
14
|
+
|
|
3
15
|
## 0.45.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks uploaded attachments and evicts the oldest ones from disk
|
|
3
|
+
* when total size exceeds the configured limit.
|
|
4
|
+
*
|
|
5
|
+
* Why this exists:
|
|
6
|
+
* Test attachments (videos, traces, screenshots) accumulate on disk during a
|
|
7
|
+
* test run. On Fargate workers with 50 GB ephemeral storage, large test suites
|
|
8
|
+
* can exhaust disk space. Since attachments are already uploaded to R2, we can
|
|
9
|
+
* safely delete them from disk — but we can't delete them immediately because
|
|
10
|
+
* Playwright's blob reporter reads them during onEnd to embed as resources/.
|
|
11
|
+
*
|
|
12
|
+
* Instead, we use an LRU approach: only evict the oldest files when total size
|
|
13
|
+
* exceeds the limit. The blob reporter's statSync guard (Playwright 1.57+)
|
|
14
|
+
* gracefully skips missing files, and patchBlobZip replaces resources/ with
|
|
15
|
+
* _empirical_urls.json pointing to R2 URLs anyway.
|
|
16
|
+
*
|
|
17
|
+
* Future: when the incremental blob reporter is mature enough to replace the
|
|
18
|
+
* standard blob reporter for all runs (not just spot instances), this class
|
|
19
|
+
* can be removed — the incremental reporter never embeds attachments as resources.
|
|
20
|
+
*/
|
|
21
|
+
export declare class AttachmentCleanup {
|
|
22
|
+
private _entries;
|
|
23
|
+
private _totalSize;
|
|
24
|
+
private _limitBytes;
|
|
25
|
+
constructor(limitBytes?: number);
|
|
26
|
+
track(attachment: {
|
|
27
|
+
path?: string | undefined;
|
|
28
|
+
}): void;
|
|
29
|
+
private _evict;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=attachment-cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-cleanup.d.ts","sourceRoot":"","sources":["../../src/reporter/attachment-cleanup.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAwC;IACxD,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,WAAW,CAAS;gBAEhB,UAAU,GAAE,MAAiC;IAIzD,KAAK,CAAC,UAAU,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE;IAc/C,OAAO,CAAC,MAAM;CAcf"}
|
|
@@ -0,0 +1,65 @@
|
|
|
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.AttachmentCleanup = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const logger_1 = require("../logger");
|
|
9
|
+
const DEFAULT_DISK_LIMIT_BYTES = 10 * 1024 * 1024 * 1024; // 10 GB
|
|
10
|
+
/**
|
|
11
|
+
* Tracks uploaded attachments and evicts the oldest ones from disk
|
|
12
|
+
* when total size exceeds the configured limit.
|
|
13
|
+
*
|
|
14
|
+
* Why this exists:
|
|
15
|
+
* Test attachments (videos, traces, screenshots) accumulate on disk during a
|
|
16
|
+
* test run. On Fargate workers with 50 GB ephemeral storage, large test suites
|
|
17
|
+
* can exhaust disk space. Since attachments are already uploaded to R2, we can
|
|
18
|
+
* safely delete them from disk — but we can't delete them immediately because
|
|
19
|
+
* Playwright's blob reporter reads them during onEnd to embed as resources/.
|
|
20
|
+
*
|
|
21
|
+
* Instead, we use an LRU approach: only evict the oldest files when total size
|
|
22
|
+
* exceeds the limit. The blob reporter's statSync guard (Playwright 1.57+)
|
|
23
|
+
* gracefully skips missing files, and patchBlobZip replaces resources/ with
|
|
24
|
+
* _empirical_urls.json pointing to R2 URLs anyway.
|
|
25
|
+
*
|
|
26
|
+
* Future: when the incremental blob reporter is mature enough to replace the
|
|
27
|
+
* standard blob reporter for all runs (not just spot instances), this class
|
|
28
|
+
* can be removed — the incremental reporter never embeds attachments as resources.
|
|
29
|
+
*/
|
|
30
|
+
class AttachmentCleanup {
|
|
31
|
+
_entries = [];
|
|
32
|
+
_totalSize = 0;
|
|
33
|
+
_limitBytes;
|
|
34
|
+
constructor(limitBytes = DEFAULT_DISK_LIMIT_BYTES) {
|
|
35
|
+
this._limitBytes = limitBytes;
|
|
36
|
+
}
|
|
37
|
+
track(attachment) {
|
|
38
|
+
try {
|
|
39
|
+
if (attachment.path) {
|
|
40
|
+
const stat = fs_1.default.statSync(attachment.path);
|
|
41
|
+
const sizeBytes = stat.size;
|
|
42
|
+
this._entries.push({ path: attachment.path, size: sizeBytes });
|
|
43
|
+
this._totalSize += sizeBytes;
|
|
44
|
+
this._evict();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Ignore errors
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
_evict() {
|
|
52
|
+
while (this._totalSize > this._limitBytes && this._entries.length > 0) {
|
|
53
|
+
const oldest = this._entries.shift();
|
|
54
|
+
try {
|
|
55
|
+
fs_1.default.unlinkSync(oldest.path);
|
|
56
|
+
logger_1.logger.debug(`[AttachmentCleanup] Evicted ${oldest.path} (${oldest.size} bytes)`);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// File may already be gone
|
|
60
|
+
}
|
|
61
|
+
this._totalSize -= oldest.size;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
exports.AttachmentCleanup = AttachmentCleanup;
|
|
@@ -7,6 +7,7 @@ declare class EmpiricalReporter implements Reporter {
|
|
|
7
7
|
private _attachmentUrlMap;
|
|
8
8
|
private _testRetryCounts;
|
|
9
9
|
private _uploader;
|
|
10
|
+
private _attachmentCleanup;
|
|
10
11
|
constructor();
|
|
11
12
|
private enqueTestAttachmentUploadTask;
|
|
12
13
|
onTestEnd(test: TestCase, result: TestResult): void;
|
|
@@ -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;AAuBnC,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;IAC1C,OAAO,CAAC,kBAAkB,CAA2B;;IAMrD,OAAO,CAAC,6BAA6B,CAmCnC;IAEF,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU;IAwGtC,KAAK,CAAC,MAAM,EAAE,UAAU;IAkG9B,OAAO,CAAC,qBAAqB;YAkBf,gBAAgB;YAOhB,iBAAiB;CAmChC;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -7,6 +7,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const logger_1 = require("../logger");
|
|
9
9
|
const telemetry_1 = require("../telemetry");
|
|
10
|
+
const attachment_cleanup_1 = require("./attachment-cleanup");
|
|
10
11
|
const blob_utils_1 = require("./blob-utils");
|
|
11
12
|
const failing_line_1 = require("./failing-line");
|
|
12
13
|
const incremental_blob_reporter_1 = require("./incremental-blob-reporter");
|
|
@@ -21,6 +22,7 @@ class EmpiricalReporter {
|
|
|
21
22
|
_attachmentUrlMap = new Map();
|
|
22
23
|
_testRetryCounts = new Map();
|
|
23
24
|
_uploader = null;
|
|
25
|
+
_attachmentCleanup = new attachment_cleanup_1.AttachmentCleanup();
|
|
24
26
|
constructor() {
|
|
25
27
|
this._uploader = (0, uploader_1.getUploader)();
|
|
26
28
|
}
|
|
@@ -40,6 +42,7 @@ class EmpiricalReporter {
|
|
|
40
42
|
const destinationPath = `data/${relativePath}`;
|
|
41
43
|
const publicUrl = await this._uploader.uploadFile(attachment.path, destinationPath);
|
|
42
44
|
if (publicUrl) {
|
|
45
|
+
this._attachmentCleanup.track(attachment);
|
|
43
46
|
return { [attachment.path]: publicUrl };
|
|
44
47
|
}
|
|
45
48
|
};
|
|
@@ -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,14 @@ 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
23
|
private _lastTestEndIndex;
|
|
24
|
+
private _completedTestIds;
|
|
25
|
+
private _completedResultIds;
|
|
26
26
|
private _startTime;
|
|
27
27
|
constructor();
|
|
28
28
|
private _sigintHandler;
|
|
@@ -30,19 +30,14 @@ declare class IncrementalBlobReporter implements Reporter {
|
|
|
30
30
|
private _removeSignalHandler;
|
|
31
31
|
private _flushAndUpload;
|
|
32
32
|
private _finalizeReportForInterrupt;
|
|
33
|
+
private _removeIncompleteTestArtifacts;
|
|
34
|
+
private _patchProjectSuites;
|
|
33
35
|
private get _zipPath();
|
|
34
36
|
private get _jsonlPath();
|
|
35
37
|
private get _urlsJsonPath();
|
|
36
38
|
private _ensureDirs;
|
|
37
|
-
private _serializePatterns;
|
|
38
|
-
private _embedAttachment;
|
|
39
|
-
private _generateId;
|
|
40
|
-
private _getResultId;
|
|
41
|
-
private _getStepId;
|
|
42
39
|
private _appendEvent;
|
|
43
|
-
private
|
|
44
|
-
private _serializeSuite;
|
|
45
|
-
private _buildUrlsJson;
|
|
40
|
+
private _appendEvents;
|
|
46
41
|
private _writeZip;
|
|
47
42
|
onBegin(config: FullConfig, suite: Suite): void;
|
|
48
43
|
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,CAAc;IACvC,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,mBAAmB,CAA0B;IACrD,OAAO,CAAC,UAAU,CAAsB;;IAexC,OAAO,CAAC,cAAc,CAA6B;IAEnD,OAAO,CAAC,mBAAmB;IA0B3B,OAAO,CAAC,oBAAoB;YAOd,eAAe;IAiC7B,OAAO,CAAC,2BAA2B;IAoCnC,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;IAenD,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,14 @@ 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
|
_lastTestEndIndex = -1;
|
|
29
|
+
_completedTestIds = new Set();
|
|
30
|
+
_completedResultIds = new Set();
|
|
35
31
|
_startTime = Date.now();
|
|
36
32
|
constructor() {
|
|
37
33
|
if (process.env.SHARD_INDEX && process.env.TOTAL_SHARDS) {
|
|
@@ -49,6 +45,8 @@ class IncrementalBlobReporter {
|
|
|
49
45
|
_sigintHandler = null;
|
|
50
46
|
_setupSignalHandler() {
|
|
51
47
|
this._sigintHandler = () => {
|
|
48
|
+
if ((0, reporter_state_1.isFinalized)())
|
|
49
|
+
return;
|
|
52
50
|
console.log("[IncrementalBlobReporter] SIGINT received, flushing and uploading...");
|
|
53
51
|
this._flushPromise = this._flushAndUpload()
|
|
54
52
|
.then(() => {
|
|
@@ -71,7 +69,6 @@ class IncrementalBlobReporter {
|
|
|
71
69
|
}
|
|
72
70
|
}
|
|
73
71
|
async _flushAndUpload() {
|
|
74
|
-
this._interrupted = true;
|
|
75
72
|
(0, reporter_state_1.setFinalized)();
|
|
76
73
|
// Wait for pending attachment uploads FIRST so URLs are available
|
|
77
74
|
if (this._uploader) {
|
|
@@ -86,7 +83,7 @@ class IncrementalBlobReporter {
|
|
|
86
83
|
await this._uploader.uploadFile(this._zipPath, `blobs/incremental-report-${this._shardIndex}.zip`);
|
|
87
84
|
await this._uploader.waitForUploads();
|
|
88
85
|
console.log("[IncrementalBlobReporter] Flush and upload complete on SIGINT");
|
|
89
|
-
if (isLocalTesting()) {
|
|
86
|
+
if ((0, ibr_utils_1.isLocalTesting)()) {
|
|
90
87
|
await (0, local_test_1.mergeForLocalTest)(this._currentWorkingDir, this._outputDir);
|
|
91
88
|
}
|
|
92
89
|
}
|
|
@@ -97,6 +94,10 @@ class IncrementalBlobReporter {
|
|
|
97
94
|
}
|
|
98
95
|
// Truncate to last onTestEnd
|
|
99
96
|
this._reportLines = this._reportLines.slice(0, this._lastTestEndIndex + 1);
|
|
97
|
+
// Remove unrun tests from onProject suites
|
|
98
|
+
this._patchProjectSuites();
|
|
99
|
+
// Remove events (onTestBegin, onStepBegin, etc.) for incomplete tests
|
|
100
|
+
this._removeIncompleteTestArtifacts();
|
|
100
101
|
// Push onEnd directly (bypass _appendEvent which checks isFinalized)
|
|
101
102
|
const now = Date.now();
|
|
102
103
|
const onEndEvent = {
|
|
@@ -112,6 +113,29 @@ class IncrementalBlobReporter {
|
|
|
112
113
|
this._reportLines.push(JSON.stringify(onEndEvent));
|
|
113
114
|
console.log(`[IncrementalBlobReporter] Finalized report at line ${this._lastTestEndIndex + 1}, status: interrupted`);
|
|
114
115
|
}
|
|
116
|
+
_removeIncompleteTestArtifacts() {
|
|
117
|
+
this._reportLines = this._reportLines.filter((line) => {
|
|
118
|
+
const parsed = JSON.parse(line);
|
|
119
|
+
const resultId = parsed.params?.resultId ?? parsed.params?.result?.id;
|
|
120
|
+
if (!resultId)
|
|
121
|
+
return true;
|
|
122
|
+
return this._completedResultIds.has(resultId);
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
_patchProjectSuites() {
|
|
126
|
+
for (let i = 0; i < this._reportLines.length; i++) {
|
|
127
|
+
const line = this._reportLines[i];
|
|
128
|
+
const parsed = JSON.parse(line);
|
|
129
|
+
if (parsed.method !== "onProject")
|
|
130
|
+
continue;
|
|
131
|
+
const project = parsed.params.project;
|
|
132
|
+
const filteredSuites = project.suites
|
|
133
|
+
.map((suite) => (0, ibr_utils_1.filterSuiteByCompletedTests)(suite, this._completedTestIds))
|
|
134
|
+
.filter(Boolean);
|
|
135
|
+
project.suites = filteredSuites;
|
|
136
|
+
this._reportLines[i] = JSON.stringify(parsed);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
115
139
|
get _zipPath() {
|
|
116
140
|
return path_1.default.join(this._outputDir, `incremental-report-${this._shardIndex}.zip`);
|
|
117
141
|
}
|
|
@@ -125,99 +149,20 @@ class IncrementalBlobReporter {
|
|
|
125
149
|
fs_1.default.mkdirSync(this._stagingDir, { recursive: true });
|
|
126
150
|
fs_1.default.mkdirSync(this._outputDir, { recursive: true });
|
|
127
151
|
}
|
|
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) {
|
|
152
|
+
_appendEvent(event) {
|
|
173
153
|
if ((0, reporter_state_1.isFinalized)())
|
|
174
|
-
return;
|
|
175
|
-
if (this._interrupted && method !== "onEnd")
|
|
176
|
-
return;
|
|
177
|
-
const event = params !== undefined ? { method, params } : { method };
|
|
154
|
+
return false;
|
|
178
155
|
this._reportLines.push(JSON.stringify(event));
|
|
156
|
+
return true;
|
|
179
157
|
}
|
|
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;
|
|
158
|
+
_appendEvents(events) {
|
|
159
|
+
let allAppended = true;
|
|
160
|
+
for (const event of events) {
|
|
161
|
+
if (!this._appendEvent(event)) {
|
|
162
|
+
allAppended = false;
|
|
163
|
+
}
|
|
219
164
|
}
|
|
220
|
-
return
|
|
165
|
+
return allAppended;
|
|
221
166
|
}
|
|
222
167
|
async _writeZip() {
|
|
223
168
|
this._ensureDirs();
|
|
@@ -225,7 +170,7 @@ class IncrementalBlobReporter {
|
|
|
225
170
|
const jsonlContent = this._reportLines.join("\n") + "\n";
|
|
226
171
|
fs_1.default.writeFileSync(this._jsonlPath, jsonlContent, "utf8");
|
|
227
172
|
// Write _empirical_urls.json with attachment URL mappings
|
|
228
|
-
const urlsJson = this.
|
|
173
|
+
const urlsJson = (0, ibr_utils_1.buildUrlsJson)(this._attachmentUrlMap);
|
|
229
174
|
fs_1.default.writeFileSync(this._urlsJsonPath, JSON.stringify(urlsJson, null, 2), "utf8");
|
|
230
175
|
// Create zip from the staging directory
|
|
231
176
|
const zipBuffer = await (0, zip_1.createZipFromDirectory)(this._stagingDir);
|
|
@@ -233,170 +178,32 @@ class IncrementalBlobReporter {
|
|
|
233
178
|
console.log(`[IncrementalBlobReporter] Zip updated: ${this._zipPath} (${this._reportLines.length} events, ${this._attachmentUrlMap.size} attachments)`);
|
|
234
179
|
}
|
|
235
180
|
onBegin(config, suite) {
|
|
236
|
-
this._config = config;
|
|
237
181
|
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");
|
|
182
|
+
this._appendEvents((0, lifecycle_events_1.buildOnBeginEvents)(config, suite, this._currentWorkingDir, this._shardIndex, this._totalShards));
|
|
301
183
|
console.log(`[IncrementalBlobReporter] Started with ${suite.allTests().length} tests`);
|
|
302
184
|
}
|
|
303
185
|
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
|
-
});
|
|
186
|
+
this._appendEvent((0, lifecycle_events_1.buildOnTestBeginEvent)(test, result, this._resultIdMap));
|
|
315
187
|
}
|
|
316
188
|
onTestEnd(test, result) {
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
attachments: [
|
|
325
|
-
{
|
|
326
|
-
name: attachment.name,
|
|
327
|
-
contentType: attachment.contentType,
|
|
328
|
-
path: embeddedPath ?? attachment.path,
|
|
329
|
-
},
|
|
330
|
-
],
|
|
331
|
-
});
|
|
189
|
+
const events = (0, lifecycle_events_1.buildOnTestEndEvents)(test, result, this._resultIdMap, this._stagingDir);
|
|
190
|
+
if (this._appendEvents(events)) {
|
|
191
|
+
this._completedTestIds.add(test.id);
|
|
192
|
+
const resultId = this._resultIdMap.get(`${test.id}-${result.retry}`);
|
|
193
|
+
if (resultId)
|
|
194
|
+
this._completedResultIds.add(resultId);
|
|
195
|
+
this._lastTestEndIndex = this._reportLines.length - 1;
|
|
332
196
|
}
|
|
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
197
|
}
|
|
354
198
|
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
|
-
});
|
|
199
|
+
this._appendEvent((0, lifecycle_events_1.buildOnStepBeginEvent)(test, result, step, this._resultIdMap, this._stepIdMap));
|
|
370
200
|
}
|
|
371
201
|
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
|
-
});
|
|
202
|
+
this._appendEvent((0, lifecycle_events_1.buildOnStepEndEvent)(test, result, step, this._resultIdMap, this._stepIdMap));
|
|
390
203
|
}
|
|
391
204
|
async onEnd(result) {
|
|
392
205
|
this._removeSignalHandler();
|
|
393
|
-
this._appendEvent(
|
|
394
|
-
result: {
|
|
395
|
-
status: result.status,
|
|
396
|
-
startTime: result.startTime.getTime(),
|
|
397
|
-
duration: result.duration,
|
|
398
|
-
},
|
|
399
|
-
});
|
|
206
|
+
this._appendEvent((0, lifecycle_events_1.buildOnEndEvent)(result));
|
|
400
207
|
// Wait for SIGINT flush to complete if it was started
|
|
401
208
|
if (this._flushPromise) {
|
|
402
209
|
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
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/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"}
|