@testomatio/reporter 2.7.8 → 2.7.9-beta.2-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/README.md CHANGED
@@ -135,6 +135,7 @@ Bring this reporter on CI and never lose test results again!
135
135
  - [Gitlab](./docs/pipes/gitlab.md)
136
136
  - [CSV](./docs/pipes/csv.md)
137
137
  - [HTML report](./docs/pipes/html.md)
138
+ - [Markdown report](./docs/pipes/markdown.md)
138
139
  - [Bitbucket](./docs/pipes/bitbucket.md)
139
140
  - 🔗 [Linking Tests](./docs/linking-tests.md)
140
141
  - 📓 [JUnit](./docs/junit.md)
package/lib/bin/cli.js CHANGED
@@ -310,7 +310,7 @@ program
310
310
  program
311
311
  .command('replay')
312
312
  .description('Replay test data from debug file and re-send to Testomat.io')
313
- .argument('[debug-file]', 'Path to debug file (defaults to /tmp/testomatio.debug.latest.json)')
313
+ .argument('[debug-file]', `Path to debug file. Defaults to ./${constants_js_1.DEBUG_FILE}.json`)
314
314
  .option('--dry-run', 'Preview the data without sending to Testomat.io')
315
315
  .action(async (debugFile, opts) => {
316
316
  try {
package/lib/client.js CHANGED
@@ -199,6 +199,9 @@ class Client {
199
199
  */
200
200
  const { rid, error = null, steps: originalSteps, title, suite_title } = testData;
201
201
  let steps = originalSteps;
202
+ // Capture step artifact paths BEFORE uploadStepArtifacts mutates them to URLs,
203
+ // so we can exclude them from the test-level artifacts list later.
204
+ const stepArtifactPaths = collectStepArtifactPaths(steps);
202
205
  // Upload artifacts from steps
203
206
  try {
204
207
  await this.uploadStepArtifacts(steps, rid);
@@ -208,8 +211,14 @@ class Client {
208
211
  }
209
212
  const uploadedFiles = [];
210
213
  const stackArtifactsEnabled = (0, utils_js_1.transformEnvVarToBoolean)(process.env.TESTOMATIO_STACK_ARTIFACTS);
211
- const { time = 0, example = null, files = [], filesBuffers = [], code = null, file, suite_id, test_id, timestamp, links, manuallyAttachedArtifacts, overwrite, tags, } = testData;
212
- let { message = '', meta = {} } = testData;
214
+ const { time = 0, example = null, filesBuffers = [], code = null, file, suite_id, test_id, timestamp, links, overwrite, tags, } = testData;
215
+ let { files = [], manuallyAttachedArtifacts, message = '', meta = {} } = testData;
216
+ if (stepArtifactPaths.size) {
217
+ files = files.filter(f => !isStepArtifact(f, stepArtifactPaths));
218
+ if (Array.isArray(manuallyAttachedArtifacts)) {
219
+ manuallyAttachedArtifacts = manuallyAttachedArtifacts.filter(a => !isStepArtifact(a, stepArtifactPaths));
220
+ }
221
+ }
213
222
  meta = Object.entries(meta)
214
223
  .filter(([, value]) => value !== null && value !== undefined)
215
224
  .reduce((acc, [key, value]) => {
@@ -371,6 +380,47 @@ class Client {
371
380
  }
372
381
  }
373
382
  exports.Client = Client;
383
+ /**
384
+ * Walks the step tree and returns the set of artifact path/url values
385
+ * referenced by `step.artifacts` at any depth.
386
+ *
387
+ * @param {any} steps
388
+ * @returns {Set<string>}
389
+ */
390
+ function collectStepArtifactPaths(steps) {
391
+ const paths = new Set();
392
+ if (!Array.isArray(steps))
393
+ return paths;
394
+ const walk = arr => {
395
+ for (const step of arr) {
396
+ if (!step)
397
+ continue;
398
+ if (Array.isArray(step.artifacts)) {
399
+ for (const a of step.artifacts) {
400
+ if (typeof a === 'string')
401
+ paths.add(a);
402
+ else if (a && typeof a === 'object' && typeof a.path === 'string')
403
+ paths.add(a.path);
404
+ }
405
+ }
406
+ if (Array.isArray(step.steps))
407
+ walk(step.steps);
408
+ }
409
+ };
410
+ walk(steps);
411
+ return paths;
412
+ }
413
+ /**
414
+ * @param {string|{path?: string}|null|undefined} item
415
+ * @param {Set<string>} paths
416
+ * @returns {boolean}
417
+ */
418
+ function isStepArtifact(item, paths) {
419
+ if (!item)
420
+ return false;
421
+ const p = typeof item === 'object' ? item.path : item;
422
+ return typeof p === 'string' && paths.has(p);
423
+ }
374
424
  /**
375
425
  *
376
426
  * @param {TestData} testData
@@ -15,6 +15,12 @@ export namespace HTML_REPORT {
15
15
  let REPORT_DEFAULT_NAME: string;
16
16
  let TEMPLATE_NAME: string;
17
17
  }
18
+ export namespace MARKDOWN_REPORT {
19
+ let FOLDER_1: string;
20
+ export { FOLDER_1 as FOLDER };
21
+ let REPORT_DEFAULT_NAME_1: string;
22
+ export { REPORT_DEFAULT_NAME_1 as REPORT_DEFAULT_NAME };
23
+ }
18
24
  export const REQUEST_TIMEOUT: number;
19
25
  export function getCreateRunRequestTimeout(): number;
20
26
  export const testomatLogoURL: "https://avatars.githubusercontent.com/u/59105116?s=36&v=4";
@@ -25,3 +31,4 @@ export namespace REPORTER_REQUEST_RETRIES {
25
31
  let withinTimeSeconds: number;
26
32
  }
27
33
  export const SCREENSHOTS_ON_STEPS: boolean;
34
+ export const DEBUG_FILE: "testomatio.debug";
package/lib/constants.js CHANGED
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SCREENSHOTS_ON_STEPS = exports.REPORTER_REQUEST_RETRIES = exports.testomatLogoURL = exports.REQUEST_TIMEOUT = exports.HTML_REPORT = exports.STATUS = exports.CSV_HEADERS = exports.TESTOMAT_TMP_STORAGE_DIR = exports.APP_PREFIX = void 0;
6
+ exports.DEBUG_FILE = exports.SCREENSHOTS_ON_STEPS = exports.REPORTER_REQUEST_RETRIES = exports.testomatLogoURL = exports.REQUEST_TIMEOUT = exports.MARKDOWN_REPORT = exports.HTML_REPORT = exports.STATUS = exports.CSV_HEADERS = exports.TESTOMAT_TMP_STORAGE_DIR = exports.APP_PREFIX = void 0;
7
7
  exports.getCreateRunRequestTimeout = getCreateRunRequestTimeout;
8
8
  const picocolors_1 = __importDefault(require("picocolors"));
9
9
  const os_1 = __importDefault(require("os"));
@@ -44,6 +44,12 @@ const HTML_REPORT = {
44
44
  TEMPLATE_NAME: 'testomatio.hbs',
45
45
  };
46
46
  exports.HTML_REPORT = HTML_REPORT;
47
+ // markdown pipe var
48
+ const MARKDOWN_REPORT = {
49
+ FOLDER: 'md-report',
50
+ REPORT_DEFAULT_NAME: 'testomatio-report.md',
51
+ };
52
+ exports.MARKDOWN_REPORT = MARKDOWN_REPORT;
47
53
  const testomatLogoURL = 'https://avatars.githubusercontent.com/u/59105116?s=36&v=4';
48
54
  exports.testomatLogoURL = testomatLogoURL;
49
55
  const REPORTER_REQUEST_RETRIES = {
@@ -53,6 +59,8 @@ const REPORTER_REQUEST_RETRIES = {
53
59
  withinTimeSeconds: Number(process.env.TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS) || 60,
54
60
  };
55
61
  exports.REPORTER_REQUEST_RETRIES = REPORTER_REQUEST_RETRIES;
62
+ const DEBUG_FILE = 'testomatio.debug';
63
+ exports.DEBUG_FILE = DEBUG_FILE;
56
64
  function getCreateRunRequestTimeout() {
57
65
  return Math.max(REQUEST_TIMEOUT, 80 * 1000);
58
66
  }
@@ -73,6 +81,10 @@ module.exports.STATUS = STATUS;
73
81
 
74
82
  module.exports.HTML_REPORT = HTML_REPORT;
75
83
 
84
+ module.exports.MARKDOWN_REPORT = MARKDOWN_REPORT;
85
+
76
86
  module.exports.testomatLogoURL = testomatLogoURL;
77
87
 
78
88
  module.exports.REPORTER_REQUEST_RETRIES = REPORTER_REQUEST_RETRIES;
89
+
90
+ module.exports.DEBUG_FILE = DEBUG_FILE;
@@ -11,6 +11,8 @@ export class DebugPipe {
11
11
  batchIndex: number;
12
12
  };
13
13
  logFilePath: string;
14
+ rootPath: string;
15
+ historyDir: string;
14
16
  testomatioEnvVars: {};
15
17
  batchUpload(): Promise<void>;
16
18
  /**
package/lib/pipe/debug.js CHANGED
@@ -6,10 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DebugPipe = 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 debug_1 = __importDefault(require("debug"));
11
10
  const pretty_ms_1 = __importDefault(require("pretty-ms"));
12
11
  const log_js_1 = require("../utils/log.js");
12
+ const debug_js_1 = require("../utils/debug.js");
13
13
  const debug = (0, debug_1.default)('@testomatio/reporter:pipe:debug');
14
14
  class DebugPipe {
15
15
  constructor(params, store) {
@@ -24,22 +24,34 @@ class DebugPipe {
24
24
  tests: [],
25
25
  batchIndex: 0,
26
26
  };
27
- this.logFilePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.debug.${Date.now()}.json`);
27
+ const suffix = process.env.TESTOMATIO_REPLAY ? 'replay' : '';
28
+ const paths = (0, debug_js_1.getDebugFilePath)(suffix);
29
+ this.logFilePath = paths.tmp;
30
+ this.rootPath = paths.root;
31
+ this.historyDir = path_1.default.dirname(paths.tmp);
28
32
  debug('Creating debug file:', this.logFilePath);
29
33
  fs_1.default.writeFileSync(this.logFilePath, '');
30
- // Create symlink to ensure consistent path to latest debug file
31
- const symlinkPath = path_1.default.join(os_1.default.tmpdir(), 'testomatio.debug.latest.json');
34
+ // Create symlink in project root pointing to the timestamped debug file.
35
+ // Symlinks may fail on Windows without admin / on filesystems that don't support them;
36
+ // fall back to printing the actual tmp path so the user-facing log isn't misleading.
32
37
  try {
33
- // Remove existing symlink if it exists
34
- if (fs_1.default.existsSync(symlinkPath)) {
35
- fs_1.default.unlinkSync(symlinkPath);
38
+ // Use lstatSync (not existsSync) so we also detect dangling symlinks —
39
+ // existsSync follows links and returns false when the target is gone,
40
+ // which would leave a stale symlink in place and make symlinkSync fail with EEXIST.
41
+ try {
42
+ fs_1.default.lstatSync(paths.root);
43
+ fs_1.default.unlinkSync(paths.root);
36
44
  }
37
- // Create new symlink pointing to the timestamped debug file
38
- fs_1.default.symlinkSync(this.logFilePath, symlinkPath);
39
- debug('Created symlink:', symlinkPath, '->', this.logFilePath);
45
+ catch (e) {
46
+ if (e.code !== 'ENOENT')
47
+ throw e;
48
+ }
49
+ fs_1.default.symlinkSync(this.logFilePath, paths.root);
50
+ debug('Created symlink:', paths.root, '->', this.logFilePath);
40
51
  }
41
52
  catch (err) {
42
- debug('Failed to create symlink:', err.message);
53
+ debug('Failed to create symlink, using tmp path directly:', err.message);
54
+ this.rootPath = this.logFilePath;
43
55
  }
44
56
  log_js_1.log.info('🪲 Debug file created');
45
57
  this.testomatioEnvVars = Object.keys(process.env)
@@ -117,8 +129,12 @@ class DebugPipe {
117
129
  await this.sync();
118
130
  if (this.batch.intervalFunction)
119
131
  clearInterval(this.batch.intervalFunction);
120
- this.logToFile({ action: 'finishRun', params });
121
- log_js_1.log.info('🪲 Debug Saved to', this.logFilePath);
132
+ const logData = { action: 'finishRun', params };
133
+ if (this.store.runId)
134
+ logData.runId = this.store.runId;
135
+ this.logToFile(logData);
136
+ log_js_1.log.info(`🪲 Debug file: ${this.rootPath}`);
137
+ log_js_1.log.info(`History: ${this.historyDir}`);
122
138
  }
123
139
  async sync() {
124
140
  if (!this.isEnabled)
package/lib/pipe/html.js CHANGED
@@ -760,116 +760,29 @@ function dropISayEcho(lines) {
760
760
  }
761
761
  return out;
762
762
  }
763
- /**
764
- * Collects all Testomatio and S3 environment variables
765
- * Uses hardcoded list to avoid file system dependencies for end users
766
- * @returns {Object} Object with TESTOMATIO_ and S3_ variables grouped
767
- */
768
- function collectEnvironmentVariables() {
769
- return getHardcodedEnvVars();
763
+ const SENSITIVE_ENV_PATTERNS = [/TOKEN/, /SECRET/, /PASSWORD/, /KEY/, /^TESTOMATIO$/];
764
+ function isSensitiveEnvName(name) {
765
+ return SENSITIVE_ENV_PATTERNS.some(re => re.test(name));
770
766
  }
771
- /**
772
- * Process environment variables configuration and collect their values
773
- * @param {Object} varConfigs - Object with variable configurations { [key]: { description } }
774
- * @param {Set} sensitiveVars - Set of sensitive variable names
775
- * @returns {Object} Processed environment variables with metadata
776
- */
777
- function processEnvironmentVariables(varConfigs, sensitiveVars) {
778
- const result = {};
779
- for (const [key, config] of Object.entries(varConfigs)) {
780
- const value = process.env[key];
781
- const isSensitive = sensitiveVars.has(key);
782
- if (isSensitive) {
783
- if (value !== undefined) {
784
- result[key] = { value: '***', description: config.description, isSet: true, isSensitive: true };
785
- }
786
- else {
787
- result[key] = { value: '', description: config.description, isSet: false, isSensitive: true };
788
- }
789
- }
790
- else {
791
- if (value !== undefined) {
792
- result[key] = { value, description: config.description, isSet: true };
793
- }
794
- else {
795
- result[key] = { value: '', description: config.description, isSet: false };
796
- }
797
- }
767
+ function collectEnvironmentVariables() {
768
+ const groups = { testomatio: {}, s3: {} };
769
+ for (const [name, value] of Object.entries(process.env)) {
770
+ if (value === undefined)
771
+ continue;
772
+ let group = null;
773
+ if (name === 'TESTOMATIO' || name.startsWith('TESTOMATIO_'))
774
+ group = 'testomatio';
775
+ else if (name.startsWith('S3_'))
776
+ group = 's3';
777
+ if (!group)
778
+ continue;
779
+ const isSensitive = isSensitiveEnvName(name);
780
+ let displayValue = value;
781
+ if (isSensitive)
782
+ displayValue = '***';
783
+ groups[group][name] = { value: displayValue, isSet: true, isSensitive };
798
784
  }
799
- return result;
800
- }
801
- /**
802
- * Hardcoded environment variables stored in code
803
- * This is the main source of truth for env vars to avoid file system dependencies
804
- * @returns {Object} Object with TESTOMATIO_ and S3_ variables
805
- */
806
- function getHardcodedEnvVars() {
807
- const allVars = {
808
- testomatio: {
809
- TESTOMATIO: { description: 'API Key for Testomat.io' },
810
- TESTOMATIO_API_KEY: { description: 'API Key (alias for TESTOMATIO)' },
811
- TESTOMATIO_CREATE: { description: 'Create new tests in Testomat.io' },
812
- TESTOMATIO_DEBUG: { description: 'Enable debug mode' },
813
- TESTOMATIO_DISABLE_BATCH_UPLOAD: { description: 'Disable batch upload' },
814
- TESTOMATIO_ENV: { description: 'Environment label (e.g., "Windows, Chrome")' },
815
- TESTOMATIO_EXCLUDE_FILES_FROM_REPORT_GLOB_PATTERN: { description: 'Glob pattern to exclude files' },
816
- TESTOMATIO_EXCLUDE_SKIPPED: { description: 'Exclude skipped tests from report' },
817
- TESTOMATIO_FILENAME: { description: 'HTML report filename' },
818
- TESTOMATIO_HTML_FILENAME: { description: 'HTML report filename' },
819
- TESTOMATIO_HTML_REPORT_FOLDER: { description: 'Folder for HTML report' },
820
- TESTOMATIO_HTML_REPORT_SAVE: { description: 'Save HTML report' },
821
- TESTOMATIO_INTERCEPT_CONSOLE_LOGS: { description: 'Intercept console logs' },
822
- TESTOMATIO_MARK_DETACHED: { description: 'Mark tests as detached' },
823
- TESTOMATIO_MAX_REQUEST_FAILURES: { description: 'Max request failures' },
824
- TESTOMATIO_MAX_REQUEST_FAILURES_COUNT: { description: 'Max request failures count' },
825
- TESTOMATIO_MAX_REQUEST_RETRIES_WITHIN_TIME_SECONDS: { description: 'Max retries within time period' },
826
- TESTOMATIO_NO_STEPS: { description: 'Disable steps reporting' },
827
- TESTOMATIO_NO_TIMESTAMP: { description: 'Remove timestamps from logs' },
828
- TESTOMATIO_PROCEED: { description: 'Proceed even if tests fail' },
829
- TESTOMATIO_PUBLISH: { description: 'Publish results to Testomat.io' },
830
- TESTOMATIO_REQUEST_TIMEOUT: { description: 'Request timeout in milliseconds' },
831
- TESTOMATIO_RUN: { description: 'Run ID to report tests to' },
832
- TESTOMATIO_RUNGROUP_TITLE: { description: 'Title for run group' },
833
- TESTOMATIO_SHARED_RUN: { description: 'Share run for parallel execution' },
834
- TESTOMATIO_SHARED_RUN_TIMEOUT: { description: 'Timeout for shared run (in seconds)' },
835
- TESTOMATIO_STACK_ARTIFACTS: { description: 'Stack artifacts in report' },
836
- TESTOMATIO_STACK_FILTER: { description: 'Filter stack traces' },
837
- TESTOMATIO_STACK_PASSED: { description: 'Report stack for passed tests' },
838
- TESTOMATIO_STEPS_PASSED: { description: 'Report steps for passed tests' },
839
- TESTOMATIO_SUITE: { description: 'Suite ID for new tests' },
840
- TESTOMATIO_TOKEN: { description: 'API Token (alias for TESTOMATIO)' },
841
- TESTOMATIO_TITLE: { description: 'Title for the test run' },
842
- TESTOMATIO_URL: { description: 'Testomat.io URL (custom instance)' },
843
- TESTOMATIO_WORKDIR: { description: 'Working directory for relative paths' },
844
- },
845
- s3: {
846
- S3_ACCESS_KEY_ID: { description: 'S3 access key ID' },
847
- S3_BUCKET: { description: 'S3 bucket name' },
848
- S3_ENDPOINT: { description: 'S3 endpoint URL' },
849
- S3_FORCE_PATH_STYLE: { description: 'S3 force path style' },
850
- S3_KEY: { description: 'S3 access key' },
851
- S3_PREFIX: { description: 'S3 key prefix' },
852
- S3_REGION: { description: 'S3 region' },
853
- S3_SECRET: { description: 'S3 secret key' },
854
- S3_SECRET_ACCESS_KEY: { description: 'S3 secret access key' },
855
- S3_SESSION_TOKEN: { description: 'S3 session token' },
856
- },
857
- };
858
- const sensitiveVars = new Set([
859
- 'TESTOMATIO',
860
- 'TESTOMATIO_TOKEN',
861
- 'TESTOMATIO_API_KEY',
862
- 'S3_KEY',
863
- 'S3_SECRET',
864
- 'S3_ACCESS_KEY_ID',
865
- 'S3_SECRET_ACCESS_KEY',
866
- 'S3_SESSION_TOKEN',
867
- ]);
868
- const envVars = {
869
- testomatio: processEnvironmentVariables(allVars.testomatio, sensitiveVars),
870
- s3: processEnvironmentVariables(allVars.s3, sensitiveVars),
871
- };
872
- return envVars;
785
+ return groups;
873
786
  }
874
787
  /**
875
788
  * Prepares test steps for HTML report display
package/lib/pipe/index.js CHANGED
@@ -45,6 +45,7 @@ const github_js_1 = __importDefault(require("./github.js"));
45
45
  const gitlab_js_1 = __importDefault(require("./gitlab.js"));
46
46
  const csv_js_1 = __importDefault(require("./csv.js"));
47
47
  const html_js_1 = __importDefault(require("./html.js"));
48
+ const markdown_js_1 = __importDefault(require("./markdown.js"));
48
49
  const coverage_js_1 = __importDefault(require("./coverage.js"));
49
50
  const bitbucket_js_1 = require("./bitbucket.js");
50
51
  const debug_js_1 = require("./debug.js");
@@ -83,6 +84,7 @@ async function pipesFactory(params, opts) {
83
84
  new gitlab_js_1.default(params, opts),
84
85
  new csv_js_1.default(params, opts),
85
86
  new html_js_1.default(params, opts),
87
+ new markdown_js_1.default(params, opts),
86
88
  new bitbucket_js_1.BitbucketPipe(params, opts),
87
89
  new coverage_js_1.default(params, opts),
88
90
  new debug_js_1.DebugPipe(params, opts),
@@ -0,0 +1,25 @@
1
+ export default MarkdownPipe;
2
+ declare class MarkdownPipe {
3
+ constructor(params: any, store?: {});
4
+ store: {};
5
+ title: any;
6
+ apiKey: any;
7
+ isMarkdown: string;
8
+ isEnabled: boolean;
9
+ markdownOutputPath: string;
10
+ filenameMsg: string;
11
+ tests: any[];
12
+ markdownReportDir: string;
13
+ markdownReportName: string;
14
+ createRun(): Promise<void>;
15
+ prepareRun(): Promise<void>;
16
+ updateRun(): void;
17
+ /**
18
+ * @param {import('../../types/types.js').MarkdownTestData} test
19
+ */
20
+ addTest(test: import("../../types/types.js").MarkdownTestData): void;
21
+ finishRun(runParams: any): Promise<void>;
22
+ buildReport(opts: any): void;
23
+ sync(): Promise<void>;
24
+ toString(): string;
25
+ }