@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.js CHANGED
@@ -909,8 +909,7 @@ var require_dist2 = __commonJS({
909
909
  });
910
910
 
911
911
  // src/index.ts
912
- import fs from "fs";
913
- import os from "os";
912
+ import fs2 from "fs";
914
913
  import path from "path";
915
914
 
916
915
  // ../node_modules/.pnpm/simple-git@3.27.0/node_modules/simple-git/dist/esm/index.js
@@ -5465,8 +5464,9 @@ init_git_response_error();
5465
5464
  var simpleGit = gitInstanceFactory;
5466
5465
 
5467
5466
  // ../reporter-core/dist/index.js
5468
- import fs2 from "fs";
5469
5467
  import { createHash } from "crypto";
5468
+ import fs from "fs";
5469
+ import os from "os";
5470
5470
  import fs3 from "fs";
5471
5471
  var __require2 = /* @__PURE__ */ ((x) => typeof __require !== "undefined" ? __require : typeof Proxy !== "undefined" ? new Proxy(x, {
5472
5472
  get: (a, b) => (typeof __require !== "undefined" ? __require : a)[b]
@@ -5578,12 +5578,16 @@ var SupatestApiClient = class {
5578
5578
  this.options.timeoutMs
5579
5579
  );
5580
5580
  try {
5581
+ const headers = {
5582
+ "Content-Type": "application/json",
5583
+ Authorization: `Bearer ${this.options.apiKey}`
5584
+ };
5585
+ if (this.options.projectId) {
5586
+ headers["X-Project-Id"] = this.options.projectId;
5587
+ }
5581
5588
  const response = await _fetch(url, {
5582
5589
  method,
5583
- headers: {
5584
- "Content-Type": "application/json",
5585
- Authorization: `Bearer ${this.options.apiKey}`
5586
- },
5590
+ headers,
5587
5591
  body: body ? JSON.stringify(body) : void 0,
5588
5592
  signal: controller.signal
5589
5593
  });
@@ -5607,6 +5611,10 @@ var SupatestApiClient = class {
5607
5611
  console.log(JSON.stringify(data, null, 2));
5608
5612
  }
5609
5613
  };
5614
+ var DEFAULT_API_URL = "https://code-api.supatest.ai";
5615
+ var DEFAULT_MAX_CONCURRENT_UPLOADS = 5;
5616
+ var DEFAULT_RETRY_ATTEMPTS = 3;
5617
+ var DEFAULT_TIMEOUT_MS = 3e4;
5610
5618
  var ErrorCollector = class {
5611
5619
  errors = [];
5612
5620
  recordError(category, message, context) {
@@ -5772,105 +5780,37 @@ async function getLocalGitInfo(rootDir) {
5772
5780
  return {};
5773
5781
  }
5774
5782
  }
5775
- var AttachmentUploader = class {
5776
- options;
5777
- constructor(options) {
5778
- this.options = options;
5779
- }
5780
- async upload(signedUrl, filePath, contentType) {
5781
- if (this.options.dryRun) {
5782
- try {
5783
- const stats = fs2.statSync(filePath);
5784
- console.log(
5785
- `[supatest][dry-run] Would upload ${filePath} (${stats.size} bytes) to S3`
5786
- );
5787
- } catch (error) {
5788
- const message = error instanceof Error ? error.message : String(error);
5789
- console.warn(
5790
- `[supatest][dry-run] Cannot access file ${filePath}: ${message}`
5791
- );
5792
- }
5793
- return;
5794
- }
5795
- let fileBuffer;
5796
- try {
5797
- fileBuffer = await fs2.promises.readFile(filePath);
5798
- } catch (error) {
5799
- const message = error instanceof Error ? error.message : String(error);
5800
- throw new Error(`Failed to read file ${filePath}: ${message}`);
5801
- }
5802
- await withRetry(async () => {
5803
- const controller = new _AbortController();
5804
- const timeoutId = setTimeout(
5805
- () => controller.abort(),
5806
- this.options.timeoutMs
5807
- );
5808
- try {
5809
- const response = await _fetch(signedUrl, {
5810
- method: "PUT",
5811
- headers: {
5812
- "Content-Type": contentType,
5813
- "Content-Length": String(fileBuffer.length)
5814
- },
5815
- body: fileBuffer,
5816
- signal: controller.signal
5817
- });
5818
- if (!response.ok) {
5819
- const error = new Error(
5820
- `S3 upload failed: ${response.status} ${response.statusText}`
5821
- );
5822
- error.statusCode = response.status;
5823
- throw error;
5824
- }
5825
- } finally {
5826
- clearTimeout(timeoutId);
5827
- }
5828
- }, defaultRetryConfig);
5829
- }
5830
- async uploadBatch(items, signedUploads) {
5831
- const pLimit = (await import("p-limit")).default;
5832
- const limit = pLimit(this.options.maxConcurrent);
5833
- const results = await Promise.allSettled(
5834
- items.map(
5835
- (item, index) => limit(async () => {
5836
- var _a2, _b;
5837
- try {
5838
- await this.upload(item.signedUrl, item.filePath, item.contentType);
5839
- return {
5840
- success: true,
5841
- attachmentId: (_a2 = signedUploads[index]) == null ? void 0 : _a2.attachmentId
5842
- };
5843
- } catch (error) {
5844
- const message = error instanceof Error ? error.message : String(error);
5845
- return {
5846
- success: false,
5847
- attachmentId: (_b = signedUploads[index]) == null ? void 0 : _b.attachmentId,
5848
- error: `${item.filePath}: ${message}`
5849
- };
5850
- }
5851
- })
5852
- )
5853
- );
5854
- return results.map((result, index) => {
5855
- var _a2;
5856
- if (result.status === "fulfilled") {
5857
- return result.value;
5858
- }
5859
- const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
5860
- return {
5861
- success: false,
5862
- attachmentId: (_a2 = signedUploads[index]) == null ? void 0 : _a2.attachmentId,
5863
- error: message
5864
- };
5865
- });
5783
+ async function getGitInfoWithCI(rootDir) {
5784
+ const ciGitInfo = {
5785
+ 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,
5786
+ commit: process.env.GITHUB_SHA ?? process.env.CI_COMMIT_SHA ?? process.env.GITLAB_CI_COMMIT_SHA ?? process.env.GIT_COMMIT,
5787
+ commitMessage: process.env.CI_COMMIT_MESSAGE ?? process.env.GITLAB_CI_COMMIT_MESSAGE,
5788
+ repo: process.env.GITHUB_REPOSITORY ?? process.env.CI_PROJECT_PATH ?? process.env.GITLAB_CI_PROJECT_PATH ?? process.env.GIT_REPO,
5789
+ author: process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_NAME,
5790
+ authorEmail: process.env.GITLAB_USER_EMAIL,
5791
+ tag: process.env.GITHUB_REF_TYPE === "tag" ? process.env.GITHUB_REF_NAME : void 0
5792
+ };
5793
+ if (ciGitInfo.branch || ciGitInfo.commit) {
5794
+ return ciGitInfo;
5866
5795
  }
5867
- };
5796
+ const localGitInfo = await getLocalGitInfo(rootDir);
5797
+ return {
5798
+ branch: ciGitInfo.branch ?? localGitInfo.branch,
5799
+ commit: ciGitInfo.commit ?? localGitInfo.commit,
5800
+ commitMessage: ciGitInfo.commitMessage ?? localGitInfo.commitMessage,
5801
+ repo: ciGitInfo.repo ?? localGitInfo.repo,
5802
+ author: ciGitInfo.author ?? localGitInfo.author,
5803
+ authorEmail: ciGitInfo.authorEmail ?? localGitInfo.authorEmail,
5804
+ tag: ciGitInfo.tag ?? localGitInfo.tag,
5805
+ dirty: localGitInfo.dirty
5806
+ };
5807
+ }
5868
5808
  function hashKey(value) {
5869
5809
  return createHash("sha256").update(value).digest("hex").slice(0, 12);
5870
5810
  }
5871
5811
  function getFileSize(filePath) {
5872
5812
  try {
5873
- return fs3.statSync(filePath).size;
5813
+ return fs.statSync(filePath).size;
5874
5814
  } catch {
5875
5815
  return 0;
5876
5816
  }
@@ -5973,9 +5913,197 @@ function getCIInfo() {
5973
5913
  }
5974
5914
  return void 0;
5975
5915
  }
5916
+ function getBaseEnvironmentInfo() {
5917
+ return {
5918
+ os: {
5919
+ platform: os.platform(),
5920
+ release: os.release(),
5921
+ arch: os.arch()
5922
+ },
5923
+ node: {
5924
+ version: process.version
5925
+ },
5926
+ machine: {
5927
+ cpus: os.cpus().length,
5928
+ memory: os.totalmem(),
5929
+ hostname: os.hostname()
5930
+ },
5931
+ ci: getCIInfo()
5932
+ };
5933
+ }
5934
+ function registerInterruptHandler(client, getRunId) {
5935
+ let completed = false;
5936
+ const handler = async (signal) => {
5937
+ if (completed) {
5938
+ return;
5939
+ }
5940
+ completed = true;
5941
+ const runId = getRunId();
5942
+ if (runId) {
5943
+ logWarn(`Received ${signal}, marking run ${runId} as interrupted`);
5944
+ try {
5945
+ await client.completeRun(runId, {
5946
+ status: "interrupted",
5947
+ endedAt: (/* @__PURE__ */ new Date()).toISOString()
5948
+ });
5949
+ } catch {
5950
+ }
5951
+ }
5952
+ process.removeListener(signal, sigintHandler);
5953
+ process.removeListener(signal, sigtermHandler);
5954
+ process.kill(process.pid, signal);
5955
+ };
5956
+ const sigintHandler = () => handler("SIGINT");
5957
+ const sigtermHandler = () => handler("SIGTERM");
5958
+ process.once("SIGINT", sigintHandler);
5959
+ process.once("SIGTERM", sigtermHandler);
5960
+ return () => {
5961
+ completed = true;
5962
+ process.removeListener("SIGINT", sigintHandler);
5963
+ process.removeListener("SIGTERM", sigtermHandler);
5964
+ };
5965
+ }
5966
+ var TAG_PATTERNS = {
5967
+ owner: /@owner:([^\s@]+)/,
5968
+ priority: /@priority:(critical|high|medium|low)/i,
5969
+ feature: /@feature:([^\s@]+)/,
5970
+ ticketId: /@ticket:([^\s@]+)/,
5971
+ testType: /@test_type:(smoke|e2e|regression|integration|unit)/i
5972
+ };
5973
+ var KNOWN_KEYS = ["owner", "priority", "feature", "ticket", "test_type", "id", "slow", "flaky"];
5974
+ function parseTestMetadata(tags) {
5975
+ const metadata = {
5976
+ isSlow: false,
5977
+ isFlakyTagged: false,
5978
+ customMetadata: {}
5979
+ };
5980
+ for (const tag of tags) {
5981
+ const lowerTag = tag.toLowerCase();
5982
+ if (lowerTag === "@slow") {
5983
+ metadata.isSlow = true;
5984
+ } else if (lowerTag === "@flaky") {
5985
+ metadata.isFlakyTagged = true;
5986
+ }
5987
+ }
5988
+ for (const tag of tags) {
5989
+ for (const [key, pattern] of Object.entries(TAG_PATTERNS)) {
5990
+ const match = tag.match(pattern);
5991
+ if (match) {
5992
+ const value = match[1];
5993
+ if (key === "priority") {
5994
+ metadata.priority = value.toLowerCase();
5995
+ } else if (key === "testType") {
5996
+ metadata.testType = value.toLowerCase();
5997
+ } else {
5998
+ metadata[key] = value;
5999
+ }
6000
+ }
6001
+ }
6002
+ const customMatch = tag.match(/@([a-zA-Z][a-zA-Z0-9_-]*):([^\s@]+)/);
6003
+ if (customMatch) {
6004
+ const [, key, value] = customMatch;
6005
+ if (!KNOWN_KEYS.includes(key.toLowerCase())) {
6006
+ metadata.customMetadata[key] = value;
6007
+ }
6008
+ }
6009
+ }
6010
+ return metadata;
6011
+ }
6012
+ var AttachmentUploader = class {
6013
+ options;
6014
+ constructor(options) {
6015
+ this.options = options;
6016
+ }
6017
+ async upload(signedUrl, filePath, contentType) {
6018
+ if (this.options.dryRun) {
6019
+ try {
6020
+ const stats = fs3.statSync(filePath);
6021
+ console.log(
6022
+ `[supatest][dry-run] Would upload ${filePath} (${stats.size} bytes) to S3`
6023
+ );
6024
+ } catch (error) {
6025
+ const message = error instanceof Error ? error.message : String(error);
6026
+ console.warn(
6027
+ `[supatest][dry-run] Cannot access file ${filePath}: ${message}`
6028
+ );
6029
+ }
6030
+ return;
6031
+ }
6032
+ let fileBuffer;
6033
+ try {
6034
+ fileBuffer = await fs3.promises.readFile(filePath);
6035
+ } catch (error) {
6036
+ const message = error instanceof Error ? error.message : String(error);
6037
+ throw new Error(`Failed to read file ${filePath}: ${message}`);
6038
+ }
6039
+ await withRetry(async () => {
6040
+ const controller = new _AbortController();
6041
+ const timeoutId = setTimeout(
6042
+ () => controller.abort(),
6043
+ this.options.timeoutMs
6044
+ );
6045
+ try {
6046
+ const response = await _fetch(signedUrl, {
6047
+ method: "PUT",
6048
+ headers: {
6049
+ "Content-Type": contentType,
6050
+ "Content-Length": String(fileBuffer.length)
6051
+ },
6052
+ body: fileBuffer,
6053
+ signal: controller.signal
6054
+ });
6055
+ if (!response.ok) {
6056
+ const error = new Error(
6057
+ `S3 upload failed: ${response.status} ${response.statusText}`
6058
+ );
6059
+ error.statusCode = response.status;
6060
+ throw error;
6061
+ }
6062
+ } finally {
6063
+ clearTimeout(timeoutId);
6064
+ }
6065
+ }, defaultRetryConfig);
6066
+ }
6067
+ async uploadBatch(items, signedUploads) {
6068
+ const pLimit = (await import("p-limit")).default;
6069
+ const limit = pLimit(this.options.maxConcurrent);
6070
+ const results = await Promise.allSettled(
6071
+ items.map(
6072
+ (item, index) => limit(async () => {
6073
+ var _a2, _b;
6074
+ try {
6075
+ await this.upload(item.signedUrl, item.filePath, item.contentType);
6076
+ return {
6077
+ success: true,
6078
+ attachmentId: (_a2 = signedUploads[index]) == null ? void 0 : _a2.attachmentId
6079
+ };
6080
+ } catch (error) {
6081
+ const message = error instanceof Error ? error.message : String(error);
6082
+ return {
6083
+ success: false,
6084
+ attachmentId: (_b = signedUploads[index]) == null ? void 0 : _b.attachmentId,
6085
+ error: `${item.filePath}: ${message}`
6086
+ };
6087
+ }
6088
+ })
6089
+ )
6090
+ );
6091
+ return results.map((result, index) => {
6092
+ var _a2;
6093
+ if (result.status === "fulfilled") {
6094
+ return result.value;
6095
+ }
6096
+ const message = result.reason instanceof Error ? result.reason.message : String(result.reason);
6097
+ return {
6098
+ success: false,
6099
+ attachmentId: (_a2 = signedUploads[index]) == null ? void 0 : _a2.attachmentId,
6100
+ error: message
6101
+ };
6102
+ });
6103
+ }
6104
+ };
5976
6105
 
5977
6106
  // src/index.ts
5978
- var DEFAULT_API_URL = "https://code-api.supatest.ai";
5979
6107
  var SupatestCypressReporter = class {
5980
6108
  options;
5981
6109
  client;
@@ -5993,15 +6121,16 @@ var SupatestCypressReporter = class {
5993
6121
  cypressVersion;
5994
6122
  browserName;
5995
6123
  browserVersion;
6124
+ unregisterInterruptHandler;
5996
6125
  constructor(options = {}) {
5997
6126
  this.options = {
5998
6127
  projectId: options.projectId || process.env.SUPATEST_PROJECT_ID || "",
5999
6128
  apiKey: options.apiKey || process.env.SUPATEST_API_KEY || "",
6000
6129
  apiUrl: options.apiUrl || process.env.SUPATEST_API_URL || DEFAULT_API_URL,
6001
6130
  uploadAssets: options.uploadAssets ?? true,
6002
- maxConcurrentUploads: options.maxConcurrentUploads ?? 5,
6003
- retryAttempts: options.retryAttempts ?? 3,
6004
- timeoutMs: options.timeoutMs ?? 3e4,
6131
+ maxConcurrentUploads: options.maxConcurrentUploads ?? DEFAULT_MAX_CONCURRENT_UPLOADS,
6132
+ retryAttempts: options.retryAttempts ?? DEFAULT_RETRY_ATTEMPTS,
6133
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
6005
6134
  dryRun: options.dryRun ?? process.env.SUPATEST_DRY_RUN === "true"
6006
6135
  };
6007
6136
  }
@@ -6020,6 +6149,7 @@ var SupatestCypressReporter = class {
6020
6149
  this.client = new SupatestApiClient({
6021
6150
  apiKey: this.options.apiKey,
6022
6151
  apiUrl: this.options.apiUrl,
6152
+ projectId: this.options.projectId,
6023
6153
  timeoutMs: this.options.timeoutMs,
6024
6154
  retryAttempts: this.options.retryAttempts,
6025
6155
  dryRun: this.options.dryRun
@@ -6054,6 +6184,10 @@ var SupatestCypressReporter = class {
6054
6184
  };
6055
6185
  const response = await this.client.createRun(runRequest);
6056
6186
  this.runId = response.runId;
6187
+ this.unregisterInterruptHandler = registerInterruptHandler(
6188
+ this.client,
6189
+ () => this.runId
6190
+ );
6057
6191
  logInfo(`Run ${this.runId} started (${details.specs.length} spec files)`);
6058
6192
  } catch (error) {
6059
6193
  this.errorCollector.recordError("RUN_CREATE", getErrorMessage2(error), { error });
@@ -6062,11 +6196,16 @@ var SupatestCypressReporter = class {
6062
6196
  }
6063
6197
  async onAfterSpec(spec, results) {
6064
6198
  if (this.disabled || !this.runId) return;
6199
+ if (!(results == null ? void 0 : results.tests)) {
6200
+ logInfo(`Spec ${spec.relative} had no test results (browser may have crashed), skipping`);
6201
+ return;
6202
+ }
6065
6203
  for (const test of results.tests) {
6066
6204
  await this.processTestResult(spec, test, results);
6067
6205
  }
6068
6206
  }
6069
6207
  async onAfterRun(results) {
6208
+ var _a2;
6070
6209
  if (this.disabled || !this.runId) {
6071
6210
  if (this.errorCollector.hasErrors()) {
6072
6211
  console.log(this.errorCollector.formatSummary());
@@ -6075,22 +6214,23 @@ var SupatestCypressReporter = class {
6075
6214
  }
6076
6215
  await Promise.allSettled(this.uploadQueue);
6077
6216
  const summary = {
6078
- total: results.totalTests,
6079
- passed: results.totalPassed,
6080
- failed: results.totalFailed,
6217
+ total: (results == null ? void 0 : results.totalTests) ?? 0,
6218
+ passed: (results == null ? void 0 : results.totalPassed) ?? 0,
6219
+ failed: (results == null ? void 0 : results.totalFailed) ?? 0,
6081
6220
  flaky: 0,
6082
- skipped: results.totalSkipped + results.totalPending,
6221
+ skipped: ((results == null ? void 0 : results.totalSkipped) ?? 0) + ((results == null ? void 0 : results.totalPending) ?? 0),
6083
6222
  timedOut: 0,
6084
6223
  interrupted: 0,
6085
- durationMs: results.totalDuration
6224
+ durationMs: (results == null ? void 0 : results.totalDuration) ?? 0
6086
6225
  };
6226
+ (_a2 = this.unregisterInterruptHandler) == null ? void 0 : _a2.call(this);
6087
6227
  try {
6088
6228
  await this.client.completeRun(this.runId, {
6089
- status: results.status === "finished" ? "complete" : "errored",
6090
- endedAt: results.endedTestsAt,
6229
+ status: (results == null ? void 0 : results.status) === "finished" ? "complete" : "errored",
6230
+ endedAt: (results == null ? void 0 : results.endedTestsAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
6091
6231
  summary,
6092
6232
  timing: {
6093
- totalDurationMs: results.totalDuration,
6233
+ totalDurationMs: (results == null ? void 0 : results.totalDuration) ?? 0,
6094
6234
  timeToFirstTest: this.firstTestStartTime ? this.firstTestStartTime - new Date(this.startedAt).getTime() : void 0,
6095
6235
  timeToFirstFailure: this.firstFailureTime ? this.firstFailureTime - new Date(this.startedAt).getTime() : void 0
6096
6236
  }
@@ -6106,16 +6246,17 @@ var SupatestCypressReporter = class {
6106
6246
  async processTestResult(spec, test, specResults) {
6107
6247
  const testId = this.getTestId(spec.relative, test.title);
6108
6248
  const status = this.mapCypressStatus(test.state);
6109
- if (!this.firstTestStartTime && test.attempts[0]) {
6110
- this.firstTestStartTime = new Date(test.attempts[0].wallClockStartedAt).getTime();
6249
+ const attempts = test.attempts ?? [];
6250
+ if (!this.firstTestStartTime && attempts[0]) {
6251
+ this.firstTestStartTime = new Date(attempts[0].wallClockStartedAt).getTime();
6111
6252
  }
6112
6253
  if (!this.firstFailureTime && status === "failed") {
6113
6254
  this.firstFailureTime = Date.now();
6114
6255
  }
6115
- const lastAttempt = test.attempts[test.attempts.length - 1];
6256
+ const lastAttempt = attempts[attempts.length - 1];
6116
6257
  const resultEntry = {
6117
- resultId: hashKey(`${testId}:${test.attempts.length - 1}`),
6118
- retry: test.attempts.length - 1,
6258
+ resultId: hashKey(`${testId}:${attempts.length - 1}`),
6259
+ retry: attempts.length - 1,
6119
6260
  status,
6120
6261
  startTime: (lastAttempt == null ? void 0 : lastAttempt.wallClockStartedAt) ?? void 0,
6121
6262
  durationMs: (lastAttempt == null ? void 0 : lastAttempt.wallClockDuration) || 0,
@@ -6140,8 +6281,8 @@ var SupatestCypressReporter = class {
6140
6281
  timeout: 0,
6141
6282
  retries: test.attempts.length - 1,
6142
6283
  status,
6143
- durationMs: test.attempts.reduce((sum, a) => sum + (a.wallClockDuration || 0), 0),
6144
- retryCount: test.attempts.length - 1,
6284
+ durationMs: attempts.reduce((sum, a) => sum + (a.wallClockDuration || 0), 0),
6285
+ retryCount: attempts.length - 1,
6145
6286
  results: [resultEntry],
6146
6287
  projectName: this.browserName || "electron",
6147
6288
  metadata
@@ -6179,9 +6320,10 @@ var SupatestCypressReporter = class {
6179
6320
  }
6180
6321
  }
6181
6322
  getOutcome(test) {
6323
+ var _a2, _b;
6182
6324
  if (test.state === "skipped" || test.state === "pending") return "skipped";
6183
6325
  if (test.state === "passed") {
6184
- if (test.attempts.length > 1 && test.attempts.some((a) => a.state === "failed")) {
6326
+ if ((((_a2 = test.attempts) == null ? void 0 : _a2.length) ?? 0) > 1 && ((_b = test.attempts) == null ? void 0 : _b.some((a) => a.state === "failed"))) {
6185
6327
  return "flaky";
6186
6328
  }
6187
6329
  return "expected";
@@ -6190,7 +6332,7 @@ var SupatestCypressReporter = class {
6190
6332
  }
6191
6333
  extractErrors(test) {
6192
6334
  const errors = [];
6193
- for (const attempt of test.attempts) {
6335
+ for (const attempt of test.attempts ?? []) {
6194
6336
  if (attempt.error) {
6195
6337
  errors.push({
6196
6338
  message: attempt.error.message,
@@ -6213,29 +6355,11 @@ var SupatestCypressReporter = class {
6213
6355
  return tags;
6214
6356
  }
6215
6357
  parseTestMetadata(tags) {
6216
- const metadata = {
6217
- isSlow: false,
6218
- isFlakyTagged: false,
6219
- customMetadata: {}
6220
- };
6221
- for (const tag of tags) {
6222
- const lower = tag.toLowerCase();
6223
- if (lower === "@slow") metadata.isSlow = true;
6224
- if (lower === "@flaky") metadata.isFlakyTagged = true;
6225
- const ownerMatch = tag.match(/@owner:([^\s@]+)/i);
6226
- if (ownerMatch) metadata.owner = ownerMatch[1];
6227
- const priorityMatch = tag.match(/@priority:(critical|high|medium|low)/i);
6228
- if (priorityMatch) metadata.priority = priorityMatch[1].toLowerCase();
6229
- const featureMatch = tag.match(/@feature:([^\s@]+)/i);
6230
- if (featureMatch) metadata.feature = featureMatch[1];
6231
- const typeMatch = tag.match(/@test_type:(smoke|e2e|regression|integration|unit)/i);
6232
- if (typeMatch) metadata.testType = typeMatch[1].toLowerCase();
6233
- }
6234
- return metadata;
6358
+ return parseTestMetadata(tags);
6235
6359
  }
6236
6360
  buildAttachmentMeta(test, specResults) {
6237
6361
  const attachments = [];
6238
- for (const screenshot of specResults.screenshots) {
6362
+ for (const screenshot of specResults.screenshots ?? []) {
6239
6363
  if (screenshot.path) {
6240
6364
  attachments.push({
6241
6365
  name: screenshot.name || "screenshot",
@@ -6259,8 +6383,8 @@ var SupatestCypressReporter = class {
6259
6383
  }
6260
6384
  async uploadAttachments(testId, testResultId, specResults, test) {
6261
6385
  const attachments = [];
6262
- for (const screenshot of specResults.screenshots) {
6263
- if (screenshot.path && fs.existsSync(screenshot.path)) {
6386
+ for (const screenshot of specResults.screenshots ?? []) {
6387
+ if (screenshot.path && fs2.existsSync(screenshot.path)) {
6264
6388
  attachments.push({
6265
6389
  path: screenshot.path,
6266
6390
  meta: {
@@ -6273,7 +6397,7 @@ var SupatestCypressReporter = class {
6273
6397
  });
6274
6398
  }
6275
6399
  }
6276
- if (specResults.video && fs.existsSync(specResults.video)) {
6400
+ if (specResults.video && fs2.existsSync(specResults.video)) {
6277
6401
  attachments.push({
6278
6402
  path: specResults.video,
6279
6403
  meta: {
@@ -6296,55 +6420,65 @@ var SupatestCypressReporter = class {
6296
6420
  filePath: attachments[i].path,
6297
6421
  contentType: attachments[i].meta.contentType
6298
6422
  }));
6299
- await this.uploader.uploadBatch(uploadItems, uploads);
6423
+ const results = await this.uploader.uploadBatch(uploadItems, uploads);
6424
+ const failures = results.filter((r) => !r.success);
6425
+ if (failures.length > 0) {
6426
+ failures.forEach((failure) => {
6427
+ const attachment = attachments.find(
6428
+ (_, i) => {
6429
+ var _a2;
6430
+ return ((_a2 = uploads[i]) == null ? void 0 : _a2.attachmentId) === failure.attachmentId;
6431
+ }
6432
+ );
6433
+ this.errorCollector.recordError(
6434
+ "ATTACHMENT_UPLOAD",
6435
+ failure.error || "Upload failed",
6436
+ {
6437
+ attachmentName: attachment == null ? void 0 : attachment.meta.name,
6438
+ filePath: attachment == null ? void 0 : attachment.path,
6439
+ error: failure.error
6440
+ }
6441
+ );
6442
+ });
6443
+ }
6300
6444
  } catch (error) {
6301
6445
  this.errorCollector.recordError("ATTACHMENT_SIGN", getErrorMessage2(error), { error });
6302
6446
  }
6303
6447
  }
6304
6448
  getEnvironmentInfo() {
6305
6449
  return {
6306
- os: {
6307
- platform: os.platform(),
6308
- release: os.release(),
6309
- arch: os.arch()
6310
- },
6311
- node: {
6312
- version: process.version
6313
- },
6314
- machine: {
6315
- cpus: os.cpus().length,
6316
- memory: os.totalmem(),
6317
- hostname: os.hostname()
6318
- },
6450
+ ...getBaseEnvironmentInfo(),
6319
6451
  cypress: {
6320
6452
  version: this.cypressVersion || "unknown"
6321
- },
6322
- ci: getCIInfo()
6453
+ }
6323
6454
  };
6324
6455
  }
6325
6456
  async getGitInfo() {
6326
- const ciGitInfo = {
6327
- branch: process.env.GITHUB_REF_NAME ?? process.env.CI_COMMIT_BRANCH ?? process.env.GIT_BRANCH,
6328
- commit: process.env.GITHUB_SHA ?? process.env.CI_COMMIT_SHA ?? process.env.GIT_COMMIT,
6329
- repo: process.env.GITHUB_REPOSITORY ?? process.env.CI_PROJECT_PATH,
6330
- author: process.env.GITHUB_ACTOR ?? process.env.GITLAB_USER_NAME
6331
- };
6332
- if (ciGitInfo.branch || ciGitInfo.commit) {
6333
- return ciGitInfo;
6334
- }
6335
- return await getLocalGitInfo(this.rootDir);
6457
+ return getGitInfoWithCI(this.rootDir);
6336
6458
  }
6337
6459
  };
6338
6460
  function supatestPlugin(on, config, options = {}) {
6339
6461
  const reporter = new SupatestCypressReporter(options);
6340
6462
  on("before:run", async (details) => {
6341
- await reporter.onBeforeRun(details);
6463
+ try {
6464
+ await reporter.onBeforeRun(details);
6465
+ } catch (error) {
6466
+ logWarn(`Supatest reporter error in before:run: ${getErrorMessage2(error)}`);
6467
+ }
6342
6468
  });
6343
6469
  on("after:spec", async (spec, results) => {
6344
- await reporter.onAfterSpec(spec, results);
6470
+ try {
6471
+ await reporter.onAfterSpec(spec, results);
6472
+ } catch (error) {
6473
+ logWarn(`Supatest reporter error in after:spec: ${getErrorMessage2(error)}`);
6474
+ }
6345
6475
  });
6346
6476
  on("after:run", async (results) => {
6347
- await reporter.onAfterRun(results);
6477
+ try {
6478
+ await reporter.onAfterRun(results);
6479
+ } catch (error) {
6480
+ logWarn(`Supatest reporter error in after:run: ${getErrorMessage2(error)}`);
6481
+ }
6348
6482
  });
6349
6483
  }
6350
6484
  export {