@testomatio/reporter 2.6.0-beta.1.allure β†’ 2.6.1

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.
Files changed (57) hide show
  1. package/README.md +9 -11
  2. package/lib/adapter/playwright.d.ts +2 -0
  3. package/lib/adapter/playwright.js +29 -5
  4. package/lib/adapter/utils/playwright.d.ts +25 -0
  5. package/lib/adapter/utils/playwright.js +123 -0
  6. package/lib/adapter/vitest.js +2 -1
  7. package/lib/bin/cli.js +36 -36
  8. package/lib/data-storage.d.ts +1 -1
  9. package/lib/data-storage.js +1 -0
  10. package/lib/junit-adapter/index.js +0 -4
  11. package/lib/pipe/coverage.js +63 -5
  12. package/lib/pipe/debug.js +1 -2
  13. package/lib/pipe/github.js +15 -0
  14. package/lib/pipe/html.d.ts +2 -3
  15. package/lib/pipe/html.js +745 -37
  16. package/lib/pipe/testomatio.js +83 -36
  17. package/lib/reporter-functions.d.ts +36 -11
  18. package/lib/reporter-functions.js +72 -22
  19. package/lib/reporter.d.ts +90 -38
  20. package/lib/services/artifacts.d.ts +1 -1
  21. package/lib/services/key-values.d.ts +1 -1
  22. package/lib/services/links.d.ts +5 -3
  23. package/lib/services/links.js +1 -1
  24. package/lib/services/logger.d.ts +1 -1
  25. package/lib/template/testomatio-old.hbs +1421 -0
  26. package/lib/template/testomatio.hbs +3200 -1157
  27. package/lib/utils/log-formatter.d.ts +1 -2
  28. package/lib/utils/log-formatter.js +8 -4
  29. package/lib/utils/utils.js +0 -9
  30. package/package.json +2 -2
  31. package/src/adapter/playwright.js +32 -6
  32. package/src/adapter/utils/playwright.js +121 -0
  33. package/src/adapter/vitest.js +2 -1
  34. package/src/bin/cli.js +39 -47
  35. package/src/data-storage.js +1 -0
  36. package/src/junit-adapter/index.js +0 -4
  37. package/src/pipe/coverage.js +90 -32
  38. package/src/pipe/debug.js +1 -2
  39. package/src/pipe/github.js +14 -0
  40. package/src/pipe/html.js +844 -38
  41. package/src/pipe/testomatio.js +98 -53
  42. package/src/reporter-functions.js +73 -25
  43. package/src/services/links.js +1 -1
  44. package/src/template/testomatio-old.hbs +1421 -0
  45. package/src/template/testomatio.hbs +3200 -1157
  46. package/src/utils/log-formatter.js +9 -4
  47. package/src/utils/utils.js +0 -5
  48. package/types/types.d.ts +30 -6
  49. package/lib/allureReader.d.ts +0 -65
  50. package/lib/allureReader.js +0 -448
  51. package/lib/junit-adapter/kotlin.d.ts +0 -5
  52. package/lib/junit-adapter/kotlin.js +0 -46
  53. package/lib/services/labels.d.ts +0 -0
  54. package/lib/services/labels.js +0 -0
  55. package/src/allureReader.js +0 -523
  56. package/src/junit-adapter/kotlin.js +0 -48
  57. package/src/services/labels.js +0 -1
package/README.md CHANGED
@@ -13,7 +13,7 @@ Testomat.io Reporter (this npm package) supports:
13
13
  - πŸ”Ž [Stack traces](./docs/stacktrace.md) and error messages
14
14
  - πŸ™ [GitHub](./docs/pipes/github.md), [GitLab](./docs/pipes/gitlab.md) & [Bitbucket](./docs/pipes/bitbucket.md) integration
15
15
  - πŸš… Realtime reports
16
- - πŸ—ƒοΈ Other test frameworks supported via [JUnit XML](./docs/junit.md) with [XML import configuration](./docs/xml-imports.md)
16
+ - πŸ—ƒοΈ Other test frameworks supported via [JUnit XML](./docs/junit.md)
17
17
  - πŸšΆβ€β™€οΈ Steps _(work in progress)_
18
18
  - πŸ“„ [Logger](./docs/logger.md) _(work in progress, supports Jest for now)_
19
19
  - ☁️ Custom properties and metadata _(work in progress)_
@@ -63,13 +63,12 @@ yarn add @testomatio/reporter --dev
63
63
  ### 1️⃣ Attach Reporter to the Test Runner
64
64
 
65
65
  | | | |
