@testomatio/reporter 2.0.0-beta-esm → 2.0.0-beta.1-xml
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/lib/adapter/codecept.d.ts +2 -0
- package/lib/adapter/codecept.js +31 -26
- package/lib/adapter/cucumber/current.d.ts +14 -0
- package/lib/adapter/cucumber/legacy.d.ts +0 -0
- package/lib/adapter/cucumber.d.ts +2 -0
- package/lib/adapter/cypress-plugin/index.d.ts +2 -0
- package/lib/adapter/cypress-plugin/index.js +10 -10
- package/lib/adapter/jasmine.d.ts +11 -0
- package/lib/adapter/jest.d.ts +13 -0
- package/lib/adapter/mocha.d.ts +2 -0
- package/lib/adapter/mocha.js +4 -4
- package/lib/adapter/nightwatch.d.ts +4 -0
- package/lib/adapter/nightwatch.js +80 -0
- package/lib/adapter/playwright.d.ts +14 -0
- package/lib/adapter/playwright.js +58 -33
- package/lib/adapter/vitest.d.ts +35 -0
- package/lib/adapter/vitest.js +6 -6
- package/lib/adapter/webdriver.d.ts +24 -0
- package/lib/adapter/webdriver.js +51 -14
- package/lib/bin/cli.d.ts +2 -0
- package/lib/bin/cli.js +250 -0
- package/lib/bin/reportXml.d.ts +2 -0
- package/lib/bin/reportXml.js +15 -11
- package/lib/bin/startTest.d.ts +2 -0
- package/lib/bin/startTest.js +12 -7
- package/lib/bin/uploadArtifacts.d.ts +2 -0
- package/lib/bin/uploadArtifacts.js +82 -0
- package/lib/client.d.ts +76 -0
- package/lib/client.js +128 -53
- package/lib/config.d.ts +1 -0
- package/lib/config.js +2 -2
- package/lib/constants.d.ts +25 -0
- package/lib/constants.js +5 -1
- package/lib/data-storage.d.ts +34 -0
- package/lib/data-storage.js +19 -9
- package/lib/junit-adapter/adapter.d.ts +9 -0
- package/lib/junit-adapter/csharp.d.ts +5 -0
- package/lib/junit-adapter/csharp.js +11 -1
- package/lib/junit-adapter/index.d.ts +3 -0
- package/lib/junit-adapter/java.d.ts +5 -0
- package/lib/junit-adapter/javascript.d.ts +4 -0
- package/lib/junit-adapter/python.d.ts +5 -0
- package/lib/junit-adapter/ruby.d.ts +4 -0
- package/lib/output.d.ts +11 -0
- package/lib/package.json +3 -1
- package/lib/pipe/bitbucket.d.ts +23 -0
- package/lib/pipe/bitbucket.js +19 -9
- package/lib/pipe/csv.d.ts +47 -0
- package/lib/pipe/csv.js +2 -2
- package/lib/pipe/debug.d.ts +29 -0
- package/lib/pipe/debug.js +108 -0
- package/lib/pipe/github.d.ts +30 -0
- package/lib/pipe/github.js +37 -5
- package/lib/pipe/gitlab.d.ts +23 -0
- package/lib/pipe/gitlab.js +2 -3
- package/lib/pipe/html.d.ts +35 -0
- package/lib/pipe/html.js +9 -4
- package/lib/pipe/index.d.ts +1 -0
- package/lib/pipe/index.js +20 -10
- package/lib/pipe/testomatio.d.ts +70 -0
- package/lib/pipe/testomatio.js +54 -39
- package/lib/reporter-functions.d.ts +34 -0
- package/lib/reporter-functions.js +17 -7
- package/lib/reporter.d.ts +232 -0
- package/lib/reporter.js +19 -33
- package/lib/services/artifacts.d.ts +33 -0
- package/lib/services/index.d.ts +9 -0
- package/lib/services/key-values.d.ts +27 -0
- package/lib/services/key-values.js +1 -1
- package/lib/services/logger.d.ts +64 -0
- package/lib/services/logger.js +1 -2
- package/lib/template/testomatio.hbs +651 -1366
- package/lib/uploader.d.ts +60 -0
- package/lib/uploader.js +312 -0
- package/lib/utils/pipe_utils.d.ts +41 -0
- package/lib/utils/pipe_utils.js +3 -5
- package/lib/utils/utils.d.ts +47 -0
- package/lib/utils/utils.js +99 -12
- package/lib/xmlReader.d.ts +92 -0
- package/lib/xmlReader.js +64 -25
- package/package.json +19 -13
- package/src/adapter/codecept.js +30 -26
- package/src/adapter/cypress-plugin/index.js +5 -5
- package/src/adapter/mocha.cjs +1 -1
- package/src/adapter/mocha.js +4 -4
- package/src/adapter/nightwatch.js +88 -0
- package/src/adapter/playwright.js +59 -31
- package/src/adapter/vitest.js +6 -6
- package/src/adapter/webdriver.js +42 -12
- package/src/bin/cli.js +303 -0
- package/src/bin/reportXml.js +19 -9
- package/src/bin/startTest.js +9 -4
- package/src/bin/uploadArtifacts.js +91 -0
- package/src/client.js +137 -57
- package/src/config.js +2 -2
- package/src/constants.js +5 -1
- package/src/data-storage.js +2 -2
- package/src/junit-adapter/csharp.js +13 -1
- package/src/pipe/bitbucket.js +2 -2
- package/src/pipe/csv.js +3 -3
- package/src/pipe/debug.js +104 -0
- package/src/pipe/github.js +3 -5
- package/src/pipe/gitlab.js +6 -7
- package/src/pipe/html.js +14 -7
- package/src/pipe/index.js +5 -7
- package/src/pipe/testomatio.js +75 -76
- package/src/reporter-functions.js +18 -7
- package/src/reporter.cjs_decprecated +21 -0
- package/src/reporter.js +20 -11
- package/src/services/key-values.js +1 -1
- package/src/services/logger.js +5 -4
- package/src/template/testomatio.hbs +651 -1366
- package/src/uploader.js +371 -0
- package/src/utils/pipe_utils.js +4 -12
- package/src/utils/utils.js +64 -15
- package/src/xmlReader.js +76 -26
- package/lib/adapter/jasmine/jasmine.js +0 -63
- package/lib/adapter/mocha/mocha.js +0 -125
- package/lib/fileUploader.js +0 -245
- package/lib/utils/chalk.js +0 -10
- package/src/fileUploader.js +0 -307
- package/src/reporter.cjs +0 -22
- package/src/utils/chalk.js +0 -13
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export class S3Uploader {
|
|
2
|
+
isEnabled: any;
|
|
3
|
+
storeEnabled: boolean;
|
|
4
|
+
config: {};
|
|
5
|
+
/**
|
|
6
|
+
* @type {{path: string, size: number}[]}
|
|
7
|
+
*/
|
|
8
|
+
skippedUploads: {
|
|
9
|
+
path: string;
|
|
10
|
+
size: number;
|
|
11
|
+
}[];
|
|
12
|
+
failedUploads: any[];
|
|
13
|
+
/**
|
|
14
|
+
* @type {{path: string, size: number, link: string}[]}
|
|
15
|
+
*/
|
|
16
|
+
successfulUploads: {
|
|
17
|
+
path: string;
|
|
18
|
+
size: number;
|
|
19
|
+
link: string;
|
|
20
|
+
}[];
|
|
21
|
+
configKeys: string[];
|
|
22
|
+
resetConfig(): void;
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
* @returns {Record<string, string>}
|
|
26
|
+
*/
|
|
27
|
+
getConfig(): Record<string, string>;
|
|
28
|
+
getMaskedConfig(): {
|
|
29
|
+
[k: string]: string;
|
|
30
|
+
};
|
|
31
|
+
checkEnabled(): any;
|
|
32
|
+
enableLogStorage(): void;
|
|
33
|
+
disableLogStorage(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Returns an array of uploaded files
|
|
36
|
+
*
|
|
37
|
+
* @returns {{rid: string, file: string, uploaded: boolean}[]}
|
|
38
|
+
*/
|
|
39
|
+
readUploadedFiles(runId: any): {
|
|
40
|
+
rid: string;
|
|
41
|
+
file: string;
|
|
42
|
+
uploaded: boolean;
|
|
43
|
+
}[];
|
|
44
|
+
storeUploadedFile(filePath: any, runId: any, rid: any, uploaded?: boolean): void;
|
|
45
|
+
/**
|
|
46
|
+
* @param {*} filePath
|
|
47
|
+
* @param {*} pathInS3 contains runId, rid and filename
|
|
48
|
+
* @returns
|
|
49
|
+
*/
|
|
50
|
+
uploadFileByPath(filePath: any, pathInS3: any): Promise<any>;
|
|
51
|
+
/**
|
|
52
|
+
* @param {Buffer} buffer
|
|
53
|
+
* @param {string[]} pathInS3
|
|
54
|
+
* @returns
|
|
55
|
+
*/
|
|
56
|
+
uploadFileAsBuffer(buffer: Buffer, pathInS3: string[]): Promise<any>;
|
|
57
|
+
checkArtifactExistsInFileSystem(filePath: any, attempts?: number, intervalMs?: number): Promise<any>;
|
|
58
|
+
getS3LocationLink(out: any): Promise<any>;
|
|
59
|
+
#private;
|
|
60
|
+
}
|
package/lib/uploader.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
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.S3Uploader = void 0;
|
|
7
|
+
const debug_1 = __importDefault(require("debug"));
|
|
8
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
9
|
+
const lib_storage_1 = require("@aws-sdk/lib-storage");
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const promise_retry_1 = __importDefault(require("promise-retry"));
|
|
14
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
15
|
+
const constants_js_1 = require("./constants.js");
|
|
16
|
+
const filesize_1 = require("filesize");
|
|
17
|
+
const debug = (0, debug_1.default)('@testomatio/reporter:file-uploader');
|
|
18
|
+
class S3Uploader {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.isEnabled = undefined;
|
|
21
|
+
this.storeEnabled = true;
|
|
22
|
+
this.config = undefined;
|
|
23
|
+
/**
|
|
24
|
+
* @type {{path: string, size: number}[]}
|
|
25
|
+
*/
|
|
26
|
+
this.skippedUploads = [];
|
|
27
|
+
this.failedUploads = [];
|
|
28
|
+
/**
|
|
29
|
+
* @type {{path: string, size: number, link: string}[]}
|
|
30
|
+
*/
|
|
31
|
+
this.successfulUploads = [];
|
|
32
|
+
this.configKeys = [
|
|
33
|
+
'S3_ENDPOINT',
|
|
34
|
+
'S3_REGION',
|
|
35
|
+
'S3_BUCKET',
|
|
36
|
+
'S3_ACCESS_KEY_ID',
|
|
37
|
+
'S3_SECRET_ACCESS_KEY',
|
|
38
|
+
'S3_SESSION_TOKEN',
|
|
39
|
+
'S3_FORCE_PATH_STYLE',
|
|
40
|
+
'TESTOMATIO_DISABLE_ARTIFACTS',
|
|
41
|
+
'TESTOMATIO_PRIVATE_ARTIFACTS',
|
|
42
|
+
'TESTOMATIO_ARTIFACT_MAX_SIZE_MB',
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
resetConfig() {
|
|
46
|
+
this.config = undefined;
|
|
47
|
+
this.isEnabled = undefined;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* @returns {Record<string, string>}
|
|
52
|
+
*/
|
|
53
|
+
getConfig() {
|
|
54
|
+
if (this.config)
|
|
55
|
+
return this.config;
|
|
56
|
+
this.config = this.configKeys.reduce((acc, key) => {
|
|
57
|
+
acc[key] = process.env[key];
|
|
58
|
+
return acc;
|
|
59
|
+
}, {});
|
|
60
|
+
return this.config;
|
|
61
|
+
}
|
|
62
|
+
getMaskedConfig() {
|
|
63
|
+
return Object.fromEntries(Object.entries(this.getConfig()).map(([key, value]) => [
|
|
64
|
+
key,
|
|
65
|
+
key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
|
|
66
|
+
]));
|
|
67
|
+
}
|
|
68
|
+
checkEnabled() {
|
|
69
|
+
if (this.isEnabled !== undefined)
|
|
70
|
+
return this.isEnabled;
|
|
71
|
+
const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = this.getConfig();
|
|
72
|
+
if (!S3_BUCKET)
|
|
73
|
+
debug(`Artifacts uploading is disabled because S3_BUCKET is not set`);
|
|
74
|
+
this.isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
|
|
75
|
+
if (this.isEnabled)
|
|
76
|
+
debug('S3 uploader is enabled');
|
|
77
|
+
debug(this.getMaskedConfig());
|
|
78
|
+
return this.isEnabled;
|
|
79
|
+
}
|
|
80
|
+
enableLogStorage() {
|
|
81
|
+
this.storeEnabled = true;
|
|
82
|
+
}
|
|
83
|
+
disableLogStorage() {
|
|
84
|
+
this.storeEnabled = false;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
*
|
|
88
|
+
* @param {*} Body
|
|
89
|
+
* @param {*} Key
|
|
90
|
+
* @param {{path: string, size?: number}} file
|
|
91
|
+
* @returns
|
|
92
|
+
*/
|
|
93
|
+
async #uploadToS3(Body, Key, file) {
|
|
94
|
+
const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
|
|
95
|
+
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
96
|
+
if (!S3_BUCKET || !Body) {
|
|
97
|
+
console.log(constants_js_1.APP_PREFIX, picocolors_1.default.bold(picocolors_1.default.red(`Failed uploading '${Key}'. Please check S3 credentials`)), this.getMaskedConfig());
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
debug('Uploading to S3:', Key);
|
|
101
|
+
const s3Config = this.#getS3Config();
|
|
102
|
+
const s3 = new client_s3_1.S3(s3Config);
|
|
103
|
+
const params = {
|
|
104
|
+
Bucket: S3_BUCKET,
|
|
105
|
+
Key,
|
|
106
|
+
Body,
|
|
107
|
+
};
|
|
108
|
+
// disable ACL for I AM roles
|
|
109
|
+
if (!s3Config.credentials.sessionToken) {
|
|
110
|
+
params.ACL = ACL;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const upload = new lib_storage_1.Upload({ client: s3, params });
|
|
114
|
+
const link = await this.getS3LocationLink(upload);
|
|
115
|
+
this.successfulUploads.push({ path: file.path, size: file.size, link });
|
|
116
|
+
debug(`📤 Uploaded artifact. File: ${file.path}, size: ${(0, filesize_1.filesize)(file.size)}, link: ${link}`);
|
|
117
|
+
return link;
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
this.failedUploads.push({ path: file.path, size: file.size });
|
|
121
|
+
debug('S3 uploading error:', e);
|
|
122
|
+
console.log(constants_js_1.APP_PREFIX, 'Upload failed:', e.message, '\nConfig:\n', this.getMaskedConfig());
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Returns an array of uploaded files
|
|
127
|
+
*
|
|
128
|
+
* @returns {{rid: string, file: string, uploaded: boolean}[]}
|
|
129
|
+
*/
|
|
130
|
+
readUploadedFiles(runId) {
|
|
131
|
+
const tempFilePath = this.#getFilePathWithUploadsList(runId);
|
|
132
|
+
debug('Reading file', tempFilePath);
|
|
133
|
+
if (!fs_1.default.existsSync(tempFilePath)) {
|
|
134
|
+
debug('File not found:', tempFilePath);
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
const stats = fs_1.default.statSync(tempFilePath);
|
|
138
|
+
debug('Artifacts file stats:', +stats.mtime);
|
|
139
|
+
debug('Current time:', +new Date());
|
|
140
|
+
const diff = +new Date() - +stats.mtime;
|
|
141
|
+
debug('Diff:', diff);
|
|
142
|
+
const diffHours = diff / 1000 / 60 / 60;
|
|
143
|
+
debug('Diff hours:', diffHours);
|
|
144
|
+
if (diffHours > 3) {
|
|
145
|
+
console.log(constants_js_1.APP_PREFIX, "Artifacts file is too old, can't process artifacts. Please re-run the tests.");
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
const data = fs_1.default.readFileSync(tempFilePath, 'utf8');
|
|
149
|
+
debug('Artifacts file contents:', data);
|
|
150
|
+
const lines = data.split('\n').filter(Boolean);
|
|
151
|
+
return lines.map(line => JSON.parse(line));
|
|
152
|
+
}
|
|
153
|
+
#getFilePathWithUploadsList(runId) {
|
|
154
|
+
const tempFilePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.run.${runId}.json`);
|
|
155
|
+
if (!fs_1.default.existsSync(tempFilePath)) {
|
|
156
|
+
debug('Creating artifacts file:', tempFilePath);
|
|
157
|
+
fs_1.default.writeFileSync(tempFilePath, '');
|
|
158
|
+
}
|
|
159
|
+
return tempFilePath;
|
|
160
|
+
}
|
|
161
|
+
storeUploadedFile(filePath, runId, rid, uploaded = false) {
|
|
162
|
+
if (!this.storeEnabled)
|
|
163
|
+
return;
|
|
164
|
+
if (!filePath || !runId || !rid)
|
|
165
|
+
return;
|
|
166
|
+
const tempFilePath = this.#getFilePathWithUploadsList(runId);
|
|
167
|
+
if (typeof filePath === 'object') {
|
|
168
|
+
filePath = filePath.path;
|
|
169
|
+
}
|
|
170
|
+
if (typeof filePath === 'string' && !path_1.default.isAbsolute(filePath)) {
|
|
171
|
+
filePath = path_1.default.join(process.cwd(), filePath);
|
|
172
|
+
}
|
|
173
|
+
const data = { rid, file: filePath, uploaded };
|
|
174
|
+
const jsonLine = `${JSON.stringify(data)}\n`;
|
|
175
|
+
fs_1.default.appendFileSync(tempFilePath, jsonLine);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* @param {*} filePath
|
|
179
|
+
* @param {*} pathInS3 contains runId, rid and filename
|
|
180
|
+
* @returns
|
|
181
|
+
*/
|
|
182
|
+
async uploadFileByPath(filePath, pathInS3) {
|
|
183
|
+
// sometimes artifacts uploading started before createRun function completion
|
|
184
|
+
this.isEnabled = this.isEnabled ?? this.checkEnabled();
|
|
185
|
+
const [runId, rid] = pathInS3;
|
|
186
|
+
if (!filePath)
|
|
187
|
+
return;
|
|
188
|
+
let fileSize = null;
|
|
189
|
+
let fileSizeInMb = null;
|
|
190
|
+
try {
|
|
191
|
+
// file may not exist
|
|
192
|
+
fileSize = fs_1.default.statSync(filePath).size;
|
|
193
|
+
fileSizeInMb = Number((fileSize / (1024 * 1024)).toFixed(2));
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
debug(`File ${filePath} does not exist`);
|
|
197
|
+
}
|
|
198
|
+
if (!this.isEnabled) {
|
|
199
|
+
this.storeUploadedFile(filePath, runId, rid, false);
|
|
200
|
+
this.skippedUploads.push({ path: filePath, size: fileSize });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const { S3_BUCKET, TESTOMATIO_ARTIFACT_MAX_SIZE_MB } = this.getConfig();
|
|
204
|
+
debug('Started upload', filePath, 'to', S3_BUCKET);
|
|
205
|
+
const isFileExist = await this.checkArtifactExistsInFileSystem(filePath, 20, 500);
|
|
206
|
+
if (!isFileExist) {
|
|
207
|
+
console.error(picocolors_1.default.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
// skipping artifact only if: 1. storing to file is enabled, 2. max size is set and 3. file size exceeds the limit
|
|
211
|
+
if (this.storeEnabled &&
|
|
212
|
+
TESTOMATIO_ARTIFACT_MAX_SIZE_MB &&
|
|
213
|
+
fileSizeInMb > parseFloat(TESTOMATIO_ARTIFACT_MAX_SIZE_MB)) {
|
|
214
|
+
const skippedArtifact = { path: filePath, size: fileSize };
|
|
215
|
+
this.storeUploadedFile(filePath, runId, rid, false);
|
|
216
|
+
this.skippedUploads.push(skippedArtifact);
|
|
217
|
+
debug(picocolors_1.default.yellow(`Artifacts file ${JSON.stringify(skippedArtifact)} exceeds the maximum allowed size. Skipping.`));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
debug('File:', filePath, 'exists, size:', (0, filesize_1.filesize)(fileSize));
|
|
221
|
+
const fileStream = fs_1.default.createReadStream(filePath);
|
|
222
|
+
const Key = pathInS3.filter(p => !!p).join('/');
|
|
223
|
+
const link = await this.#uploadToS3(fileStream, Key, { path: filePath, size: fileSize });
|
|
224
|
+
this.storeUploadedFile(filePath, runId, rid, !!link);
|
|
225
|
+
return link;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* @param {Buffer} buffer
|
|
229
|
+
* @param {string[]} pathInS3
|
|
230
|
+
* @returns
|
|
231
|
+
*/
|
|
232
|
+
async uploadFileAsBuffer(buffer, pathInS3) {
|
|
233
|
+
if (!this.isEnabled)
|
|
234
|
+
return;
|
|
235
|
+
let Key = pathInS3.filter(p => !!p).join('/');
|
|
236
|
+
const ext = this.#getFileExtBase64(buffer);
|
|
237
|
+
if (ext) {
|
|
238
|
+
Key = `${Key}.${ext}`;
|
|
239
|
+
}
|
|
240
|
+
return this.#uploadToS3(buffer, Key, { path: Key });
|
|
241
|
+
}
|
|
242
|
+
async checkArtifactExistsInFileSystem(filePath, attempts = 5, intervalMs = 500) {
|
|
243
|
+
return (0, promise_retry_1.default)(async (retry, number) => {
|
|
244
|
+
try {
|
|
245
|
+
fs_1.default.accessSync(filePath);
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
if (number === attempts) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
debug(`File not found, retrying (attempt ${number}/${attempts})`);
|
|
253
|
+
await new Promise(resolve => {
|
|
254
|
+
setTimeout(resolve, intervalMs);
|
|
255
|
+
});
|
|
256
|
+
retry(err);
|
|
257
|
+
}
|
|
258
|
+
}, {
|
|
259
|
+
retries: attempts,
|
|
260
|
+
minTimeout: intervalMs,
|
|
261
|
+
maxTimeout: intervalMs,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
async getS3LocationLink(out) {
|
|
265
|
+
const response = await out.done();
|
|
266
|
+
let s3Location = response?.Location?.trim();
|
|
267
|
+
if (!s3Location) {
|
|
268
|
+
s3Location = out?.singleUploadResult?.Location;
|
|
269
|
+
debug('Uploaded singleUploadResult.Location', s3Location);
|
|
270
|
+
if (!s3Location) {
|
|
271
|
+
throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Normalize the URL
|
|
275
|
+
if (!s3Location.startsWith('http')) {
|
|
276
|
+
s3Location = `https://${s3Location}`;
|
|
277
|
+
}
|
|
278
|
+
return s3Location;
|
|
279
|
+
}
|
|
280
|
+
#getFileExtBase64(str) {
|
|
281
|
+
const type = str.charAt(0);
|
|
282
|
+
return ({
|
|
283
|
+
'/': 'jpg',
|
|
284
|
+
i: 'png',
|
|
285
|
+
R: 'gif',
|
|
286
|
+
U: 'webp',
|
|
287
|
+
}[type] || '');
|
|
288
|
+
}
|
|
289
|
+
#getS3Config() {
|
|
290
|
+
const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } = this.getConfig();
|
|
291
|
+
const cfg = {
|
|
292
|
+
region: S3_REGION,
|
|
293
|
+
credentials: {
|
|
294
|
+
accessKeyId: S3_ACCESS_KEY_ID,
|
|
295
|
+
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
if (S3_FORCE_PATH_STYLE) {
|
|
299
|
+
cfg.forcePathStyle = !['false', '0'].includes(String(S3_FORCE_PATH_STYLE || '').toLowerCase());
|
|
300
|
+
}
|
|
301
|
+
if (S3_SESSION_TOKEN) {
|
|
302
|
+
cfg.credentials.sessionToken = S3_SESSION_TOKEN;
|
|
303
|
+
}
|
|
304
|
+
if (S3_ENDPOINT) {
|
|
305
|
+
cfg.endpoint = S3_ENDPOINT;
|
|
306
|
+
}
|
|
307
|
+
return cfg;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
exports.S3Uploader = S3Uploader;
|
|
311
|
+
|
|
312
|
+
module.exports.S3Uploader = S3Uploader;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update and validate the filter type.
|
|
3
|
+
* @param {string} type - The original filter type.
|
|
4
|
+
* @returns {string|undefined} The updated and validated filter type.
|
|
5
|
+
* Returns undefined if the type is not valid.
|
|
6
|
+
*/
|
|
7
|
+
export function updateFilterType(type: string): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Parse filter parameters from a string in the format "type=id".
|
|
10
|
+
* @param {string} opts - The input string containing the filter parameters.
|
|
11
|
+
* @returns {Object} An object containing the parsed filter parameters.
|
|
12
|
+
* The object has properties "type" and "id".
|
|
13
|
+
*/
|
|
14
|
+
export function parseFilterParams(opts: string): any;
|
|
15
|
+
/**
|
|
16
|
+
* Generates mode request parameters based on the input params.
|
|
17
|
+
* @param {{type: string, id?: string, apiKey: string}} params - The input parameters for the request.
|
|
18
|
+
* @returns {Object|null} - An object containing the generated request parameters, or null if the type is invalid.
|
|
19
|
+
*/
|
|
20
|
+
export function generateFilterRequestParams(params: {
|
|
21
|
+
type: string;
|
|
22
|
+
id?: string;
|
|
23
|
+
apiKey: string;
|
|
24
|
+
}): any | null;
|
|
25
|
+
/**
|
|
26
|
+
* Set S3 credentials from the provided artifacts object.
|
|
27
|
+
* @param {Object} artifacts - The artifacts object containing S3 credentials.
|
|
28
|
+
*/
|
|
29
|
+
export function setS3Credentials(artifacts: any): void;
|
|
30
|
+
/**
|
|
31
|
+
* Return an emoji based on the provided status.
|
|
32
|
+
* @param {string} status - The status value ('passed', 'failed', or 'skipped').
|
|
33
|
+
* @returns {string} - An emoji corresponding to the provided status.
|
|
34
|
+
*/
|
|
35
|
+
export function statusEmoji(status: string): string;
|
|
36
|
+
/**
|
|
37
|
+
* Generate a full name string based on the provided test object.
|
|
38
|
+
* @param {object} t - The test object.
|
|
39
|
+
* @returns {string} - A formatted full name string for the test object.
|
|
40
|
+
*/
|
|
41
|
+
export function fullName(t: object): string;
|
package/lib/utils/pipe_utils.js
CHANGED
|
@@ -6,7 +6,6 @@ exports.generateFilterRequestParams = generateFilterRequestParams;
|
|
|
6
6
|
exports.setS3Credentials = setS3Credentials;
|
|
7
7
|
exports.statusEmoji = statusEmoji;
|
|
8
8
|
exports.fullName = fullName;
|
|
9
|
-
const fileUploader_js_1 = require("../fileUploader.js");
|
|
10
9
|
const constants_js_1 = require("../constants.js");
|
|
11
10
|
/**
|
|
12
11
|
* Set S3 credentials from the provided artifacts object.
|
|
@@ -15,7 +14,7 @@ const constants_js_1 = require("../constants.js");
|
|
|
15
14
|
function setS3Credentials(artifacts) {
|
|
16
15
|
if (!Object.keys(artifacts).length)
|
|
17
16
|
return;
|
|
18
|
-
console.log(constants_js_1.APP_PREFIX, 'S3
|
|
17
|
+
console.log(constants_js_1.APP_PREFIX, 'S3 credentials obtained from Testomat.io...');
|
|
19
18
|
if (artifacts.ACCESS_KEY_ID)
|
|
20
19
|
process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
|
|
21
20
|
if (artifacts.SECRET_ACCESS_KEY)
|
|
@@ -24,13 +23,12 @@ function setS3Credentials(artifacts) {
|
|
|
24
23
|
process.env.S3_REGION = artifacts.REGION;
|
|
25
24
|
if (artifacts.BUCKET)
|
|
26
25
|
process.env.S3_BUCKET = artifacts.BUCKET;
|
|
27
|
-
if (artifacts.ENDPOINT)
|
|
28
|
-
process.env.S3_ENDPOINT = artifacts.ENDPOINT;
|
|
29
26
|
if (artifacts.SESSION_TOKEN)
|
|
30
27
|
process.env.S3_SESSION_TOKEN = artifacts.SESSION_TOKEN;
|
|
31
28
|
if (artifacts.presign)
|
|
32
29
|
process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
|
|
33
|
-
|
|
30
|
+
// endpoint is not received from the server; and shuld be empty if IAM used (credentails obtained from the testomat)
|
|
31
|
+
process.env.S3_ENDPOINT = artifacts.ENDPOINT || '';
|
|
34
32
|
}
|
|
35
33
|
/**
|
|
36
34
|
* Generates mode request parameters based on the input params.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function getPackageVersion(): any;
|
|
2
|
+
export const TEST_ID_REGEX: RegExp;
|
|
3
|
+
export function ansiRegExp(): RegExp;
|
|
4
|
+
export function isSameTest(test: any, t: any): boolean;
|
|
5
|
+
export function fetchSourceCode(contents: any, opts?: {}): string;
|
|
6
|
+
export function fetchSourceCodeFromStackTrace(stack?: string): string;
|
|
7
|
+
export function fetchIdFromCode(code: any, opts?: {}): any;
|
|
8
|
+
export function fetchIdFromOutput(output: any): any;
|
|
9
|
+
export function fetchFilesFromStackTrace(stack?: string): string[];
|
|
10
|
+
export namespace fileSystem {
|
|
11
|
+
function createDir(dirPath: any): void;
|
|
12
|
+
function clearDir(dirPath: any): void;
|
|
13
|
+
}
|
|
14
|
+
export function foundedTestLog(app: any, tests: any): void;
|
|
15
|
+
export function formatStep(step: any, shift?: number): any;
|
|
16
|
+
export function getCurrentDateTime(): string;
|
|
17
|
+
/**
|
|
18
|
+
* @param {String} testTitle - Test title
|
|
19
|
+
*
|
|
20
|
+
* @returns {String|null} testId
|
|
21
|
+
*/
|
|
22
|
+
export function getTestomatIdFromTestTitle(testTitle: string): string | null;
|
|
23
|
+
export function humanize(text: any): any;
|
|
24
|
+
export function isValidUrl(s: any): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* @param {String} suiteTitle - suite title
|
|
27
|
+
*
|
|
28
|
+
* @returns {String|null} suiteId
|
|
29
|
+
*/
|
|
30
|
+
export function parseSuite(suiteTitle: string): string | null;
|
|
31
|
+
export function readLatestRunId(): string;
|
|
32
|
+
/**
|
|
33
|
+
* Used to remove color codes
|
|
34
|
+
* @param {*} input
|
|
35
|
+
* @returns
|
|
36
|
+
*/
|
|
37
|
+
export function removeColorCodes(input: any): any;
|
|
38
|
+
/**
|
|
39
|
+
* @param {Object} test - Test adapter object
|
|
40
|
+
*
|
|
41
|
+
* @returns {String|null} testInfo as one string
|
|
42
|
+
*/
|
|
43
|
+
export function specificTestInfo(test: any): string | null;
|
|
44
|
+
export function storeRunId(runId: any): void;
|
|
45
|
+
export namespace testRunnerHelper {
|
|
46
|
+
function getNameOfCurrentlyRunningTest(): any;
|
|
47
|
+
}
|
package/lib/utils/utils.js
CHANGED
|
@@ -1,17 +1,57 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.testRunnerHelper = exports.
|
|
39
|
+
exports.testRunnerHelper = exports.specificTestInfo = exports.parseSuite = exports.isValidUrl = exports.humanize = exports.getTestomatIdFromTestTitle = exports.getCurrentDateTime = exports.foundedTestLog = exports.fileSystem = exports.fetchFilesFromStackTrace = exports.fetchIdFromOutput = exports.fetchIdFromCode = exports.fetchSourceCodeFromStackTrace = exports.fetchSourceCode = exports.isSameTest = exports.ansiRegExp = exports.TEST_ID_REGEX = void 0;
|
|
40
|
+
exports.getPackageVersion = getPackageVersion;
|
|
41
|
+
exports.formatStep = formatStep;
|
|
42
|
+
exports.readLatestRunId = readLatestRunId;
|
|
7
43
|
exports.removeColorCodes = removeColorCodes;
|
|
44
|
+
exports.storeRunId = storeRunId;
|
|
8
45
|
const url_1 = require("url");
|
|
9
|
-
const path_1 = require("path");
|
|
46
|
+
const path_1 = __importStar(require("path"));
|
|
10
47
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
11
48
|
const fs_1 = __importDefault(require("fs"));
|
|
12
49
|
const is_valid_path_1 = __importDefault(require("is-valid-path"));
|
|
13
50
|
const debug_1 = __importDefault(require("debug"));
|
|
51
|
+
const os_1 = __importDefault(require("os"));
|
|
52
|
+
const url_2 = require("url");
|
|
14
53
|
const debug = (0, debug_1.default)('@testomatio/reporter:util');
|
|
54
|
+
// Use __dirname directly since we're compiling to CommonJS
|
|
15
55
|
/**
|
|
16
56
|
* @param {String} testTitle - Test title
|
|
17
57
|
*
|
|
@@ -50,7 +90,6 @@ const ansiRegExp = () => {
|
|
|
50
90
|
exports.ansiRegExp = ansiRegExp;
|
|
51
91
|
const isValidUrl = s => {
|
|
52
92
|
try {
|
|
53
|
-
// eslint-disable-next-line no-new
|
|
54
93
|
new url_1.URL(s);
|
|
55
94
|
return true;
|
|
56
95
|
}
|
|
@@ -104,7 +143,7 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
104
143
|
.join('\n');
|
|
105
144
|
};
|
|
106
145
|
exports.fetchSourceCodeFromStackTrace = fetchSourceCodeFromStackTrace;
|
|
107
|
-
|
|
146
|
+
exports.TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
108
147
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
109
148
|
const comments = code
|
|
110
149
|
.split('\n')
|
|
@@ -118,15 +157,12 @@ const fetchIdFromCode = (code, opts = {}) => {
|
|
|
118
157
|
return l.startsWith('// ');
|
|
119
158
|
}
|
|
120
159
|
});
|
|
121
|
-
return comments.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
|
|
160
|
+
return comments.find(c => c.match(exports.TEST_ID_REGEX))?.match(exports.TEST_ID_REGEX)?.[1];
|
|
122
161
|
};
|
|
123
162
|
exports.fetchIdFromCode = fetchIdFromCode;
|
|
124
163
|
const fetchIdFromOutput = output => {
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
.map(l => l.trim())
|
|
128
|
-
.filter(l => l.startsWith('tid://'));
|
|
129
|
-
return lines.find(c => c.match(TEST_ID_REGEX))?.match(TEST_ID_REGEX)?.[1];
|
|
164
|
+
const TID_FULL_PATTERN = new RegExp(`tid:\\/\\/.*?(${exports.TEST_ID_REGEX.source})`);
|
|
165
|
+
return output.match(TID_FULL_PATTERN)?.[2];
|
|
130
166
|
};
|
|
131
167
|
exports.fetchIdFromOutput = fetchIdFromOutput;
|
|
132
168
|
const fetchSourceCode = (contents, opts = {}) => {
|
|
@@ -150,6 +186,12 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
150
186
|
if (lineIndex === -1)
|
|
151
187
|
lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
152
188
|
}
|
|
189
|
+
else if (opts.lang === 'csharp') {
|
|
190
|
+
if (lineIndex === -1)
|
|
191
|
+
lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
192
|
+
if (lineIndex === -1)
|
|
193
|
+
lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
194
|
+
}
|
|
153
195
|
else {
|
|
154
196
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
155
197
|
}
|
|
@@ -297,7 +339,6 @@ const decamelize = text => {
|
|
|
297
339
|
* @returns
|
|
298
340
|
*/
|
|
299
341
|
function removeColorCodes(input) {
|
|
300
|
-
// eslint-disable-next-line no-control-regex
|
|
301
342
|
return input.replace(/\x1b\[[0-9;]*m/g, '');
|
|
302
343
|
}
|
|
303
344
|
const testRunnerHelper = {
|
|
@@ -310,7 +351,6 @@ const testRunnerHelper = {
|
|
|
310
351
|
try {
|
|
311
352
|
// TODO: expect?.getState()?.testPath + ' ' + expect?.getState()?.currentTestName
|
|
312
353
|
// @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
|
|
313
|
-
// eslint-disable-next-line no-undef
|
|
314
354
|
return expect?.getState()?.currentTestName;
|
|
315
355
|
}
|
|
316
356
|
catch (e) {
|
|
@@ -319,9 +359,56 @@ const testRunnerHelper = {
|
|
|
319
359
|
},
|
|
320
360
|
};
|
|
321
361
|
exports.testRunnerHelper = testRunnerHelper;
|
|
362
|
+
function storeRunId(runId) {
|
|
363
|
+
if (!runId || runId === 'undefined')
|
|
364
|
+
return;
|
|
365
|
+
const filePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.latest.run`);
|
|
366
|
+
fs_1.default.writeFileSync(filePath, runId);
|
|
367
|
+
}
|
|
368
|
+
function readLatestRunId() {
|
|
369
|
+
try {
|
|
370
|
+
const filePath = path_1.default.join(os_1.default.tmpdir(), `testomatio.latest.run`);
|
|
371
|
+
const stats = fs_1.default.statSync(filePath);
|
|
372
|
+
const diff = +new Date() - +stats.mtime;
|
|
373
|
+
const diffHours = diff / 1000 / 60 / 60;
|
|
374
|
+
if (diffHours > 1)
|
|
375
|
+
return;
|
|
376
|
+
return fs_1.default.readFileSync(filePath)?.toString()?.trim();
|
|
377
|
+
}
|
|
378
|
+
catch (e) {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function formatStep(step, shift = 0) {
|
|
383
|
+
const prefix = ' '.repeat(shift);
|
|
384
|
+
const lines = [];
|
|
385
|
+
if (step.error) {
|
|
386
|
+
lines.push(`${prefix}${picocolors_1.default.red(step.title)} ${picocolors_1.default.gray(`${step.duration}ms`)}`);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
lines.push(`${prefix}${step.title} ${picocolors_1.default.gray(`${step.duration}ms`)}`);
|
|
390
|
+
}
|
|
391
|
+
for (const child of step.steps || []) {
|
|
392
|
+
lines.push(...formatStep(child, shift + 2));
|
|
393
|
+
}
|
|
394
|
+
return lines;
|
|
395
|
+
}
|
|
396
|
+
function getPackageVersion() {
|
|
397
|
+
const packageJsonPath = path_1.default.resolve(__dirname, '../../package.json');
|
|
398
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
399
|
+
return packageJson.version;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
module.exports.getPackageVersion = getPackageVersion;
|
|
403
|
+
|
|
404
|
+
module.exports.formatStep = formatStep;
|
|
405
|
+
|
|
406
|
+
module.exports.readLatestRunId = readLatestRunId;
|
|
322
407
|
|
|
323
408
|
module.exports.removeColorCodes = removeColorCodes;
|
|
324
409
|
|
|
410
|
+
module.exports.storeRunId = storeRunId;
|
|
411
|
+
|
|
325
412
|
module.exports.getTestomatIdFromTestTitle = getTestomatIdFromTestTitle;
|
|
326
413
|
|
|
327
414
|
module.exports.parseSuite = parseSuite;
|