@supatest/cypress-reporter 0.0.4 → 0.0.5

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/dist/index.cjs CHANGED
@@ -5471,8 +5471,8 @@ init_git_response_error();
5471
5471
  var simpleGit = gitInstanceFactory;
5472
5472
 
5473
5473
  // ../reporter-core/dist/index.js
5474
- var import_fs = __toESM(require("fs"), 1);
5475
5474
  var import_crypto = require("crypto");
5475
+ var import_fs = __toESM(require("fs"), 1);
5476
5476
  var import_fs2 = __toESM(require("fs"), 1);
5477
5477
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
5478
5478
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
@@ -5778,105 +5778,12 @@ async function getLocalGitInfo(rootDir) {
5778
5778
  return {};
5779
5779
  }
5780
5780
  }
5781
- var AttachmentUploader = class {
5782
- options;
5783
- constructor(options) {
5784
- this.options = options;
5785
- }
5786
- async upload(signedUrl, filePath, contentType) {
5787
- if (this.options.dryRun) {
5788
- try {
5789
- const stats = import_fs.default.statSync(filePath);
5790
- console.log(
5791
- `[supatest][dry-run] Would upload ${filePath} (${stats.size} bytes) to S3`
5792
- );
5793
- } catch (error) {
5794
- const message = error instanceof Error ? error.message : String(error);
5795
- console.warn(
5796
- `[supatest][dry-run] Cannot access file ${filePath}: ${message}`
5797
- );
5798
- }
5799
- return;
5800
- }
5801
- let fileBuffer;
5802
- try {
5803
- fileBuffer = await import_fs.default.promises.readFile(filePath);
5804
- } catch (error) {
5805
- const message = error instanceof Error ? error.message : String(error);
5806
- throw new Error(`Failed to read file ${filePath}: ${message}`);
5807
- }
5808
- await withRetry(async () => {
5809
- const controller = new _AbortController();
5810
- const timeoutId = setTimeout(
5811
- () => controller.abort(),
5812
- this.options.timeoutMs
5813
- );
5814
- try {
5815
- const response = await _fetch(signedUrl, {
5816
- method: "PUT",
5817
- headers: {
5818
- "Content-Type": contentType,
5819
- "Content-Length": String(fileBuffer.length)
5820
- },
5821
- body: fileBuffer,
5822
- signal: controller.signal
5823
- });
5824
- if (!response.ok) {
5825
- const error = new Error(
5826
- `S3 upload failed: ${response.status} ${response.statusText}`
5827
- );
5828
- error.statusCode = response.status;
5829
- throw error;
5830
- }
5831
- } finally {
5832
- clearTimeout(timeoutId);
5833
- }
5834
- }, defaultRetryConfig);
5835
- }
5836
- async uploadBatch(items, signedUploads) {
5837
- const pLimit = (await import("p-limit")).default;
5838
- const limit = pLimit(this.options.maxConcurrent);
5839
- const results = await Promise.allSettled(
5840
- items.map(
5841
- (item, index) => limit(async () => {
5842
- var _a2, _b;
5843
- try {
5844
- await this.upload(item.signedUrl, item.filePath, item.contentType);
5845
- return {
5846
- success: true,
5847
- attachmentId: (_a2 = signedUploads[index]) == null ? void 0 : _a2.attachmentId
5848
- };
5849
- } catch (error) {
5850
- const message = error instanceof Error ? error.message : String(error);
5851
- return {
5852
- success: false,
5853
- attachmentId: (_b = signedUploads[index]) == null ? void 0 : _b.attachmentId,
5854
- error: `${item.filePath}: ${message}`
5855
- };
5856
- }
5857
- })
5858
- )
5859
- );
5860
- return results.map((result, index) => {
5861
- var _a2;
5862
- if (result.status === "fulfilled") {
5863
- return result.value;
5864
- }
5865
- const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
5866
- return {
5867
- success: false,
5868
- attachmentId: (_a2 = signedUploads[index]) == null ? void 0 : _a2.attachmentId,
5869
- error: message
5870
- };
5871
- });
5872
- }
5873
- };
5874
5781
  function hashKey(value) {
5875
5782
  return (0, import_crypto.createHash)("sha256").update(value).digest("hex").slice(0, 12);
5876
5783
  }
