@empiricalrun/playwright-utils 0.27.19 → 0.28.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,40 @@
1
1
  # @empiricalrun/playwright-utils
2
2
 
3
+ ## 0.28.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f349076: feat: update summary.json attachment paths to actual urls
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [6b1d98c]
12
+ - Updated dependencies [b6a04f5]
13
+ - Updated dependencies [4cbc287]
14
+ - Updated dependencies [a96a03c]
15
+ - Updated dependencies [df226a5]
16
+ - Updated dependencies [32905df]
17
+ - Updated dependencies [8e3c7a4]
18
+ - Updated dependencies [5b0d43a]
19
+ - Updated dependencies [6ac65ed]
20
+ - Updated dependencies [fc6f97c]
21
+ - Updated dependencies [440e851]
22
+ - Updated dependencies [a23b38f]
23
+ - Updated dependencies [12c69cc]
24
+ - Updated dependencies [5ed01c4]
25
+ - Updated dependencies [e1d01c8]
26
+ - Updated dependencies [450b79a]
27
+ - Updated dependencies [7009d67]
28
+ - Updated dependencies [d570c55]
29
+ - Updated dependencies [622aa35]
30
+ - Updated dependencies [23708d1]
31
+ - Updated dependencies [162e461]
32
+ - Updated dependencies [349003e]
33
+ - Updated dependencies [d5c7696]
34
+ - Updated dependencies [7afa5c1]
35
+ - @empiricalrun/test-gen@0.72.0
36
+ - @empiricalrun/llm@0.20.0
37
+
3
38
  ## 0.27.19
4
39
 
5
40
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAczD,wBAAgB,gBAAgB,IAAI,MAAM,CAazC;AA0CD,eAAO,MAAM,UAAU,EAAE,oBAyBxB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,iBAgBrB,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAczD,wBAAgB,gBAAgB,IAAI,MAAM,CAazC;AA2CD,eAAO,MAAM,UAAU,EAAE,oBAyBxB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,iBAgBrB,CAAC"}
package/dist/config.js CHANGED
@@ -53,6 +53,7 @@ function isPlaywrightV147() {
53
53
  return version && version.includes("1.47") ? true : false;
54
54
  }
