@empiricalrun/playwright-utils 0.63.2 → 0.65.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.
@@ -13,6 +13,8 @@ declare class EmpiricalReporter implements Reporter {
13
13
  onBegin(config: FullConfig): void;
14
14
  private _piSandboxLog;
15
15
  private enqueTestAttachmentUploadTask;
16
+ private _shardIndex;
17
+ onTestBegin(test: TestCase, result: TestResult): void;
16
18
  onTestEnd(test: TestCase, result: TestResult): void;
17
19
  onEnd(result: FullResult): Promise<void>;
18
20
  private _logReportDirContents;
@@ -1 +1 @@
1
- {"version":3,"file":"empirical-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/empirical-reporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;AAuBnC,cAAM,iBAAkB,YAAW,QAAQ;IACzC,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,oBAAoB,CAG1B;IACF,OAAO,CAAC,sBAAsB,CAAuB;IACrD,OAAO,CAAC,YAAY,CAAgD;IACpE,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,gBAAgB,CAAkC;IAC1D,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,kBAAkB,CAA2B;IACrD,OAAO,CAAC,YAAY,CAAqB;IAEzC,cAEC;IAED,OAAO,CAAC,MAAM,EAAE,UAAU,QAEzB;IAED,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,6BAA6B,CA2CnC;IAEF,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,QA+I3C;IAEK,KAAK,CAAC,MAAM,EAAE,UAAU,iBAkH7B;IAED,OAAO,CAAC,qBAAqB;YAkBf,gBAAgB;YAOhB,iBAAiB;CAqChC;eAEc,iBAAiB"}
1
+ {"version":3,"file":"empirical-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/empirical-reporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;AAwBnC,cAAM,iBAAkB,YAAW,QAAQ;IACzC,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,oBAAoB,CAG1B;IACF,OAAO,CAAC,sBAAsB,CAAuB;IACrD,OAAO,CAAC,YAAY,CAAgD;IACpE,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,gBAAgB,CAAkC;IAC1D,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,kBAAkB,CAA2B;IACrD,OAAO,CAAC,YAAY,CAAqB;IAEzC,cAEC;IAED,OAAO,CAAC,MAAM,EAAE,UAAU,QAEzB;IAED,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,6BAA6B,CA2CnC;IAEF,OAAO,CAAC,WAAW;IAMnB,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,QAyB7C;IAED,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,QA4I3C;IAEK,KAAK,CAAC,MAAM,EAAE,UAAU,iBAkH7B;IAED,OAAO,CAAC,qBAAqB;YAkBf,gBAAgB;YAOhB,iBAAiB;CAqChC;eAEc,iBAAiB"}
@@ -59,6 +59,35 @@ class EmpiricalReporter {
59
59
  return { [attachment.path]: publicUrl };
60
60
  }
61
61
  };
