@testomatio/reporter 2.7.8 → 2.7.9-beta.1-markdown

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/lib/replay.js CHANGED
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.Replay = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
- const os_1 = __importDefault(require("os"));
10
9
  const client_js_1 = __importDefault(require("./client.js"));
11
10
  const constants_js_1 = require("./constants.js");
12
11
  const config_js_1 = require("./config.js");
@@ -19,11 +18,12 @@ class Replay {
19
18
  this.onError = options.onError || console.error;
20
19
  }
21
20
  /**
22
- * Get the default debug file path
21
+ * Get the default debug file path.
22
+ * Returns ./testomatio.debug.json (actual file in CI, symlink to tmp file in local).
23
23
  * @returns {string} Path to the latest debug file
24
24
  */
25
25
  getDefaultDebugFile() {
26
- return path_1.default.join(os_1.default.tmpdir(), 'testomatio.debug.latest.json');
26
+ return path_1.default.join(process.cwd(), `${constants_js_1.DEBUG_FILE}.json`);
27
27
  }
28
28
  /**
29
29
  * Parse a debug file and extract test data
@@ -125,8 +125,11 @@ class Replay {
125
125
  testsWithoutRid.push({ ...test });
126
126
  }
127
127
  }
128
- else if (logEntry.actions === 'finishRun') {
128
+ else if (logEntry.action === 'finishRun') {
129
129
  finishParams = logEntry.params || {};
130
+ if (logEntry.runId && !runId) {
131
+ runId = logEntry.runId;
132
+ }
130
133
  }
131
134
  }
132
135
  catch (err) {
@@ -197,22 +200,25 @@ class Replay {
197
200
  dryRun: true,
198
201
  };
199
202
  }
200
- // Create client and restore the run
203
+ if (runId) {
204
+ this.onLog(`Using existing run ID: ${runId}`);
205
+ process.env.TESTOMATIO_RUN = runId;
206
+ }
207
+ else {
208
+ this.onLog('Publishing to run...');
209
+ }
210
+ process.env.TESTOMATIO_REPLAY = '1';
201
211
  const client = new client_js_1.default({
202
212
  apiKey: this.apiKey,
203
213
  isBatchEnabled: true,
204
214
  ...runParams,
215
+ ...(runId && { runId }),
205
216
  });
206
- // Use the stored runId if available, otherwise create a new run
207
217
  if (runId) {
208
- this.onLog(`Using existing run ID: ${runId}`);
209
218
  client.runId = runId;
219
+ client.pipeStore.runId = runId;
210
220
  }
211
- else {
212
- this.onLog('Publishing to run...');
213
- await client.createRun(runParams);
214
- }
215
- // Send each test result
221
+ await client.createRun(runParams);
216
222
  let successCount = 0;
217
223
  let failureCount = 0;
218
224
  for (const [index, test] of tests.entries()) {
@@ -239,7 +245,8 @@ class Replay {
239
245
  }
240
246
  }
241
247
  await client.updateRunStatus(finishParams.status || constants_js_1.STATUS.FINISHED);
242
- const result = {
248
+ this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
249
+ return {
243
250
  success: true,
244
251
  testsCount: tests.length,
245
252
  successCount,
@@ -249,8 +256,6 @@ class Replay {
249
256
  envVars,
250
257
  runId: runId || client.runId,
251
258
  };
252
- this.onLog(`Successfully replayed ${successCount}/${tests.length} tests from debug file`);
253
- return result;
254
259
  }
255
260
  }
256
261
  exports.Replay = Replay;
@@ -2242,7 +2242,6 @@
2242
2242
  <div class='env-var-item {{#unless this.isSet}}env-var-unset{{/unless}}'>
2243
2243
  <div class='env-var-info'>
2244
2244
  <div class='env-var-key'>{{@key}}</div>
2245
- <div class='env-var-description'>{{this.description}}</div>
2246
2245
  </div>
2247
2246
  <div class='env-var-value {{#unless this.isSet}}env-var-empty{{/unless}} {{#if this.isSensitive}}env-var-confidential{{/if}}'>
2248
2247
  {{#if this.isSensitive}}
@@ -2267,7 +2266,6 @@
2267
2266
  <div class='env-var-item {{#unless this.isSet}}env-var-unset{{/unless}}'>
2268
2267
  <div class='env-var-info'>
2269
2268
  <div class='env-var-key'>{{@key}}</div>
2270
- <div class='env-var-description'>{{this.description}}</div>
2271
2269
  </div>
2272
2270
  <div class='env-var-value {{#unless this.isSet}}env-var-empty{{/unless}} {{#if this.isSensitive}}env-var-confidential{{/if}}'>
2273
2271
  {{#if this.isSensitive}}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Get the debug file path(s).
3
+ *
4
+ * Always creates a timestamped file in tmp dir and a symlink in project root.
5
+ *
6
+ * @param {string} [suffix] - Optional suffix appended to the base name (e.g. 'replay').
7
+ * @returns {{root: string, tmp: string}} root path (symlink), tmp path (actual file)
8
+ */
9
+ export function getDebugFilePath(suffix?: string): {
10
+ root: string;
11
+ tmp: string;
12
+ };
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getDebugFilePath = getDebugFilePath;
7
+ const path_1 = __importDefault(require("path"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const constants_js_1 = require("../constants.js");
10
+ /**
11
+ * Get the debug file path(s).
12
+ *
13
+ * Always creates a timestamped file in tmp dir and a symlink in project root.
14
+ *
15
+ * @param {string} [suffix] - Optional suffix appended to the base name (e.g. 'replay').
16
+ * @returns {{root: string, tmp: string}} root path (symlink), tmp path (actual file)
17
+ */
18
+ function getDebugFilePath(suffix = '') {
19
+ const dateTime = new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-');
20
+ const baseName = suffix ? `${constants_js_1.DEBUG_FILE}-${suffix}` : constants_js_1.DEBUG_FILE;
21
+ return {
22
+ root: path_1.default.join(process.cwd(), `${baseName}.json`),
23
+ tmp: path_1.default.join(os_1.default.tmpdir(), `${baseName}.${dateTime}.json`),
24
+ };
25
+ }
26
+
27
+ module.exports.getDebugFilePath = getDebugFilePath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.7.8",
3
+ "version": "2.7.9-beta.1-markdown",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
package/src/bin/cli.js CHANGED
@@ -6,7 +6,7 @@ import { glob } from 'glob';
6
6
  import createDebugMessages from 'debug';
7
7
  import TestomatClient from '../client.js';
8
8
  import XmlReader from '../xmlReader.js';
9
- import { APP_PREFIX, STATUS } from '../constants.js';
9
+ import { APP_PREFIX, STATUS, DEBUG_FILE } from '../constants.js';
10
10
  import { cleanLatestRunId, getPackageVersion, applyFilter } from '../utils/utils.js';
11
11
  import { config } from '../config.js';
12
12
  import { readLatestRunId } from '../utils/utils.js';
@@ -370,7 +370,7 @@ program
370
370
  program
371
371
  .command('replay')
372
372
  .description('Replay test data from debug file and re-send to Testomat.io')
373
- .argument('[debug-file]', 'Path to debug file (defaults to /tmp/testomatio.debug.latest.json)')
373
+ .argument('[debug-file]', `Path to debug file. Defaults to ./${DEBUG_FILE}.json`)
374
374
  .option('--dry-run', 'Preview the data without sending to Testomat.io')
375
375
  .action(async (debugFile, opts) => {
376
376
  try {
package/src/constants.js CHANGED
@@ -35,6 +35,12 @@ const HTML_REPORT = {
35
35
  TEMPLATE_NAME: 'testomatio.hbs',
36
36
  };
37
37
 
38
+ // markdown pipe var
39
+ const MARKDOWN_REPORT = {
40
+ FOLDER: 'md-report',
41
+ REPORT_DEFAULT_NAME: 'testomatio-report.md',
42
+ };
43
+
38
44
  const testomatLogoURL = 'https://avatars.githubusercontent.com/u/59105116?s=36&v=4';
39
45
 
40
46
  const REPORTER_REQUEST_RETRIES = {
@@ -44,6 +50,8 @@ const REPORTER_REQUEST_RETRIES = {
44
50
  withinTimeSeconds: Number(process.env.TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS) || 60,
45
51
  };
46
52
 
53
+ const DEBUG_FILE = 'testomatio.debug';
54
+
47
55
  function getCreateRunRequestTimeout() {
48
56
  return Math.max(REQUEST_TIMEOUT, 80 * 1000);
49
57
  }
@@ -54,9 +62,11 @@ export {
54
62
  CSV_HEADERS,
55
63
  STATUS,
56
64
  HTML_REPORT,
65
+ MARKDOWN_REPORT,
57
66
  REQUEST_TIMEOUT,
58
67
  getCreateRunRequestTimeout,
59
68
  testomatLogoURL,
60
69
  REPORTER_REQUEST_RETRIES,
61
70
  SCREENSHOTS_ON_STEPS,
71
+ DEBUG_FILE,
62
72
  };
package/src/pipe/debug.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import os from 'os';
4
3
  import createDebugMessages from 'debug';
5
4
  import prettyMs from 'pretty-ms';
6
5
  import { log } from '../utils/log.js';
6
+ import { getDebugFilePath } from '../utils/debug.js';
7
7
 
8
8
  const debug = createDebugMessages('@testomatio/reporter:pipe:debug');
9
9
 
@@ -21,23 +21,33 @@ export class DebugPipe {
21
21
  tests: [],
22
22
  batchIndex: 0,
23
23
  };
24
- this.logFilePath = path.join(os.tmpdir(), `testomatio.debug.${Date.now()}.json`);
24
+ const suffix = process.env.TESTOMATIO_REPLAY ? 'replay' : '';
25
+ const paths = getDebugFilePath(suffix);
26
+ this.logFilePath = paths.tmp;
27
+ this.rootPath = paths.root;
28
+ this.historyDir = path.dirname(paths.tmp);
25
29
 
26
30
  debug('Creating debug file:', this.logFilePath);
27
31
  fs.writeFileSync(this.logFilePath, '');
28
32
 
29
- // Create symlink to ensure consistent path to latest debug file
30
- const symlinkPath = path.join(os.tmpdir(), 'testomatio.debug.latest.json');
33
+ // Create symlink in project root pointing to the timestamped debug file.
34
+ // Symlinks may fail on Windows without admin / on filesystems that don't support them;
35
+ // fall back to printing the actual tmp path so the user-facing log isn't misleading.
31
36
  try {
32
- // Remove existing symlink if it exists
33
- if (fs.existsSync(symlinkPath)) {
34
- fs.unlinkSync(symlinkPath);
37
+ // Use lstatSync (not existsSync) so we also detect dangling symlinks —
38
+ // existsSync follows links and returns false when the target is gone,
39
+ // which would leave a stale symlink in place and make symlinkSync fail with EEXIST.
40
+ try {
41
+ fs.lstatSync(paths.root);
42
+ fs.unlinkSync(paths.root);
43
+ } catch (e) {
44
+ if (e.code !== 'ENOENT') throw e;
35
45
  }
36
- // Create new symlink pointing to the timestamped debug file
37
- fs.symlinkSync(this.logFilePath, symlinkPath);
38
- debug('Created symlink:', symlinkPath, '->', this.logFilePath);
46
+ fs.symlinkSync(this.logFilePath, paths.root);
47
+ debug('Created symlink:', paths.root, '->', this.logFilePath);
39
48
  } catch (err) {
40
- debug('Failed to create symlink:', err.message);
49
+ debug('Failed to create symlink, using tmp path directly:', err.message);
50
+ this.rootPath = this.logFilePath;
41
51
  }
42
52
 
43
53
  log.info('🪲 Debug file created');
@@ -114,8 +124,12 @@ export class DebugPipe {
114
124
  if (!this.isEnabled) return;
115
125
  await this.sync();
116
126
  if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
117
- this.logToFile({ action: 'finishRun', params });
118
- log.info('🪲 Debug Saved to', this.logFilePath);
127
+ const logData = { action: 'finishRun', params };
128
+ if (this.store.runId) logData.runId = this.store.runId;
129
+ this.logToFile(logData);
130
+
131
+ log.info(`🪲 Debug file: ${this.rootPath}`);
132
+ log.info(`History: ${this.historyDir}`);
119
133
  }
120
134
 
121
135
  async sync() {
package/src/pipe/html.js CHANGED
@@ -906,121 +906,31 @@ function dropISayEcho(lines) {
906
906
  return out;
907
907
  }
908
908
 
909
- /**
910
- * Collects all Testomatio and S3 environment variables
911
- * Uses hardcoded list to avoid file system dependencies for end users
912
- * @returns {Object} Object with TESTOMATIO_ and S3_ variables grouped
913
- */
914
- function collectEnvironmentVariables() {
915
- return getHardcodedEnvVars();
916
- }
909
+ const SENSITIVE_ENV_PATTERNS = [/TOKEN/, /SECRET/, /PASSWORD/, /KEY/, /^TESTOMATIO$/];
917
910
 
918
- /**
919
- * Process environment variables configuration and collect their values
920
- * @param {Object} varConfigs - Object with variable configurations { [key]: { description } }
921
- * @param {Set} sensitiveVars - Set of sensitive variable names
922
- * @returns {Object} Processed environment variables with metadata
923
- */
924
- function processEnvironmentVariables(varConfigs, sensitiveVars) {
925
- const result = {};
911
+ function isSensitiveEnvName(name) {
912
+ return SENSITIVE_ENV_PATTERNS.some(re => re.test(name));
913
+ }
926
914
 
927
- for (const [key, config] of Object.entries(varConfigs)) {
928
- const value = process.env[key];
929
- const isSensitive = sensitiveVars.has(key);
915
+ function collectEnvironmentVariables() {
916
+ const groups = { testomatio: {}, s3: {} };
930
917
 
931
- if (isSensitive) {
932
- if (value !== undefined) {
933
- result[key] = { value: '***', description: config.description, isSet: true, isSensitive: true };
934
- } else {
935
- result[key] = { value: '', description: config.description, isSet: false, isSensitive: true };
936
- }
937
- } else {
938
- if (value !== undefined) {
939
- result[key] = { value, description: config.description, isSet: true };
940
- } else {
941
- result[key] = { value: '', description: config.description, isSet: false };
942
- }
943
- }
944
- }
918
+ for (const [name, value] of Object.entries(process.env)) {
919
+ if (value === undefined) continue;
945
920
 
946
- return result;
947
- }
921
+ let group = null;
922
+ if (name === 'TESTOMATIO' || name.startsWith('TESTOMATIO_')) group = 'testomatio';
923
+ else if (name.startsWith('S3_')) group = 's3';
924
+ if (!group) continue;
948
925
 
949
- /**
950
- * Hardcoded environment variables stored in code
951
- * This is the main source of truth for env vars to avoid file system dependencies
952
- * @returns {Object} Object with TESTOMATIO_ and S3_ variables
953
- */
954
- function getHardcodedEnvVars() {
955
- const allVars = {
956
- testomatio: {
957
- TESTOMATIO: { description: 'API Key for Testomat.io' },
958
- TESTOMATIO_API_KEY: { description: 'API Key (alias for TESTOMATIO)' },
959
- TESTOMATIO_CREATE: { description: 'Create new tests in Testomat.io' },
960
- TESTOMATIO_DEBUG: { description: 'Enable debug mode' },
961
- TESTOMATIO_DISABLE_BATCH_UPLOAD: { description: 'Disable batch upload' },
962
- TESTOMATIO_ENV: { description: 'Environment label (e.g., "Windows, Chrome")' },
963
- TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN: { description: 'Glob pattern to exclude files' },
964
- TESTOMATIO_EXCLUDE_SKIPPED: { description: 'Exclude skipped tests from report' },
965
- TESTOMATIO_FILENAME: { description: 'HTML report filename' },
966
- TESTOMATIO_HTML_FILENAME: { description: 'HTML report filename' },
967
- TESTOMATIO_HTML_REPORT_FOLDER: { description: 'Folder for HTML report' },
968
- TESTOMATIO_HTML_REPORT_SAVE: { description: 'Save HTML report' },
969
- TESTOMATIO_INTERCEPT_CONSOLE_LOGS: { description: 'Intercept console logs' },
970
- TESTOMATIO_MARK_DETACHED: { description: 'Mark tests as detached' },
971
- TESTOMATIO_MAX_REQUEST_FAILURES: { description: 'Max request failures' },
972
- TESTOMATIO_MAX_REQUEST_FAILURES_COUNT: { description: 'Max request failures count' },
973
- TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS: { description: 'Max retries within time period' },
974
- TESTOMATIO_NO_STEPS: { description: 'Disable steps reporting' },
975
- TESTOMATIO_NO_TIMESTAMP: { description: 'Remove timestamps from logs' },
976
- TESTOMATIO_PROCEED: { description: 'Proceed even if tests fail' },
977
- TESTOMATIO_PUBLISH: { description: 'Publish results to Testomat.io' },
978
- TESTOMATIO_REQUEST_TIMEOUT: { description: 'Request timeout in milliseconds' },
979
- TESTOMATIO_RUN: { description: 'Run ID to report tests to' },
980
- TESTOMATIO_RUNGROUP_TITLE: { description: 'Title for run group' },
981
- TESTOMATIO_SHARED_RUN: { description: 'Share run for parallel execution' },
982
- TESTOMATIO_SHARED_RUN_TIMEOUT: { description: 'Timeout for shared run (in seconds)' },
983
- TESTOMATIO_STACK_ARTIFACTS: { description: 'Stack artifacts in report' },
984
- TESTOMATIO_STACK_FILTER: { description: 'Filter stack traces' },
985
- TESTOMATIO_STACK_PASSED: { description: 'Report stack for passed tests' },
986
- TESTOMATIO_STEPS_PASSED: { description: 'Report steps for passed tests' },
987
- TESTOMATIO_SUITE: { description: 'Suite ID for new tests' },
988
- TESTOMATIO_TOKEN: { description: 'API Token (alias for TESTOMATIO)' },
989
- TESTOMATIO_TITLE: { description: 'Title for the test run' },
990
- TESTOMATIO_URL: { description: 'Testomat.io URL (custom instance)' },
991
- TESTOMATIO_WORKDIR: { description: 'Working directory for relative paths' },
992
- },
993
- s3: {
994
- S3_ACCESS_KEY_ID: { description: 'S3 access key ID' },
995
- S3_BUCKET: { description: 'S3 bucket name' },
996
- S3_ENDPOINT: { description: 'S3 endpoint URL' },
997
- S3_FORCE_PATH_STYLE: { description: 'S3 force path style' },
998
- S3_KEY: { description: 'S3 access key' },
999
- S3_PREFIX: { description: 'S3 key prefix' },
1000
- S3_REGION: { description: 'S3 region' },
1001
- S3_SECRET: { description: 'S3 secret key' },
1002
- S3_SECRET_ACCESS_KEY: { description: 'S3 secret access key' },
1003
- S3_SESSION_TOKEN: { description: 'S3 session token' },
1004
- },
1005
- };
926
+ const isSensitive = isSensitiveEnvName(name);
927
+ let displayValue = value;
928
+ if (isSensitive) displayValue = '***';
1006
929
 
1007
- const sensitiveVars = new Set([
1008
- 'TESTOMATIO',
1009
- 'TESTOMATIO_TOKEN',
1010
- 'TESTOMATIO_API_KEY',
1011
- 'S3_KEY',
1012
- 'S3_SECRET',
1013
- 'S3_ACCESS_KEY_ID',
1014
- 'S3_SECRET_ACCESS_KEY',
1015
- 'S3_SESSION_TOKEN',
1016
- ]);
1017
-
1018
- const envVars = {
1019
- testomatio: processEnvironmentVariables(allVars.testomatio, sensitiveVars),
1020
- s3: processEnvironmentVariables(allVars.s3, sensitiveVars),
1021
- };
930
+ groups[group][name] = { value: displayValue, isSet: true, isSensitive };
931
+ }
1022
932
 
1023
- return envVars;
933
+ return groups;
1024
934
  }
1025
935
 
1026
936
  /**
package/src/pipe/index.js CHANGED
@@ -6,6 +6,7 @@ import GitHubPipe from './github.js';
6
6
  import GitLabPipe from './gitlab.js';
7
7
  import CsvPipe from './csv.js';
8
8
  import HtmlPipe from './html.js';
9
+ import MarkdownPipe from './markdown.js';
9
10
  import CoveragePipe from './coverage.js';
10
11
  import { BitbucketPipe } from './bitbucket.js';
11
12
  import { DebugPipe } from './debug.js';
@@ -48,6 +49,7 @@ export async function pipesFactory(params, opts) {
48
49
  new GitLabPipe(params, opts),
49
50
  new CsvPipe(params, opts),
50
51
  new HtmlPipe(params, opts),
52
+ new MarkdownPipe(params, opts),
51
53
  new BitbucketPipe(params, opts),
52
54
  new CoveragePipe(params, opts),
53
55
  new DebugPipe(params, opts),