@trackunit/iris-app-e2e 1.8.85-alpha-20757f7c966.0 → 1.8.85

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/index.esm.js CHANGED
@@ -1,6 +1,8 @@
1
- import installLogsCollector from 'cypress-terminal-report/src/installLogsCollector';
2
- import '@neuralegion/cypress-har-generator/commands';
3
- import { f as fileNameBuilder } from './fileNameBuilder.esm.js';
1
+ import fs, { writeFileSync, existsSync, readFileSync } from 'fs';
2
+ import * as path from 'path';
3
+ import path__default from 'path';
4
+ import crypto from 'crypto';
5
+ import { parse } from 'node-xlsx';
4
6
 
5
7
  /**
6
8
  * Sets up default Cypress commands for E2E testing.
@@ -134,12 +136,367 @@ function setupDefaultCommands() {
134
136
  });
135
137
  }
136
138
 
139
+ /* eslint-disable no-console */
140
+ /**
141
+ * Writes a file with Prettier formatting applied.
142
+ * Automatically detects parser based on file extension.
143
+ */
144
+ const writeFileWithPrettier = async (nxRoot, filePath, content, writeOptions = { encoding: "utf-8" }, writer) => {
145
+ const prettierConfigPath = path.join(nxRoot, ".prettierrc");
146
+ const options = await writer
147
+ .resolveConfig(prettierConfigPath)
148
+ .catch(error => console.log("Prettier config error: ", error));
149
+ if (!options) {
150
+ throw new Error("Could not find prettier config");
151
+ }
152
+ if (filePath.endsWith("json")) {
153
+ options.parser = "json";
154
+ }
155
+ else {
156
+ options.parser = "typescript";
157
+ }
158
+ try {
159
+ const prettySrc = await writer.format(content, options);
160
+ writeFileSync(filePath, prettySrc, writeOptions);
161
+ }
162
+ catch (error) {
163
+ console.error("Error in prettier.format:", error);
164
+ }
165
+ };
166
+
167
+ const isNetworkCall = (log) => {
168
+ return log.type === "cy:fetch" || log.type === "cy:request" || log.type === "cy:response" || log.type === "cy:xrh";
169
+ };
170
+ /**
171
+ * Creates log files for Cypress test runs.
172
+ * Generates separate files for all logs, errors, and network errors.
173
+ */
174
+ function createLogFile(nxRoot, logsPath, fileNameWithoutExtension, logs, logWriter) {
175
+ if (!existsSync(logsPath)) {
176
+ fs.mkdirSync(logsPath, { recursive: true });
177
+ }
178
+ const logFilePath = path__default.join(logsPath, fileNameWithoutExtension);
179
+ writeFileWithPrettier(nxRoot, logFilePath + "-all.json", JSON.stringify(logs), { flag: "a" }, logWriter);
180
+ const errorCmds = logs.filter(log => log.severity === "error" &&
181
+ // This seems to be when apollo has cancelled requests it marks it as failed to fetch only on cypress ?!??
182
+ // might be fixed by https://github.com/Trackunit/manager/pull/12917
183
+ !log.message.includes("TypeError: Failed to fetch"));
184
+ if (errorCmds.length > 0) {
185
+ writeFileWithPrettier(nxRoot, logFilePath + "-errors.json", JSON.stringify(errorCmds), {
186
+ flag: "a",
187
+ }, logWriter);
188
+ }
189
+ const networkErrorsCmds = logs.filter(log => isNetworkCall(log) &&
190
+ !log.message.includes("Status: 200") &&
191
+ log.severity !== "success" &&
192
+ !log.message.includes("sentry.io/api/") &&
193
+ // This seems to be when apollo has cancelled requests it marks it as failed to fetch only on cypress ?!??
194
+ !log.message.includes("TypeError: Failed to fetch"));
195
+ if (networkErrorsCmds.length > 0) {
196
+ writeFileWithPrettier(nxRoot, logFilePath + "-network-errors.json", JSON.stringify(networkErrorsCmds), {
197
+ flag: "a",
198
+ }, logWriter);
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Parses lines from a CODEOWNERS‐style file into a mapping of patterns → owners.
204
+ * Skips blank lines and comments (lines beginning with `#`).
205
+ *
206
+ * @param {string[]} lines - Each element is a line from your CODEOWNERS file.
207
+ * @returns {Record<string, string>} An object whose keys are normalized path patterns (no leading/trailing slashes)
208
+ * and whose values are the owner (e.g. a GitHub team handle).
209
+ * @example
210
+ * ```ts
211
+ * const lines = [
212
+ * "# Our CODEOWNERS",
213
+ * "/src @team/backend",
214
+ * "README.md @team/docs",
215
+ * "", // blank lines ignored
216
+ * "# end of file"
217
+ * ];
218
+ * const owners = parseCodeowners(lines);
219
+ * // → { "src": "@team/backend", "README.md": "@team/docs" }
220
+ * ```
221
+ */
222
+ const parseCodeowners = (lines) => {
223
+ const patterns = {};
224
+ for (const line of lines) {
225
+ const trimmed = line.trim();
226
+ if (!trimmed || trimmed.startsWith("#")) {
227
+ continue;
228
+ }
229
+ const [pattern, owner] = trimmed.split(/\s+/, 2);
230
+ if (pattern && owner) {
231
+ const normalizedPattern = pattern.replace(/^\/+|\/+$/g, "");
232
+ patterns[normalizedPattern] = owner;
233
+ }
234
+ }
235
+ return patterns;
236
+ };
237
+ /**
238
+ * Converts a full team handle (potentially namespaced and with platform suffix)
239
+ * into its “short” team name.
240
+ *
241
+ * - Strips any leading `@org/` prefix
242
+ * - Removes `-be` or `-fe` suffix if present
243
+ *
244
+ * @param {string} teamName - e.g. `"@trackunit/backend-be"` or `"@trackunit/frontend-fe"`
245
+ * @returns {string} e.g. `"backend"` or `"frontend"`
246
+ * @example
247
+ * ```ts
248
+ * toShortTeamName("@trackunit/backend-be"); // → "backend"
249
+ * toShortTeamName("@trackunit/frontend-fe"); // → "frontend"
250
+ * toShortTeamName("infra"); // → "infra"
251
+ * ```
252
+ */
253
+ const toShortTeamName = (teamName) => {
254
+ if (!teamName) {
255
+ return undefined;
256
+ }
257
+ let shortName = teamName;
258
+ if (teamName.startsWith("@")) {
259
+ shortName = shortName.slice(shortName.indexOf("/") + 1);
260
+ }
261
+ if (shortName.endsWith("-be") || shortName.endsWith("-fe")) {
262
+ shortName = shortName.slice(0, shortName.lastIndexOf("-"));
263
+ }
264
+ return shortName;
265
+ };
266
+ /**
267
+ * Recursively looks up the CODEOWNER for a given file or directory path
268
+ * by reading your workspace’s CODEOWNERS file.
269
+ *
270
+ * Walks up the directory tree until it finds a matching pattern. If no
271
+ * deeper match is found but there is a `"."` entry, returns that.
272
+ *
273
+ * @param {string} currentPath - Absolute path to the file/directory you’re querying.
274
+ * @param {string} workspaceRoot - Absolute path to your repo/workspace root.
275
+ * @param {string} [codeownersFileName="TEAM_CODEOWNERS"] - Filename to read at the root.
276
+ * @returns {string|undefined} The owner handle (e.g. `"@team/backend"`) or `undefined` if none found.
277
+ * @example
278
+ * ```ts
279
+ * // Suppose your repo root has a TEAM_CODEOWNERS file containing:
280
+ * // src @team/backend
281
+ * // src/utils @team/utils
282
+ * // . @team/root
283
+ *
284
+ * const owner1 = getCodeowner(
285
+ * "/Users/alice/project/src/utils/helpers.ts",
286
+ * "/Users/alice/project"
287
+ * );
288
+ * // → "@team/utils"
289
+ *
290
+ * const owner2 = getCodeowner(
291
+ * "/Users/alice/project/other/file.txt",
292
+ * "/Users/alice/project"
293
+ * );
294
+ * // → "@team/root" (falls back to the "." entry)
295
+ * ```
296
+ */
297
+ const getCodeowner = (currentPath, workspaceRoot, codeownersFileName = "TEAM_CODEOWNERS") => {
298
+ if (!workspaceRoot) {
299
+ return undefined;
300
+ }
301
+ const codeownersPath = path__default.join(workspaceRoot, codeownersFileName);
302
+ if (!fs.existsSync(codeownersPath)) {
303
+ return undefined;
304
+ }
305
+ const codeownersLines = fs.readFileSync(codeownersPath, "utf8").split("\n");
306
+ const codeowners = parseCodeowners(codeownersLines);
307
+ let relPath = path__default
308
+ .relative(workspaceRoot, currentPath)
309
+ .replace(/\\/g, "/")
310
+ .replace(/^\/+|\/+$/g, "");
311
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
312
+ while (true) {
313
+ const codeowner = codeowners[relPath];
314
+ if (codeowner) {
315
+ return codeowner;
316
+ }
317
+ const parent = path__default.posix.dirname(relPath);
318
+ if ((parent === relPath || parent === ".") && codeowners["."]) {
319
+ return codeowners["."];
320
+ }
321
+ if (parent === relPath || parent === ".") {
322
+ break;
323
+ }
324
+ relPath = parent;
325
+ }
326
+ return undefined;
327
+ };
328
+
329
+ /**
330
+ * Sanitizes a string to be safe for use in filenames.
331
+ * Removes special characters and truncates if too long.
332
+ *
333
+ * @param str - The string to sanitize (e.g., test suite or test name)
334
+ * @returns {string} Sanitized string safe for filesystem use
335
+ */
336
+ function sanitizeForFilename(str) {
337
+ return str
338
+ .replace(/[^a-zA-Z0-9-_\s\[\]\(\)]/g, "") // Remove special chars - with exceptions
339
+ .replace(/-+/g, "-") // Collapse multiple hyphens into one
340
+ .replace(/\s+/g, " ") // Collapse multiple spaces into one
341
+ .replace(/^-|-$/g, ""); // Trim leading/trailing hyphens
342
+ }
343
+ /**
344
+ * Builds a standardized filename for test artifacts (e.g., HAR files, screenshots).
345
+ * Combines test name, state, and attempt number into a readable filename.
346
+ *
347
+ * @param testName - The name of the test (will be sanitized)
348
+ * @param state - The test state (e.g., 'passed', 'failed')
349
+ * @param currentRetry - The current retry attempt number (0-based)
350
+ * @returns {string} A sanitized filename in the format: "testName (state) (attempt N)"
351
+ * @example
352
+ * fileNameBuilder("Login Flow", "failed", 0)
353
+ * // Returns: "Login Flow (failed) (attempt 1)"
354
+ * @example
355
+ * fileNameBuilder("Test: with -> special chars", "passed", 2)
356
+ * // Returns: "Test with - special chars (passed) (attempt 3)"
357
+ */
358
+ function fileNameBuilder(testName, state, currentRetry) {
359
+ const fileName = `${testName} ${currentRetry !== undefined ? `(Attempt ${currentRetry + 1})` : ""} (${state.toLowerCase()})`;
360
+ return sanitizeForFilename(fileName);
361
+ }
362
+
363
+ /**
364
+ * Utility function to find NX workspace root by looking for nx.json or workspace.json.
365
+ * This is more reliable than hardcoded relative paths and works from any directory.
366
+ *
367
+ * @param startDir Starting directory for the search (defaults to current working directory)
368
+ * @returns {string} Absolute path to the workspace root
369
+ * @throws Error if workspace root cannot be found
370
+ */
371
+ function findWorkspaceRoot(startDir = process.cwd()) {
372
+ let currentDir = startDir;
373
+ while (currentDir !== path__default.dirname(currentDir)) {
374
+ if (existsSync(path__default.join(currentDir, "nx.json")) || existsSync(path__default.join(currentDir, "workspace.json"))) {
375
+ return currentDir;
376
+ }
377
+ currentDir = path__default.dirname(currentDir);
378
+ }
379
+ throw new Error("Could not find NX workspace root (nx.json or workspace.json not found)");
380
+ }
381
+ /**
382
+ * Creates default Cypress configuration for E2E testing.
383
+ * Supports both legacy string parameter (dirname) and new options object for backward compatibility.
384
+ */
385
+ const defaultCypressConfig = optionsOrDirname => {
386
+ // Support both old API (string/undefined) and new API (object) for backward compatibility
387
+ const options = typeof optionsOrDirname === "object" ? optionsOrDirname : {};
388
+ const { nxRoot: providedNxRoot, outputDirOverride, projectConfig = {}, behaviorConfig = {}, pluginConfig = {}, } = options;
389
+ // Use NX workspace detection for reliable root finding
390
+ const nxRoot = providedNxRoot ?? findWorkspaceRoot();
391
+ // For output path calculation, determine the relative path from caller to workspace root
392
+ const callerDirname = typeof optionsOrDirname === "string" ? optionsOrDirname : process.cwd();
393
+ const relativePath = path__default.relative(nxRoot, callerDirname);
394
+ const dotsToNxRoot = relativePath
395
+ .split(path__default.sep)
396
+ .map(_ => "..")
397
+ .join("/");
398
+ const envOutputDirOverride = process.env.NX_E2E_OUTPUT_DIR || outputDirOverride;
399
+ // Function to build output paths that respects the override
400
+ const buildOutputPath = (subPath) => {
401
+ if (envOutputDirOverride) {
402
+ return path__default.join(dotsToNxRoot, envOutputDirOverride, subPath);
403
+ }
404
+ return `${dotsToNxRoot}/dist/cypress/${relativePath}/${subPath}`;
405
+ };
406
+ return {
407
+ projectId: projectConfig.projectId ?? process.env.CYPRESS_PROJECT_ID,
408
+ defaultCommandTimeout: behaviorConfig.defaultCommandTimeout ?? 20000,
409
+ execTimeout: behaviorConfig.execTimeout ?? 300000,
410
+ taskTimeout: behaviorConfig.taskTimeout ?? 35000,
411
+ pageLoadTimeout: behaviorConfig.pageLoadTimeout ?? 35000,
412
+ // setting to undefined makes no effect on the baseUrl so child projects can override it
413
+ baseUrl: process.env.NX_FEATURE_BRANCH_BASE_URL ?? undefined,
414
+ // This avoid issues with SRI hashes changing
415
+ modifyObstructiveCode: false,
416
+ requestTimeout: behaviorConfig.requestTimeout ?? 25000,
417
+ responseTimeout: behaviorConfig.responseTimeout ?? 150000,
418
+ retries: behaviorConfig.retries ?? {
419
+ runMode: 3,
420
+ openMode: 0,
421
+ },
422
+ fixturesFolder: projectConfig.fixturesFolder ?? "./src/fixtures",
423
+ downloadsFolder: pluginConfig.outputPath ?? buildOutputPath("downloads"),
424
+ logsFolder: pluginConfig.logsFolder ?? buildOutputPath("logs"),
425
+ chromeWebSecurity: false,
426
+ reporter: "junit",
427
+ reporterOptions: {
428
+ mochaFile: buildOutputPath("results-[hash].xml"),
429
+ },
430
+ specPattern: projectConfig.specPattern ?? "src/e2e/**/*.e2e.{js,jsx,ts,tsx}",
431
+ supportFile: projectConfig.supportFile ?? "src/support/e2e.ts",
432
+ nxRoot,
433
+ fileServerFolder: ".",
434
+ video: true,
435
+ trashAssetsBeforeRuns: false, // Don't delete videos from previous test runs (important for sequential runs)
436
+ videosFolder: pluginConfig.videosFolder ?? buildOutputPath("videos"),
437
+ screenshotsFolder: pluginConfig.screenshotsFolder ?? buildOutputPath("screenshots"),
438
+ env: {
439
+ hars_folders: pluginConfig.harFolder ?? buildOutputPath("hars"),
440
+ CYPRESS_RUN_UNIQUE_ID: crypto.randomUUID(),
441
+ },
442
+ };
443
+ };
444
+ /**
445
+ * Sets up Cypress plugins for logging, tasks, and HAR generation.
446
+ * Configures terminal reporting, XLSX parsing, and HTTP archive recording.
447
+ */
448
+ const setupPlugins = (on, config, logWriter, installHarGenerator) => {
449
+ /* ---- BEGIN: Logging setup ---- */
450
+ // Read options https://github.com/archfz/cypress-terminal-report
451
+ const options = {
452
+ printLogsToFile: "always", // Ensures logs are always printed to a file
453
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
454
+ collectTestLogs: (context, logs) => {
455
+ const testName = fileNameBuilder(context.test, context.state);
456
+ createLogFile(config.nxRoot, config.logsFolder, testName, logs, logWriter);
457
+ },
458
+ };
459
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
460
+ require("cypress-terminal-report/src/installLogsPrinter")(on, options);
461
+ /* ---- END: Logging setup ---- */
462
+ /* ---- BEGIN: Task setup ---- */
463
+ on("task", {
464
+ parseXlsx(filePath) {
465
+ return new Promise((resolve, reject) => {
466
+ try {
467
+ const jsonData = parse(readFileSync(filePath));
468
+ resolve(jsonData);
469
+ }
470
+ catch (e) {
471
+ reject(e);
472
+ }
473
+ });
474
+ },
475
+ fileExists(filename) {
476
+ return existsSync(filename);
477
+ },
478
+ });
479
+ /* ---- END: Task setup ---- */
480
+ /* ---- BEGIN: codeowner setup ---- */
481
+ const codeowner = toShortTeamName(getCodeowner(config.projectRoot, config.nxRoot));
482
+ config.env.codeowner = codeowner;
483
+ /* ---- END: codeowner setup ---- */
484
+ /* ---- BEGIN: HAR setup ---- */
485
+ // Installing the HAR geneartor should happen last according to the documentation
486
+ // https://github.com/NeuraLegion/cypress-har-generator?tab=readme-ov-file#setting-up-the-plugin
487
+ installHarGenerator(on);
488
+ /* ---- END: HAR setup ---- */
489
+ return config;
490
+ };
491
+
137
492
  /* eslint-disable @typescript-eslint/no-explicit-any */
138
493
  /**
139
494
  * Sets up HAR (HTTP Archive) recording for E2E tests.
140
495
  * Records network activity and saves HAR files for failed tests.
141
496
  */
142
497
  function setupHarRecording() {
498
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
499
+ require("@neuralegion/cypress-har-generator/commands");
143
500
  beforeEach(() => {
144
501
  const harDir = Cypress.env("hars_folders");
145
502
  cy.recordHar({ rootDir: harDir });
@@ -158,7 +515,8 @@ function setupHarRecording() {
158
515
  */
159
516
  function setupE2E() {
160
517
  setupHarRecording();
161
- installLogsCollector({
518
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
519
+ require("cypress-terminal-report/src/installLogsCollector")({
162
520
  xhr: {
163
521
  printHeaderData: true,
164
522
  printRequestData: true,
@@ -172,7 +530,7 @@ function setupE2E() {
172
530
  return false;
173
531
  });
174
532
  }
175
- const originalDescribe = globalThis.describe;
533
+ const originalDescribe = global.describe;
176
534
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
177
535
  if (originalDescribe) {
178
536
  const codeowner = Cypress.env("codeowner");
@@ -189,7 +547,7 @@ if (originalDescribe) {
189
547
  return originalDescribe.skip(addOwnerToTitle(title), fn);
190
548
  };
191
549
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
192
- globalThis.describe = patchedDescribe;
550
+ global.describe = patchedDescribe;
193
551
  }
194
552
 
195
- export { setupDefaultCommands, setupE2E, setupHarRecording };
553
+ export { createLogFile, defaultCypressConfig, setupDefaultCommands, setupE2E, setupHarRecording, setupPlugins, writeFileWithPrettier };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/iris-app-e2e",
3
- "version": "1.8.85-alpha-20757f7c966.0",
3
+ "version": "1.8.85",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "generators": "./generators.json",
@@ -15,21 +15,6 @@
15
15
  "prettier": "^3.4.2",
16
16
  "@nx/devkit": "22.4.4"
17
17
  },
18
- "exports": {
19
- "./package.json": "./package.json",
20
- ".": {
21
- "module": "./index.esm.js",
22
- "types": "./index.d.ts",
23
- "import": "./index.cjs.mjs",
24
- "default": "./index.cjs.js"
25
- },
26
- "./node": {
27
- "module": "./node.esm.js",
28
- "types": "./node.d.ts",
29
- "import": "./node.cjs.mjs",
30
- "default": "./node.cjs.js"
31
- }
32
- },
33
18
  "module": "./index.esm.js",
34
19
  "main": "./index.cjs.js",
35
20
  "types": "./index.d.ts"
package/src/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from "./commands/defaultCommands";
2
+ export * from "./plugins/createLogFile";
3
+ export * from "./plugins/defaultPlugins";
4
+ export * from "./plugins/writeFileWithPrettier";
2
5
  export * from "./setup/defaultE2ESetup";
3
6
  export * from "./setup/setupHarRecording";
4
- export type { CypressPluginConfig, E2EBehaviorConfig, E2EConfigOptions, E2EPluginConfig, E2EProjectConfig, } from "./plugins/defaultPlugins";
5
- export type { Formatter } from "./plugins/writeFileWithPrettier";
@@ -1,4 +1,3 @@
1
- import "@neuralegion/cypress-har-generator/commands";
2
1
  /**
3
2
  * Sets up HAR (HTTP Archive) recording for E2E tests.
4
3
  * Records network activity and saves HAR files for failed tests.
@@ -1,37 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * Sanitizes a string to be safe for use in filenames.
5
- * Removes special characters and truncates if too long.
6
- *
7
- * @param str - The string to sanitize (e.g., test suite or test name)
8
- * @returns {string} Sanitized string safe for filesystem use
9
- */
10
- function sanitizeForFilename(str) {
11
- return str
12
- .replace(/[^a-zA-Z0-9-_\s\[\]\(\)]/g, "") // Remove special chars - with exceptions
13
- .replace(/-+/g, "-") // Collapse multiple hyphens into one
14
- .replace(/\s+/g, " ") // Collapse multiple spaces into one
15
- .replace(/^-|-$/g, ""); // Trim leading/trailing hyphens
16
- }
17
- /**
18
- * Builds a standardized filename for test artifacts (e.g., HAR files, screenshots).
19
- * Combines test name, state, and attempt number into a readable filename.
20
- *
21
- * @param testName - The name of the test (will be sanitized)
22
- * @param state - The test state (e.g., 'passed', 'failed')
23
- * @param currentRetry - The current retry attempt number (0-based)
24
- * @returns {string} A sanitized filename in the format: "testName (state) (attempt N)"
25
- * @example
26
- * fileNameBuilder("Login Flow", "failed", 0)
27
- * // Returns: "Login Flow (failed) (attempt 1)"
28
- * @example
29
- * fileNameBuilder("Test: with -> special chars", "passed", 2)
30
- * // Returns: "Test with - special chars (passed) (attempt 3)"
31
- */
32
- function fileNameBuilder(testName, state, currentRetry) {
33
- const fileName = `${testName} ${currentRetry !== undefined ? `(Attempt ${currentRetry + 1})` : ""} (${state.toLowerCase()})`;
34
- return sanitizeForFilename(fileName);
35
- }
36
-
37
- exports.fileNameBuilder = fileNameBuilder;
@@ -1,35 +0,0 @@
1
- /**
2
- * Sanitizes a string to be safe for use in filenames.
3
- * Removes special characters and truncates if too long.
4
- *
5
- * @param str - The string to sanitize (e.g., test suite or test name)
6
- * @returns {string} Sanitized string safe for filesystem use
7
- */
8
- function sanitizeForFilename(str) {
9
- return str
10
- .replace(/[^a-zA-Z0-9-_\s\[\]\(\)]/g, "") // Remove special chars - with exceptions
11
- .replace(/-+/g, "-") // Collapse multiple hyphens into one
12
- .replace(/\s+/g, " ") // Collapse multiple spaces into one
13
- .replace(/^-|-$/g, ""); // Trim leading/trailing hyphens
14
- }
15
- /**
16
- * Builds a standardized filename for test artifacts (e.g., HAR files, screenshots).
17
- * Combines test name, state, and attempt number into a readable filename.
18
- *
19
- * @param testName - The name of the test (will be sanitized)
20
- * @param state - The test state (e.g., 'passed', 'failed')
21
- * @param currentRetry - The current retry attempt number (0-based)
22
- * @returns {string} A sanitized filename in the format: "testName (state) (attempt N)"
23
- * @example
24
- * fileNameBuilder("Login Flow", "failed", 0)
25
- * // Returns: "Login Flow (failed) (attempt 1)"
26
- * @example
27
- * fileNameBuilder("Test: with -> special chars", "passed", 2)
28
- * // Returns: "Test with - special chars (passed) (attempt 3)"
29
- */
30
- function fileNameBuilder(testName, state, currentRetry) {
31
- const fileName = `${testName} ${currentRetry !== undefined ? `(Attempt ${currentRetry + 1})` : ""} (${state.toLowerCase()})`;
32
- return sanitizeForFilename(fileName);
33
- }
34
-
35
- export { fileNameBuilder as f };
@@ -1 +0,0 @@
1
- exports._default = require('./index.cjs.js').default;
package/index.cjs.mjs DELETED
@@ -1,2 +0,0 @@
1
- export * from './index.cjs.js';
2
- export { _default as default } from './index.cjs.default.js';
@@ -1 +0,0 @@
1
- exports._default = require('./node.cjs.js').default;