62
+ _shardIndex() {
63
+ return this._hasSharding && process.env.SHARD_INDEX
64
+ ? parseInt(process.env.SHARD_INDEX, 10)
65
+ : undefined;
66
+ }
67
+ onTestBegin(test, result) {
68
+ if ((0, reporter_state_1.isFinalized)() || !this._uploader) {
69
+ return;
70
+ }
71
+ const runId = process.env.TEST_RUN_GITHUB_ACTION_ID;
72
+ if (!runId) {
73
+ return;
74
+ }
75
+ const { suites, projectName } = (0, util_1.suitesAndProjectForTest)(test);
76
+ this._pendingTestCaseEvents.push((0, util_1.sendTestCaseBeginToDashboard)({
77
+ runId,
78
+ test: {
79
+ id: test.id,
80
+ title: test.title,
81
+ location: test.location,
82
+ expectedStatus: test.expectedStatus,
83
+ retries: test.retries,
84
+ },
85
+ suites,
86
+ result: { retry: result.retry, startTime: result.startTime },
87
+ projectName,
88
+ shardIndex: this._shardIndex(),
89
+ }));
90
+ }
62
91
  onTestEnd(test, result) {
63
92
  if ((0, reporter_state_1.isFinalized)() && (0, reporter_state_1.isShutdownArtifact)(result.status, true)) {
64
93
  return;
@@ -119,9 +148,7 @@ class EmpiricalReporter {
119
148
  const failingLine = errorLocation
120
149
  ? (0, failing_line_1.getFailingLineFromSource)(errorLocation.file, errorLocation.line, errorLocation.column)
121
150
  : undefined;
122
- const shardIndex = this._hasSharding && process.env.SHARD_INDEX
123
- ? parseInt(process.env.SHARD_INDEX, 10)
124
- : undefined;
151
+ const shardIndex = this._shardIndex();
125
152
  const params = {
126
153
  test: {
127
154
  id: test.id,
@@ -0,0 +1,15 @@
1
+ import { type FileMap } from "@empiricalrun/r2-uploader";
2
+ import { ApiWorkerClient } from "../api-worker-client";
3
+ export type PresignedUploadFile = {
4
+ filePath: string;
5
+ reportPath: string;
6
+ };
7
+ export declare function directoryUploadFiles(sourceDir: string, reportDir: string): PresignedUploadFile[];
8
+ export declare function uploadPresigned({ client, runId, baseUrl, destinationDir, files, }: {
9
+ client: ApiWorkerClient;
10
+ runId: string;
11
+ baseUrl: string;
12
+ destinationDir: string;
13
+ files: PresignedUploadFile[];
14
+ }): Promise<FileMap>;
15
+ //# sourceMappingURL=presigned-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presigned-upload.d.ts","sourceRoot":"","sources":["../../src/reporter/presigned-upload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAkB,MAAM,2BAA2B,CAAC;AAOzE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAKvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AASF,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,mBAAmB,EAAE,CAUvB;AA8CD,wBAAsB,eAAe,CAAC,EACpC,MAAM,EACN,KAAK,EACL,OAAO,EACP,cAAc,EACd,KAAK,GACN,EAAE;IACD,MAAM,EAAE,eAAe,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,mBAAmB,EAAE,CAAC;CAC9B,GAAG,OAAO,CAAC,OAAO,CAAC,CAwCnB"}
@@ -0,0 +1,96 @@
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.directoryUploadFiles = directoryUploadFiles;
7
+ exports.uploadPresigned = uploadPresigned;
8
+ const r2_uploader_1 = require("@empiricalrun/r2-uploader");
9
+ const async_retry_1 = __importDefault(require("async-retry"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const mime_1 = __importDefault(require("mime"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const stream_1 = require("stream");
14
+ const logger_1 = require("../logger");
15
+ const MAX_FILES_PER_REQUEST = 100;
16
+ function listFiles(dir) {
17
+ return fs_1.default.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
18
+ const fullPath = path_1.default.join(dir, entry.name);
19
+ return entry.isDirectory() ? listFiles(fullPath) : [fullPath];
20
+ });
21
+ }
22
+ function directoryUploadFiles(sourceDir, reportDir) {
23
+ return listFiles(sourceDir)
24
+ .filter((filePath) => !filePath.includes(".gitkeep"))
25
+ .map((filePath) => ({
26
+ filePath,
27
+ reportPath: path_1.default.posix.join(reportDir, path_1.default.relative(sourceDir, filePath).split(path_1.default.sep).join("/")),
28
+ }));
29
+ }
30
+ async function putFile(uploadUrl, filePath, size, headers) {
31
+ await (0, async_retry_1.default)(async (bail) => {
32
+ const response = await fetch(uploadUrl, {
33
+ method: "PUT",
34
+ headers: { ...headers, "Content-Length": String(size) },
35
+ body: stream_1.Readable.toWeb(fs_1.default.createReadStream(filePath)),
36
+ duplex: "half",
37
+ });
38
+ if (response.ok) {
39
+ return;
40
+ }
41
+ const detail = (await response.text().catch(() => "")).slice(0, 500);
42
+ const error = new Error(`Report upload of ${filePath} failed: ${response.status} ${detail}`);
43
+ if (response.status < 500 &&
44
+ response.status !== 408 &&
45
+ response.status !== 429) {
46
+ bail(error);
47
+ return;
48
+ }
49
+ throw error;
50
+ }, {
51
+ retries: 5,
52
+ factor: 3,
53
+ minTimeout: 1_000,
54
+ maxTimeout: 60_000,
55
+ randomize: true,
56
+ onRetry: (_err, attempt) => {
57
+ logger_1.logger.debug(`[Uploader] upload retry ${attempt}: ${filePath}`);
58
+ },
59
+ });
60
+ }
61
+ async function uploadPresigned({ client, runId, baseUrl, destinationDir, files, }) {
62
+ const urls = {};
63
+ for (let i = 0; i < files.length; i += MAX_FILES_PER_REQUEST) {
64
+ const batch = await Promise.all(files.slice(i, i + MAX_FILES_PER_REQUEST).map(async (file) => {
65
+ const size = (await fs_1.default.promises.stat(file.filePath)).size;
66
+ return {
67
+ ...file,
68
+ size,
69
+ contentType: mime_1.default.getType(file.filePath) || "application/octet-stream",
70
+ storageClass: (0, r2_uploader_1.iaStorageClass)("r2", size)?.StorageClass,
71
+ };
72
+ }));
73
+ const { data } = await client.createTestReportUploadUrls({
74
+ run_id: runId,
75
+ files: batch.map((file) => ({
76
+ path: file.reportPath,
77
+ content_type: file.contentType,
78
+ storage_class: file.storageClass,
79
+ })),
80
+ });
81
+ await Promise.all(batch.map(async (file, index) => {
82
+ const signed = data.files[index];
83
+ if (!signed || signed.path !== file.reportPath) {
84
+ throw new Error(`No upload URL returned for ${file.reportPath}`);
85
+ }
86
+ await putFile(signed.upload_url, file.filePath, file.size, {
87
+ "Content-Type": file.contentType,
88
+ ...(file.storageClass && {
89
+ "x-amz-storage-class": file.storageClass,
90
+ }),
91
+ });
92
+ urls[file.filePath] = `${baseUrl}/${destinationDir}/${file.reportPath}`;
93
+ }));
94
+ }
95
+ return urls;
96
+ }
@@ -1,14 +1,21 @@
1
1
  import { StorageCredentials, UploadStorageClass } from "@empiricalrun/r2-uploader";
2
+ import { ApiWorkerClient } from "../api-worker-client";
3
+ type PresignedCredentials = {
4
+ provider: "r2-presigned";
5
+ client: ApiWorkerClient;
6
+ };
7
+ export type UploaderCredentials = StorageCredentials | PresignedCredentials;
2
8
  export interface UploaderConfig {
3
9
  projectSlug: string;
4
10
  runId: string;
5
11
  baseUrl: string;
6
12
  uploadBucket: string;
7
- credentials: StorageCredentials;
13
+ credentials: UploaderCredentials;
8
14
  }
9
15
  export declare class Uploader {
10
16
  private _baseUrl;
11
17
  private _uploadBucket;
18
+ private _runId;
12
19
  private _destinationDir;
13
20
  private _credentials;
14
21
  constructor(config: UploaderConfig);
@@ -19,10 +26,12 @@ export declare class Uploader {
19
26
  get baseUrl(): string;
20
27
  get destinationDir(): string;
21
28
  get urlPrefix(): string;
29
+ private _presignedTask;
22
30
  uploadFile(filePath: string, destinationPath: string, storageClass?: UploadStorageClass): Promise<string | null>;
23
31
  uploadDirectory(sourceDir: string, destinationPrefix: string): Promise<void>;
24
32
  waitForUploads(): Promise<void>;
25
33
  }
26
34
  export declare function getUploader(): Uploader | null;
27
35
  export declare function createUploader(): Uploader | null;
36
+ export {};
28
37
  //# sourceMappingURL=uploader.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../src/reporter/uploader.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,kBAAkB,EAElB,kBAAkB,EAEnB,MAAM,2BAA2B,CAAC;AAKnC,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,kBAAkB,CAAC;CACjC;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,YAAY,CAAqB;IAEzC,YAAY,MAAM,EAAE,cAAc,EASjC;IAED,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAgBhC,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAUhC,MAAM,CAAC,qBAAqB,CAAC,QAAQ,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAKzE;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAEK,UAAU,CACd,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,kBAAkB,GAChC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgCxB;IAEK,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,IAAI,CAAC,CAef;IAEK,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAyBpC;CACF;AAID,wBAAgB,WAAW,IAAI,QAAQ,GAAG,IAAI,CAM7C;AAED,wBAAgB,cAAc,IAAI,QAAQ,GAAG,IAAI,CA6DhD"}
1
+ {"version":3,"file":"uploader.d.ts","sourceRoot":"","sources":["../../src/reporter/uploader.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,kBAAkB,EAElB,kBAAkB,EAEnB,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAWvD,KAAK,oBAAoB,GAAG;IAC1B,QAAQ,EAAE,cAAc,CAAC;IACzB,MAAM,EAAE,eAAe,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,kBAAkB,GAAG,oBAAoB,CAAC;AAE5E,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,mBAAmB,CAAC;CAClC;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,YAAY,CAAsB;IAE1C,YAAY,MAAM,EAAE,cAAc,EAUjC;IAED,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAgBhC,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAUhC,MAAM,CAAC,qBAAqB,CAAC,QAAQ,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CAKzE;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED,IAAI,cAAc,IAAI,MAAM,CAE3B;IAED,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,OAAO,CAAC,cAAc;IAoBhB,UAAU,CACd,QAAQ,EAAE,MAAM,EAChB,eAAe,EAAE,MAAM,EACvB,YAAY,CAAC,EAAE,kBAAkB,GAChC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAyCxB;IAEK,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,GACxB,OAAO,CAAC,IAAI,CAAC,CAyBf;IAEK,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAyBpC;CACF;AAID,wBAAgB,WAAW,IAAI,QAAQ,GAAG,IAAI,CAM7C;AAED,wBAAgB,cAAc,IAAI,QAAQ,GAAG,IAAI,CAmEhD"}
@@ -8,15 +8,21 @@ exports.getUploader = getUploader;
8
8
  exports.createUploader = createUploader;
9
9
  const r2_uploader_1 = require("@empiricalrun/r2-uploader");
10
10
  const path_1 = __importDefault(require("path"));
11
+ const api_worker_client_1 = require("../api-worker-client");
11
12
  const logger_1 = require("../logger");
13
+ const presigned_upload_1 = require("./presigned-upload");
14
+ const R2_REPORTS_BASE_URL = "https://reports-r2.empirical.run";
15
+ const R2_REPORTS_BUCKET = "test-report";
12
16
  class Uploader {
13
17
  _baseUrl;
14
18
  _uploadBucket;
19
+ _runId;
15
20
  _destinationDir;
16
21
  _credentials;
17
22
  constructor(config) {
18
23
  this._baseUrl = config.baseUrl;
19
24
  this._uploadBucket = config.uploadBucket;
25
+ this._runId = config.runId;
20
26
  this._destinationDir = `${config.projectSlug}/${config.runId}`;
21
27
  this._credentials = config.credentials;
22
28
  logger_1.logger.debug(`[Uploader] Initialized with destination: ${this._destinationDir}, provider: ${this._credentials.provider}`);
@@ -61,8 +67,31 @@ class Uploader {
61
67
  get urlPrefix() {
62
68
  return `${this._baseUrl}/${this._destinationDir}`;
63
69
  }
70
+ _presignedTask(credentials, files) {
71
+ return async () => {
72
+ try {
73
+ return await (0, presigned_upload_1.uploadPresigned)({
74
+ client: credentials.client,
75
+ runId: this._runId,
76
+ baseUrl: this._baseUrl,
77
+ destinationDir: this._destinationDir,
78
+ files,
79
+ });
80
+ }
81
+ catch (error) {
82
+ logger_1.logger.error("[Uploader] Error uploading files", error);
83
+ return {};
84
+ }
85
+ };
86
+ }
64
87
  async uploadFile(filePath, destinationPath, storageClass) {
65
88
  try {
89
+ if (this._credentials.provider === "r2-presigned") {
90
+ const result = await (0, r2_uploader_1.sendTaskToQueue)(this._presignedTask(this._credentials, [
91
+ { filePath, reportPath: destinationPath },
92
+ ]));
93
+ return (result && result[filePath]) || null;
94
+ }
66
95
  const fullDestinationPath = path_1.default.join(this._destinationDir, destinationPath);
67
96
  const sourceDir = path_1.default.dirname(filePath);
68
97
  const uploadTask = (0, r2_uploader_1.createUploadTask)({
@@ -90,6 +119,10 @@ class Uploader {
90
119
  }
91
120
  }
92
121
  async uploadDirectory(sourceDir, destinationPrefix) {
122
+ if (this._credentials.provider === "r2-presigned") {
123
+ await (0, r2_uploader_1.sendTaskToQueue)(this._presignedTask(this._credentials, (0, presigned_upload_1.directoryUploadFiles)(sourceDir, destinationPrefix)));
124
+ return;
125
+ }
93
126
  const fullDestinationDir = path_1.default.join(this._destinationDir, destinationPrefix);
94
127
  const uploadTask = (0, r2_uploader_1.createUploadTask)({
95
128
  sourceDir,
@@ -145,6 +178,9 @@ function createUploader() {
145
178
  logger_1.logger.debug(`[Uploader] REPORT_STORAGE_BACKEND must be "s3" or "r2" (got ${backend ?? "unset"})`);
146
179
  return null;
147
180
  }
181
+ if (backend === "r2" && process.env.REPORT_UPLOAD_PRESIGNED === "true") {
182
+ return createPresignedUploader(process.env.PROJECT_NAME, process.env.TEST_RUN_GITHUB_ACTION_ID);
183
+ }
148
184
  const enableS3 = backend === "s3";
149
185
  const credentials = Uploader.getCredentialsFromEnv(enableS3);
150
186
  if (!credentials) {
@@ -168,12 +204,12 @@ function createUploader() {
168
204
  }
169
205
  else {
170
206
  const accountId = process.env.R2_ACCOUNT_ID;
171
- const bucket = "test-report";
207
+ const bucket = R2_REPORTS_BUCKET;
172
208
  if (!accountId) {
173
209
  logger_1.logger.debug("[Uploader] R2_ACCOUNT_ID not configured");
174
210
  return null;
175
211
  }
176
- baseUrl = `https://reports-r2.empirical.run`;
212
+ baseUrl = R2_REPORTS_BASE_URL;
177
213
  uploadBucket = bucket;
178
214
  }
179
215
  return new Uploader({
@@ -184,3 +220,16 @@ function createUploader() {
184
220
  credentials,
185
221
  });
186
222
  }
223
+ function createPresignedUploader(projectSlug, runId) {
224
+ if (!process.env.EMPIRICALRUN_API_KEY) {
225
+ logger_1.logger.debug("[Uploader] EMPIRICALRUN_API_KEY is required for presigned uploads");
226
+ return null;
227
+ }
228
+ return new Uploader({
229
+ projectSlug,
230
+ runId,
231
+ baseUrl: R2_REPORTS_BASE_URL,
232
+ uploadBucket: R2_REPORTS_BUCKET,
233
+ credentials: { provider: "r2-presigned", client: new api_worker_client_1.ApiWorkerClient() },
234
+ });
235
+ }
@@ -1,5 +1,5 @@
1
1
  import type { TestAttachmentMap } from "@empiricalrun/reporter";
2
- import { TestCaseRunEndEventV2 } from "@empiricalrun/shared-types/playwright-utils";
2
+ import { TestCaseRunBeginEvent, TestCaseRunEndEventV2 } from "@empiricalrun/shared-types/playwright-utils";
3
3
  import { JSONReport as PlaywrightJSONReport, TestCase } from "@playwright/test/reporter";
4
4
  export declare function getPackageJsonPath(folderPath: string): string;
5
5
  export declare function resolveReporterOutputPath(defaultValue: string, configDir: string, configValue: string | undefined): string;
@@ -18,6 +18,7 @@ export declare function suitesAndProjectForTest(test: TestCase): {
18
18
  projectName: string;
19
19
  };
20
20
  export declare function sendTestCaseUpdateToDashboard(params: TestCaseRunEndEventV2): Promise<void>;
21
+ export declare function sendTestCaseBeginToDashboard(params: TestCaseRunBeginEvent): Promise<void>;
21
22
  export declare function safelySerialiseJSON(obj: any, keyFilter?: (key: string) => boolean): any;
22
23
  export declare function updateHtmlZipFileAttachmentPaths(jsonFilePath: string, htmlFilePath: string, repoPath: string): Promise<void>;
23
24
  export declare function buildTestAttachmentMapFromSummary(summaryJsonFilePath: string, repoPath: string, doNotNormalize?: boolean): TestAttachmentMap;
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/reporter/util.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAQhE,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACpF,OAAO,EACL,UAAU,IAAI,oBAAoB,EAElC,QAAQ,EACT,MAAM,2BAA2B,CAAC;AA0BnC,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAqB7D;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,UAQhC;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,CA6CD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ;IAmBlD,MAAM;IACN,WAAW;EAEd;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA2Bf;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,iBAkFjB;AAED,wBAAgB,iCAAiC,CAC/C,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,OAAe,GAC9B,iBAAiB,CAgBnB;AAED,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E;AAED,wBAAsB,gCAAgC,CACpD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,6CA+BlB"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../src/reporter/util.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAQhE,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACtB,MAAM,6CAA6C,CAAC;AACrD,OAAO,EACL,UAAU,IAAI,oBAAoB,EAElC,QAAQ,EACT,MAAM,2BAA2B,CAAC;AA0BnC,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAqB7D;AAED,wBAAgB,yBAAyB,CACvC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAAG,SAAS,UAQhC;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,CA6CD;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ;IAmBlD,MAAM;IACN,WAAW;EAEd;AAED,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,IAAI,CAAC,CA2Bf;AAED,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAoBf;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,iBAkFjB;AAED,wBAAgB,iCAAiC,CAC/C,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,cAAc,GAAE,OAAe,GAC9B,iBAAiB,CAgBnB;AAED,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7E;AAED,wBAAsB,gCAAgC,CACpD,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,6CA+BlB"}
@@ -8,6 +8,7 @@ exports.resolveReporterOutputPath = resolveReporterOutputPath;
8
8
  exports.normalizeAndSaveAttachment = normalizeAndSaveAttachment;
9
9
  exports.suitesAndProjectForTest = suitesAndProjectForTest;
10
10
  exports.sendTestCaseUpdateToDashboard = sendTestCaseUpdateToDashboard;
11
+ exports.sendTestCaseBeginToDashboard = sendTestCaseBeginToDashboard;
11
12
  exports.safelySerialiseJSON = safelySerialiseJSON;
12
13
  exports.updateHtmlZipFileAttachmentPaths = updateHtmlZipFileAttachmentPaths;
13
14
  exports.buildTestAttachmentMapFromSummary = buildTestAttachmentMapFromSummary;
@@ -148,6 +149,27 @@ async function sendTestCaseUpdateToDashboard(params) {
148
149
  console.error(e.message);
149
150
  }
150
151
  }
152
+ async function sendTestCaseBeginToDashboard(params) {
153
+ try {
154
+ if (!process.env.EMPIRICALRUN_API_KEY) {
155
+ return;
156
+ }
157
+ const startTime = params.result.startTime;
158
+ await new api_worker_client_1.ApiWorkerClient().putTestCaseRunBeginEvent({
159
+ name: "test_case_run_begin",
160
+ data: {
161
+ ...params,
162
+ result: {
163
+ ...params.result,
164
+ startTime: startTime instanceof Date ? startTime.toISOString() : startTime,
165
+ },
166
+ },
167
+ });
168
+ }
169
+ catch (e) {
170
+ console.error(e.message);
171
+ }
172
+ }
151
173
  function safelySerialiseJSON(obj, keyFilter) {
152
174
  return JSON.parse(JSON.stringify(obj, (key, value) => (keyFilter?.(key) ? undefined : value)));
153
175
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empiricalrun/playwright-utils",
3
- "version": "0.63.2",
3
+ "version": "0.65.0",
4
4
  "engines": {
5
5
  "node": ">=22"
6
6
  },
@@ -44,7 +44,8 @@
44
44
  "openapi-typescript": "7.6.1",
45
45
  "playwright-core": "1.63.0",
46
46
  "serve-handler": "^6.1.6",
47
- "@empiricalrun/shared-types": "0.21.2"
47
+ "typescript": "7.0.2",
48
+ "@empiricalrun/shared-types": "0.23.0"
48
49
  },
49
50
  "dependencies": {
50
51
  "async-retry": "^1.3.3",
@@ -54,21 +55,21 @@
54
55
  "openai": "^7.19.0",
55
56
  "openapi-fetch": "0.14.1",
56
57
  "puppeteer-extra-plugin-recaptcha": "^3.6.8",
57
- "ts-morph": "^23.0.0",
58
+ "ts-morph": "^28.0.0",
58
59
  "@empiricalrun/cua": "^0.5.4",
59
60
  "@empiricalrun/llm": "^0.28.3",
60
- "@empiricalrun/r2-uploader": "^0.11.4",
61
+ "@empiricalrun/r2-uploader": "^0.11.5",
61
62
  "@empiricalrun/reporter": "^0.30.3"
62
63
  },
63
64
  "scripts": {
64
- "dev": "pnpm gen:api-worker-openapi && tsgo --build --watch",
65
- "build": "pnpm gen:api-worker-openapi && tsgo --build",
65
+ "dev": "pnpm gen:api-worker-openapi && tsc --build --watch",
66
+ "build": "pnpm gen:api-worker-openapi && tsc --build",
66
67
  "clean": "rm -rf dist && tsc --build --clean",
67
68
  "gen:api-worker-openapi": "openapi-typescript ../../apps/api-worker/openapi/openapi.internal.json -o src/generated/api-worker-openapi.ts",
68
69
  "lint": "biome check --unsafe",
69
70
  "test": "pnpm gen:api-worker-openapi && vitest run",
70
71
  "test-browser": "npx playwright test",
71
72
  "test:watch": "vitest",
72
- "typecheck": "pnpm gen:api-worker-openapi && tsgo --noEmit"
73
+ "typecheck": "pnpm gen:api-worker-openapi && tsc --noEmit"
73
74
  }
74
75
  }