@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.
- package/README.md +9 -11
- package/lib/adapter/playwright.d.ts +2 -0
- package/lib/adapter/playwright.js +29 -5
- package/lib/adapter/utils/playwright.d.ts +25 -0
- package/lib/adapter/utils/playwright.js +123 -0
- package/lib/adapter/vitest.js +2 -1
- package/lib/bin/cli.js +36 -36
- package/lib/data-storage.d.ts +1 -1
- package/lib/data-storage.js +1 -0
- package/lib/junit-adapter/index.js +0 -4
- package/lib/pipe/coverage.js +63 -5
- package/lib/pipe/debug.js +1 -2
- package/lib/pipe/github.js +15 -0
- package/lib/pipe/html.d.ts +2 -3
- package/lib/pipe/html.js +745 -37
- package/lib/pipe/testomatio.js +83 -36
- package/lib/reporter-functions.d.ts +36 -11
- package/lib/reporter-functions.js +72 -22
- package/lib/reporter.d.ts +90 -38
- package/lib/services/artifacts.d.ts +1 -1
- package/lib/services/key-values.d.ts +1 -1
- package/lib/services/links.d.ts +5 -3
- package/lib/services/links.js +1 -1
- package/lib/services/logger.d.ts +1 -1
- package/lib/template/testomatio-old.hbs +1421 -0
- package/lib/template/testomatio.hbs +3200 -1157
- package/lib/utils/log-formatter.d.ts +1 -2
- package/lib/utils/log-formatter.js +8 -4
- package/lib/utils/utils.js +0 -9
- package/package.json +2 -2
- package/src/adapter/playwright.js +32 -6
- package/src/adapter/utils/playwright.js +121 -0
- package/src/adapter/vitest.js +2 -1
- package/src/bin/cli.js +39 -47
- package/src/data-storage.js +1 -0
- package/src/junit-adapter/index.js +0 -4
- package/src/pipe/coverage.js +90 -32
- package/src/pipe/debug.js +1 -2
- package/src/pipe/github.js +14 -0
- package/src/pipe/html.js +844 -38
- package/src/pipe/testomatio.js +98 -53
- package/src/reporter-functions.js +73 -25
- package/src/services/links.js +1 -1
- package/src/template/testomatio-old.hbs +1421 -0
- package/src/template/testomatio.hbs +3200 -1157
- package/src/utils/log-formatter.js +9 -4
- package/src/utils/utils.js +0 -5
- package/types/types.d.ts +30 -6
- package/lib/allureReader.d.ts +0 -65
- package/lib/allureReader.js +0 -448
- package/lib/junit-adapter/kotlin.d.ts +0 -5
- package/lib/junit-adapter/kotlin.js +0 -46
- package/lib/services/labels.d.ts +0 -0
- package/lib/services/labels.js +0 -0
- package/src/allureReader.js +0 -523
- package/src/junit-adapter/kotlin.js +0 -48
- 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)
|
|
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#
|
|
68
|
-
| [Jest](./docs/frameworks.md#
|
|
69
|
-
| [TestCafe](./docs/frameworks.md#
|
|
70
|
-
| [Newman (Postman)](./docs/frameworks.md#
|
|
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
|
-

|
|
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
|
-
|
|
57
|
-
|
|
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:
|
|
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;
|
package/lib/adapter/vitest.js
CHANGED
|
@@ -100,7 +100,7 @@ class VitestReporter {
|
|
|
100
100
|
*
|
|
101
101
|
* @param {VitestTest} test
|
|
102
102
|
*
|
|
103
|
-
* @returns {TestData & {status:
|
|
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:
|
|
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('
|
|
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
|
-
|
|
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
|
|
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')
|
package/lib/data-storage.d.ts
CHANGED
package/lib/data-storage.js
CHANGED
|
@@ -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;
|
package/lib/pipe/coverage.js
CHANGED
|
@@ -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.
|
|
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 ?? !
|
|
21
|
+
isEnabled: this.params.isBatchEnabled ?? !process.env.TESTOMATIO_DISABLE_BATCH_UPLOAD ?? true,
|
|
23
22
|
intervalFunction: null,
|
|
24
23
|
intervalTime: 5000,
|
|
25
24
|
tests: [],
|
package/lib/pipe/github.js
CHANGED
|
@@ -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) {
|
package/lib/pipe/html.d.ts
CHANGED
|
@@ -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').
|
|
21
|
+
* @param {import('../../types/types.js').HtmlTestData} test - object which includes each test entry.
|
|
23
22
|
*/
|
|
24
|
-
addTest(test: import("../../types/types.js").
|
|
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.
|