@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
|
@@ -102,11 +102,16 @@ export function formatError(error, message) {
|
|
|
102
102
|
* @returns {boolean}
|
|
103
103
|
*/
|
|
104
104
|
function isNotInternalFrame(frame) {
|
|
105
|
+
const fileName = frame.getFileName();
|
|
106
|
+
if (!fileName) return false;
|
|
107
|
+
|
|
108
|
+
const isFileUrl = fileName.startsWith('file://');
|
|
109
|
+
const hasPathSeparator = fileName.includes(sep) || fileName.includes('/') || isFileUrl;
|
|
110
|
+
|
|
105
111
|
return (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
!
|
|
109
|
-
!frame.getFileName().includes('internal')
|
|
112
|
+
hasPathSeparator &&
|
|
113
|
+
!fileName.includes('node_modules') &&
|
|
114
|
+
!fileName.includes('internal')
|
|
110
115
|
);
|
|
111
116
|
}
|
|
112
117
|
|
package/src/utils/utils.js
CHANGED
|
@@ -276,11 +276,6 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
276
276
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
|
|
277
277
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
278
278
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
279
|
-
} else if (opts.lang === 'kotlin') {
|
|
280
|
-
lineIndex = lines.findIndex(l => l.includes(`fun test${title}`));
|
|
281
|
-
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
|
|
282
|
-
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`fun ${title}`));
|
|
283
|
-
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
284
279
|
} else if (opts.lang === 'csharp') {
|
|
285
280
|
// Find the method declaration line
|
|
286
281
|
let methodLineIndex = lines.findIndex(l => l.includes(`public void ${title}(`));
|
package/types/types.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ declare module '@testomatio/reporter' {
|
|
|
17
17
|
* @param message - step message
|
|
18
18
|
* @param logs - optional key-value object with additional info (e.g. logs)
|
|
19
19
|
*/
|
|
20
|
-
export function step(message: string, logs?: {[key: string]: any}): void;
|
|
20
|
+
export function step(message: string, logs?: { [key: string]: any }): void;
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Add key-value pair(s) to the test report
|
|
@@ -28,22 +28,22 @@ declare module '@testomatio/reporter' {
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Add a single label to the test report
|
|
31
|
-
* @param key - label key (e.g. 'severity', 'feature', or just 'smoke' for labels without values)
|
|
32
|
-
* @param value - optional label value (
|
|
31
|
+
* @param {string | {[key: string]: string}} key - label key (e.g. 'severity', 'feature', or just 'smoke' for labels without values)
|
|
32
|
+
* @param {string | null} [value=null] - optional label value (of custom field value) (used when key is a string)
|
|
33
33
|
*/
|
|
34
|
-
export function label(key: string, value?: string | null): void;
|
|
34
|
+
export function label(key: string | { [key: string]: string }, value?: string | null): void;
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Add link(s) to the test report
|
|
38
38
|
* @param testIds - test IDs to link
|
|
39
39
|
*/
|
|
40
|
-
export function linkTest(...testIds: string[]): void;
|
|
40
|
+
export function linkTest(...testIds: (string | string[])[]): void;
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* Add JIRA issue link(s) to the test report
|
|
44
44
|
* @param jiraIds - JIRA issue IDs to link
|
|
45
45
|
*/
|
|
46
|
-
export function linkJira(...jiraIds: string[]): void;
|
|
46
|
+
export function linkJira(...jiraIds: (string | string[])[]): void;
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Logger service for intercepting and managing logs
|
|
@@ -159,6 +159,12 @@ export interface TestData {
|
|
|
159
159
|
/** The unique identifier from Testomat.io of the test case. If provided, updates the existing test case with the given ID. */
|
|
160
160
|
test_id?: string;
|
|
161
161
|
|
|
162
|
+
/** The status of the test (passed, failed, skipped, etc.) */
|
|
163
|
+
status?: 'passed' | 'failed' | 'skipped';
|
|
164
|
+
|
|
165
|
+
/** An array of artifacts associated with the test case (screenshots, videos, traces, etc.) */
|
|
166
|
+
artifacts?: ArtifactData[];
|
|
167
|
+
|
|
162
168
|
/** An object representing an error that occurred during the execution of the test case. */
|
|
163
169
|
error?: Error;
|
|
164
170
|
|
|
@@ -207,6 +213,24 @@ export interface TestData {
|
|
|
207
213
|
overwrite?: boolean;
|
|
208
214
|
}
|
|
209
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Extended test data for HTML reporter.
|
|
218
|
+
*/
|
|
219
|
+
export interface HtmlTestData extends TestData {
|
|
220
|
+
meta?: { [key: string]: any } & {
|
|
221
|
+
attachments?: any[];
|
|
222
|
+
traces?: any;
|
|
223
|
+
console?: any;
|
|
224
|
+
stdout?: any;
|
|
225
|
+
stderr?: any;
|
|
226
|
+
logs?: any;
|
|
227
|
+
retryCount?: number;
|
|
228
|
+
isFlaky?: boolean;
|
|
229
|
+
attempts?: any[];
|
|
230
|
+
retries?: any[];
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
210
234
|
/**
|
|
211
235
|
* Object representing a result of a Run.
|
|
212
236
|
*/
|
package/lib/allureReader.d.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
export default AllureReader;
|
|
2
|
-
declare class AllureReader {
|
|
3
|
-
constructor(opts?: {});
|
|
4
|
-
requestParams: {
|
|
5
|
-
apiKey: any;
|
|
6
|
-
url: any;
|
|
7
|
-
title: string;
|
|
8
|
-
env: string;
|
|
9
|
-
group_title: string;
|
|
10
|
-
isBatchEnabled: boolean;
|
|
11
|
-
};
|
|
12
|
-
runId: any;
|
|
13
|
-
opts: {};
|
|
14
|
-
withPackage: any;
|
|
15
|
-
store: {};
|
|
16
|
-
pipesPromise: Promise<any[]>;
|
|
17
|
-
_tests: any[];
|
|
18
|
-
stats: {};
|
|
19
|
-
suites: {};
|
|
20
|
-
uploader: S3Uploader;
|
|
21
|
-
version: any;
|
|
22
|
-
set tests(value: any[]);
|
|
23
|
-
get tests(): any[];
|
|
24
|
-
createRun(): Promise<any[]>;
|
|
25
|
-
pipes: any;
|
|
26
|
-
parse(resultsPattern: any): {};
|
|
27
|
-
parseContainerFiles(containerFiles: any): void;
|
|
28
|
-
processAllureResult(result: any, resultsDir: any): {
|
|
29
|
-
rid: any;
|
|
30
|
-
title: any;
|
|
31
|
-
status: any;
|
|
32
|
-
suite_title: any;
|
|
33
|
-
file: string;
|
|
34
|
-
run_time: number;
|
|
35
|
-
steps: any;
|
|
36
|
-
message: any;
|
|
37
|
-
stack: any;
|
|
38
|
-
meta: {};
|
|
39
|
-
links: {
|
|
40
|
-
label: string;
|
|
41
|
-
}[];
|
|
42
|
-
artifacts: any[];
|
|
43
|
-
create: boolean;
|
|
44
|
-
overwrite: boolean;
|
|
45
|
-
};
|
|
46
|
-
mapStatus(status: any): any;
|
|
47
|
-
extractSuiteTitle(result: any): any;
|
|
48
|
-
stripNamespace(suiteName: any): any;
|
|
49
|
-
extractFile(result: any): string;
|
|
50
|
-
getFileExtension(result: any): any;
|
|
51
|
-
extractMeta(result: any): {};
|
|
52
|
-
extractLinks(result: any): {
|
|
53
|
-
label: string;
|
|
54
|
-
}[];
|
|
55
|
-
convertSteps(steps: any, depth?: number): any;
|
|
56
|
-
calculateRunTime(item: any): number;
|
|
57
|
-
convertParameters(parameters: any): {};
|
|
58
|
-
combineRetryAttempts(attempts: any): any;
|
|
59
|
-
calculateStats(): {};
|
|
60
|
-
fetchSourceCode(): void;
|
|
61
|
-
getLanguage(): any;
|
|
62
|
-
uploadArtifacts(): Promise<void>;
|
|
63
|
-
uploadData(): Promise<any[]>;
|
|
64
|
-
}
|
|
65
|
-
import { S3Uploader } from './uploader.js';
|
package/lib/allureReader.js
DELETED
|
@@ -1,448 +0,0 @@
|
|
|
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
|
-
const debug_1 = __importDefault(require("debug"));
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
|
-
const fs_1 = __importDefault(require("fs"));
|
|
10
|
-
const glob_1 = require("glob");
|
|
11
|
-
const constants_js_1 = require("./constants.js");
|
|
12
|
-
const crypto_1 = require("crypto");
|
|
13
|
-
const url_1 = require("url");
|
|
14
|
-
const config_js_1 = require("./config.js");
|
|
15
|
-
const uploader_js_1 = require("./uploader.js");
|
|
16
|
-
const index_js_1 = require("./pipe/index.js");
|
|
17
|
-
const utils_js_1 = require("./utils/utils.js");
|
|
18
|
-
const index_js_2 = __importDefault(require("./junit-adapter/index.js"));
|
|
19
|
-
// @ts-ignore
|
|
20
|
-
const debug = (0, debug_1.default)('@testomatio/reporter:allure');
|
|
21
|
-
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
22
|
-
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_SUITE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
|
|
23
|
-
class AllureReader {
|
|
24
|
-
constructor(opts = {}) {
|
|
25
|
-
this.requestParams = {
|
|
26
|
-
apiKey: opts.apiKey || config_js_1.config.TESTOMATIO,
|
|
27
|
-
url: opts.url || TESTOMATIO_URL,
|
|
28
|
-
title: TESTOMATIO_TITLE,
|
|
29
|
-
env: TESTOMATIO_ENV,
|
|
30
|
-
group_title: TESTOMATIO_RUNGROUP_TITLE,
|
|
31
|
-
isBatchEnabled: true,
|
|
32
|
-
};
|
|
33
|
-
this.runId = opts.runId || TESTOMATIO_RUN;
|
|
34
|
-
this.opts = opts || {};
|
|
35
|
-
this.withPackage = opts.withPackage || false;
|
|
36
|
-
this.store = {};
|
|
37
|
-
this.pipesPromise = (0, index_js_1.pipesFactory)(opts, this.store);
|
|
38
|
-
this._tests = [];
|
|
39
|
-
this.stats = {};
|
|
40
|
-
this.suites = {};
|
|
41
|
-
this.uploader = new uploader_js_1.S3Uploader();
|
|
42
|
-
const packageJsonPath = path_1.default.resolve(__dirname, '..', 'package.json');
|
|
43
|
-
this.version = JSON.parse(fs_1.default.readFileSync(packageJsonPath).toString()).version;
|
|
44
|
-
console.log(constants_js_1.APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
45
|
-
}
|
|
46
|
-
get tests() {
|
|
47
|
-
return this._tests;
|
|
48
|
-
}
|
|
49
|
-
set tests(value) {
|
|
50
|
-
this._tests = value;
|
|
51
|
-
}
|
|
52
|
-
async createRun() {
|
|
53
|
-
const runParams = {
|
|
54
|
-
api_key: this.requestParams.apiKey,
|
|
55
|
-
title: this.requestParams.title,
|
|
56
|
-
env: this.requestParams.env,
|
|
57
|
-
group_title: this.requestParams.group_title,
|
|
58
|
-
isBatchEnabled: this.requestParams.isBatchEnabled,
|
|
59
|
-
};
|
|
60
|
-
debug('Run', runParams);
|
|
61
|
-
this.pipes = this.pipes || (await this.pipesPromise);
|
|
62
|
-
const run = await Promise.all(this.pipes.map(p => p.createRun(runParams)));
|
|
63
|
-
this.uploader.checkEnabled();
|
|
64
|
-
return run;
|
|
65
|
-
}
|
|
66
|
-
parse(resultsPattern) {
|
|
67
|
-
this._tests = [];
|
|
68
|
-
let pattern = resultsPattern;
|
|
69
|
-
// Auto-append wildcard if pattern refers to a directory (like XML command does)
|
|
70
|
-
if (!pattern.endsWith('.json') && !pattern.includes('*')) {
|
|
71
|
-
if (pattern.endsWith('/') || (fs_1.default.existsSync(pattern) && fs_1.default.statSync(pattern).isDirectory())) {
|
|
72
|
-
pattern = pattern.replace(/\/+$/, '') + '/*-result.json';
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
pattern += '*-result.json';
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const resultsDir = path_1.default.dirname(pattern);
|
|
79
|
-
console.log(constants_js_1.APP_PREFIX, `Scanning for Allure results in: ${resultsDir}`);
|
|
80
|
-
console.log(constants_js_1.APP_PREFIX, `Using pattern: ${pattern}`);
|
|
81
|
-
const resultFiles = glob_1.glob.sync(pattern);
|
|
82
|
-
const containerFiles = glob_1.glob.sync(pattern.replace('*-result.json', '*-container.json'));
|
|
83
|
-
if (resultFiles.length === 0 && containerFiles.length === 0) {
|
|
84
|
-
throw new Error(`No Allure result files found matching pattern: ${pattern}`);
|
|
85
|
-
}
|
|
86
|
-
console.log(constants_js_1.APP_PREFIX, `Found ${resultFiles.length} result files and ${containerFiles.length} container files`);
|
|
87
|
-
this.parseContainerFiles(containerFiles);
|
|
88
|
-
// Store all tests temporarily for deduplication by historyId
|
|
89
|
-
const allTests = [];
|
|
90
|
-
for (const file of resultFiles) {
|
|
91
|
-
const fullPath = file;
|
|
92
|
-
const fileDir = path_1.default.dirname(file);
|
|
93
|
-
try {
|
|
94
|
-
const resultData = JSON.parse(fs_1.default.readFileSync(fullPath, 'utf8'));
|
|
95
|
-
const test = this.processAllureResult(resultData, fileDir);
|
|
96
|
-
if (test) {
|
|
97
|
-
test._historyId = resultData.historyId;
|
|
98
|
-
test._stop = resultData.stop || 0;
|
|
99
|
-
allTests.push(test);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
catch (err) {
|
|
103
|
-
console.warn(constants_js_1.APP_PREFIX, `Failed to parse ${file}:`, err.message);
|
|
104
|
-
debug('Parse error:', err);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const attemptsMap = new Map();
|
|
108
|
-
for (const test of allTests) {
|
|
109
|
-
const historyId = test._historyId || test.rid;
|
|
110
|
-
if (!attemptsMap.has(historyId)) {
|
|
111
|
-
attemptsMap.set(historyId, []);
|
|
112
|
-
}
|
|
113
|
-
attemptsMap.get(historyId).push(test);
|
|
114
|
-
}
|
|
115
|
-
const uniqueTestsMap = new Map();
|
|
116
|
-
for (const [historyId, attempts] of attemptsMap) {
|
|
117
|
-
uniqueTestsMap.set(historyId, this.combineRetryAttempts(attempts));
|
|
118
|
-
}
|
|
119
|
-
// Convert map to array and clean up internal fields
|
|
120
|
-
this._tests = Array.from(uniqueTestsMap.values()).map(t => {
|
|
121
|
-
delete t._historyId;
|
|
122
|
-
delete t._stop;
|
|
123
|
-
return t;
|
|
124
|
-
});
|
|
125
|
-
console.log(constants_js_1.APP_PREFIX, `Processed ${this._tests.length} unique tests (from ${allTests.length} result files)`);
|
|
126
|
-
return this.calculateStats();
|
|
127
|
-
}
|
|
128
|
-
parseContainerFiles(containerFiles) {
|
|
129
|
-
for (const file of containerFiles) {
|
|
130
|
-
try {
|
|
131
|
-
const data = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
132
|
-
if (data.name && data.children) {
|
|
133
|
-
data.children.forEach(uuid => {
|
|
134
|
-
this.suites[uuid] = data.name;
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
catch (err) {
|
|
139
|
-
debug('Failed to parse container file:', file, err.message);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
debug('Parsed suites:', this.suites);
|
|
143
|
-
}
|
|
144
|
-
processAllureResult(result, resultsDir) {
|
|
145
|
-
const test = {
|
|
146
|
-
rid: result.uuid || (0, crypto_1.randomUUID)(),
|
|
147
|
-
title: result.name || 'Unknown test',
|
|
148
|
-
status: this.mapStatus(result.status),
|
|
149
|
-
suite_title: this.extractSuiteTitle(result),
|
|
150
|
-
file: this.extractFile(result),
|
|
151
|
-
run_time: this.calculateRunTime(result),
|
|
152
|
-
steps: this.convertSteps(result.steps || []),
|
|
153
|
-
message: result.statusDetails?.message || '',
|
|
154
|
-
stack: result.statusDetails?.trace || '',
|
|
155
|
-
meta: this.extractMeta(result),
|
|
156
|
-
links: this.extractLinks(result),
|
|
157
|
-
artifacts: [],
|
|
158
|
-
create: true,
|
|
159
|
-
overwrite: true,
|
|
160
|
-
};
|
|
161
|
-
// Add description if present
|
|
162
|
-
if (result.description) {
|
|
163
|
-
test.description = result.description;
|
|
164
|
-
}
|
|
165
|
-
if (result.parameters && result.parameters.length > 0) {
|
|
166
|
-
test.example = this.convertParameters(result.parameters);
|
|
167
|
-
}
|
|
168
|
-
if (result.attachments && result.attachments.length > 0) {
|
|
169
|
-
const attachments = result.attachments
|
|
170
|
-
.map(att => {
|
|
171
|
-
const fullPath = path_1.default.join(resultsDir, att.source);
|
|
172
|
-
if (fs_1.default.existsSync(fullPath)) {
|
|
173
|
-
return fullPath;
|
|
174
|
-
}
|
|
175
|
-
debug('Attachment file not found:', fullPath);
|
|
176
|
-
return null;
|
|
177
|
-
})
|
|
178
|
-
.filter(Boolean);
|
|
179
|
-
if (attachments.length > 0) {
|
|
180
|
-
test.files = attachments;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return test;
|
|
184
|
-
}
|
|
185
|
-
mapStatus(status) {
|
|
186
|
-
const statusMap = {
|
|
187
|
-
passed: 'passed',
|
|
188
|
-
failed: 'failed',
|
|
189
|
-
broken: 'failed',
|
|
190
|
-
skipped: 'skipped',
|
|
191
|
-
pending: 'skipped',
|
|
192
|
-
};
|
|
193
|
-
return statusMap[status] || 'failed';
|
|
194
|
-
}
|
|
195
|
-
extractSuiteTitle(result) {
|
|
196
|
-
const labels = result.labels || [];
|
|
197
|
-
// Only use suite label for suite_title
|
|
198
|
-
// Epic and Feature are sent as separate labels in meta
|
|
199
|
-
const suiteLabel = labels.find(l => l.name === 'suite')?.value;
|
|
200
|
-
if (suiteLabel) {
|
|
201
|
-
return this.stripNamespace(suiteLabel);
|
|
202
|
-
}
|
|
203
|
-
// Fallback to parentSuite or subSuite if no suite label
|
|
204
|
-
const parentSuite = labels.find(l => l.name === 'parentSuite')?.value;
|
|
205
|
-
const subSuite = labels.find(l => l.name === 'subSuite')?.value;
|
|
206
|
-
if (parentSuite && subSuite) {
|
|
207
|
-
return `${parentSuite} / ${subSuite}`;
|
|
208
|
-
}
|
|
209
|
-
if (parentSuite)
|
|
210
|
-
return parentSuite;
|
|
211
|
-
if (subSuite)
|
|
212
|
-
return subSuite;
|
|
213
|
-
return 'Default Suite';
|
|
214
|
-
}
|
|
215
|
-
stripNamespace(suiteName) {
|
|
216
|
-
if (suiteName && suiteName.includes('.')) {
|
|
217
|
-
return suiteName.split('.').pop();
|
|
218
|
-
}
|
|
219
|
-
return suiteName;
|
|
220
|
-
}
|
|
221
|
-
extractFile(result) {
|
|
222
|
-
const labels = result.labels || [];
|
|
223
|
-
const packageLabel = labels.find(l => l.name === 'package')?.value;
|
|
224
|
-
const testClassLabel = labels.find(l => l.name === 'testClass')?.value;
|
|
225
|
-
if (!packageLabel) {
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
const ext = this.getFileExtension(result);
|
|
229
|
-
let className;
|
|
230
|
-
if (testClassLabel) {
|
|
231
|
-
className = testClassLabel.split('.').pop();
|
|
232
|
-
}
|
|
233
|
-
else if (result.fullName) {
|
|
234
|
-
const fullNameParts = result.fullName.split('.');
|
|
235
|
-
className = fullNameParts[fullNameParts.length - 2] || fullNameParts[fullNameParts.length - 1];
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
if (this.withPackage) {
|
|
241
|
-
const parts = packageLabel.split('.');
|
|
242
|
-
return `${parts.join('/')}/${className}.${ext}`;
|
|
243
|
-
}
|
|
244
|
-
return `${className}.${ext}`;
|
|
245
|
-
}
|
|
246
|
-
getFileExtension(result) {
|
|
247
|
-
const labels = result.labels || [];
|
|
248
|
-
const languageLabel = labels.find(l => l.name === 'language')?.value;
|
|
249
|
-
const extMap = {
|
|
250
|
-
java: 'java',
|
|
251
|
-
kotlin: 'kt',
|
|
252
|
-
javascript: 'js',
|
|
253
|
-
typescript: 'ts',
|
|
254
|
-
python: 'py',
|
|
255
|
-
ruby: 'rb',
|
|
256
|
-
'c#': 'cs',
|
|
257
|
-
php: 'php',
|
|
258
|
-
};
|
|
259
|
-
return extMap[languageLabel?.toLowerCase()] || 'java';
|
|
260
|
-
}
|
|
261
|
-
extractMeta(result) {
|
|
262
|
-
const labels = result.labels || [];
|
|
263
|
-
const excludedLabels = [
|
|
264
|
-
'suite', 'package', 'parentSuite', 'subSuite',
|
|
265
|
-
'testClass', 'testMethod', 'epic', 'feature',
|
|
266
|
-
];
|
|
267
|
-
const meta = {};
|
|
268
|
-
labels.forEach(label => {
|
|
269
|
-
if (!excludedLabels.includes(label.name)) {
|
|
270
|
-
meta[label.name] = label.value;
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
return meta;
|
|
274
|
-
}
|
|
275
|
-
extractLinks(result) {
|
|
276
|
-
const labels = result.labels || [];
|
|
277
|
-
const links = [];
|
|
278
|
-
const epicLabel = labels.find(l => l.name === 'epic');
|
|
279
|
-
const featureLabel = labels.find(l => l.name === 'feature');
|
|
280
|
-
if (epicLabel?.value) {
|
|
281
|
-
links.push({ label: `epic:${epicLabel.value}` });
|
|
282
|
-
}
|
|
283
|
-
if (featureLabel?.value) {
|
|
284
|
-
links.push({ label: `feature:${featureLabel.value}` });
|
|
285
|
-
}
|
|
286
|
-
return links.length > 0 ? links : undefined;
|
|
287
|
-
}
|
|
288
|
-
convertSteps(steps, depth = 0) {
|
|
289
|
-
if (depth >= 10)
|
|
290
|
-
return null;
|
|
291
|
-
return steps
|
|
292
|
-
.map(step => {
|
|
293
|
-
const convertedStep = {
|
|
294
|
-
category: 'user',
|
|
295
|
-
title: step.name || step.title || 'Unknown step',
|
|
296
|
-
duration: this.calculateRunTime(step),
|
|
297
|
-
steps: this.convertSteps(step.steps || [], depth + 1),
|
|
298
|
-
};
|
|
299
|
-
if (convertedStep.steps && convertedStep.steps.length === 0) {
|
|
300
|
-
delete convertedStep.steps;
|
|
301
|
-
}
|
|
302
|
-
if (convertedStep.duration === 0) {
|
|
303
|
-
delete convertedStep.duration;
|
|
304
|
-
}
|
|
305
|
-
return convertedStep;
|
|
306
|
-
})
|
|
307
|
-
.filter(Boolean);
|
|
308
|
-
}
|
|
309
|
-
calculateRunTime(item) {
|
|
310
|
-
if (item.start && item.stop) {
|
|
311
|
-
const durationMs = item.stop - item.start;
|
|
312
|
-
return durationMs / 1000;
|
|
313
|
-
}
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
convertParameters(parameters) {
|
|
317
|
-
const example = {};
|
|
318
|
-
parameters.forEach(param => {
|
|
319
|
-
if (param.name) {
|
|
320
|
-
example[param.name] = param.value;
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
return example;
|
|
324
|
-
}
|
|
325
|
-
combineRetryAttempts(attempts) {
|
|
326
|
-
attempts.sort((a, b) => (a._stop || 0) - (b._stop || 0));
|
|
327
|
-
const finalTest = attempts[attempts.length - 1];
|
|
328
|
-
const retryCount = attempts.length - 1;
|
|
329
|
-
if (retryCount > 0) {
|
|
330
|
-
finalTest.retries = retryCount;
|
|
331
|
-
}
|
|
332
|
-
const failedAttempts = attempts.filter(t => t.status === 'failed');
|
|
333
|
-
if (failedAttempts.length === 0) {
|
|
334
|
-
return finalTest;
|
|
335
|
-
}
|
|
336
|
-
const failureMessages = [];
|
|
337
|
-
const failureStacks = [];
|
|
338
|
-
for (const failed of failedAttempts) {
|
|
339
|
-
const attemptNum = attempts.indexOf(failed) + 1;
|
|
340
|
-
if (failed.message) {
|
|
341
|
-
failureMessages.push(`[Attempt ${attemptNum}] ${failed.message}`);
|
|
342
|
-
}
|
|
343
|
-
if (failed.stack) {
|
|
344
|
-
failureStacks.push(`\n--- Attempt ${attemptNum} ---\n${failed.stack}`);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
if (failureMessages.length > 0) {
|
|
348
|
-
finalTest.message = failureMessages.join('\n');
|
|
349
|
-
}
|
|
350
|
-
if (failureStacks.length > 0) {
|
|
351
|
-
finalTest.stack = failureStacks.join('\n');
|
|
352
|
-
}
|
|
353
|
-
if (finalTest.status === 'passed') {
|
|
354
|
-
finalTest.status = 'failed';
|
|
355
|
-
const retryMsg = `Test passed after ${failedAttempts.length} retries. Previous failures:\n`;
|
|
356
|
-
finalTest.message = retryMsg + finalTest.message;
|
|
357
|
-
}
|
|
358
|
-
return finalTest;
|
|
359
|
-
}
|
|
360
|
-
calculateStats() {
|
|
361
|
-
this.stats = {
|
|
362
|
-
create_tests: true,
|
|
363
|
-
tests_count: this._tests.length,
|
|
364
|
-
passed_count: 0,
|
|
365
|
-
failed_count: 0,
|
|
366
|
-
skipped_count: 0,
|
|
367
|
-
duration: 0,
|
|
368
|
-
status: 'passed',
|
|
369
|
-
tests: this._tests,
|
|
370
|
-
};
|
|
371
|
-
this._tests.forEach(t => {
|
|
372
|
-
if (t.status === 'passed')
|
|
373
|
-
this.stats.passed_count++;
|
|
374
|
-
if (t.status === 'failed')
|
|
375
|
-
this.stats.failed_count++;
|
|
376
|
-
if (t.status === 'skipped')
|
|
377
|
-
this.stats.skipped_count++;
|
|
378
|
-
});
|
|
379
|
-
this.stats.duration = this._tests.reduce((acc, t) => acc + (t.run_time || 0), 0);
|
|
380
|
-
if (this.stats.failed_count)
|
|
381
|
-
this.stats.status = 'failed';
|
|
382
|
-
debug('Stats:', this.stats);
|
|
383
|
-
return this.stats;
|
|
384
|
-
}
|
|
385
|
-
fetchSourceCode() {
|
|
386
|
-
const adapter = this.adapter || (0, index_js_2.default)(this.getLanguage(), this.opts);
|
|
387
|
-
this._tests.forEach(t => {
|
|
388
|
-
try {
|
|
389
|
-
let filePath = t.file;
|
|
390
|
-
if (adapter && adapter.getFilePath) {
|
|
391
|
-
filePath = adapter.getFilePath(t);
|
|
392
|
-
}
|
|
393
|
-
if (!filePath)
|
|
394
|
-
return;
|
|
395
|
-
if (!fs_1.default.existsSync(filePath)) {
|
|
396
|
-
debug('Source file not found:', filePath);
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
const contents = fs_1.default.readFileSync(filePath).toString();
|
|
400
|
-
const code = (0, utils_js_1.fetchSourceCode)(contents, { ...t, lang: this.getLanguage() });
|
|
401
|
-
if (code) {
|
|
402
|
-
t.code = code;
|
|
403
|
-
debug('Fetched code for test %s', t.title);
|
|
404
|
-
}
|
|
405
|
-
const testId = (0, utils_js_1.fetchIdFromCode)(contents, { lang: this.getLanguage() });
|
|
406
|
-
if (testId) {
|
|
407
|
-
t.test_id = testId;
|
|
408
|
-
debug('Fetched test id %s for test %s', testId, t.title);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
catch (err) {
|
|
412
|
-
debug('Failed to fetch source code:', err.message);
|
|
413
|
-
}
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
getLanguage() {
|
|
417
|
-
if (this._tests.length === 0)
|
|
418
|
-
return null;
|
|
419
|
-
return this._tests[0].meta?.language || this.opts.lang;
|
|
420
|
-
}
|
|
421
|
-
async uploadArtifacts() {
|
|
422
|
-
for (const test of this._tests.filter(t => t.files && t.files.length > 0)) {
|
|
423
|
-
const runId = this.runId || this.store.runId || Date.now().toString();
|
|
424
|
-
const artifacts = await Promise.all(test.files.map(f => this.uploader.uploadFileByPath(f, [runId, test.rid, path_1.default.basename(f)])));
|
|
425
|
-
test.artifacts = artifacts.filter(a => a && a.link).map(a => a.link);
|
|
426
|
-
delete test.files;
|
|
427
|
-
if (test.artifacts.length > 0) {
|
|
428
|
-
console.log(constants_js_1.APP_PREFIX, `🗄️ Uploaded ${picocolors_1.default.bold(`${test.artifacts.length} artifacts`)} for test ${test.title}`);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
async uploadData() {
|
|
433
|
-
await this.uploadArtifacts();
|
|
434
|
-
this.calculateStats();
|
|
435
|
-
this.fetchSourceCode();
|
|
436
|
-
const dataString = {
|
|
437
|
-
...this.stats,
|
|
438
|
-
api_key: this.requestParams.apiKey,
|
|
439
|
-
status: 'finished',
|
|
440
|
-
duration: this.stats.duration,
|
|
441
|
-
tests: this._tests,
|
|
442
|
-
};
|
|
443
|
-
debug('Uploading data', dataString);
|
|
444
|
-
this.pipes = this.pipes || (await this.pipesPromise);
|
|
445
|
-
return Promise.all(this.pipes.map(p => p.finishRun(dataString)));
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
module.exports = AllureReader;
|
|
@@ -1,46 +0,0 @@
|
|
|
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
|
-
const path_1 = __importDefault(require("path"));
|
|
7
|
-
const adapter_js_1 = __importDefault(require("./adapter.js"));
|
|
8
|
-
class KotlinAdapter extends adapter_js_1.default {
|
|
9
|
-
getFilePath(t) {
|
|
10
|
-
const fileName = namespaceToFileName(t.suite_title);
|
|
11
|
-
return this.opts.javaTests + path_1.default.sep + fileName;
|
|
12
|
-
}
|
|
13
|
-
formatTest(t) {
|
|
14
|
-
const fileParts = t.suite_title.split('.');
|
|
15
|
-
t.file = namespaceToFileName(t.suite_title);
|
|
16
|
-
t.title = t.title.split('(')[0];
|
|
17
|
-
// detect params
|
|
18
|
-
const paramMatches = t.title.match(/\[(.*?)\]/g);
|
|
19
|
-
if (paramMatches) {
|
|
20
|
-
const params = paramMatches.map((_match, index) => `param${index + 1}`);
|
|
21
|
-
if (params.length === 1)
|
|
22
|
-
params[0] = 'param';
|
|
23
|
-
let paramIndex = 0;
|
|
24
|
-
t.title = t.title.replace(/: \[(.*?)\]/g, () => {
|
|
25
|
-
if (params.length < 2)
|
|
26
|
-
return `\${param}`;
|
|
27
|
-
const paramName = params[paramIndex] || `param${paramIndex + 1}`;
|
|
28
|
-
paramIndex++;
|
|
29
|
-
return `\${${paramName}}`;
|
|
30
|
-
});
|
|
31
|
-
const example = {};
|
|
32
|
-
paramMatches.forEach((match, index) => {
|
|
33
|
-
example[params[index]] = match.replace(/[[\]]/g, '');
|
|
34
|
-
});
|
|
35
|
-
t.example = example;
|
|
36
|
-
}
|
|
37
|
-
t.suite_title = fileParts[fileParts.length - 1].replace(/\$/g, ' | ');
|
|
38
|
-
return t;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function namespaceToFileName(fileName) {
|
|
42
|
-
const fileParts = fileName.split('.');
|
|
43
|
-
fileParts[fileParts.length - 1] = fileParts[fileParts.length - 1]?.replace(/\$.*/, '');
|
|
44
|
-
return `${fileParts.join(path_1.default.sep)}.kt`;
|
|
45
|
-
}
|
|
46
|
-
module.exports = KotlinAdapter;
|
package/lib/services/labels.d.ts
DELETED
|
File without changes
|
package/lib/services/labels.js
DELETED
|
File without changes
|