@testomatio/reporter 2.3.9 → 2.4.0

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.
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ const helpers_js_1 = require("./helpers.js");
3
4
  const index_js_1 = require("./services/index.js");
4
5
  /**
5
6
  * Stores path to file as artifact and uploads it to the S3 storage
@@ -8,9 +9,7 @@ const index_js_1 = require("./services/index.js");
8
9
  * @returns {void}
9
10
  */
10
11
  function saveArtifact(data, context = null) {
11
- if (process.env.IS_PLAYWRIGHT)
12
- throw new Error(`This function is not available in Playwright framework.
13
- /Playwright supports artifacts out of the box`);
12
+ showPlaywrightWarning('artifact', 'Playwright supports artifacts out of the box.');
14
13
  if (!data)
15
14
  return;
16
15
  index_js_1.services.artifacts.put(data, context);
@@ -21,8 +20,6 @@ function saveArtifact(data, context = null) {
21
20
  * @returns {void}
22
21
  */
23
22
  function logMessage(...args) {
24
- if (process.env.IS_PLAYWRIGHT)
25
- throw new Error('This function is not available in Playwright framework');
26
23
  index_js_1.services.logger._templateLiteralLog(...args);
27
24
  }
28
25
  /**
@@ -31,9 +28,11 @@ function logMessage(...args) {
31
28
  * @returns {void}
32
29
  */
33
30
  function addStep(message) {
34
- if (process.env.IS_PLAYWRIGHT)
35
- throw new Error('This function is not available in Playwright framework. Use playwright steps');
36
31
  index_js_1.services.logger.step(message);
32
+ // this is done because Playwright reporter intercepts console logs and then we gather them and show on Testomat
33
+ // if not console.log, the step message will be lost from reporter
34
+ if (helpers_js_1.isPlaywright)
35
+ console.log(`Step: ${message}`);
37
36
  }
38
37
  /**
39
38
  * Add key-value pair(s) to the test report
@@ -42,8 +41,7 @@ function addStep(message) {
42
41
  * @returns {void}
43
42
  */
44
43
  function setKeyValue(keyValue, value = null) {
45
- if (process.env.IS_PLAYWRIGHT)
46
- throw new Error('This function is not available in Playwright framework. Use test tag instead.');
44
+ showPlaywrightWarning('meta', 'Use test annotations instead.');
47
45
  if (typeof keyValue === 'string') {
48
46
  keyValue = { [keyValue]: value };
49
47
  }
@@ -56,6 +54,7 @@ function setKeyValue(keyValue, value = null) {
56
54
  * @returns {void}
57
55
  */
58
56
  function setLabel(key, value = null) {
57
+ showPlaywrightWarning('label', 'Use test tag instead.');
59
58
  if (Array.isArray(value)) {
60
59
  return value.forEach(label => setLabel(key, label));
61
60
  }
@@ -80,6 +79,11 @@ function linkJira(...jiraIds) {
80
79
  const links = jiraIds.map(jiraId => ({ jira: jiraId }));
81
80
  index_js_1.services.links.put(links);
82
81
  }
82
+ function showPlaywrightWarning(functionName, recommendation) {
83
+ if (helpers_js_1.isPlaywright) {
84
+ console.warn(`[TESTOMATIO] '${functionName}' function is not supported for Playwright. ${recommendation}`);
85
+ }
86
+ }
83
87
  module.exports = {
84
88
  artifact: saveArtifact,
85
89
  log: logMessage,
package/lib/reporter.d.ts CHANGED
@@ -12,7 +12,7 @@ export const artifact: (data: string | {
12
12
  }, context?: any) => void;
13
13
  export const log: (...args: any[]) => void;
14
14
  export const logger: {
15
- "__#13@#originalUserLogger": {
15
+ "__#14@#originalUserLogger": {
16
16
  assert(condition?: boolean, ...data: any[]): void;
17
17
  assert(value: any, message?: string, ...optionalParams: any[]): void;
18
18
  clear(): void;
@@ -57,13 +57,13 @@ export const logger: {
57
57
  profile(label?: string): void;
58
58
  profileEnd(label?: string): void;
59
59
  };
60
- "__#13@#userLoggerWithOverridenMethods": any;
60
+ "__#14@#userLoggerWithOverridenMethods": any;
61
61
  logLevel: string;
62
62
  step(strings: any, ...values: any[]): void;
63
63
  getLogs(context: string): string[];
64
- "__#13@#stringifyLogs"(...args: any[]): string;
64
+ "__#14@#stringifyLogs"(...args: any[]): string;
65
65
  _templateLiteralLog(strings: any, ...args: any[]): void;
66
- "__#13@#logWrapper"(argsArray: any, level: any): void;
66
+ "__#14@#logWrapper"(argsArray: any, level: any): void;
67
67
  assert(...args: any[]): void;
68
68
  debug(...args: any[]): void;
69
69
  error(...args: any[]): void;
@@ -88,7 +88,7 @@ export const linkTest: (...testIds: string[]) => void;
88
88
  export const linkJira: (...jiraIds: string[]) => void;
89
89
  declare namespace _default {
90
90
  export let testomatioLogger: {
91
- "__#13@#originalUserLogger": {
91
+ "__#14@#originalUserLogger": {
92
92
  assert(condition?: boolean, ...data: any[]): void;
93
93
  assert(value: any, message?: string, ...optionalParams: any[]): void;
94
94
  clear(): void;
@@ -133,13 +133,13 @@ declare namespace _default {
133
133
  profile(label?: string): void;
134
134
  profileEnd(label?: string): void;
135
135
  };
136
- "__#13@#userLoggerWithOverridenMethods": any;
136
+ "__#14@#userLoggerWithOverridenMethods": any;
137
137
  logLevel: string;
138
138
  step(strings: any, ...values: any[]): void;
139
139
  getLogs(context: string): string[];
140
- "__#13@#stringifyLogs"(...args: any[]): string;
140
+ "__#14@#stringifyLogs"(...args: any[]): string;
141
141
  _templateLiteralLog(strings: any, ...args: any[]): void;
142
- "__#13@#logWrapper"(argsArray: any, level: any): void;
142
+ "__#14@#logWrapper"(argsArray: any, level: any): void;
143
143
  assert(...args: any[]): void;
144
144
  debug(...args: any[]): void;
145
145
  error(...args: any[]): void;
@@ -162,7 +162,7 @@ declare namespace _default {
162
162
  }, context?: any) => void;
163
163
  export let log: (...args: any[]) => void;
164
164
  export let logger: {
165
- "__#13@#originalUserLogger": {
165
+ "__#14@#originalUserLogger": {
166
166
  assert(condition?: boolean, ...data: any[]): void;
167
167
  assert(value: any, message?: string, ...optionalParams: any[]): void;
168
168
  clear(): void;
@@ -207,13 +207,13 @@ declare namespace _default {
207
207
  profile(label?: string): void;
208
208
  profileEnd(label?: string): void;
209
209
  };
210
- "__#13@#userLoggerWithOverridenMethods": any;
210
+ "__#14@#userLoggerWithOverridenMethods": any;
211
211
  logLevel: string;
212
212
  step(strings: any, ...values: any[]): void;
213
213
  getLogs(context: string): string[];
214
- "__#13@#stringifyLogs"(...args: any[]): string;
214
+ "__#14@#stringifyLogs"(...args: any[]): string;
215
215
  _templateLiteralLog(strings: any, ...args: any[]): void;
216
- "__#13@#logWrapper"(argsArray: any, level: any): void;
216
+ "__#14@#logWrapper"(argsArray: any, level: any): void;
217
217
  assert(...args: any[]): void;
218
218
  debug(...args: any[]): void;
219
219
  error(...args: any[]): void;
@@ -3,7 +3,7 @@ export const artifactStorage: ArtifactStorage;
3
3
  * Artifact storage is supposed to store file paths
4
4
  */
5
5
  declare class ArtifactStorage {
6
- static "__#14@#instance": any;
6
+ static "__#15@#instance": any;
7
7
  /**
8
8
  * Singleton
9
9
  * @returns {ArtifactStorage}
@@ -1,6 +1,6 @@
1
1
  export const keyValueStorage: KeyValueStorage;
2
2
  declare class KeyValueStorage {
3
- static "__#15@#instance": any;
3
+ static "__#16@#instance": any;
4
4
  /**
5
5
  *
6
6
  * @returns {KeyValueStorage}
@@ -1,6 +1,6 @@
1
1
  export const linkStorage: LinkStorage;
2
2
  declare class LinkStorage {
3
- static "__#16@#instance": any;
3
+ static "__#17@#instance": any;
4
4
  /**
5
5
  *
6
6
  * @returns {LinkStorage}
@@ -5,7 +5,7 @@ export const logger: Logger;
5
5
  * Supports different syntaxes to satisfy any user preferences.
6
6
  */
7
7
  declare class Logger {
8
- static "__#13@#instance": any;
8
+ static "__#14@#instance": any;
9
9
  /**
10
10
  *
11
11
  * @returns {Logger}
@@ -39,3 +39,18 @@ export function statusEmoji(status: string): string;
39
39
  * @returns {string} - A formatted full name string for the test object.
40
40
  */
41
41
  export function fullName(t: object): string;
42
+ /**
43
+ * Parses a comma-separated list of key-value pairs into an options object.
44
+ *
45
+ * The input string should be formatted as `"key1=value1,key2=value2,..."`.
46
+ * Whitespace around keys and values is trimmed. If the input is empty or undefined,
47
+ * an empty object is returned.
48
+ *
49
+ * @param {string} [optionsStr] - A comma-separated string of key=value pairs.
50
+ * @returns {Object} An object mapping option keys to their string values.
51
+ *
52
+ * @example
53
+ * parsePipeOptions('foo=bar,baz=qux');
54
+ * => Returns: { foo: 'bar', baz: 'qux' }
55
+ */
56
+ export function parsePipeOptions(optionsStr?: string): any;
@@ -6,6 +6,7 @@ exports.generateFilterRequestParams = generateFilterRequestParams;
6
6
  exports.setS3Credentials = setS3Credentials;
7
7
  exports.statusEmoji = statusEmoji;
8
8
  exports.fullName = fullName;
9
+ exports.parsePipeOptions = parsePipeOptions;
9
10
  const constants_js_1 = require("../constants.js");
10
11
  /**
11
12
  * Set S3 credentials from the provided artifacts object.
@@ -27,6 +28,8 @@ function setS3Credentials(artifacts) {
27
28
  process.env.S3_SESSION_TOKEN = artifacts.SESSION_TOKEN;
28
29
  if (artifacts.presign)
29
30
  process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
31
+ if (artifacts.stack_artifacts)
32
+ process.env.TESTOMATIO_STACK_ARTIFACTS = '1';
30
33
  // endpoint is not received from the server; and shuld be empty if IAM used (credentails obtained from the testomat)
31
34
  process.env.S3_ENDPOINT = artifacts.ENDPOINT || '';
32
35
  }
@@ -36,6 +39,11 @@ function setS3Credentials(artifacts) {
36
39
  * @returns {Object|null} - An object containing the generated request parameters, or null if the type is invalid.
37
40
  */
38
41
  function generateFilterRequestParams(params) {
42
+ // Defensive check: ensure params is an object
43
+ if (!params || typeof params !== 'object') {
44
+ console.error(constants_js_1.APP_PREFIX, `Invalid parameters provided. Expected an object, got: ${typeof params}`);
45
+ return;
46
+ }
39
47
  const { type, id, apiKey } = params;
40
48
  if (!type) {
41
49
  return;
@@ -60,8 +68,11 @@ function generateFilterRequestParams(params) {
60
68
  * The object has properties "type" and "id".
61
69
  */
62
70
  function parseFilterParams(opts) {
63
- const [type, id] = opts.split('=');
71
+ const [type, ...idParts] = opts.split('=');
72
+ const id = idParts.join('=');
64
73
  const validType = updateFilterType(type);
74
+ if (!validType)
75
+ return undefined;
65
76
  return {
66
77
  type: validType,
67
78
  id,
@@ -74,6 +85,8 @@ function parseFilterParams(opts) {
74
85
  * Returns undefined if the type is not valid.
75
86
  */
76
87
  function updateFilterType(type) {
88
+ if (!type || typeof type !== 'string')
89
+ return;
77
90
  let typeLowerCase = type.toLowerCase();
78
91
  const filterTypes = ['tag-name', 'plan', 'label', 'jira-ticket'];
79
92
  if (typeLowerCase === 'plan-id') {
@@ -87,7 +100,7 @@ function updateFilterType(type) {
87
100
  // "ims-issue", //TODO: WIP
88
101
  ];
89
102
  if (!filterTypes.includes(typeLowerCase)) {
90
- console.log(constants_js_1.APP_PREFIX, `❗❗❗ Invalid "filter=${type}" start settings! Available option list: ${filterTypes}`);
103
+ console.log(constants_js_1.APP_PREFIX, `❗❗❗ Invalid filter: "${type}" start settings! Available option list: ${filterTypes}`);
91
104
  return;
92
105
  }
93
106
  const index = filterTypes.indexOf(typeLowerCase);
@@ -121,6 +134,33 @@ function fullName(t) {
121
134
  line += ` \`[${Object.values(t.example)}]\``;
122
135
  return line;
123
136
  }
137
+ /**
138
+ * Parses a comma-separated list of key-value pairs into an options object.
139
+ *
140
+ * The input string should be formatted as `"key1=value1,key2=value2,..."`.
141
+ * Whitespace around keys and values is trimmed. If the input is empty or undefined,
142
+ * an empty object is returned.
143
+ *
144
+ * @param {string} [optionsStr] - A comma-separated string of key=value pairs.
145
+ * @returns {Object} An object mapping option keys to their string values.
146
+ *
147
+ * @example
148
+ * parsePipeOptions('foo=bar,baz=qux');
149
+ * => Returns: { foo: 'bar', baz: 'qux' }
150
+ */
151
+ function parsePipeOptions(optionsStr) {
152
+ const options = {};
153
+ if (!optionsStr)
154
+ return options;
155
+ const pairs = optionsStr.split(',');
156
+ for (const pair of pairs) {
157
+ const [key, value] = pair.split('=');
158
+ if (key && value) {
159
+ options[key.trim()] = value.trim();
160
+ }
161
+ }
162
+ return options;
163
+ }
124
164
 
125
165
  module.exports.updateFilterType = updateFilterType;
126
166
 
@@ -133,3 +173,5 @@ module.exports.setS3Credentials = setS3Credentials;
133
173
  module.exports.statusEmoji = statusEmoji;
134
174
 
135
175
  module.exports.fullName = fullName;
176
+
177
+ module.exports.parsePipeOptions = parsePipeOptions;
@@ -17,6 +17,11 @@ export namespace fileSystem {
17
17
  export function foundedTestLog(app: any, tests: any): void;
18
18
  export function formatStep(step: any, shift?: number): any;
19
19
  export function getCurrentDateTime(): string;
20
+ /**
21
+ * Gets current git commit SHA
22
+ * @returns {String|null} git commit SHA or null if not available
23
+ */
24
+ export function getGitCommitSha(): string | null;
20
25
  /**
21
26
  * @param {String} testTitle - Test title
22
27
  *
@@ -59,3 +64,4 @@ export function transformEnvVarToBoolean(value: any): boolean;
59
64
  * @returns {String|null} validated suite ID or null if invalid
60
65
  */
61
66
  export function validateSuiteId(suiteId: string): string | null;
67
+ export function applyFilter(command: any, tests: any): any;
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.validateSuiteId = exports.testRunnerHelper = exports.specificTestInfo = exports.parseSuite = exports.isValidUrl = exports.humanize = exports.getTestomatIdFromTestTitle = exports.getCurrentDateTime = exports.foundedTestLog = exports.fileSystem = exports.fetchFilesFromStackTrace = exports.fetchIdFromOutput = exports.fetchIdFromCode = exports.fetchSourceCodeFromStackTrace = exports.fetchSourceCode = exports.isSameTest = exports.ansiRegExp = exports.SUITE_ID_REGEX = exports.TEST_ID_REGEX = void 0;
39
+ exports.validateSuiteId = exports.testRunnerHelper = exports.specificTestInfo = exports.parseSuite = exports.isValidUrl = exports.humanize = exports.getTestomatIdFromTestTitle = exports.getGitCommitSha = exports.getCurrentDateTime = exports.foundedTestLog = exports.fileSystem = exports.fetchFilesFromStackTrace = exports.fetchIdFromOutput = exports.fetchIdFromCode = exports.fetchSourceCodeFromStackTrace = exports.fetchSourceCode = exports.isSameTest = exports.ansiRegExp = exports.SUITE_ID_REGEX = exports.TEST_ID_REGEX = void 0;
40
40
  exports.getPackageVersion = getPackageVersion;
41
41
  exports.truncate = truncate;
42
42
  exports.cleanLatestRunId = cleanLatestRunId;
@@ -45,6 +45,7 @@ exports.readLatestRunId = readLatestRunId;
45
45
  exports.removeColorCodes = removeColorCodes;
46
46
  exports.storeRunId = storeRunId;
47
47
  exports.transformEnvVarToBoolean = transformEnvVarToBoolean;
48
+ exports.applyFilter = applyFilter;
48
49
  const url_1 = require("url");
49
50
  const path_1 = __importStar(require("path"));
50
51
  const picocolors_1 = __importDefault(require("picocolors"));
@@ -53,6 +54,7 @@ const is_valid_path_1 = __importDefault(require("is-valid-path"));
53
54
  const debug_1 = __importDefault(require("debug"));
54
55
  const os_1 = __importDefault(require("os"));
55
56
  const url_2 = require("url");
57
+ const child_process_1 = require("child_process");
56
58
  const debug = (0, debug_1.default)('@testomatio/reporter:util');
57
59
  // Use __dirname directly since we're compiling to CommonJS
58
60
  // prettier-ignore
@@ -98,6 +100,22 @@ const validateSuiteId = suiteId => {
98
100
  return match ? match[0] : null;
99
101
  };
100
102
  exports.validateSuiteId = validateSuiteId;
103
+ /**
104
+ * Gets current git commit SHA
105
+ * @returns {String|null} git commit SHA or null if not available
106
+ */
107
+ const getGitCommitSha = () => {
108
+ try {
109
+ const sha = (0, child_process_1.execSync)('git rev-parse --short HEAD', {
110
+ stdio: ['ignore', 'pipe', 'ignore']
111
+ }).toString().trim();
112
+ return sha || null;
113
+ }
114
+ catch (error) {
115
+ return null;
116
+ }
117
+ };
118
+ exports.getGitCommitSha = getGitCommitSha;
101
119
  const ansiRegExp = () => {
102
120
  const pattern = [
103
121
  '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
@@ -641,6 +659,54 @@ function truncate(s, size = 255) {
641
659
  }
642
660
  return `${str.substring(0, size)}...`;
643
661
  }
662
+ function applyFilter(command, tests) {
663
+ if (!tests || !tests.length)
664
+ return command;
665
+ const lower = (command || '').toLowerCase();
666
+ const regexPattern = `(${tests.join('|')})`;
667
+ if (lower.includes('jest')) {
668
+ return `${command} --testNamePattern ${regexPattern}`;
669
+ }
670
+ if (lower.includes('cypress')) {
671
+ const grepValue = tests.join(',');
672
+ const baseEnv = {
673
+ grep: grepValue,
674
+ grepFilterSpecs: true,
675
+ grepOmitFiltered: true,
676
+ };
677
+ if (command.includes('--env')) {
678
+ return command.replace(/--env\s+(['"]?)([^\s'"]+)\1/, (match, quote, envVal) => {
679
+ const existingEnv = {};
680
+ if (envVal.startsWith('{') && envVal.endsWith('}')) {
681
+ try {
682
+ Object.assign(existingEnv, JSON.parse(envVal));
683
+ }
684
+ catch (e) {
685
+ }
686
+ }
687
+ if (!Object.keys(existingEnv).length) {
688
+ envVal.split(',').forEach((pair) => {
689
+ const [k, v] = pair.split('=');
690
+ if (!k)
691
+ return;
692
+ if (v === 'true')
693
+ existingEnv[k] = true;
694
+ else if (v === 'false')
695
+ existingEnv[k] = false;
696
+ else
697
+ existingEnv[k] = v;
698
+ });
699
+ }
700
+ const merged = { ...existingEnv, ...baseEnv };
701
+ const json = JSON.stringify(merged);
702
+ return `--env ${json}`;
703
+ });
704
+ }
705
+ const json = JSON.stringify(baseEnv);
706
+ return `${command} --env ${json}`;
707
+ }
708
+ return `${command} --grep ${regexPattern}`;
709
+ }
644
710
 
645
711
  module.exports.getPackageVersion = getPackageVersion;
646
712
 
@@ -658,12 +724,16 @@ module.exports.storeRunId = storeRunId;
658
724
 
659
725
  module.exports.transformEnvVarToBoolean = transformEnvVarToBoolean;
660
726
 
727
+ module.exports.applyFilter = applyFilter;
728
+
661
729
  module.exports.getTestomatIdFromTestTitle = getTestomatIdFromTestTitle;
662
730
 
663
731
  module.exports.parseSuite = parseSuite;
664
732
 
665
733
  module.exports.validateSuiteId = validateSuiteId;
666
734
 
735
+ module.exports.getGitCommitSha = getGitCommitSha;
736
+
667
737
  module.exports.ansiRegExp = ansiRegExp;
668
738
 
669
739
  module.exports.isValidUrl = isValidUrl;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testomatio/reporter",
3
- "version": "2.3.9",
3
+ "version": "2.4.0",
4
4
  "description": "Testomatio Reporter Client",
5
5
  "engines": {
6
6
  "node": ">=18"
@@ -18,21 +18,22 @@
18
18
  "@cucumber/cucumber": "^10.9.0",
19
19
  "@octokit/rest": "^21.1.1",
20
20
  "aws-sdk": "^2.1072.0",
21
- "gaxios": ">=6.0 || >=7.0.0-rc.4 || <8",
22
21
  "callsite-record": "^4.1.4",
23
22
  "commander": "^12",
24
23
  "cross-spawn": "^7.0.3",
25
24
  "csv-writer": "^1.6.0",
26
- "debug": "^4.3.4",
25
+ "debug": "4.3.4",
27
26
  "dotenv": "^16.0.1",
28
27
  "fast-xml-parser": "^4.4.1",
29
28
  "file-url": "3.0.0",
30
29
  "filesize": "^10.1.6",
30
+ "gaxios": ">=6.0 || >=7.0.0-rc.4 || <8",
31
31
  "glob": "^10.3",
32
32
  "handlebars": "^4.7.8",
33
33
  "has-flag": "^5.0.1",
34
34
  "humanize-duration": "^3.27.3",
35
35
  "is-valid-path": "^0.1.1",
36
+ "js-yaml": "^4.1.1",
36
37
  "json-cycle": "^1.3.0",
37
38
  "lodash.memoize": "^4.1.2",
38
39
  "lodash.merge": "^4.6.2",
@@ -40,7 +41,7 @@
40
41
  "picocolors": "^1.0.1",
41
42
  "pretty-ms": "^7.0.1",
42
43
  "promise-retry": "^2.0.1",
43
- "strip-ansi": "^7.1.0",
44
+ "strip-ansi": "7.1.0",
44
45
  "uuid": "^9.0.0"
45
46
  },
46
47
  "files": [
package/src/bin/cli.js CHANGED
@@ -7,7 +7,7 @@ import createDebugMessages from 'debug';
7
7
  import TestomatClient from '../client.js';
8
8
  import XmlReader from '../xmlReader.js';
9
9
  import { APP_PREFIX, STATUS } from '../constants.js';
10
- import { cleanLatestRunId, getPackageVersion } from '../utils/utils.js';
10
+ import { cleanLatestRunId, getPackageVersion, applyFilter } from '../utils/utils.js';
11
11
  import { config } from '../config.js';
12
12
  import { readLatestRunId } from '../utils/utils.js';
13
13
  import pc from 'picocolors';
@@ -81,6 +81,7 @@ program
81
81
  .description('Run tests with the specified command')
82
82
  .argument('<command>', 'Test runner command')
83
83
  .option('--filter <filter>', 'Additional execution filter')
84
+ .option('--filter-list <filter>', 'Get a list of all tests by filter before running')
84
85
  .option('--kind <type>', 'Specify run type: automated, manual, or mixed')
85
86
  .action(async (command, opts) => {
86
87
  const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
@@ -93,17 +94,39 @@ program
93
94
 
94
95
  const client = new TestomatClient({ apiKey, title });
95
96
 
96
- if (opts.filter) {
97
- const [pipe, ...optsArray] = opts.filter.split(':');
97
+ if (opts.filter || opts.filterList) {
98
+ // Example of use: npx @testomatio/reporter run "npx jest" --filter "testomatio:tag-name=frontend"
99
+ // Example of use: npx @testomatio/reporter run "npx jest" --filter "coverage:file=coverage.yml"
100
+ // Example of use: npx @testomatio/reporter run "npx jest" --filter-list "coverage:file=coverage.yml"
101
+ const [pipe, ...optsArray] = opts?.filter ? opts?.filter.split(':') : opts?.filterList.split(':');
98
102
  const pipeOptions = optsArray.join(':');
99
103
 
104
+ const prepareRunParams = { pipe, pipeOptions };
105
+
100
106
  try {
101
- const tests = await client.prepareRun({ pipe, pipeOptions });
102
- if (tests && tests.length > 0) {
103
- command += ` --grep (${tests.join('|')})`;
107
+ const tests = await client.prepareRun(prepareRunParams);
108
+
109
+ if (!tests || tests.length === 0) {
110
+ console.log(APP_PREFIX, pc.yellow('No tests found.'));
111
+ return;
104
112
  }
105
- } catch (err) {
106
- console.log(APP_PREFIX, err);
113
+
114
+ const pattern = `(${tests.join('|')})`;
115
+ const filteredCommand = applyFilter(command, tests);
116
+
117
+ debug(`Execution pattern: "${pattern}"`);
118
+
119
+ if(opts.filterList) {
120
+ console.log(APP_PREFIX, pc.blue(`Matched test/suite IDs: ${tests.join(', ')}`));
121
+ console.log(APP_PREFIX, pc.green(`Full Running Command: ${filteredCommand}`));
122
+ return;
123
+ }
124
+
125
+ command = filteredCommand;
126
+ }
127
+ catch (err) {
128
+ console.log(APP_PREFIX, err.message || err);
129
+ return;
107
130
  }
108
131
  }
109
132
 
@@ -113,7 +136,7 @@ program
113
136
  const testCmds = command.split(' ');
114
137
  const cmd = spawn(testCmds[0], testCmds.slice(1), {
115
138
  stdio: 'inherit',
116
- env: { ...process.env, TESTOMATIO_PROCEED: 'true', runId: client.runId },
139
+ env: { ...process.env, TESTOMATIO_PROCEED: 'true', runId: client.runId, TESTOMATIO_RUN: client.runId },
117
140
  });
118
141
 
119
142
  cmd.on('close', async code => {
@@ -128,6 +151,9 @@ program
128
151
  };
129
152
 
130
153
  const createRunParams = {};
154
+ if (title) {
155
+ createRunParams.title = title;
156
+ }
131
157
  if (opts.kind) {
132
158
  createRunParams.kind = opts.kind;
133
159
  }
package/src/client.js CHANGED
@@ -65,35 +65,43 @@ class Client {
65
65
  * array containing the prepared execution list,
66
66
  * or resolves to undefined if no valid results are found or if all pipes are disabled.
67
67
  */
68
+
68
69
  async prepareRun(params) {
69
- this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
70
70
  const { pipe, pipeOptions } = params;
71
+
72
+ // ❗ Validation: pipe is required
73
+ if (!pipe || !pipeOptions) {
74
+ console.warn(`❗ No valid pipe found in filter cmd. Expected format: <pipe>:<options>
75
+ Examples:
76
+ --filter "testomatio:tag-name=frontend"
77
+ --filter "coverage:file=coverage.yml"
78
+ --filter-list "coverage:file=coverage.yml"
79
+ Received: "${params}"`);
80
+ return;
81
+ }
82
+
83
+ this.pipes = await pipesFactory(params || this.paramsForPipesFactory || {}, this.pipeStore);
84
+
71
85
  // all pipes disabled, skipping
72
86
  if (!this.pipes.some(p => p.isEnabled)) {
73
87
  return Promise.resolve();
74
88
  }
75
89
 
76
90
  try {
77
- const filterPipe = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
91
+ const p = this.pipes.find(p => p.constructor.name.toLowerCase() === `${pipe.toLowerCase()}pipe`);
92
+ // const p = this.pipes.find(p => p.id === `${pipe.toLowerCase()}`); TODO: as future updates
78
93
 
79
- if (!filterPipe?.isEnabled) {
80
- // TODO:for the future for the another pipes
94
+ if (!p?.isEnabled) {
81
95
  console.warn(
82
96
  APP_PREFIX,
83
- `At the moment processing is available only for the "testomatio" key. Example: "testomatio:tag-name=xxx"`,
97
+ "🚫 No active pipes were found in the system. Execution aborted!"
84
98
  );
85
99
  return;
86
100
  }
87
101
 
88
- const results = await Promise.all(
89
- this.pipes.map(async p => ({ pipe: p.toString(), result: await p.prepareRun(pipeOptions) })),
90
- );
91
-
92
- const result = results.filter(p => p.pipe.includes('Testomatio'))[0]?.result;
93
-
94
- if (!result || result.length === 0) {
95
- return;
96
- }
102
+ // Run only the selected pipe
103
+ const rawResult = await p.prepareRun(pipeOptions);
104
+ const result = Array.isArray(rawResult) ? rawResult : [];
97
105
 
98
106
  debug('Execution tests list', result);
99
107
 
package/src/helpers.js ADDED
@@ -0,0 +1 @@
1
+ export const isPlaywright = Boolean(process.env.PLAYWRIGHT_TEST || process.env.PLAYWRIGHT);