5877
5784
  function getFileSize(filePath) {
5878
5785
  try {
5879
- return import_fs2.default.statSync(filePath).size;
5786
+ return import_fs.default.statSync(filePath).size;
5880
5787
  } catch {
5881
5788
  return 0;
5882
5789
  }
@@ -5979,6 +5886,131 @@ function getCIInfo() {
5979
5886
  }
5980
5887
  return void 0;
5981
5888
  }
5889
+ function registerInterruptHandler(client, getRunId) {
5890
+ let completed = false;
5891
+ const handler = async (signal) => {
5892
+ if (completed) {
5893
+ return;
5894
+ }
5895
+ completed = true;
5896
+ const runId = getRunId();
5897
+ if (runId) {
5898
+ logWarn(`Received ${signal}, marking run ${runId} as interrupted`);
5899
+ try {
5900
+ await client.completeRun(runId, {
5901
+ status: "interrupted",
5902
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
5903
+ });
5904
+ } catch {
5905
+ }
5906
+ }
5907
+ process.removeListener(signal, sigintHandler);
5908
+ process.removeListener(signal, sigtermHandler);
5909
+ process.kill(process.pid, signal);
5910
+ };
5911
+ const sigintHandler = () => handler("SIGINT");
5912
+ const sigtermHandler = () => handler("SIGTERM");
5913
+ process.once("SIGINT", sigintHandler);
5914
+ process.once("SIGTERM", sigtermHandler);
5915
+ return () => {
5916
+ completed = true;
5917
+ process.removeListener("SIGINT", sigintHandler);
5918
+ process.removeListener("SIGTERM", sigtermHandler);
5919
+ };
5920
+ }
5921
+ var AttachmentUploader = class {
5922
+ options;
5923
+ constructor(options) {
5924
+ this.options = options;
5925
+ }
5926
+ async upload(signedUrl, filePath, contentType) {
5927
+ if (this.options.dryRun) {
5928
+ try {
5929
+ const stats = import_fs2.default.statSync(filePath);
5930
+ console.log(
5931
+ `[supatest][dry-run] Would upload ${filePath} (${stats.size} bytes) to S3`
5932
+ );
5933
+ } catch (error) {
5934
+ const message = error instanceof Error ? error.message : String(error);
5935
+ console.warn(
5936
+ `[supatest][dry-run] Cannot access file ${filePath}: ${message}`
5937
+ );
5938
+ }
5939
+ return;
5940
+ }
5941
+ let fileBuffer;
5942
+ try {
5943
+ fileBuffer = await import_fs2.default.promises.readFile(filePath);
5944
+ } catch (error) {
5945
+ const message = error instanceof Error ? error.message : String(error);
5946
+ throw new Error(`Failed to read file ${filePath}: ${message}`);
5947
+ }
5948
+ await withRetry(async () => {
5949
+ const controller = new _AbortController();
5950
+ const timeoutId = setTimeout(
5951
+ () => controller.abort(),
5952
+ this.options.timeoutMs
5953
+ );
5954
+ try {
5955
+ const response = await _fetch(signedUrl, {
5956
+ method: "PUT",
5957
+ headers: {
5958
+ "Content-Type": contentType,
5959
+ "Content-Length": String(fileBuffer.length)
5960
+ },
5961
+ body: fileBuffer,
5962
+ signal: controller.signal
5963
+ });
5964
+ if (!response.ok) {
5965
+ const error = new Error(
5966
+ `S3 upload failed: ${response.status} ${response.statusText}`
5967
+ );
5968
+ error.statusCode = response.status;
5969
+ throw error;
5970
+ }
5971
+ } finally {
5972
+ clearTimeout(timeoutId);
5973
+ }
5974
+ }, defaultRetryConfig);
5975
+ }
5976
+ async uploadBatch(items, signedUploads) {
5977
+ const pLimit = (await import("p-limit")).default;
5978
+ const limit = pLimit(this.options.maxConcurrent);
5979
+ const results = await Promise.allSettled(
5980
+ items.map(
5981
+ (item, index) => limit(async () => {
5982
+ var _a2, _b;
5983
+ try {
5984
+ await this.upload(item.signedUrl, item.filePath, item.contentType);
5985
+ return {
5986
+ success: true,
5987
+ attachmentId: (_a2 = signedUploads[index]) == null ? void 0 : _a2.attachmentId
5988
+ };
5989
+ } catch (error) {
5990
+ const message = error instanceof Error ? error.message : String(error);
5991
+ return {
5992
+ success: false,
5993
+ attachmentId: (_b = signedUploads[index]) == null ? void 0 : _b.attachmentId,
5994
+ error: `${item.filePath}: ${message}`
5995
+ };
5996
+ }
5997
+ })
5998
+ )
5999
+ );
6000
+ return results.map((result, index) => {
6001
+ var _a2;
6002
+ if (result.status === "fulfilled") {
6003
+ return result.value;
6004
+ }
6005
+ const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
6006
+ return {
6007
+ success: false,
6008
+ attachmentId: (_a2 = signedUploads[index]) == null ? void 0 : _a2.attachmentId,
6009
+ error: message
6010
+ };
6011
+ });
6012
+ }
6013
+ };
5982
6014
 
