@empiricalrun/playwright-utils 0.45.3 → 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 CHANGED
@@ -1,5 +1,11 @@
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
+
3
9
  ## 0.45.3
4
10
 
5
11
  ### 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,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 _serializeSuiteEntry;
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;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,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
- _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) {
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
- _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;
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 urlMap;
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._buildUrlsJson();
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
- // 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");
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
- 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
- });
186
+ this._appendEvent((0, lifecycle_events_1.buildOnTestBeginEvent)(test, result, this._resultIdMap));
315
187
  }
316
188
  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
- });
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
- 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
- });
199
+ this._appendEvent((0, lifecycle_events_1.buildOnStepBeginEvent)(test, result, step, this._resultIdMap, this._stepIdMap));
370
200
  }
371
201
  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
- });
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("onEnd", {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/playwright-utils",
3
- "version": "0.45.3",
3
+ "version": "0.46.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -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"}