55
55
  function getReporters() {
56
+ // TODO: Remove this once we have migrated to 1.53.X for all repos
56
57
  if (isPlaywrightV147()) {
57
58
  return [
58
59
  ["list"],
@@ -4,6 +4,9 @@ declare class EmpiricalReporter implements Reporter {
4
4
  private _uploadBucket;
5
5
  private _currentWorkingDir;
6
6
  private _willUploadArtifacts;
7
+ private _testResultSourceDir;
8
+ private _baseUrl;
9
+ private _urlPrefix;
7
10
  private checkR2Creds;
8
11
  constructor();
9
12
  private processTestAttachment;
@@ -1 +1 @@
1
- {"version":3,"file":"empirical-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/empirical-reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;AAgBnC,cAAM,iBAAkB,YAAW,QAAQ;IACzC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,oBAAoB,CAAkB;IAE9C,OAAO,CAAC,YAAY;;IAyBpB,OAAO,CAAC,qBAAqB,CA0B3B;IAEF,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU;IAyEtC,KAAK,CAAC,MAAM,EAAE,UAAU;CA+F/B;AAED,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"empirical-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/empirical-reporter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;AAiBnC,cAAM,iBAAkB,YAAW,QAAQ;IACzC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,oBAAoB,CAAkB;IAC9C,OAAO,CAAC,oBAAoB,CAG1B;IACF,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,UAAU,CAA2F;IAE7G,OAAO,CAAC,YAAY;;IAyBpB,OAAO,CAAC,qBAAqB,CAuB3B;IAEF,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU;IAyEtC,KAAK,CAAC,MAAM,EAAE,UAAU;CA+G/B;AAED,eAAe,iBAAiB,CAAC"}
@@ -15,6 +15,9 @@ class EmpiricalReporter {
15
15
  _uploadBucket = "test-report";
16
16
  _currentWorkingDir = process.cwd();
17
17
  _willUploadArtifacts = false;
18
+ _testResultSourceDir = path_1.default.join(this._currentWorkingDir, "test-results");
19
+ _baseUrl = `https://reports.empirical.run`;
20
+ _urlPrefix = `${this._baseUrl}/${process.env.PROJECT_NAME}/${process.env.TEST_RUN_GITHUB_ACTION_ID}`;
18
21
  checkR2Creds() {
19
22
  if (!process.env.R2_ACCOUNT_ID ||
20
23
  !process.env.R2_ACCESS_KEY_ID ||
@@ -36,7 +39,6 @@ class EmpiricalReporter {
36
39
  processTestAttachment = async (attachment) => {
37
40
  if (!attachment.path)
38
41
  return;
39
- const testResultSourceDir = path_1.default.join(this._currentWorkingDir, "test-results");
40
42
  const exists = await (0, util_1.checkFileExistsAsync)(attachment.path);
41
43
  if (exists) {
42
44
  logger_1.logger.debug(`[Empirical Reporter] Processing attachment: File exists`);
@@ -46,10 +48,11 @@ class EmpiricalReporter {
46
48
  return;
47
49
  }
48
50
  const uploadTask = (0, uploader_1.createUploadTaskV2)({
49
- sourceDir: testResultSourceDir,
51
+ sourceDir: this._testResultSourceDir,
50
52
  fileList: [attachment.path],
51
53
  destinationDir: path_1.default.join(this._destinationDir, "data"),
52
54
  uploadBucket: this._uploadBucket,
55
+ baseUrl: this._baseUrl,
53
56
  });
54
57
  return (0, queue_1.sendTaskToQueue)(uploadTask);
55
58
  };
@@ -126,36 +129,40 @@ class EmpiricalReporter {
126
129
  }).format(startTime));
127
130
  const jsonFilePath = path_1.default.join(this._currentWorkingDir, "summary.json");
128
131
  const jsonExists = fs_1.default.existsSync(jsonFilePath);
129
- if (jsonExists) {
130
- logger_1.logger.debug("[Empirical Reporter] Uploading JSON file");
131
- const uploadJsonTask = (0, uploader_1.createUploadTaskV2)({
132
- sourceDir: this._currentWorkingDir,
133
- fileList: [jsonFilePath],
134
- destinationDir: this._destinationDir,
135
- uploadBucket: this._uploadBucket,
136
- });
137
- void (0, queue_1.sendTaskToQueue)(uploadJsonTask);
138
- }
139
- else {
140
- logger_1.logger.error(`[Empirical Reporter] summary.json does not exist at: ${jsonFilePath}`);
141
- }
142
132
  const htmlFilePath = path_1.default.join(this._currentWorkingDir, "playwright-report/index.html");
143
133
  const htmlExists = await (0, util_1.checkFileExistsAsync)(htmlFilePath);
144
- if (htmlExists) {
134
+ if (htmlExists && jsonExists) {
145
135
  logger_1.logger.debug("[Empirical Reporter] Updating the HTML Zip");
146
- await (0, util_1.updateHtmlZipFileAttachmentPaths)(jsonFilePath, htmlFilePath);
136
+ await (0, util_1.updateHtmlZipFileAttachmentPaths)(jsonFilePath, htmlFilePath, this._testResultSourceDir);
147
137
  logger_1.logger.debug("[Empirical Reporter] Uploading HTML file");
148
138
  const uploadHtmlTask = (0, uploader_1.createUploadTaskV2)({
149
139
  sourceDir: path_1.default.join(this._currentWorkingDir, "playwright-report"),
150
140
  fileList: [htmlFilePath],
151
141
  destinationDir: this._destinationDir,
152
142
  uploadBucket: this._uploadBucket,
143
+ baseUrl: this._baseUrl,
153
144
  });
154
145
  void (0, queue_1.sendTaskToQueue)(uploadHtmlTask);
155
146
  }
156
147
  else {
157
148
  logger_1.logger.error(`[Empirical Reporter] playwright-report/index.html does not exist at: ${htmlFilePath}`);
158
149
  }
150
+ if (jsonExists) {
151
+ logger_1.logger.debug("[Empirical Reporter] Updating the JSON file");
152
+ await (0, util_1.updateSummaryJsonAttachmentPaths)(jsonFilePath, this._testResultSourceDir, `${this._urlPrefix}/data`);
153
+ logger_1.logger.debug("[Empirical Reporter] Uploading JSON file");
154
+ const uploadJsonTask = (0, uploader_1.createUploadTaskV2)({
155
+ sourceDir: this._currentWorkingDir,
156
+ fileList: [jsonFilePath],
157
+ destinationDir: this._destinationDir,
158
+ uploadBucket: this._uploadBucket,
159
+ baseUrl: this._baseUrl,
160
+ });
161
+ void (0, queue_1.sendTaskToQueue)(uploadJsonTask);
162
+ }
163
+ else {
164
+ logger_1.logger.error(`[Empirical Reporter] summary.json does not exist at: ${jsonFilePath}`);
165
+ }
159
166
  const traceFilePath = path_1.default.join(this._currentWorkingDir, "playwright-report/trace");
160
167
  const traceExists = await (0, util_1.checkFileExistsAsync)(traceFilePath);
161
168
  if (traceExists) {
@@ -164,6 +171,7 @@ class EmpiricalReporter {
164
171
  sourceDir: traceFilePath,
165
172
  destinationDir: path_1.default.join(this._destinationDir, "trace"),
166
173
  uploadBucket: this._uploadBucket,
174
+ baseUrl: this._baseUrl,
167
175
  });
168
176
  void (0, queue_1.sendTaskToQueue)(uploadTraceTask);
169
177
  }
@@ -24,10 +24,11 @@ export declare function createUploadTask({ fileList, outputFolder, relativePath,
24
24
  * @param {string} params.uploadBucket - The R2 bucket to upload to
25
25
  * @returns {AsyncTask} An async task that when executed will upload the files and return a map of file paths to URLs
26
26
  */
27
- export declare function createUploadTaskV2({ sourceDir, fileList, destinationDir, uploadBucket, }: {
27
+ export declare function createUploadTaskV2({ sourceDir, fileList, destinationDir, uploadBucket, baseUrl, }: {
28
28
  sourceDir: string;
29
29
  fileList?: string[];
30
30
  destinationDir: string;
31
31
  uploadBucket: string;
32
+ baseUrl: string;
32
33
  }): AsyncTask;
33
34
  //# sourceMappingURL=uploader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../src/reporter/uploader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAmB,MAAM,2BAA2B,CAAC;AAIrE,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAKpC,wBAAsB,WAAW,CAAC,EAChC,QAAQ,EACR,YAAY,EACZ,YAAY,GACb,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAwB1B;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,YAAY,EACZ,YAAiB,GAClB,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,SAAS,CAaZ;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,QAAQ,EACR,cAAc,EACd,YAAY,GACb,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,SAAS,CAiCZ"}
1
+ {"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../src/reporter/uploader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAmB,MAAM,2BAA2B,CAAC;AAIrE,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAOpC,wBAAsB,WAAW,CAAC,EAChC,QAAQ,EACR,YAAY,EACZ,YAAY,GACb,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAwB1B;AAGD,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,YAAY,EACZ,YAAiB,GAClB,EAAE;IACD,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,SAAS,CAaZ;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,OAAO,GACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,SAAS,CA8BZ"}
@@ -9,8 +9,10 @@ exports.createUploadTaskV2 = createUploadTaskV2;
9
9
  const r2_uploader_1 = require("@empiricalrun/r2-uploader");
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const logger_1 = require("../logger");
12
+ // TODO: Remove this once we have migrated to 1.53.X for all repos
12
13
  const R2_BUCKET_NAME = "test-report";
13
14
  const R2_BASE_URL = "https://reports.empirical.run";
15
+ // TODO: Remove this once we have migrated to 1.53.X for all repos
14
16
  async function uploadFiles({ fileList, outputFolder, relativePath, }) {
15
17
  if (!process.env.PROJECT_NAME || !process.env.TEST_RUN_GITHUB_ACTION_ID) {
16
18
  return;
@@ -32,6 +34,7 @@ async function uploadFiles({ fileList, outputFolder, relativePath, }) {
32
34
  logger_1.logger.debug("Finished uploading files:", urls);
33
35
  return urls;
34
36
  }
37
+ // TODO: Remove this once we have migrated to 1.53.X for all repos
35
38
  function createUploadTask({ fileList, outputFolder, relativePath = "", }) {
36
39
  return async () => {
37
40
  return uploadFiles({
@@ -60,7 +63,7 @@ function createUploadTask({ fileList, outputFolder, relativePath = "", }) {
60
63
  * @param {string} params.uploadBucket - The R2 bucket to upload to
61
64
  * @returns {AsyncTask} An async task that when executed will upload the files and return a map of file paths to URLs
62
65
  */
63
- function createUploadTaskV2({ sourceDir, fileList, destinationDir, uploadBucket, }) {
66
+ function createUploadTaskV2({ sourceDir, fileList, destinationDir, uploadBucket, baseUrl, }) {
64
67
  return async () => {
65
68
  if (!process.env.R2_ACCOUNT_ID ||
66
69
  !process.env.R2_ACCESS_KEY_ID ||
@@ -78,7 +81,7 @@ function createUploadTaskV2({ sourceDir, fileList, destinationDir, uploadBucket,
78
81
  Object.entries(uploadedFiles).forEach(([filePath]) => {
79
82
  // Example: If R2_BASE_URL is "https://r2.example.com" and destinationDir is "test-results/123" and filePath is "screenshot.png"
80
83
  // The resulting URL will be: "https://r2.example.com/test-results/123/screenshot.png"
81
- const fileUrl = new URL(path_1.default.join(destinationDir, filePath), R2_BASE_URL);
84
+ const fileUrl = new URL(path_1.default.join(destinationDir, filePath), baseUrl);
82
85
  urls[filePath] = fileUrl.toString();
83
86
  });
84
87
  logger_1.logger.debug("Finished Upload Files");
@@ -16,7 +16,7 @@
16
16
  * limitations under the License.
17
17
  */
18
18
  import { GenericPlaywrightAttachment } from "@empiricalrun/shared-types";
19
- import { TestCase } from "@playwright/test/reporter";
19
+ import { JSONReport as PlaywrightJSONReport, JSONReportSpec, TestCase } from "@playwright/test/reporter";
20
20
  import { TestCaseRunEndEvent } from "./types";
21
21
  export declare function getPackageJsonPath(folderPath: string): string;
22
22
  export declare function resolveReporterOutputPath(defaultValue: string, configDir: string, configValue: string | undefined): string;
@@ -36,7 +36,7 @@ export declare function suitesAndProjectForTest(test: TestCase): {
36
36
  };
37
37
  export declare function sendTestCaseUpdateToDashboard(params: TestCaseRunEndEvent): Promise<void>;
38
38
  export declare function safelySerialiseJSON(obj: any, keyFilter?: (key: string) => boolean): any;
39
- export declare function updateHtmlZipFileAttachmentPaths(jsonFilePath: string, htmlFilePath: string): Promise<void>;
39
+ export declare function updateHtmlZipFileAttachmentPaths(jsonFilePath: string, htmlFilePath: string, repoPath: string): Promise<void>;
40
40
  /**
41
41
  * Replaces media paths in test results with normalized paths from the test attachment map.
42
42
  * This function updates the attachments array in each test result with the corresponding
@@ -51,13 +51,14 @@ export declare function updateHtmlZipFileAttachmentPaths(jsonFilePath: string, h
51
51
  export declare function replaceMediaPaths(jsonData: any, testAttachmentMap: Map<string, {
52
52
  attachments: GenericPlaywrightAttachment[];
53
53
  }[]>): void;
54
+ export declare function traverseJsonReportSuites(summaryJson: PlaywrightJSONReport, specFn: (spec: JSONReportSpec) => void): void;
54
55
  /**
55
56
  * Builds a map from test ID to an array of objects containing attachments for each result (retry).
56
57
  * The attachments are normalized to use relative paths starting with 'data/'.
57
58
  * @param summaryJsonFilePath Path to the summary JSON file
58
59
  * @returns Map<string, { attachments: GenericPlaywrightAttachment[] }[]>
59
60
  */
60
- export declare function buildTestAttachmentMapFromSummary(summaryJsonFilePath: string): Map<string, {
61
+ export declare function buildTestAttachmentMapFromSummary(summaryJsonFilePath: string, repoPath: string, doNotNormalize?: boolean): Map<string, {
61
62
  attachments: GenericPlaywrightAttachment[];
62
63
  }[]>;
63
64
  /**
@@ -66,4 +67,16 @@ export declare function buildTestAttachmentMapFromSummary(summaryJsonFilePath: s
66
67
  * @returns Promise<boolean> - true if the file exists, false otherwise
67
68
  */
68
69
  export declare function checkFileExistsAsync(filePath: string): Promise<boolean>;
70
+ /**
71
+ * Updates the attachment paths in the summary JSON file to use remote URLs.
72
+ *
73
+ * This function rewrites all attachment `path` fields in the given Playwright summary JSON file
74
+ * so that they point to remote URLs based on the current `PROJECT_NAME` and `TEST_RUN_GITHUB_ACTION_ID`
75
+ * environment variables.
76
+ *
77
+ * @param filePath - The path to the summary JSON file
78
+ * @returns The updated summary JSON
79
+ *
80
+ */
81
+ export declare function updateSummaryJsonAttachmentPaths(filePath: string, repoPath: string, urlPrefix: string): Promise<PlaywrightJSONReport | undefined>;
69
82
  //# sourceMappingURL=util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/reporter/util.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAKL,QAAQ,EACT,MAAM,2BAA2B,CAAC;AAiBnC,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAI9C,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAmB7D;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,UAMhC;AAED,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5E,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAyCD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ;;;EAwBrD;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAyDf;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GACnC,GAAG,CAIL;AAED,wBAAsB,gCAAgC,CACpD,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,iBAoFrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,GAAG,EACb,iBAAiB,EAAE,GAAG,CACpB,MAAM,EACN;IAAE,WAAW,EAAE,2BAA2B,EAAE,CAAA;CAAE,EAAE,CACjD,QA+BF;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,mBAAmB,EAAE,MAAM,GAC1B,GAAG,CAAC,MAAM,EAAE;IAAE,WAAW,EAAE,2BAA2B,EAAE,CAAA;CAAE,EAAE,CAAC,CAqD/D;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/reporter/util.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EACL,UAAU,IAAI,oBAAoB,EAClC,cAAc,EAId,QAAQ,EACT,MAAM,2BAA2B,CAAC;AAiBnC,OAAO,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAI9C,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAmB7D;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,UAMhC;AAED,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5E,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,CAyCD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ;;;EAwBrD;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAyDf;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GACnC,GAAG,CAIL;AAED,wBAAsB,gCAAgC,CACpD,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,iBAuFjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,GAAG,EACb,iBAAiB,EAAE,GAAG,CACpB,MAAM,EACN;IAAE,WAAW,EAAE,2BAA2B,EAAE,CAAA;CAAE,EAAE,CACjD,QA+BF;AAGD,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,oBAAoB,EACjC,MAAM,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,QAavC;AAED;;;;;GAKG;AACH,wBAAgB,iCAAiC,CAC/C,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,OAAe,GAC9B,GAAG,CAAC,MAAM,EAAE;IAAE,WAAW,EAAE,2BAA2B,EAAE,CAAA;CAAE,EAAE,CAAC,CA4C/D;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,gCAAgC,CACpD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,6CAoClB"}
@@ -28,8 +28,10 @@ exports.sendTestCaseUpdateToDashboard = sendTestCaseUpdateToDashboard;
28
28
  exports.safelySerialiseJSON = safelySerialiseJSON;
29
29
  exports.updateHtmlZipFileAttachmentPaths = updateHtmlZipFileAttachmentPaths;
30
30
  exports.replaceMediaPaths = replaceMediaPaths;
31
+ exports.traverseJsonReportSuites = traverseJsonReportSuites;
31
32
  exports.buildTestAttachmentMapFromSummary = buildTestAttachmentMapFromSummary;
32
33
  exports.checkFileExistsAsync = checkFileExistsAsync;
34
+ exports.updateSummaryJsonAttachmentPaths = updateSummaryJsonAttachmentPaths;
33
35
  const adm_zip_1 = __importDefault(require("adm-zip"));
34
36
  const async_retry_1 = __importDefault(require("async-retry"));
35
37
  const fs_1 = __importDefault(require("fs"));
@@ -171,8 +173,8 @@ async function sendTestCaseUpdateToDashboard(params) {
171
173
  function safelySerialiseJSON(obj, keyFilter) {
172
174
  return JSON.parse(JSON.stringify(obj, (key, value) => (keyFilter?.(key) ? undefined : value)));
173
175
  }
174
- async function updateHtmlZipFileAttachmentPaths(jsonFilePath, htmlFilePath) {
175
- const testAttachmentMap = buildTestAttachmentMapFromSummary(jsonFilePath);
176
+ async function updateHtmlZipFileAttachmentPaths(jsonFilePath, htmlFilePath, repoPath) {
177
+ const testAttachmentMap = buildTestAttachmentMapFromSummary(jsonFilePath, repoPath);
176
178
  let htmlFile;
177
179
  try {
178
180
  htmlFile = await fs_1.default.promises.readFile(htmlFilePath, "utf8");
@@ -269,46 +271,53 @@ function replaceMediaPaths(jsonData, testAttachmentMap) {
269
271
  }
270
272
  }
271
273
  }
274
+ // Generic suite traversal utility
275
+ function traverseJsonReportSuites(summaryJson, specFn) {
276
+ function recurse(suite) {
277
+ for (const spec of suite.specs || []) {
278
+ specFn(spec);
279
+ }
280
+ for (const childSuite of suite.suites || []) {
281
+ recurse(childSuite);
282
+ }
283
+ }
284
+ for (const suite of summaryJson.suites || []) {
285
+ recurse(suite);
286
+ }
287
+ }
272
288
  /**
273
289
  * Builds a map from test ID to an array of objects containing attachments for each result (retry).
274
290
  * The attachments are normalized to use relative paths starting with 'data/'.
275
291
  * @param summaryJsonFilePath Path to the summary JSON file
276
292
  * @returns Map<string, { attachments: GenericPlaywrightAttachment[] }[]>
277
293
  */
278
- function buildTestAttachmentMapFromSummary(summaryJsonFilePath) {
294
+ function buildTestAttachmentMapFromSummary(summaryJsonFilePath, repoPath, doNotNormalize = false) {
279
295
  const summaryJson = JSON.parse(fs_1.default.readFileSync(summaryJsonFilePath, "utf8"));
280
296
  const testAttachmentMap = new Map();
281
297
  function normalizeAttachmentPath(attachment) {
282
298
  if (!attachment?.path)
283
299
  return attachment;
284
- const relativePath = path_1.default.relative(path_1.default.join(process.cwd(), "test-results"), attachment.path);
300
+ const relativePath = path_1.default.relative(repoPath, attachment.path);
285
301
  attachment.path = path_1.default.join("data", relativePath);
286
302
  return attachment;
287
303
  }
288
- function processSuite(suite) {
289
- for (const spec of suite.specs || []) {
290
- const testId = spec.id;
291
- for (const test of spec.tests) {
292
- if (!testId) {
293
- console.warn("⚠️ Test without testId encountered, skipping.");
294
- continue;
295
- }
296
- const resultAttachments = (test.results || []).map((result) => ({
297
- attachments: (result.attachments || []).map(normalizeAttachmentPath),
298
- }));
299
- testAttachmentMap.set(testId, [
300
- ...(testAttachmentMap.get(testId) || []),
301
- ...resultAttachments,
302
- ]);
304
+ const buildTestAttachmentMap = (spec) => {
305
+ const testId = spec.id;
306
+ for (const test of spec.tests) {
307
+ if (!testId) {
308
+ console.warn("⚠️ Test without testId encountered, skipping.");
309
+ continue;
303
310
  }
311
+ const resultAttachments = (test.results || []).map((result) => ({
312
+ attachments: (result.attachments || []).map(doNotNormalize ? (a) => a : normalizeAttachmentPath),
313
+ }));
314
+ testAttachmentMap.set(testId, [
315
+ ...(testAttachmentMap.get(testId) || []),
316
+ ...resultAttachments,
317
+ ]);
304
318
  }
305
- for (const childSuite of suite.suites || []) {
306
- processSuite(childSuite);
307
- }
308
- }
309
- for (const suite of summaryJson.suites || []) {
310
- processSuite(suite);
311
- }
319
+ };
320
+ traverseJsonReportSuites(summaryJson, buildTestAttachmentMap);
312
321
  return testAttachmentMap;
313
322
  }
314
323
  /**
@@ -325,3 +334,39 @@ async function checkFileExistsAsync(filePath) {
325
334
  return false;
326
335
  }
327
336
  }
337
+ /**
338
+ * Updates the attachment paths in the summary JSON file to use remote URLs.
339
+ *
340
+ * This function rewrites all attachment `path` fields in the given Playwright summary JSON file
341
+ * so that they point to remote URLs based on the current `PROJECT_NAME` and `TEST_RUN_GITHUB_ACTION_ID`
342
+ * environment variables.
343
+ *
344
+ * @param filePath - The path to the summary JSON file
345
+ * @returns The updated summary JSON
346
+ *
347
+ */
348
+ async function updateSummaryJsonAttachmentPaths(filePath, repoPath, urlPrefix) {
349
+ if (!process.env.PROJECT_NAME || !process.env.TEST_RUN_GITHUB_ACTION_ID) {
350
+ console.error("PROJECT_NAME and TEST_RUN_GITHUB_ACTION_ID must be set");
351
+ return;
352
+ }
353
+ const summaryJson = JSON.parse(await fs_1.default.promises.readFile(filePath, "utf8"));
354
+ const updateAttachmentPath = (spec) => {
355
+ for (const test of spec.tests) {
356
+ for (const result of test.results || []) {
357
+ if (result.attachments) {
358
+ for (const attachment of result.attachments) {
359
+ if (attachment && attachment.path) {
360
+ const relativePath = path_1.default.relative(repoPath, attachment.path);
361
+ const newPath = `${urlPrefix}/${relativePath}`;
362
+ attachment.path = newPath;
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }
368
+ };
369
+ traverseJsonReportSuites(summaryJson, updateAttachmentPath);
370
+ await fs_1.default.promises.writeFile(filePath, JSON.stringify(summaryJson, null, 2), "utf8");
371
+ return summaryJson;
372
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/playwright-utils",
3
- "version": "0.27.19",
3
+ "version": "0.28.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -32,7 +32,7 @@
32
32
  "@types/adm-zip": "^0.5.7",
33
33
  "playwright-core": "1.53.2",
34
34
  "serve-handler": "^6.1.6",
35
- "@empiricalrun/shared-types": "0.6.4"
35
+ "@empiricalrun/shared-types": "0.7.0"
36
36
  },
37
37
  "dependencies": {
38
38
  "@babel/code-frame": "^7.24.7",
@@ -43,9 +43,9 @@
43
43
  "mailosaur": "^8.6.1",
44
44
  "puppeteer-extra-plugin-recaptcha": "^3.6.8",
45
45
  "rimraf": "^6.0.1",
46
- "@empiricalrun/llm": "^0.19.4",
46
+ "@empiricalrun/llm": "^0.20.0",
47
47
  "@empiricalrun/r2-uploader": "^0.3.9",
48
- "@empiricalrun/test-gen": "^0.71.2"
48
+ "@empiricalrun/test-gen": "^0.72.0"
49
49
  },
50
50
  "scripts": {
51
51
  "dev": "tsc --build --watch",