5983
6015
  // src/index.ts
5984
6016
  var DEFAULT_API_URL = "https://code-api.supatest.ai";
@@ -5999,6 +6031,7 @@ var SupatestCypressReporter = class {
5999
6031
  cypressVersion;
6000
6032
  browserName;
6001
6033
  browserVersion;
6034
+ unregisterInterruptHandler;
6002
6035
  constructor(options = {}) {
6003
6036
  this.options = {
6004
6037
  projectId: options.projectId || process.env.SUPATEST_PROJECT_ID || "",
@@ -6060,6 +6093,10 @@ var SupatestCypressReporter = class {
6060
6093
  };
6061
6094
  const response = await this.client.createRun(runRequest);
6062
6095
  this.runId = response.runId;
6096
+ this.unregisterInterruptHandler = registerInterruptHandler(
6097
+ this.client,
6098
+ () => this.runId
6099
+ );
6063
6100
  logInfo(`Run ${this.runId} started (${details.specs.length} spec files)`);
6064
6101
  } catch (error) {
6065
6102
  this.errorCollector.recordError("RUN_CREATE", getErrorMessage2(error), { error });
@@ -6068,11 +6105,16 @@ var SupatestCypressReporter = class {
6068
6105
  }
6069
6106
  async onAfterSpec(spec, results) {
6070
6107
  if (this.disabled || !this.runId) return;
6108
+ if (!(results == null ? void 0 : results.tests)) {
6109
+ logInfo(`Spec ${spec.relative} had no test results (browser may have crashed), skipping`);
6110
+ return;
6111
+ }
6071
6112
  for (const test of results.tests) {
6072
6113
  await this.processTestResult(spec, test, results);
6073
6114
  }
6074
6115
  }
6075
6116
  async onAfterRun(results) {
6117
+ var _a2;
6076
6118
  if (this.disabled || !this.runId) {
6077
6119
  if (this.errorCollector.hasErrors()) {
6078
6120
  console.log(this.errorCollector.formatSummary());
@@ -6081,22 +6123,23 @@ var SupatestCypressReporter = class {
6081
6123
  }
6082
6124
  await Promise.allSettled(this.uploadQueue);
6083
6125
  const summary = {
6084
- total: results.totalTests,
6085
- passed: results.totalPassed,
6086
- failed: results.totalFailed,
6126
+ total: (results == null ? void 0 : results.totalTests) ?? 0,
6127
+ passed: (results == null ? void 0 : results.totalPassed) ?? 0,
6128
+ failed: (results == null ? void 0 : results.totalFailed) ?? 0,
6087
6129
  flaky: 0,
6088
- skipped: results.totalSkipped + results.totalPending,
6130
+ skipped: ((results == null ? void 0 : results.totalSkipped) ?? 0) + ((results == null ? void 0 : results.totalPending) ?? 0),
6089
6131
  timedOut: 0,
6090
6132
  interrupted: 0,
6091
- durationMs: results.totalDuration
6133
+ durationMs: (results == null ? void 0 : results.totalDuration) ?? 0
6092
6134
  };
6135
+ (_a2 = this.unregisterInterruptHandler) == null ? void 0 : _a2.call(this);
6093
6136
  try {
6094
6137
  await this.client.completeRun(this.runId, {
6095
- status: results.status === "finished" ? "complete" : "errored",
6096
- endedAt: results.endedTestsAt,
6138
+ status: (results == null ? void 0 : results.status) === "finished" ? "complete" : "errored",
6139
+ endedAt: (results == null ? void 0 : results.endedTestsAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
6097
6140
  summary,
6098
6141
  timing: {
6099
- totalDurationMs: results.totalDuration,
6142
+ totalDurationMs: (results == null ? void 0 : results.totalDuration) ?? 0,
6100
6143
  timeToFirstTest: this.firstTestStartTime ? this.firstTestStartTime - new Date(this.startedAt).getTime() : void 0,
6101
6144
  timeToFirstFailure: this.firstFailureTime ? this.firstFailureTime - new Date(this.startedAt).getTime() : void 0
6102
6145
  }
@@ -6112,16 +6155,17 @@ var SupatestCypressReporter = class {
6112
6155
  async processTestResult(spec, test, specResults) {
6113
6156
  const testId = this.getTestId(spec.relative, test.title);
6114
6157
  const status = this.mapCypressStatus(test.state);
6115
- if (!this.firstTestStartTime && test.attempts[0]) {
6116
- this.firstTestStartTime = new Date(test.attempts[0].wallClockStartedAt).getTime();
6158
+ const attempts = test.attempts ?? [];
6159
+ if (!this.firstTestStartTime && attempts[0]) {
6160
+ this.firstTestStartTime = new Date(attempts[0].wallClockStartedAt).getTime();
6117
6161
  }
6118
6162
  if (!this.firstFailureTime && status === "failed") {
6119
6163
  this.firstFailureTime = Date.now();
6120
6164
  }
6121
- const lastAttempt = test.attempts[test.attempts.length - 1];
6165
+ const lastAttempt = attempts[attempts.length - 1];
6122
6166
  const resultEntry = {
6123
- resultId: hashKey(`${testId}:${test.attempts.length - 1}`),
6124
- retry: test.attempts.length - 1,
6167
+ resultId: hashKey(`${testId}:${attempts.length - 1}`),
6168
+ retry: attempts.length - 1,
6125
6169
  status,
6126
6170
  startTime: (lastAttempt == null ? void 0 : lastAttempt.wallClockStartedAt) ?? void 0,
6127
6171
  durationMs: (lastAttempt == null ? void 0 : lastAttempt.wallClockDuration) || 0,
@@ -6146,8 +6190,8 @@ var SupatestCypressReporter = class {
6146
6190
  timeout: 0,
6147
6191
  retries: test.attempts.length - 1,
6148
6192
  status,
6149
- durationMs: test.attempts.reduce((sum, a) => sum + (a.wallClockDuration || 0), 0),
6150
- retryCount: test.attempts.length - 1,
6193
+ durationMs: attempts.reduce((sum, a) => sum + (a.wallClockDuration || 0), 0),
6194
+ retryCount: attempts.length - 1,
6151
6195
  results: [resultEntry],
6152
6196
  projectName: this.browserName || "electron",
6153
6197
  metadata
@@ -6185,9 +6229,10 @@ var SupatestCypressReporter = class {
6185
6229
  }
6186
6230
  }
6187
6231
  getOutcome(test) {
6232
+ var _a2, _b;
6188
6233
  if (test.state === "skipped" || test.state === "pending") return "skipped";
6189
6234
  if (test.state === "passed") {
6190
- if (test.attempts.length > 1 && test.attempts.some((a) => a.state === "failed")) {
6235
+ if ((((_a2 = test.attempts) == null ? void 0 : _a2.length) ?? 0) > 1 && ((_b = test.attempts) == null ? void 0 : _b.some((a) => a.state === "failed"))) {
6191
6236
  return "flaky";
6192
6237
  }
6193
6238
  return "expected";
@@ -6196,7 +6241,7 @@ var SupatestCypressReporter = class {
6196
6241
  }
6197
6242
  extractErrors(test) {
6198
6243
  const errors = [];
6199
- for (const attempt of test.attempts) {
6244
+ for (const attempt of test.attempts ?? []) {
6200
6245
  if (attempt.error) {
6201
6246
  errors.push({
6202
6247
  message: attempt.error.message,
@@ -6241,7 +6286,7 @@ var SupatestCypressReporter = class {
6241
6286
  }
6242
6287
  buildAttachmentMeta(test, specResults) {
6243
6288
  const attachments = [];
6244
- for (const screenshot of specResults.screenshots) {
6289
+ for (const screenshot of specResults.screenshots ?? []) {
6245
6290
  if (screenshot.path) {
6246
6291
  attachments.push({
6247
6292
  name: screenshot.name || "screenshot",
@@ -6265,7 +6310,7 @@ var SupatestCypressReporter = class {
6265
6310
  }
6266
6311
  async uploadAttachments(testId, testResultId, specResults, test) {
6267
6312
  const attachments = [];
6268
- for (const screenshot of specResults.screenshots) {
6313
+ for (const screenshot of specResults.screenshots ?? []) {
6269
6314
  if (screenshot.path && import_node_fs.default.existsSync(screenshot.path)) {
6270
6315
  attachments.push({
6271
6316
  path: screenshot.path,
@@ -6344,13 +6389,25 @@ var SupatestCypressReporter = class {
6344
6389
  function supatestPlugin(on, config, options = {}) {
6345
6390
  const reporter = new SupatestCypressReporter(options);
6346
6391
  on("before:run", async (details) => {
6347
- await reporter.onBeforeRun(details);
6392
+ try {
6393
+ await reporter.onBeforeRun(details);
6394
+ } catch (error) {
6395
+ logWarn(`Supatest reporter error in before:run: ${getErrorMessage2(error)}`);
6396
+ }
6348
6397
  });
6349
6398
  on("after:spec", async (spec, results) => {
6350
- await reporter.onAfterSpec(spec, results);
6399
+ try {
6400
+ await reporter.onAfterSpec(spec, results);
6401
+ } catch (error) {
6402
+ logWarn(`Supatest reporter error in after:spec: ${getErrorMessage2(error)}`);
6403
+ }
6351
6404
  });
6352
6405
  on("after:run", async (results) => {
6353
- await reporter.onAfterRun(results);
6406
+ try {
6407
+ await reporter.onAfterRun(results);
6408
+ } catch (error) {
6409
+ logWarn(`Supatest reporter error in after:run: ${getErrorMessage2(error)}`);
6410
+ }
6354
6411
  });
6355
6412
  }
6356
6413
  // Annotate the CommonJS export names for ESM import in node: