@supatest/cypress-reporter 0.0.4 → 0.0.6

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
@@ -916,7 +916,6 @@ __export(index_exports, {
916
916
  });
917
917
  module.exports = __toCommonJS(index_exports);
918
918
  var import_node_fs = __toESM(require("fs"), 1);
919
- var import_node_os = __toESM(require("os"), 1);
920
919
  var import_node_path = __toESM(require("path"), 1);
921
920
 
922
921
  // ../node_modules/.pnpm/simple-git@3.27.0/node_modules/simple-git/dist/esm/index.js
@@ -5471,8 +5470,9 @@ init_git_response_error();
5471
5470
  var simpleGit = gitInstanceFactory;
5472
5471
 
5473
5472
  // ../reporter-core/dist/index.js
5474
- var import_fs = __toESM(require("fs"), 1);
5475
5473
  var import_crypto = require("crypto");
5474
+ var import_fs = __toESM(require("fs"), 1);
5475
+ var import_os = __toESM(require("os"), 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]
@@ -5584,12 +5584,16 @@ var SupatestApiClient = class {
5584
5584
  this.options.timeoutMs
5585
5585
  );
5586
5586
  try {
5587
+ const headers = {
5588
+ "Content-Type": "application/json",
5589
+ Authorization: `Bearer ${this.options.apiKey}`
5590
+ };
5591
+ if (this.options.projectId) {
5592
+ headers["X-Project-Id"] = this.options.projectId;
5593
+ }
5587
5594
  const response = await _fetch(url, {
5588
5595
  method,
5589
- headers: {
5590
- "Content-Type": "application/json",
5591
- Authorization: `Bearer ${this.options.apiKey}`
5592
- },
5596
+ headers,
5593
5597
  body: body ? JSON.stringify(body) : void 0,
5594
5598
  signal: controller.signal
5595
5599
  });
@@ -5613,6 +5617,10 @@ var SupatestApiClient = class {
5613
5617
  console.log(JSON.stringify(data, null, 2));
5614
5618
  }
5615
5619
  };
5620
+ var DEFAULT_API_URL = "https://code-api.supatest.ai";
5621
+ var DEFAULT_MAX_CONCURRENT_UPLOADS = 5;
5622
+ var DEFAULT_RETRY_ATTEMPTS = 3;
5623
+ var DEFAULT_TIMEOUT_MS = 3e4;
5616
5624
  var ErrorCollector = class {
5617
5625
  errors = [];
5618
5626
  recordError(category, message, context) {
@@ -5778,105 +5786,37 @@ async function getLocalGitInfo(rootDir) {
5778
5786
  return {};
5779
5787
  }
5780
5788
  }
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
- });
5789
+ async function getGitInfoWithCI(rootDir) {
5790
+ const ciGitInfo = {
5791
+ branch: process.env.GITHUB_REF_NAME ?? process.env.GITHUB_HEAD_REF ?? process.env.CI_COMMIT_BRANCH ?? process.env.GITLAB_CI_COMMIT_BRANCH ?? process.env.GIT_BRANCH,
5792
+ commit: process.env.GITHUB_SHA ?? process.env.CI_COMMIT_SHA ?? process.env.GITLAB_CI_COMMIT_SHA ?? process.env.GIT_COMMIT,
5793
+ commitMessage: process.env.CI_COMMIT_MESSAGE ?? process.env.GITLAB_CI_COMMIT_MESSAGE,
5794
+ repo: process.env.GITHUB_REPOSITORY ?? process.env.CI_PROJECT_PATH ?? process.env.GITLAB_CI_PROJECT_PATH ?? process.env.GIT_REPO,
5795
+ author: process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_NAME,
5796
+ authorEmail: process.env.GITLAB_USER_EMAIL,
5797
+ tag: process.env.GITHUB_REF_TYPE === "tag" ? process.env.GITHUB_REF_NAME : void 0
5798
+ };
5799
+ if (ciGitInfo.branch || ciGitInfo.commit) {
5800
+ return ciGitInfo;
5872
5801
  }
5873
- };
5802
+ const localGitInfo = await getLocalGitInfo(rootDir);
5803
+ return {
5804
+ branch: ciGitInfo.branch ?? localGitInfo.branch,
5805
+ commit: ciGitInfo.commit ?? localGitInfo.commit,
5806
+ commitMessage: ciGitInfo.commitMessage ?? localGitInfo.commitMessage,
5807
+ repo: ciGitInfo.repo ?? localGitInfo.repo,
5808
+ author: ciGitInfo.author ?? localGitInfo.author,
5809
+ authorEmail: ciGitInfo.authorEmail ?? localGitInfo.authorEmail,
5810
+ tag: ciGitInfo.tag ?? localGitInfo.tag,
5811
+ dirty: localGitInfo.dirty
5812
+ };
5813
+ }
5874
5814
  function hashKey(value) {
5875
5815
  return (0, import_crypto.createHash)("sha256").update(value).digest("hex").slice(0, 12);
5876
5816
  }
5877
5817
  function getFileSize(filePath) {
5878
5818
  try {
5879
- return import_fs2.default.statSync(filePath).size;
5819
+ return import_fs.default.statSync(filePath).size;
5880
5820
  } catch {
5881
5821
  return 0;
5882
5822
  }
@@ -5979,9 +5919,197 @@ function getCIInfo() {
5979
5919
  }
5980
5920
  return void 0;
5981
5921
  }
5922
+ function getBaseEnvironmentInfo() {
5923
+ return {
5924
+ os: {
5925
+ platform: import_os.default.platform(),
5926
+ release: import_os.default.release(),
5927
+ arch: import_os.default.arch()
5928
+ },
5929
+ node: {
5930
+ version: process.version
5931
+ },
5932
+ machine: {
5933
+ cpus: import_os.default.cpus().length,
5934
+ memory: import_os.default.totalmem(),
5935
+ hostname: import_os.default.hostname()
5936
+ },
5937
+ ci: getCIInfo()
5938
+ };
5939
+ }
5940
+ function registerInterruptHandler(client, getRunId) {
5941
+ let completed = false;
5942
+ const handler = async (signal) => {
5943
+ if (completed) {
5944
+ return;
5945
+ }
5946
+ completed = true;
5947
+ const runId = getRunId();
5948
+ if (runId) {
5949
+ logWarn(`Received ${signal}, marking run ${runId} as interrupted`);
5950
+ try {
5951
+ await client.completeRun(runId, {
5952
+ status: "interrupted",
5953
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
5954
+ });
5955
+ } catch {
5956
+ }
5957
+ }
5958
+ process.removeListener(signal, sigintHandler);
5959
+ process.removeListener(signal, sigtermHandler);
5960
+ process.kill(process.pid, signal);
5961
+ };
5962
+ const sigintHandler = () => handler("SIGINT");
5963
+ const sigtermHandler = () => handler("SIGTERM");
5964
+ process.once("SIGINT", sigintHandler);
5965
+ process.once("SIGTERM", sigtermHandler);
5966
+ return () => {
5967
+ completed = true;
5968
+ process.removeListener("SIGINT", sigintHandler);
5969
+ process.removeListener("SIGTERM", sigtermHandler);
5970
+ };
5971
+ }
5972
+ var TAG_PATTERNS = {
5973
+ owner: /@owner:([^\s@]+)/,
5974
+ priority: /@priority:(critical|high|medium|low)/i,
5975
+ feature: /@feature:([^\s@]+)/,
5976
+ ticketId: /@ticket:([^\s@]+)/,
5977
+ testType: /@test_type:(smoke|e2e|regression|integration|unit)/i
5978
+ };
5979
+ var KNOWN_KEYS = ["owner", "priority", "feature", "ticket", "test_type", "id", "slow", "flaky"];
5980
+ function parseTestMetadata(tags) {
5981
+ const metadata = {
5982
+ isSlow: false,
5983
+ isFlakyTagged: false,
5984
+ customMetadata: {}
5985
+ };
5986
+ for (const tag of tags) {
5987
+ const lowerTag = tag.toLowerCase();
5988
+ if (lowerTag === "@slow") {
5989
+ metadata.isSlow = true;
5990
+ } else if (lowerTag === "@flaky") {
5991
+ metadata.isFlakyTagged = true;
5992
+ }
5993
+ }
5994
+ for (const tag of tags) {
5995
+ for (const [key, pattern] of Object.entries(TAG_PATTERNS)) {
5996
+ const match = tag.match(pattern);
5997
+ if (match) {
5998
+ const value = match[1];
5999
+ if (key === "priority") {
6000
+ metadata.priority = value.toLowerCase();
6001
+ } else if (key === "testType") {
6002
+ metadata.testType = value.toLowerCase();
6003
+ } else {
6004
+ metadata[key] = value;
6005
+ }
6006
+ }
6007
+ }
6008
+ const customMatch = tag.match(/@([a-zA-Z][a-zA-Z0-9_-]*):([^\s@]+)/);
6009
+ if (customMatch) {
6010
+ const [, key, value] = customMatch;
6011
+ if (!KNOWN_KEYS.includes(key.toLowerCase())) {
6012
+ metadata.customMetadata[key] = value;
6013
+ }
6014
+ }
6015
+ }
6016
+ return metadata;
6017
+ }
6018
+ var AttachmentUploader = class {
6019
+ options;
6020
+ constructor(options) {
6021
+ this.options = options;
6022
+ }
6023
+ async upload(signedUrl, filePath, contentType) {
6024
+ if (this.options.dryRun) {
6025
+ try {
6026
+ const stats = import_fs2.default.statSync(filePath);
6027
+ console.log(
6028
+ `[supatest][dry-run] Would upload ${filePath} (${stats.size} bytes) to S3`
6029
+ );
6030
+ } catch (error) {
6031
+ const message = error instanceof Error ? error.message : String(error);
6032
+ console.warn(
6033
+ `[supatest][dry-run] Cannot access file ${filePath}: ${message}`
6034
+ );
6035
+ }
6036
+ return;
6037
+ }
6038
+ let fileBuffer;
6039
+ try {
6040
+ fileBuffer = await import_fs2.default.promises.readFile(filePath);
6041
+ } catch (error) {
6042
+ const message = error instanceof Error ? error.message : String(error);
6043
+ throw new Error(`Failed to read file ${filePath}: ${message}`);
6044
+ }
6045
+ await withRetry(async () => {
6046
+ const controller = new _AbortController();
6047
+ const timeoutId = setTimeout(
6048
+ () => controller.abort(),
6049
+ this.options.timeoutMs
6050
+ );
6051
+ try {
6052
+ const response = await _fetch(signedUrl, {
6053
+ method: "PUT",
6054
+ headers: {
6055
+ "Content-Type": contentType,
6056
+ "Content-Length": String(fileBuffer.length)
6057
+ },
6058
+ body: fileBuffer,
6059
+ signal: controller.signal
6060
+ });
6061
+ if (!response.ok) {
6062
+ const error = new Error(
6063
+ `S3 upload failed: ${response.status} ${response.statusText}`
6064
+ );
6065
+ error.statusCode = response.status;
6066
+ throw error;
6067
+ }
6068
+ } finally {
6069
+ clearTimeout(timeoutId);
6070
+ }
6071
+ }, defaultRetryConfig);
6072
+ }
6073
+ async uploadBatch(items, signedUploads) {
6074
+ const pLimit = (await import("p-limit")).default;
6075
+ const limit = pLimit(this.options.maxConcurrent);
6076
+ const results = await Promise.allSettled(
6077
+ items.map(
6078
+ (item, index) => limit(async () => {
6079
+ var _a2, _b;
6080
+ try {
6081
+ await this.upload(item.signedUrl, item.filePath, item.contentType);
6082
+ return {
6083
+ success: true,
6084
+ attachmentId: (_a2 = signedUploads[index]) == null ? void 0 : _a2.attachmentId
6085
+ };
6086
+ } catch (error) {
6087
+ const message = error instanceof Error ? error.message : String(error);
6088
+ return {
6089
+ success: false,
6090
+ attachmentId: (_b = signedUploads[index]) == null ? void 0 : _b.attachmentId,
6091
+ error: `${item.filePath}: ${message}`
6092
+ };
6093
+ }
6094
+ })
6095
+ )
6096
+ );
6097
+ return results.map((result, index) => {
6098
+ var _a2;
6099
+ if (result.status === "fulfilled") {
6100
+ return result.value;
6101
+ }
6102
+ const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
6103
+ return {
6104
+ success: false,
6105
+ attachmentId: (_a2 = signedUploads[index]) == null ? void 0 : _a2.attachmentId,
6106
+ error: message
6107
+ };
6108
+ });
6109
+ }
6110
+ };
5982
6111
 