66
- | ----------------------------------------------- | --------------------------------------------- | --------------------------------------------------------- |
67
- | [Playwright](./docs/frameworks.md#playwright) | [CodeceptJS](./docs/frameworks.md#CodeceptJS) | [Cypress](./docs/frameworks.md#Cypress) |
68
- | [Jest](./docs/frameworks.md#Jest) | [Mocha](./docs/frameworks.md#Mocha) | [WebDriverIO](./docs/frameworks.md#WebDriverIO) |
69
- | [TestCafe](./docs/frameworks.md#TestCafe) | [Detox](./docs/frameworks.md#Detox) | [Codeception](https://github.com/testomatio/php-reporter) |
70
- | [Newman (Postman)](./docs/frameworks.md#Newman) | [JUnit](./docs/junit.md#junit) | [NUnit](./docs/junit.md#nunit) |
66
+ |-------------------------------------------------|-----------------------------------------------|-----------------------------------------------------------|
67
+ | [Playwright](./docs/frameworks.md#playwright) | [CodeceptJS](./docs/frameworks.md#codeceptjs) | [Cypress](./docs/frameworks.md#cypress) |
68
+ | [Jest](./docs/frameworks.md#jest) | [Mocha](./docs/frameworks.md#mocha) | [WebDriverIO](./docs/frameworks.md#webdriverIO) |
69
+ | [TestCafe](./docs/frameworks.md#testcafe) | [Detox](./docs/frameworks.md#detox) | [Codeception](https://github.com/testomatio/php-reporter) |
70
+ | [Newman (Postman)](./docs/frameworks.md#newman) | [JUnit](./docs/junit.md#junit) | [NUnit](./docs/junit.md#nunit) |
71
71
  | [PyTest](./docs/junit.md#pytest) | [PHPUnit](./docs/junit.md#phpunit) | [Protractor](./docs/frameworks.md#protractor) |
72
- | [Allure](./docs/allure.md) | | |
73
72
 
74
73
  or **any [other via JUnit](./docs/junit.md)** report....
75
74
 
@@ -104,7 +103,7 @@ With our reporter, you can:
104
103
  * Visualize data on successful and failed tests, including statistics and error details.
105
104
  * Quickly share reports with your team members or stakeholders.
106
105
 
107
- ![HTML report](./docs/images/html_reporter_example.gif)
106
+ ![HTML report](./docs/images/html-reporter-example.gif)
108
107
 
109
108
  Learn more about generating HTML reports [here](./docs/pipes/html.md)
110
109
 
@@ -130,10 +129,9 @@ Bring this reporter on CI and never lose test results again!
130
129
  - [CSV](./docs/pipes/csv.md)
131
130
  - [HTML report](./docs/pipes/html.md)
132
131
  - [Bitbucket](./docs/pipes/bitbucket.md)
133
- - πŸ““ [JUnit Reports](./docs/junit.md)
134
- - πŸ—„οΈ [Artifacts](./docs/artifacts.md)
135
- - πŸ”¬ [Allure Reports](./docs/allure.md)
136
132
  - πŸ”— [Linking Tests](./docs/linking-tests.md)
133
+ - πŸ““ [JUnit](./docs/junit.md)
134
+ - πŸ—„οΈ [Artifacts](./docs/artifacts.md)
137
135
  - πŸ”‚ [Workflows](./docs/workflows.md)
138
136
  - πŸ–ŠοΈ [Logger](./docs/logger.md)
139
137
  - πŸͺ² [Debug File Format](./docs/debug-file-format.md)
@@ -18,4 +18,6 @@ declare class PlaywrightReporter {
18
18
  * @returns {string[]} - array of normalized tags with @ prefix
19
19
  */
20
20
  export function extractTags(test: any): string[];
21
+ import { fetchLinksFromLogs } from './utils/playwright.js';
21
22
  import TestomatioClient from '../client.js';
23
+ export { fetchLinksFromLogs };
@@ -3,8 +3,8 @@ 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.fetchLinksFromLogs = void 0;
6
7
  exports.extractTags = extractTags;
7
- const picocolors_1 = __importDefault(require("picocolors"));
8
8
  const crypto_1 = __importDefault(require("crypto"));
9
9
  const os_1 = __importDefault(require("os"));
10
10
  const path_1 = __importDefault(require("path"));
@@ -16,6 +16,9 @@ const utils_js_1 = require("../utils/utils.js");
16
16
  const index_js_1 = require("../services/index.js");
17
17
  const data_storage_js_1 = require("../data-storage.js");
18
18
  const constants_js_2 = require("../utils/constants.js");
19
+ const picocolors_1 = __importDefault(require("picocolors"));
20
+ const playwright_js_1 = require("./utils/playwright.js");
21
+ Object.defineProperty(exports, "fetchLinksFromLogs", { enumerable: true, get: function () { return playwright_js_1.fetchLinksFromLogs; } });
19
22
  const reportTestPromises = [];
20
23
  class PlaywrightReporter {
21
24
  constructor(config = {}) {
@@ -41,6 +44,14 @@ class PlaywrightReporter {
41
44
  return;
42
45
  const { title } = test;
43
46
  const { error, duration } = result;
47
+ const pwAttachments = (result.attachments || []).filter(a => a.body || a.path);
48
+ const files = pwAttachments
49
+ .map(att => ({
50
+ path: this.#getArtifactPath(att),
51
+ title: att.name || title,
52
+ type: att.contentType,
53
+ }))
54
+ .filter(f => f.path);
44
55
  const suite_title = test.parent ? test.parent?.title : path_1.default.basename(test?.location?.file);
45
56
  const steps = [];
46
57
  for (const step of result.steps) {
@@ -53,12 +64,23 @@ class PlaywrightReporter {
53
64
  const tags = extractTags(test);
54
65
  const fullTestTitle = getTestContextName(test);
55
66
  let logs = '';
56
- if (result.stderr.length || result.stdout.length) {
57
- logs = `\n\n${picocolors_1.default.bold('Logs:')}\n${picocolors_1.default.red(result.stderr.join(''))}\n${result.stdout.join('')}`;
67
+ // get links along with filtered logs (liks related logs removed)
68
+ const { stdout: filteredStdout, links, meta } = (0, playwright_js_1.fetchLinksFromLogs)(result.stdout);
69
+ if (filteredStdout?.length || result.stderr?.length) {
70
+ logs = `\n\n${picocolors_1.default.bold('Logs:')}\n${picocolors_1.default.red(result.stderr.join(''))}\n${filteredStdout.join('')}`;
58
71
  }
72
+ /*
73
+ All services fucntions work different for Playwright.
74
+ We don't have access to test title (as result, to test id) when calling this functions inside a test.
75
+ Thus, when user calls services functions inside a test, we just log this data to console.
76
+ Playwright intercepts the console.log on it's end and we just get this data from it.
77
+ Thus, we have a tiny drawback: all data from services functions inside a test will be logged to console.
78
+ And this requires a condition to be added for each service function – if its Playwright, then log to console.
79
+
80
+ "get" method of services will not return data for Playwright, we should parse stdout.
81
+ */
59
82
  const manuallyAttachedArtifacts = index_js_1.services.artifacts.get(fullTestTitle);
60
83
  const testMeta = index_js_1.services.keyValues.get(fullTestTitle);
61
- const links = index_js_1.services.links.get(fullTestTitle);
62
84
  const rid = test.id || test.testId || (0, uuid_1.v4)();
63
85
  /**
64
86
  * @type {{
@@ -98,12 +120,14 @@ class PlaywrightReporter {
98
120
  logs,
99
121
  links,
100
122
  manuallyAttachedArtifacts,
123
+ files: files.length ? files : undefined,
101
124
  meta: {
102
125
  browser: project.browser,
103
126
  isMobile: project.isMobile,
104
127
  project: project.name,
105
128
  projectDependencies: project.dependencies?.length ? project.dependencies : null,
106
129
  ...testMeta,
130
+ ...meta,
107
131
  ...project.metadata, // metadata has any type (in playwright), but we will stringify it in client.js
108
132
  ...test.annotations?.reduce((acc, annotation) => {
109
133
  acc[annotation.type] = annotation.description;
@@ -115,7 +139,7 @@ class PlaywrightReporter {
115
139
  this.uploads.push({
116
140
  rid: `${rid}-${project.name}`,
117
141
  title: test.title,
118
- files: result.attachments.filter(a => a.body || a.path),
142
+ files: pwAttachments,
119
143
  file: test.location?.file,
120
144
  });
121
145
  // remove empty uploads
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Fetches links from stdout. Returns links and filtered stdout (without data containing markers)
3
+ *
4
+ * @param {(string | Buffer)[]} stdout
5
+ * @returns {{
6
+ * links: { [key: 'test' | 'jira' | 'label']: string }[],
7
+ * meta: { [key: string]: any },
8
+ * stdout: (string | Buffer)[]
9
+ * }}
10
+ */
11
+ export function fetchLinksFromLogs(stdout: (string | Buffer)[]): {
12
+ links: {
13
+ [key: "test" | "jira" | "label"]: string;
14
+ }[];
15
+ meta: {
16
+ [key: string]: any;
17
+ };
18
+ stdout: (string | Buffer)[];
19
+ };
20
+ export namespace playwrightLogsMarkers {
21
+ let label: string;
22
+ let meta: string;
23
+ let linkTest: string;
24
+ let linkJira: string;
25
+ }
@@ -0,0 +1,123 @@
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.playwrightLogsMarkers = void 0;
7
+ exports.fetchLinksFromLogs = fetchLinksFromLogs;
8
+ const debug_1 = __importDefault(require("debug"));
9
+ const debug = (0, debug_1.default)('@testomatio/reporter:adapter-playwright-utils');
10
+ exports.playwrightLogsMarkers = {
11
+ label: '[TESTOMATIO-LABEL]',
12
+ meta: '[TESTOMATIO-META]',
13
+ linkTest: '[TESTOMATIO-LINK-TEST]',
14
+ linkJira: '[TESTOMATIO-LINK-JIRA]',
15
+ };
16
+ /**
17
+ * Fetches links from stdout. Returns links and filtered stdout (without data containing markers)
18
+ *
19
+ * @param {(string | Buffer)[]} stdout
20
+ * @returns {{
21
+ * links: { [key: 'test' | 'jira' | 'label']: string }[],
22
+ * meta: { [key: string]: any },
23
+ * stdout: (string | Buffer)[]
24
+ * }}
25
+ */
26
+ function fetchLinksFromLogs(stdout) {
27
+ const links = [];
28
+ const meta = {};
29
+ const markers = [
30
+ { key: exports.playwrightLogsMarkers.linkTest, type: 'test' },
31
+ { key: exports.playwrightLogsMarkers.linkJira, type: 'jira' },
32
+ { key: exports.playwrightLogsMarkers.label, type: 'label' },
33
+ { key: exports.playwrightLogsMarkers.meta, type: 'meta' },
34
+ ];
35
+ const filteredStdout = [];
36
+ stdout.forEach(entry => {
37
+ if (typeof entry !== 'string') {
38
+ filteredStdout.push(entry);
39
+ return;
40
+ }
41
+ // check if entry contains any of markers
42
+ if (!markers.some(m => entry.includes(m.key))) {
43
+ filteredStdout.push(entry);
44
+ return;
45
+ }
46
+ const newEntryLines = [];
47
+ entry.split('\n').forEach(line => {
48
+ line = line.trim();
49
+ let hasMarker = false;
50
+ for (const marker of markers) {
51
+ // links (test/jira/label) stored as array of objects
52
+ if (line.includes(marker.key)) {
53
+ hasMarker = true;
54
+ try {
55
+ const rawData = line.split(marker.key)[1]?.trim();
56
+ if (!rawData)
57
+ continue;
58
+ let data;
59
+ try {
60
+ data = JSON.parse(rawData);
61
+ }
62
+ catch (e) {
63
+ // Try to extract JSON from the beginning of the string (to handle trailing text)
64
+ const jsonMatch = rawData.match(/^\s*(\[.*?\]|\{.*?\})/);
65
+ if (jsonMatch) {
66
+ try {
67
+ data = JSON.parse(jsonMatch[1]);
68
+ }
69
+ catch (jsonError) {
70
+ // If JSON extraction fails, skip this entry
71
+ debug('Error parsing links from string:', line, '\n', jsonError);
72
+ continue;
73
+ }
74
+ }
75
+ else {
76
+ // No JSON found, skip this entry
77
+ debug('No valid JSON found in:', line);
78
+ continue;
79
+ }
80
+ }
81
+ if (marker.type === 'meta') {
82
+ // meta stored as an object, thus make it similar to links
83
+ Object.assign(meta, data);
84
+ }
85
+ else {
86
+ const ids = Array.isArray(data) ? data : [data];
87
+ links.push(...ids
88
+ // filter non-truthy ids
89
+ .filter(id => !!id)
90
+ .map(id => {
91
+ // If id is already an object with the marker type key, return it as is
92
+ if (typeof id === 'object' && id !== null && marker.type in id) {
93
+ return id;
94
+ }
95
+ // Otherwise, wrap it with the marker type key
96
+ return {
97
+ // marker type is either 'test' or 'jira' or 'label'
98
+ [marker.type]: id,
99
+ };
100
+ }));
101
+ }
102
+ }
103
+ catch (e) {
104
+ debug('Error parsing links from string:', line, '\n', e);
105
+ }
106
+ }
107
+ }
108
+ if (!hasMarker && line) {
109
+ newEntryLines.push(line);
110
+ }
111
+ });
112
+ if (newEntryLines.length) {
113
+ filteredStdout.push(newEntryLines.join('\n'));
114
+ }
115
+ });
116
+ return {
117
+ stdout: filteredStdout,
118
+ links,
119
+ meta,
120
+ };
121
+ }
122
+
123
+ module.exports.fetchLinksFromLogs = fetchLinksFromLogs;
@@ -100,7 +100,7 @@ class VitestReporter {
100
100
  *
101
101
  * @param {VitestTest} test
102
102
  *
103
- * @returns {TestData & {status: string}}
103
+ * @returns {TestData & {status: 'passed' | 'failed' | 'skipped'}}
104
104
  */
105
105
  #getDataFromTest(test) {
106
106
  return {
@@ -108,6 +108,7 @@ class VitestReporter {
108
108
  file: test.file.name,
109
109
  logs: test.logs ? transformLogsToString(test.logs) : '',
110
110
  meta: test.meta,
111
+ // @ts-ignore - STATUS values are string literals but type system sees them as string
111
112
  status: getTestStatus(test),
112
113
  suite_title: test.suite.name || test.file?.name,
113
114
  test_id: (0, utils_js_1.getTestomatIdFromTestTitle)(test.name),
package/lib/bin/cli.js CHANGED
@@ -10,7 +10,6 @@ const glob_1 = require("glob");
10
10
  const debug_1 = __importDefault(require("debug"));
11
11
  const client_js_1 = __importDefault(require("../client.js"));
12
12
  const xmlReader_js_1 = __importDefault(require("../xmlReader.js"));
13
- const allureReader_js_1 = __importDefault(require("../allureReader.js"));
14
13
  const constants_js_1 = require("../constants.js");
15
14
  const utils_js_1 = require("../utils/utils.js");
16
15
  const config_js_1 = require("../config.js");
@@ -19,7 +18,7 @@ const picocolors_1 = __importDefault(require("picocolors"));
19
18
  const filesize_1 = require("filesize");
20
19
  const dotenv_1 = __importDefault(require("dotenv"));
21
20
  const replay_js_1 = __importDefault(require("../replay.js"));
22
- const debug = (0, debug_1.default)('@testomatio/reporter:xml-cli');
21
+ const debug = (0, debug_1.default)('@testomatio/reporter:cli');
23
22
  const version = (0, utils_js_1.getPackageVersion)();
24
23
  console.log(picocolors_1.default.cyan(picocolors_1.default.bold(` 🀩 Testomat.io Reporter v${version}`)));
25
24
  const program = new commander_1.Command();
@@ -74,25 +73,25 @@ program
74
73
  .command('run')
75
74
  .alias('test')
76
75
  .description('Run tests with the specified command')
77
- .argument('<command>', 'Test runner command')
76
+ .argument('[command]', 'Test runner command')
78
77
  .option('--filter <filter>', 'Additional execution filter')
79
78
  .option('--filter-list <filter>', 'Get a list of all tests by filter before running')
80
79
  .option('--kind <type>', 'Specify run type: automated, manual, or mixed')
81
80
  .action(async (command, opts) => {
82
81
  const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config_js_1.config.TESTOMATIO;
83
82
  const title = process.env.TESTOMATIO_TITLE;
84
- if (!command || !command.split) {
85
- console.log(constants_js_1.APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
86
- return process.exit(255);
87
- }
88
83
  const client = new client_js_1.default({ apiKey, title });
89
84
  if (opts.filter || opts.filterList) {
85
+ console.log(constants_js_1.APP_PREFIX, 'Filtering tests...');
90
86
  // Example of use: npx @testomatio/reporter run "npx jest" --filter "testomatio:tag-name=frontend"
91
87
  // Example of use: npx @testomatio/reporter run "npx jest" --filter "coverage:file=coverage.yml"
92
88
  // Example of use: npx @testomatio/reporter run "npx jest" --filter-list "coverage:file=coverage.yml"
93
89
  const [pipe, ...optsArray] = opts?.filter ? opts?.filter.split(':') : opts?.filterList.split(':');
94
90
  const pipeOptions = optsArray.join(':');
95
91
  const prepareRunParams = { pipe, pipeOptions };
92
+ if (opts.filterList) {
93
+ client.pipeStore.filterList = true;
94
+ }
96
95
  try {
97
96
  const tests = await client.prepareRun(prepareRunParams);
98
97
  if (!tests || tests.length === 0) {
@@ -104,16 +103,44 @@ program
104
103
  debug(`Execution pattern: "${pattern}"`);
105
104
  if (opts.filterList) {
106
105
  console.log(constants_js_1.APP_PREFIX, picocolors_1.default.blue(`Matched test/suite IDs: ${tests.join(', ')}`));
107
- console.log(constants_js_1.APP_PREFIX, picocolors_1.default.green(`Full Running Command: ${filteredCommand}`));
106
+ if (command)
107
+ console.log(constants_js_1.APP_PREFIX, picocolors_1.default.green(`Full Running Command: ${filteredCommand}`));
108
108
  return;
109
109
  }
110
- command = filteredCommand;
110
+ if (command && command.split) {
111
+ command = filteredCommand;
112
+ }
111
113
  }
112
114
  catch (err) {
113
115
  console.log(constants_js_1.APP_PREFIX, err.message || err);
114
116
  return;
115
117
  }
116
118
  }
119
+ // just create a run (wich tests which match filters) without executing tests
120
+ if (!command || !command.split) {
121
+ const createRunParams = {};
122
+ if (title) {
123
+ createRunParams.title = title;
124
+ }
125
+ if (opts.kind) {
126
+ createRunParams.kind = opts.kind;
127
+ }
128
+ if (apiKey) {
129
+ await client.createRun(createRunParams);
130
+ const runId = process.env.TESTOMATIO_RUN || process.env.runId;
131
+ if (client.pipeStore.runUrl)
132
+ console.log(constants_js_1.APP_PREFIX, `πŸ“Š Report URL: ${picocolors_1.default.magenta(client.pipeStore.runUrl)}`);
133
+ if (opts.kind !== 'manual') {
134
+ console.log(constants_js_1.APP_PREFIX, `No command passed, so you need to run tests yourself:`);
135
+ console.log(constants_js_1.APP_PREFIX, `TESTOMATIO_RUN=${runId} <command>`);
136
+ }
137
+ }
138
+ else {
139
+ console.log(constants_js_1.APP_PREFIX, '⚠️ No API key provided. Cannot create run without TESTOMATIO key.');
140
+ process.exit(1);
141
+ }
142
+ return process.exit(0);
143
+ }
117
144
  console.log(constants_js_1.APP_PREFIX, `πŸš€ Running`, picocolors_1.default.green(command));
118
145
  const runTests = async () => {
119
146
  const testCmds = command.split(' ');
@@ -209,33 +236,6 @@ program
209
236
  if (timeoutTimer)
210
237
  clearTimeout(timeoutTimer);
211
238
  });
212
- program
213
- .command('allure')
214
- .description('Parse Allure result files and upload to Testomat.io')
215
- .argument('<pattern>', 'Allure result directory pattern')
216
- .option('-d, --dir <dir>', 'Project directory')
217
- .option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
218
- .option('--with-package', 'Keep full package path in file names (default: strip package prefix)')
219
- .action(async (pattern, opts) => {
220
- const runReader = new allureReader_js_1.default({ withPackage: opts.withPackage });
221
- let timeoutTimer;
222
- if (opts.timelimit) {
223
- timeoutTimer = setTimeout(() => {
224
- console.log(`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`);
225
- process.exit(0);
226
- }, parseInt(opts.timelimit, 10) * 1000);
227
- }
228
- try {
229
- await runReader.parse(pattern);
230
- await runReader.createRun();
231
- await runReader.uploadData();
232
- }
233
- catch (err) {
234
- console.log(constants_js_1.APP_PREFIX, 'Error uploading Allure results:', err);
235
- }
236
- if (timeoutTimer)
237
- clearTimeout(timeoutTimer);
238
- });
239
239
  program
240
240
  .command('upload-artifacts')
241
241
  .description('Upload artifacts to Testomat.io')
@@ -1,6 +1,6 @@
1
1
  export const dataStorage: DataStorage;
2
2
  declare class DataStorage {
3
- static "__#private@#instance": any;
3
+ static "__#13@#instance": any;
4
4
  /**
5
5
  *
6
6
  * @returns {DataStorage}
@@ -167,6 +167,7 @@ class DataStorage {
167
167
  if (testDataAsText)
168
168
  debug('<=', dataType, 'file', context, testDataAsText);
169
169
  const testDataArr = testDataAsText?.split(os_1.default.EOL) || [];
170
+ debug('<=', dataType, 'file', context, testDataArr);
170
171
  return testDataArr;
171
172
  }
172
173
  // debug(`No ${this.dataType} data for ${context} in <file> storage`);
@@ -9,7 +9,6 @@ const java_js_1 = __importDefault(require("./java.js"));
9
9
  const python_js_1 = __importDefault(require("./python.js"));
10
10
  const ruby_js_1 = __importDefault(require("./ruby.js"));
11
11
  const csharp_js_1 = __importDefault(require("./csharp.js"));
12
- const kotlin_js_1 = __importDefault(require("./kotlin.js"));
13
12
  function AdapterFactory(lang, opts) {
14
13
  if (lang === 'java') {
15
14
  return new java_js_1.default(opts);
@@ -26,9 +25,6 @@ function AdapterFactory(lang, opts) {
26
25
  if (lang === 'c#' || lang === 'csharp') {
27
26
  return new csharp_js_1.default(opts);
28
27
  }
29
- if (lang === 'kotlin') {
30
- return new kotlin_js_1.default(opts);
31
- }
32
28
  return new adapter_js_1.default(opts);
33
29
  }
34
30
  module.exports = AdapterFactory;
@@ -89,7 +89,7 @@ class CoveragePipe {
89
89
  }
90
90
  }
91
91
  });
92
- // In case if we have all needed data
92
+ // In case if we have all needed data
93
93
  this.isEnabled = true;
94
94
  debug('Coverage Pipe initialized', {
95
95
  branch: this.branch,
@@ -110,6 +110,9 @@ class CoveragePipe {
110
110
  this.suiteIds.clear();
111
111
  this.tagLabels.clear();
112
112
  this.results = [];
113
+ if (this.store) {
114
+ this.store.coverageConfiguration = undefined;
115
+ }
113
116
  if (!this.isEnabled)
114
117
  return [];
115
118
  // Step 1: Validate coverage file path & Git changes & Coverage parsing
@@ -117,6 +120,9 @@ class CoveragePipe {
117
120
  return [];
118
121
  // Step 2: Extract all available tests and compare with coverage file
119
122
  const lines = await this.extractRelevantTestsFromChanges();
123
+ if (this.store?.filterList && lines.size > 0) {
124
+ console.log(constants_js_1.APP_PREFIX, `Matched files: ${[...lines].join(', ')}`);
125
+ }
120
126
  if (lines.size === 0) {
121
127
  console.log(constants_js_1.APP_PREFIX, 'ℹ️ No matching entries in coverage file for provided Git changes.');
122
128
  return [];
@@ -134,11 +140,22 @@ class CoveragePipe {
134
140
  tests.forEach(testId => this.tests.add(testId));
135
141
  }
136
142
  }
137
- if (this.tests.size === 0) {
143
+ if (this.tests.size === 0 && this.suiteIds.size === 0) {
138
144
  console.log(constants_js_1.APP_PREFIX, 'ℹ️ No tests found for execution based on Git changes.');
139
145
  return [];
140
146
  }
141
- this.results = [...this.tests];
147
+ this.results = [...this.tests, ...this.suiteIds];
148
+ if (this.store) {
149
+ this.store.coverageConfiguration = {
150
+ tests: [...this.tests],
151
+ suites: [...this.suiteIds],
152
+ };
153
+ this.store.coverageDescription = this.#buildRunDescription({
154
+ matchedLines: lines,
155
+ testsCount: this.tests.size,
156
+ suitesCount: this.suiteIds.size,
157
+ });
158
+ }
142
159
  return this.results;
143
160
  }
144
161
  addTest(data) { }
@@ -230,7 +247,7 @@ class CoveragePipe {
230
247
  #buildGitCommand() {
231
248
  if (!this.branch)
232
249
  throw new Error(`❌ Invalid changes option for setted branch!`);
233
- return `git diff ${this.branch} --name-only`; // Example: 'git diff <master> --name-only'
250
+ return `git diff ${this.branch} --name-only`; // Example: 'git diff <master> --name-only'
234
251
  }
235
252
  /**
236
253
  * Retrieves the list of files changed in the current Git working directory
@@ -356,7 +373,7 @@ class CoveragePipe {
356
373
  }
357
374
  // Example: "@Sd74099c1"
358
375
  else if (id.startsWith('@S')) {
359
- this.tests.add(id.slice(1));
376
+ this.suiteIds.add(id.slice(1));
360
377
  }
361
378
  // Example: "tag:@TestSmoke"
362
379
  else if (id.startsWith('tag')) {
@@ -369,5 +386,46 @@ class CoveragePipe {
369
386
  debug(`Matched lines: ${this.matchedLines}`);
370
387
  return this.matchedLines;
371
388
  }
389
+ #buildRunDescription({ matchedLines, testsCount, suitesCount }) {
390
+ const sourceBranch = process.env.GITHUB_HEAD_REF ||
391
+ process.env.GITHUB_REF_NAME ||
392
+ process.env.CI_COMMIT_REF_NAME ||
393
+ this.#getCurrentGitBranch() ||
394
+ 'current branch';
395
+ const targetBranch = this.branch || 'target branch';
396
+ const coverageFile = this.coverageFilePath ? path_1.default.basename(this.coverageFilePath) : 'coverage.yml';
397
+ const updatedFiles = matchedLines && matchedLines.size > 0 ? [...matchedLines] : this.changedFiles;
398
+ let description = `Changes to **${updatedFiles.length}** files in ${sourceBranch} to ${targetBranch}.\n\n`;
399
+ if (suitesCount > 0 || testsCount > 0) {
400
+ const affectedItems = [];
401
+ if (suitesCount > 0)
402
+ affectedItems.push(`**${suitesCount} suites**`);
403
+ if (testsCount > 0)
404
+ affectedItems.push(`**${testsCount} individual tests**`);
405
+ description += `May affect ${affectedItems.join(' and ')} which are recommended to be tested for regression.\n\n`; // eslint-disable-line
406
+ }
407
+ description += 'Updated source files:\n';
408
+ if (updatedFiles.length) {
409
+ description += updatedFiles.map(file => `* \`${file}\``).join('\n');
410
+ description += '\n\n';
411
+ }
412
+ else {
413
+ description += '* No matched files found\n\n';
414
+ }
415
+ description += `Mapping source files to tests set via \`${coverageFile}\` file.`;
416
+ return description;
417
+ }
418
+ #getCurrentGitBranch() {
419
+ try {
420
+ const branch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
421
+ encoding: 'utf-8',
422
+ stdio: ['pipe', 'pipe', 'ignore'],
423
+ }).trim();
424
+ return branch || undefined;
425
+ }
426
+ catch (err) {
427
+ return undefined;
428
+ }
429
+ }
372
430
  }
373
431
  module.exports = CoveragePipe;
package/lib/pipe/debug.js CHANGED
@@ -10,7 +10,6 @@ const os_1 = __importDefault(require("os"));
10
10
  const debug_1 = __importDefault(require("debug"));
11
11
  const constants_js_1 = require("../constants.js");
12
12
  const pretty_ms_1 = __importDefault(require("pretty-ms"));
13
- const utils_js_1 = require("../utils/utils.js");
14
13
  const debug = (0, debug_1.default)('@testomatio/reporter:pipe:debug');
15
14
  class DebugPipe {
16
15
  constructor(params, store) {
@@ -19,7 +18,7 @@ class DebugPipe {
19
18
  this.isEnabled = !!process.env.TESTOMATIO_DEBUG || !!process.env.DEBUG;
20
19
  if (this.isEnabled) {
21
20
  this.batch = {
22
- isEnabled: this.params.isBatchEnabled ?? !(0, utils_js_1.transformEnvVarToBoolean)(process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD),
21
+ isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
23
22
  intervalFunction: null,
24
23
  intervalTime: 5000,
25
24
  tests: [],
@@ -153,6 +153,21 @@ class GitHubPipe {
153
153
  return text;
154
154
  });
155
155
  let body = summary;
156
+ const coverageConfiguration = this.store?.coverageConfiguration;
157
+ const isManualRun = this.store?.runKind === 'manual';
158
+ if (isManualRun && coverageConfiguration) {
159
+ const testsCount = coverageConfiguration.tests?.length || 0;
160
+ const suitesCount = coverageConfiguration.suites?.length || 0;
161
+ body += '\n\n<details>\n<summary><h3>🧭 Coverage Scope</h3></summary>\n\n';
162
+ if (!testsCount && !suitesCount) {
163
+ body += '- No tests were affected, run disabled\n';
164
+ }
165
+ else {
166
+ body += `- Suites: ${suitesCount}\n`;
167
+ body += `- Tests: ${testsCount}\n`;
168
+ }
169
+ body += '\n</details>';
170
+ }
156
171
  if (failures.length) {
157
172
  body += `\n<details>\n<summary><h3>πŸŸ₯ Failures (${failures.length})</h4></summary>\n\n${failures.join('\n')}\n`;
158
173
  if (failures.length > 20) {
@@ -7,7 +7,6 @@ declare class HtmlPipe {
7
7
  isHtml: string;
8
8
  isEnabled: boolean;
9
9
  htmlOutputPath: string;
10
- fullHtmlOutputPath: string;
11
10
  filenameMsg: string;
12
11
  tests: any[];
13
12
  htmlReportDir: string;
@@ -19,9 +18,9 @@ declare class HtmlPipe {
19
18
  updateRun(): void;
20
19
  /**
21
20
  * Add test data to the result array for saving. As a result of this function, we get a result object to save.
22
- * @param {import('../../types/types.js').RunData} test - object which includes each test entry.
21
+ * @param {import('../../types/types.js').HtmlTestData} test - object which includes each test entry.
23
22
  */
24
- addTest(test: import("../../types/types.js").RunData): void;
23
+ addTest(test: import("../../types/types.js").HtmlTestData): void;
25
24
  finishRun(runParams: any): Promise<void>;
26
25
  /**
27
26
  * Generates an HTML report based on provided test data and a template.