@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 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 _lastTestEndIndex;
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 _serializeSuiteEntry;
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;AAenC;;;;;;;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,OAAO,CAA2B;IAC1C,OAAO,CAAC,iBAAiB,CAA+B;IACxD,OAAO,CAAC,YAAY,CAAkC;IACtD,OAAO,CAAC,UAAU,CAAoC;IACtD,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,iBAAiB,CAAc;IACvC,OAAO,CAAC,UAAU,CAAsB;;IAexC,OAAO,CAAC,cAAc,CAA6B;IAEnD,OAAO,CAAC,mBAAmB;IAyB3B,OAAO,CAAC,oBAAoB;YAOd,eAAe;IAkC7B,OAAO,CAAC,2BAA2B;IA8BnC,OAAO,KAAK,QAAQ,GAKnB;IAED,OAAO,KAAK,UAAU,GAErB;IAED,OAAO,KAAK,aAAa,GAExB;IAED,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,gBAAgB;IAuBxB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,oBAAoB;IAgB5B,OAAO,CAAC,eAAe;IA2BvB,OAAO,CAAC,cAAc;YAQR,SAAS;IAwBvB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IA+E/C,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAerD,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAyCnD,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAmBrE,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;IAsB7D,KAAK,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAwB9C;;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"}
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
- _lastTestEndIndex = -1;
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
- if (this._lastTestEndIndex === -1) {
95
- console.log("[IncrementalBlobReporter] No onTestEnd found, cannot finalize report. Merge may fail");
96
- return;
97
- }
98
- // Truncate to last onTestEnd
99
- this._reportLines = this._reportLines.slice(0, this._lastTestEndIndex + 1);
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
- console.log(`[IncrementalBlobReporter] Finalized report at line ${this._lastTestEndIndex + 1}, status: interrupted`);
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
- _serializePatterns(patterns) {
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
- _serializeSuiteEntry(test) {
181
- return {
182
- testId: test.id,
183
- title: test.title,
184
- location: {
185
- file: path_1.default.relative(this._config?.rootDir || "", test.location.file),
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 urlMap;
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._buildUrlsJson();
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
- // 1. Emit onBlobReportMetadata (first event)
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
- const resultId = this._getResultId(test.id, result.retry);
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 resultId = this._getResultId(test.id, result.retry);
318
- // Emit separate onAttach for each attachment (matching Playwright's blob format)
319
- for (const attachment of result.attachments) {
320
- const embeddedPath = this._embedAttachment(attachment.path, resultId);
321
- this._appendEvent("onAttach", {
322
- testId: test.id,
323
- resultId,
324
- attachments: [
325
- {
326
- name: attachment.name,
327
- contentType: attachment.contentType,
328
- path: embeddedPath ?? attachment.path,
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
- const resultId = this._getResultId(test.id, result.retry);
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
- const resultId = this._getResultId(test.id, result.retry);
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("onEnd", {
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.45.3",
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",
@@ -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"}