5983
6112
  // src/index.ts
5984
- var DEFAULT_API_URL = "https://code-api.supatest.ai";
5985
6113
  var SupatestCypressReporter = class {
5986
6114
  options;
5987
6115
  client;
@@ -5999,15 +6127,16 @@ var SupatestCypressReporter = class {
5999
6127
  cypressVersion;
6000
6128
  browserName;
6001
6129
  browserVersion;
6130
+ unregisterInterruptHandler;
6002
6131
  constructor(options = {}) {
6003
6132
  this.options = {
6004
6133
  projectId: options.projectId || process.env.SUPATEST_PROJECT_ID || "",
6005
6134
  apiKey: options.apiKey || process.env.SUPATEST_API_KEY || "",
6006
6135
  apiUrl: options.apiUrl || process.env.SUPATEST_API_URL || DEFAULT_API_URL,
6007
6136
  uploadAssets: options.uploadAssets ?? true,
6008
- maxConcurrentUploads: options.maxConcurrentUploads ?? 5,
6009
- retryAttempts: options.retryAttempts ?? 3,
6010
- timeoutMs: options.timeoutMs ?? 3e4,
6137
+ maxConcurrentUploads: options.maxConcurrentUploads ?? DEFAULT_MAX_CONCURRENT_UPLOADS,
6138
+ retryAttempts: options.retryAttempts ?? DEFAULT_RETRY_ATTEMPTS,
6139
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
6011
6140
  dryRun: options.dryRun ?? process.env.SUPATEST_DRY_RUN === "true"
6012
6141
  };
6013
6142
  }
@@ -6026,6 +6155,7 @@ var SupatestCypressReporter = class {
6026
6155
  this.client = new SupatestApiClient({
6027
6156
  apiKey: this.options.apiKey,
6028
6157
  apiUrl: this.options.apiUrl,
6158
+ projectId: this.options.projectId,
6029
6159
  timeoutMs: this.options.timeoutMs,
6030
6160
  retryAttempts: this.options.retryAttempts,
6031
6161
  dryRun: this.options.dryRun
@@ -6060,6 +6190,10 @@ var SupatestCypressReporter = class {
6060
6190
  };
6061
6191
  const response = await this.client.createRun(runRequest);
6062
6192
  this.runId = response.runId;
6193
+ this.unregisterInterruptHandler = registerInterruptHandler(
6194
+ this.client,
6195
+ () => this.runId
6196
+ );
6063
6197
  logInfo(`Run ${this.runId} started (${details.specs.length} spec files)`);
6064
6198
  } catch (error) {
6065
6199
  this.errorCollector.recordError("RUN_CREATE", getErrorMessage2(error), { error });
@@ -6068,11 +6202,16 @@ var SupatestCypressReporter = class {
6068
6202
  }
6069
6203
  async onAfterSpec(spec, results) {
6070
6204
  if (this.disabled || !this.runId) return;
6205
+ if (!(results == null ? void 0 : results.tests)) {
6206
+ logInfo(`Spec ${spec.relative} had no test results (browser may have crashed), skipping`);
6207
+ return;
6208
+ }
6071
6209
  for (const test of results.tests) {
6072
6210
  await this.processTestResult(spec, test, results);
6073
6211
  }
6074
6212
  }
6075
6213
  async onAfterRun(results) {
6214
+ var _a2;
6076
6215
  if (this.disabled || !this.runId) {
6077
6216
  if (this.errorCollector.hasErrors()) {
6078
6217
  console.log(this.errorCollector.formatSummary());
@@ -6081,22 +6220,23 @@ var SupatestCypressReporter = class {
6081
6220
  }
6082
6221
  await Promise.allSettled(this.uploadQueue);
6083
6222
  const summary = {
6084
- total: results.totalTests,
6085
- passed: results.totalPassed,
6086
- failed: results.totalFailed,
6223
+ total: (results == null ? void 0 : results.totalTests) ?? 0,
6224
+ passed: (results == null ? void 0 : results.totalPassed) ?? 0,
6225
+ failed: (results == null ? void 0 : results.totalFailed) ?? 0,
6087
6226
  flaky: 0,
6088
- skipped: results.totalSkipped + results.totalPending,
6227
+ skipped: ((results == null ? void 0 : results.totalSkipped) ?? 0) + ((results == null ? void 0 : results.totalPending) ?? 0),
6089
6228
  timedOut: 0,
6090
6229
  interrupted: 0,
6091
- durationMs: results.totalDuration
6230
+ durationMs: (results == null ? void 0 : results.totalDuration) ?? 0
6092
6231
  };
6232
+ (_a2 = this.unregisterInterruptHandler) == null ? void 0 : _a2.call(this);
6093
6233
  try {
6094
6234
  await this.client.completeRun(this.runId, {
6095
- status: results.status === "finished" ? "complete" : "errored",
6096
- endedAt: results.endedTestsAt,
6235
+ status: (results == null ? void 0 : results.status) === "finished" ? "complete" : "errored",
6236
+ endedAt: (results == null ? void 0 : results.endedTestsAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
6097
6237
  summary,
6098
6238
  timing: {
6099
- totalDurationMs: results.totalDuration,
6239
+ totalDurationMs: (results == null ? void 0 : results.totalDuration) ?? 0,
6100
6240
  timeToFirstTest: this.firstTestStartTime ? this.firstTestStartTime - new Date(this.startedAt).getTime() : void 0,
6101
6241
  timeToFirstFailure: this.firstFailureTime ? this.firstFailureTime - new Date(this.startedAt).getTime() : void 0
6102
6242
  }
@@ -6112,16 +6252,17 @@ var SupatestCypressReporter = class {
6112
6252
  async processTestResult(spec, test, specResults) {
6113
6253
  const testId = this.getTestId(spec.relative, test.title);
6114
6254
  const status = this.mapCypressStatus(test.state);
6115
- if (!this.firstTestStartTime && test.attempts[0]) {
6116
- this.firstTestStartTime = new Date(test.attempts[0].wallClockStartedAt).getTime();
6255
+ const attempts = test.attempts ?? [];
6256
+ if (!this.firstTestStartTime && attempts[0]) {
6257
+ this.firstTestStartTime = new Date(attempts[0].wallClockStartedAt).getTime();
6117
6258
  }
6118
6259
  if (!this.firstFailureTime && status === "failed") {
6119
6260
  this.firstFailureTime = Date.now();
6120
6261
  }
6121
- const lastAttempt = test.attempts[test.attempts.length - 1];
6262
+ const lastAttempt = attempts[attempts.length - 1];
6122
6263
  const resultEntry = {
6123
- resultId: hashKey(`${testId}:${test.attempts.length - 1}`),
6124
- retry: test.attempts.length - 1,
6264
+ resultId: hashKey(`${testId}:${attempts.length - 1}`),
6265
+ retry: attempts.length - 1,
6125
6266
  status,
6126
6267
  startTime: (lastAttempt == null ? void 0 : lastAttempt.wallClockStartedAt) ?? void 0,
6127
6268
  durationMs: (lastAttempt == null ? void 0 : lastAttempt.wallClockDuration) || 0,
@@ -6146,8 +6287,8 @@ var SupatestCypressReporter = class {
6146
6287
  timeout: 0,
6147
6288
  retries: test.attempts.length - 1,
6148
6289
  status,
6149
- durationMs: test.attempts.reduce((sum, a) => sum + (a.wallClockDuration || 0), 0),
6150
- retryCount: test.attempts.length - 1,
6290
+ durationMs: attempts.reduce((sum, a) => sum + (a.wallClockDuration || 0), 0),
6291
+ retryCount: attempts.length - 1,
6151
6292
  results: [resultEntry],
6152
6293
  projectName: this.browserName || "electron",
6153
6294
  metadata
@@ -6185,9 +6326,10 @@ var SupatestCypressReporter = class {
6185
6326
  }
6186
6327
  }
6187
6328
  getOutcome(test) {
6329
+ var _a2, _b;
6188
6330
  if (test.state === "skipped" || test.state === "pending") return "skipped";
6189
6331
  if (test.state === "passed") {
6190
- if (test.attempts.length > 1 && test.attempts.some((a) => a.state === "failed")) {
6332
+ if ((((_a2 = test.attempts) == null ? void 0 : _a2.length) ?? 0) > 1 && ((_b = test.attempts) == null ? void 0 : _b.some((a) => a.state === "failed"))) {
6191
6333
  return "flaky";
6192
6334
  }
6193
6335
  return "expected";
@@ -6196,7 +6338,7 @@ var SupatestCypressReporter = class {
6196
6338
  }
6197
6339
  extractErrors(test) {
6198
6340
  const errors = [];
6199
- for (const attempt of test.attempts) {
6341
+ for (const attempt of test.attempts ?? []) {
6200
6342
  if (attempt.error) {
6201
6343
  errors.push({
6202
6344
  message: attempt.error.message,
@@ -6219,29 +6361,11 @@ var SupatestCypressReporter = class {
6219
6361
  return tags;
6220
6362
  }
6221
6363
  parseTestMetadata(tags) {
6222
- const metadata = {
6223
- isSlow: false,
6224
- isFlakyTagged: false,
6225
- customMetadata: {}
6226
- };
6227
- for (const tag of tags) {
6228
- const lower = tag.toLowerCase();
6229
- if (lower === "@slow") metadata.isSlow = true;
6230
- if (lower === "@flaky") metadata.isFlakyTagged = true;
6231
- const ownerMatch = tag.match(/@owner:([^\s@]+)/i);
6232
- if (ownerMatch) metadata.owner = ownerMatch[1];
6233
- const priorityMatch = tag.match(/@priority:(critical|high|medium|low)/i);
6234
- if (priorityMatch) metadata.priority = priorityMatch[1].toLowerCase();
6235
- const featureMatch = tag.match(/@feature:([^\s@]+)/i);
6236
- if (featureMatch) metadata.feature = featureMatch[1];
6237
- const typeMatch = tag.match(/@test_type:(smoke|e2e|regression|integration|unit)/i);
6238
- if (typeMatch) metadata.testType = typeMatch[1].toLowerCase();
6239
- }
6240
- return metadata;
6364
+ return parseTestMetadata(tags);
6241
6365
  }
6242
6366
  buildAttachmentMeta(test, specResults) {
6243
6367
  const attachments = [];
6244
- for (const screenshot of specResults.screenshots) {
6368
+ for (const screenshot of specResults.screenshots ?? []) {
6245
6369
  if (screenshot.path) {
6246
6370
  attachments.push({
6247
6371
  name: screenshot.name || "screenshot",
@@ -6265,7 +6389,7 @@ var SupatestCypressReporter = class {
6265
6389
  }
6266
6390
  async uploadAttachments(testId, testResultId, specResults, test) {
6267
6391
  const attachments = [];
6268
- for (const screenshot of specResults.screenshots) {
6392
+ for (const screenshot of specResults.screenshots ?? []) {
6269
6393
  if (screenshot.path && import_node_fs.default.existsSync(screenshot.path)) {
6270
6394
  attachments.push({
6271
6395
  path: screenshot.path,
@@ -6302,55 +6426,65 @@ var SupatestCypressReporter = class {
6302
6426
  filePath: attachments[i].path,
6303
6427
  contentType: attachments[i].meta.contentType
6304
6428
  }));
6305
- await this.uploader.uploadBatch(uploadItems, uploads);
6429
+ const results = await this.uploader.uploadBatch(uploadItems, uploads);
6430
+ const failures = results.filter((r) => !r.success);
6431
+ if (failures.length > 0) {
6432
+ failures.forEach((failure) => {
6433
+ const attachment = attachments.find(
6434
+ (_, i) => {
6435
+ var _a2;
6436
+ return ((_a2 = uploads[i]) == null ? void 0 : _a2.attachmentId) === failure.attachmentId;
6437
+ }
6438
+ );
6439
+ this.errorCollector.recordError(
6440
+ "ATTACHMENT_UPLOAD",
6441
+ failure.error || "Upload failed",
6442
+ {
6443
+ attachmentName: attachment == null ? void 0 : attachment.meta.name,
6444
+ filePath: attachment == null ? void 0 : attachment.path,
6445
+ error: failure.error
6446
+ }
6447
+ );
6448
+ });
6449
+ }
6306
6450
  } catch (error) {
6307
6451
  this.errorCollector.recordError("ATTACHMENT_SIGN", getErrorMessage2(error), { error });
6308
6452
  }
6309
6453
  }
6310
6454
  getEnvironmentInfo() {
6311
6455
  return {
6312
- os: {
6313
- platform: import_node_os.default.platform(),
6314
- release: import_node_os.default.release(),
6315
- arch: import_node_os.default.arch()
6316
- },
6317
- node: {
6318
- version: process.version
6319
- },
6320
- machine: {
6321
- cpus: import_node_os.default.cpus().length,
6322
- memory: import_node_os.default.totalmem(),
6323
- hostname: import_node_os.default.hostname()
6324
- },
6456
+ ...getBaseEnvironmentInfo(),
6325
6457
  cypress: {
6326
6458
  version: this.cypressVersion || "unknown"
6327
- },
6328
- ci: getCIInfo()
6459
+ }
6329
6460
  };
6330
6461
  }
6331
6462
  async getGitInfo() {
6332
- const ciGitInfo = {
6333
- branch: process.env.GITHUB_REF_NAME ?? process.env.CI_COMMIT_BRANCH ?? process.env.GIT_BRANCH,
6334
- commit: process.env.GITHUB_SHA ?? process.env.CI_COMMIT_SHA ?? process.env.GIT_COMMIT,
6335
- repo: process.env.GITHUB_REPOSITORY ?? process.env.CI_PROJECT_PATH,
6336
- author: process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_NAME
6337
- };
6338
- if (ciGitInfo.branch || ciGitInfo.commit) {
6339
- return ciGitInfo;
6340
- }
6341
- return await getLocalGitInfo(this.rootDir);
6463
+ return getGitInfoWithCI(this.rootDir);
6342
6464
  }
6343
6465
  };
6344
6466
  function supatestPlugin(on, config, options = {}) {
6345
6467
  const reporter = new SupatestCypressReporter(options);
6346
6468
  on("before:run", async (details) => {
6347
- await reporter.onBeforeRun(details);
6469
+ try {
6470
+ await reporter.onBeforeRun(details);
6471
+ } catch (error) {
6472
+ logWarn(`Supatest reporter error in before:run: ${getErrorMessage2(error)}`);
6473
+ }
6348
6474
  });
6349
6475
  on("after:spec", async (spec, results) => {
6350
- await reporter.onAfterSpec(spec, results);
6476
+ try {
6477
+ await reporter.onAfterSpec(spec, results);
6478
+ } catch (error) {
6479
+ logWarn(`Supatest reporter error in after:spec: ${getErrorMessage2(error)}`);
6480
+ }
6351
6481
  });
6352
6482
  on("after:run", async (results) => {
6353
- await reporter.onAfterRun(results);
6483
+ try {
6484
+ await reporter.onAfterRun(results);
6485
+ } catch (error) {
6486
+ logWarn(`Supatest reporter error in after:run: ${getErrorMessage2(error)}`);
6487
+ }
6354
6488
  });
6355
6489
  }
6356
6490
  // Annotate the CommonJS export names for ESM import in node: