@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
package/src/uploader.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import createDebugMessages from 'debug';
|
|
2
|
+
import { S3 } from '@aws-sdk/client-s3';
|
|
3
|
+
import { Upload } from '@aws-sdk/lib-storage';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import promiseRetry from 'promise-retry';
|
|
8
|
+
import pc from 'picocolors';
|
|
9
|
+
import { APP_PREFIX } from './constants.js';
|
|
10
|
+
import { filesize as prettyBytes } from 'filesize';
|
|
11
|
+
|
|
12
|
+
const debug = createDebugMessages('@testomatio/reporter:file-uploader');
|
|
13
|
+
|
|
14
|
+
export class S3Uploader {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.isEnabled = undefined;
|
|
17
|
+
this.storeEnabled = true;
|
|
18
|
+
this.config = undefined;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @type {{path: string, size: number}[]}
|
|
22
|
+
*/
|
|
23
|
+
this.skippedUploads = [];
|
|
24
|
+
this.failedUploads = [];
|
|
25
|
+
/**
|
|
26
|
+
* @type {{path: string, size: number, link: string}[]}
|
|
27
|
+
*/
|
|
28
|
+
this.successfulUploads = [];
|
|
29
|
+
|
|
30
|
+
this.configKeys = [
|
|
31
|
+
'S3_ENDPOINT',
|
|
32
|
+
'S3_REGION',
|
|
33
|
+
'S3_BUCKET',
|
|
34
|
+
'S3_ACCESS_KEY_ID',
|
|
35
|
+
'S3_SECRET_ACCESS_KEY',
|
|
36
|
+
'S3_SESSION_TOKEN',
|
|
37
|
+
'S3_FORCE_PATH_STYLE',
|
|
38
|
+
'TESTOMATIO_DISABLE_ARTIFACTS',
|
|
39
|
+
'TESTOMATIO_PRIVATE_ARTIFACTS',
|
|
40
|
+
'TESTOMATIO_ARTIFACT_MAX_SIZE_MB',
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
resetConfig() {
|
|
45
|
+
this.config = undefined;
|
|
46
|
+
this.isEnabled = undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
*
|
|
51
|
+
* @returns {Record<string, string>}
|
|
52
|
+
*/
|
|
53
|
+
getConfig() {
|
|
54
|
+
if (this.config) return this.config;
|
|
55
|
+
this.config = this.configKeys.reduce((acc, key) => {
|
|
56
|
+
acc[key] = process.env[key];
|
|
57
|
+
return acc;
|
|
58
|
+
}, {});
|
|
59
|
+
return this.config;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getMaskedConfig() {
|
|
63
|
+
return Object.fromEntries(
|
|
64
|
+
Object.entries(this.getConfig()).map(([key, value]) => [
|
|
65
|
+
key,
|
|
66
|
+
key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
|
|
67
|
+
]),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
checkEnabled() {
|
|
72
|
+
if (this.isEnabled !== undefined) return this.isEnabled;
|
|
73
|
+
|
|
74
|
+
const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = this.getConfig();
|
|
75
|
+
if (!S3_BUCKET) debug(`Artifacts uploading is disabled because S3_BUCKET is not set`);
|
|
76
|
+
this.isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
|
|
77
|
+
|
|
78
|
+
if (this.isEnabled) debug('S3 uploader is enabled');
|
|
79
|
+
debug(this.getMaskedConfig());
|
|
80
|
+
|
|
81
|
+
return this.isEnabled;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
enableLogStorage() {
|
|
85
|
+
this.storeEnabled = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
disableLogStorage() {
|
|
89
|
+
this.storeEnabled = false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
*
|
|
94
|
+
* @param {*} Body
|
|
95
|
+
* @param {*} Key
|
|
96
|
+
* @param {{path: string, size?: number}} file
|
|
97
|
+
* @returns
|
|
98
|
+
*/
|
|
99
|
+
async #uploadToS3(Body, Key, file) {
|
|
100
|
+
const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
|
|
101
|
+
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
102
|
+
|
|
103
|
+
if (!S3_BUCKET || !Body) {
|
|
104
|
+
console.log(
|
|
105
|
+
APP_PREFIX,
|
|
106
|
+
pc.bold(pc.red(`Failed uploading '${Key}'. Please check S3 credentials`)),
|
|
107
|
+
this.getMaskedConfig(),
|
|
108
|
+
);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
debug('Uploading to S3:', Key);
|
|
113
|
+
|
|
114
|
+
const s3Config = this.#getS3Config();
|
|
115
|
+
const s3 = new S3(s3Config);
|
|
116
|
+
const params = {
|
|
117
|
+
Bucket: S3_BUCKET,
|
|
118
|
+
Key,
|
|
119
|
+
Body,
|
|
120
|
+
};
|
|
121
|
+
// disable ACL for I AM roles
|
|
122
|
+
if (!s3Config.credentials.sessionToken) {
|
|
123
|
+
params.ACL = ACL;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
const upload = new Upload({ client: s3, params });
|
|
128
|
+
|
|
129
|
+
const link = await this.getS3LocationLink(upload);
|
|
130
|
+
this.successfulUploads.push({ path: file.path, size: file.size, link });
|
|
131
|
+
debug(`📤 Uploaded artifact. File: ${file.path}, size: ${prettyBytes(file.size)}, link: ${link}`);
|
|
132
|
+
return link;
|
|
133
|
+
} catch (e) {
|
|
134
|
+
this.failedUploads.push({ path: file.path, size: file.size });
|
|
135
|
+
debug('S3 uploading error:', e);
|
|
136
|
+
console.log(APP_PREFIX, 'Upload failed:', e.message, '\nConfig:\n', this.getMaskedConfig());
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Returns an array of uploaded files
|
|
142
|
+
*
|
|
143
|
+
* @returns {{rid: string, file: string, uploaded: boolean}[]}
|
|
144
|
+
*/
|
|
145
|
+
readUploadedFiles(runId) {
|
|
146
|
+
const tempFilePath = this.#getFilePathWithUploadsList(runId);
|
|
147
|
+
|
|
148
|
+
debug('Reading file', tempFilePath);
|
|
149
|
+
|
|
150
|
+
if (!fs.existsSync(tempFilePath)) {
|
|
151
|
+
debug('File not found:', tempFilePath);
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const stats = fs.statSync(tempFilePath);
|
|
156
|
+
debug('Artifacts file stats:', +stats.mtime);
|
|
157
|
+
debug('Current time:', +new Date());
|
|
158
|
+
const diff = +new Date() - +stats.mtime;
|
|
159
|
+
debug('Diff:', diff);
|
|
160
|
+
const diffHours = diff / 1000 / 60 / 60;
|
|
161
|
+
debug('Diff hours:', diffHours);
|
|
162
|
+
if (diffHours > 3) {
|
|
163
|
+
console.log(APP_PREFIX, "Artifacts file is too old, can't process artifacts. Please re-run the tests.");
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const data = fs.readFileSync(tempFilePath, 'utf8');
|
|
168
|
+
debug('Artifacts file contents:', data);
|
|
169
|
+
const lines = data.split('\n').filter(Boolean);
|
|
170
|
+
return lines.map(line => JSON.parse(line));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#getFilePathWithUploadsList(runId) {
|
|
174
|
+
const tempFilePath = path.join(os.tmpdir(), `testomatio.run.${runId}.json`);
|
|
175
|
+
if (!fs.existsSync(tempFilePath)) {
|
|
176
|
+
debug('Creating artifacts file:', tempFilePath);
|
|
177
|
+
fs.writeFileSync(tempFilePath, '');
|
|
178
|
+
}
|
|
179
|
+
return tempFilePath;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
storeUploadedFile(filePath, runId, rid, uploaded = false) {
|
|
183
|
+
if (!this.storeEnabled) return;
|
|
184
|
+
|
|
185
|
+
if (!filePath || !runId || !rid) return;
|
|
186
|
+
|
|
187
|
+
const tempFilePath = this.#getFilePathWithUploadsList(runId);
|
|
188
|
+
|
|
189
|
+
if (typeof filePath === 'object') {
|
|
190
|
+
filePath = filePath.path;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (typeof filePath === 'string' && !path.isAbsolute(filePath)) {
|
|
194
|
+
filePath = path.join(process.cwd(), filePath);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const data = { rid, file: filePath, uploaded };
|
|
198
|
+
const jsonLine = `${JSON.stringify(data)}\n`;
|
|
199
|
+
fs.appendFileSync(tempFilePath, jsonLine);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* @param {*} filePath
|
|
204
|
+
* @param {*} pathInS3 contains runId, rid and filename
|
|
205
|
+
* @returns
|
|
206
|
+
*/
|
|
207
|
+
async uploadFileByPath(filePath, pathInS3) {
|
|
208
|
+
// sometimes artifacts uploading started before createRun function completion
|
|
209
|
+
this.isEnabled = this.isEnabled ?? this.checkEnabled();
|
|
210
|
+
|
|
211
|
+
const [runId, rid] = pathInS3;
|
|
212
|
+
|
|
213
|
+
if (!filePath) return;
|
|
214
|
+
|
|
215
|
+
let fileSize = null;
|
|
216
|
+
let fileSizeInMb = null;
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
// file may not exist
|
|
220
|
+
fileSize = fs.statSync(filePath).size;
|
|
221
|
+
fileSizeInMb = Number((fileSize / (1024 * 1024)).toFixed(2));
|
|
222
|
+
} catch (e) {
|
|
223
|
+
debug(`File ${filePath} does not exist`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!this.isEnabled) {
|
|
227
|
+
this.storeUploadedFile(filePath, runId, rid, false);
|
|
228
|
+
this.skippedUploads.push({ path: filePath, size: fileSize });
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const { S3_BUCKET, TESTOMATIO_ARTIFACT_MAX_SIZE_MB } = this.getConfig();
|
|
233
|
+
|
|
234
|
+
debug('Started upload', filePath, 'to', S3_BUCKET);
|
|
235
|
+
|
|
236
|
+
const isFileExist = await this.checkArtifactExistsInFileSystem(filePath, 20, 500);
|
|
237
|
+
|
|
238
|
+
if (!isFileExist) {
|
|
239
|
+
console.error(pc.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// skipping artifact only if: 1. storing to file is enabled, 2. max size is set and 3. file size exceeds the limit
|
|
244
|
+
if (
|
|
245
|
+
this.storeEnabled &&
|
|
246
|
+
TESTOMATIO_ARTIFACT_MAX_SIZE_MB &&
|
|
247
|
+
fileSizeInMb > parseFloat(TESTOMATIO_ARTIFACT_MAX_SIZE_MB)
|
|
248
|
+
) {
|
|
249
|
+
const skippedArtifact = { path: filePath, size: fileSize };
|
|
250
|
+
this.storeUploadedFile(filePath, runId, rid, false);
|
|
251
|
+
this.skippedUploads.push(skippedArtifact);
|
|
252
|
+
debug(pc.yellow(`Artifacts file ${JSON.stringify(skippedArtifact)} exceeds the maximum allowed size. Skipping.`));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
debug('File:', filePath, 'exists, size:', prettyBytes(fileSize));
|
|
256
|
+
|
|
257
|
+
const fileStream = fs.createReadStream(filePath);
|
|
258
|
+
const Key = pathInS3.filter(p => !!p).join('/');
|
|
259
|
+
|
|
260
|
+
const link = await this.#uploadToS3(fileStream, Key, { path: filePath, size: fileSize });
|
|
261
|
+
|
|
262
|
+
this.storeUploadedFile(filePath, runId, rid, !!link);
|
|
263
|
+
|
|
264
|
+
return link;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @param {Buffer} buffer
|
|
269
|
+
* @param {string[]} pathInS3
|
|
270
|
+
* @returns
|
|
271
|
+
*/
|
|
272
|
+
async uploadFileAsBuffer(buffer, pathInS3) {
|
|
273
|
+
if (!this.isEnabled) return;
|
|
274
|
+
|
|
275
|
+
let Key = pathInS3.filter(p => !!p).join('/');
|
|
276
|
+
const ext = this.#getFileExtBase64(buffer);
|
|
277
|
+
|
|
278
|
+
if (ext) {
|
|
279
|
+
Key = `${Key}.${ext}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return this.#uploadToS3(buffer, Key, { path: Key });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async checkArtifactExistsInFileSystem(filePath, attempts = 5, intervalMs = 500) {
|
|
286
|
+
return promiseRetry(
|
|
287
|
+
async (retry, number) => {
|
|
288
|
+
try {
|
|
289
|
+
fs.accessSync(filePath);
|
|
290
|
+
return true;
|
|
291
|
+
} catch (err) {
|
|
292
|
+
if (number === attempts) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
debug(`File not found, retrying (attempt ${number}/${attempts})`);
|
|
296
|
+
await new Promise(resolve => {
|
|
297
|
+
setTimeout(resolve, intervalMs);
|
|
298
|
+
});
|
|
299
|
+
retry(err);
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
retries: attempts,
|
|
304
|
+
minTimeout: intervalMs,
|
|
305
|
+
maxTimeout: intervalMs,
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async getS3LocationLink(out) {
|
|
311
|
+
const response = await out.done();
|
|
312
|
+
|
|
313
|
+
let s3Location = response?.Location?.trim();
|
|
314
|
+
|
|
315
|
+
if (!s3Location) {
|
|
316
|
+
s3Location = out?.singleUploadResult?.Location;
|
|
317
|
+
debug('Uploaded singleUploadResult.Location', s3Location);
|
|
318
|
+
|
|
319
|
+
if (!s3Location) {
|
|
320
|
+
throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Normalize the URL
|
|
325
|
+
if (!s3Location.startsWith('http')) {
|
|
326
|
+
s3Location = `https://${s3Location}`;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return s3Location;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
#getFileExtBase64(str) {
|
|
333
|
+
const type = str.charAt(0);
|
|
334
|
+
|
|
335
|
+
return (
|
|
336
|
+
{
|
|
337
|
+
'/': 'jpg',
|
|
338
|
+
i: 'png',
|
|
339
|
+
R: 'gif',
|
|
340
|
+
U: 'webp',
|
|
341
|
+
}[type] || ''
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#getS3Config() {
|
|
346
|
+
const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
|
|
347
|
+
this.getConfig();
|
|
348
|
+
|
|
349
|
+
const cfg = {
|
|
350
|
+
region: S3_REGION,
|
|
351
|
+
credentials: {
|
|
352
|
+
accessKeyId: S3_ACCESS_KEY_ID,
|
|
353
|
+
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
354
|
+
},
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
if (S3_FORCE_PATH_STYLE) {
|
|
358
|
+
cfg.forcePathStyle = !['false', '0'].includes(String(S3_FORCE_PATH_STYLE || '').toLowerCase());
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (S3_SESSION_TOKEN) {
|
|
362
|
+
cfg.credentials.sessionToken = S3_SESSION_TOKEN;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (S3_ENDPOINT) {
|
|
366
|
+
cfg.endpoint = S3_ENDPOINT;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return cfg;
|
|
370
|
+
}
|
|
371
|
+
}
|
package/src/utils/pipe_utils.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { upload } from '../fileUploader.js';
|
|
2
1
|
import { APP_PREFIX } from '../constants.js';
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -8,16 +7,16 @@ import { APP_PREFIX } from '../constants.js';
|
|
|
8
7
|
function setS3Credentials(artifacts) {
|
|
9
8
|
if (!Object.keys(artifacts).length) return;
|
|
10
9
|
|
|
11
|
-
console.log(APP_PREFIX, 'S3
|
|
10
|
+
console.log(APP_PREFIX, 'S3 credentials obtained from Testomat.io...');
|
|
12
11
|
|
|
13
12
|
if (artifacts.ACCESS_KEY_ID) process.env.S3_ACCESS_KEY_ID = artifacts.ACCESS_KEY_ID;
|
|
14
13
|
if (artifacts.SECRET_ACCESS_KEY) process.env.S3_SECRET_ACCESS_KEY = artifacts.SECRET_ACCESS_KEY;
|
|
15
14
|
if (artifacts.REGION) process.env.S3_REGION = artifacts.REGION;
|
|
16
15
|
if (artifacts.BUCKET) process.env.S3_BUCKET = artifacts.BUCKET;
|
|
17
|
-
if (artifacts.ENDPOINT) process.env.S3_ENDPOINT = artifacts.ENDPOINT;
|
|
18
16
|
if (artifacts.SESSION_TOKEN) process.env.S3_SESSION_TOKEN = artifacts.SESSION_TOKEN;
|
|
19
17
|
if (artifacts.presign) process.env.TESTOMATIO_PRIVATE_ARTIFACTS = '1';
|
|
20
|
-
|
|
18
|
+
// endpoint is not received from the server; and shuld be empty if IAM used (credentails obtained from the testomat)
|
|
19
|
+
process.env.S3_ENDPOINT = artifacts.ENDPOINT || '';
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
/**
|
|
@@ -117,11 +116,4 @@ function fullName(t) {
|
|
|
117
116
|
return line;
|
|
118
117
|
}
|
|
119
118
|
|
|
120
|
-
export {
|
|
121
|
-
updateFilterType,
|
|
122
|
-
parseFilterParams,
|
|
123
|
-
generateFilterRequestParams,
|
|
124
|
-
setS3Credentials,
|
|
125
|
-
statusEmoji,
|
|
126
|
-
fullName,
|
|
127
|
-
};
|
|
119
|
+
export { updateFilterType, parseFilterParams, generateFilterRequestParams, setS3Credentials, statusEmoji, fullName };
|
package/src/utils/utils.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { URL } from 'url';
|
|
2
|
-
import { sep, basename } from 'path';
|
|
2
|
+
import path, { sep, basename } from 'path';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import isValid from 'is-valid-path';
|
|
6
6
|
import createDebugMessages from 'debug';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
7
9
|
|
|
8
10
|
const debug = createDebugMessages('@testomatio/reporter:util');
|
|
9
11
|
|
|
12
|
+
// Use __dirname directly since we're compiling to CommonJS
|
|
13
|
+
const __dirname = path.resolve();
|
|
14
|
+
|
|
10
15
|
/**
|
|
11
16
|
* @param {String} testTitle - Test title
|
|
12
17
|
*
|
|
@@ -49,7 +54,6 @@ const ansiRegExp = () => {
|
|
|
49
54
|
|
|
50
55
|
const isValidUrl = s => {
|
|
51
56
|
try {
|
|
52
|
-
// eslint-disable-next-line no-new
|
|
53
57
|
new URL(s);
|
|
54
58
|
return true;
|
|
55
59
|
} catch (err) {
|
|
@@ -107,7 +111,7 @@ const fetchSourceCodeFromStackTrace = (stack = '') => {
|
|
|
107
111
|
.join('\n');
|
|
108
112
|
};
|
|
109
113
|
|
|
110
|
-
const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
114
|
+
export const TEST_ID_REGEX = /@T([\w\d]{8})/;
|
|
111
115
|
|
|
112
116
|
const fetchIdFromCode = (code, opts = {}) => {
|
|
113
117
|
const comments = code
|
|
@@ -127,12 +131,9 @@ const fetchIdFromCode = (code, opts = {}) => {
|
|
|
127
131
|
};
|
|
128
132
|
|
|
129
133
|
const fetchIdFromOutput = output => {
|
|
130
|
-
const
|
|
131
|
-
.split('\n')
|
|
132
|
-
.map(l => l.trim())
|
|
133
|
-
.filter(l => l.startsWith('tid://'));
|
|
134
|
+
const TID_FULL_PATTERN = new RegExp(`tid:\\/\\/.*?(${TEST_ID_REGEX.source})`);
|
|
134
135
|
|
|
135
|
-
return
|
|
136
|
+
return output.match(TID_FULL_PATTERN)?.[2];
|
|
136
137
|
};
|
|
137
138
|
|
|
138
139
|
const fetchSourceCode = (contents, opts = {}) => {
|
|
@@ -153,6 +154,9 @@ const fetchSourceCode = (contents, opts = {}) => {
|
|
|
153
154
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`@DisplayName("${title}`));
|
|
154
155
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
155
156
|
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
157
|
+
} else if (opts.lang === 'csharp') {
|
|
158
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`public void ${title}`));
|
|
159
|
+
if (lineIndex === -1) lineIndex = lines.findIndex(l => l.includes(`${title}(`));
|
|
156
160
|
} else {
|
|
157
161
|
lineIndex = lines.findIndex(l => l.includes(title));
|
|
158
162
|
}
|
|
@@ -299,7 +303,6 @@ const decamelize = text => {
|
|
|
299
303
|
* @returns
|
|
300
304
|
*/
|
|
301
305
|
function removeColorCodes(input) {
|
|
302
|
-
// eslint-disable-next-line no-control-regex
|
|
303
306
|
return input.replace(/\x1b\[[0-9;]*m/g, '');
|
|
304
307
|
}
|
|
305
308
|
|
|
@@ -312,7 +315,6 @@ const testRunnerHelper = {
|
|
|
312
315
|
try {
|
|
313
316
|
// TODO: expect?.getState()?.testPath + ' ' + expect?.getState()?.currentTestName
|
|
314
317
|
// @ts-expect-error "expect" could only be defined inside Jest environement (forbidden to import it outside)
|
|
315
|
-
// eslint-disable-next-line no-undef
|
|
316
318
|
return expect?.getState()?.currentTestName;
|
|
317
319
|
} catch (e) {
|
|
318
320
|
return null;
|
|
@@ -320,7 +322,52 @@ const testRunnerHelper = {
|
|
|
320
322
|
},
|
|
321
323
|
};
|
|
322
324
|
|
|
325
|
+
function storeRunId(runId) {
|
|
326
|
+
if (!runId || runId === 'undefined') return;
|
|
327
|
+
const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
|
|
328
|
+
fs.writeFileSync(filePath, runId);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function readLatestRunId() {
|
|
332
|
+
try {
|
|
333
|
+
const filePath = path.join(os.tmpdir(), `testomatio.latest.run`);
|
|
334
|
+
const stats = fs.statSync(filePath);
|
|
335
|
+
const diff = +new Date() - +stats.mtime;
|
|
336
|
+
const diffHours = diff / 1000 / 60 / 60;
|
|
337
|
+
if (diffHours > 1) return;
|
|
338
|
+
|
|
339
|
+
return fs.readFileSync(filePath)?.toString()?.trim();
|
|
340
|
+
} catch (e) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function formatStep(step, shift = 0) {
|
|
346
|
+
const prefix = ' '.repeat(shift);
|
|
347
|
+
|
|
348
|
+
const lines = [];
|
|
349
|
+
|
|
350
|
+
if (step.error) {
|
|
351
|
+
lines.push(`${prefix}${pc.red(step.title)} ${pc.gray(`${step.duration}ms`)}`);
|
|
352
|
+
} else {
|
|
353
|
+
lines.push(`${prefix}${step.title} ${pc.gray(`${step.duration}ms`)}`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
for (const child of step.steps || []) {
|
|
357
|
+
lines.push(...formatStep(child, shift + 2));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return lines;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function getPackageVersion() {
|
|
364
|
+
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
|
365
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
366
|
+
return packageJson.version;
|
|
367
|
+
}
|
|
368
|
+
|
|
323
369
|
export {
|
|
370
|
+
ansiRegExp,
|
|
324
371
|
isSameTest,
|
|
325
372
|
fetchSourceCode,
|
|
326
373
|
fetchSourceCodeFromStackTrace,
|
|
@@ -328,14 +375,16 @@ export {
|
|
|
328
375
|
fetchIdFromOutput,
|
|
329
376
|
fetchFilesFromStackTrace,
|
|
330
377
|
fileSystem,
|
|
378
|
+
foundedTestLog,
|
|
379
|
+
formatStep,
|
|
331
380
|
getCurrentDateTime,
|
|
332
|
-
specificTestInfo,
|
|
333
|
-
isValidUrl,
|
|
334
|
-
ansiRegExp,
|
|
335
381
|
getTestomatIdFromTestTitle,
|
|
336
|
-
parseSuite,
|
|
337
382
|
humanize,
|
|
383
|
+
isValidUrl,
|
|
384
|
+
parseSuite,
|
|
385
|
+
readLatestRunId,
|
|
338
386
|
removeColorCodes,
|
|
339
|
-
|
|
387
|
+
specificTestInfo,
|
|
388
|
+
storeRunId,
|
|
340
389
|
testRunnerHelper,
|
|
341
390
|
};
|