@testomatio/reporter 2.0.1-beta.3 → 2.0.1-beta.5-timestamp
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.js +335 -293
- package/lib/adapter/cucumber/current.js +203 -195
- package/lib/adapter/cucumber/legacy.js +155 -130
- package/lib/adapter/cucumber.js +16 -5
- package/lib/adapter/cypress-plugin/index.js +105 -91
- package/lib/adapter/jasmine.js +53 -54
- package/lib/adapter/jest.js +99 -97
- package/lib/adapter/mocha.js +141 -112
- package/lib/adapter/playwright.js +231 -199
- package/lib/adapter/vitest.js +149 -150
- package/lib/adapter/webdriver.js +121 -144
- package/lib/bin/cli.js +211 -229
- package/lib/bin/reportXml.js +52 -51
- package/lib/bin/startTest.js +95 -83
- package/lib/bin/uploadArtifacts.js +61 -56
- package/lib/client.js +465 -424
- package/lib/config.js +23 -18
- package/lib/constants.js +44 -50
- package/lib/data-storage.js +188 -216
- package/lib/junit-adapter/adapter.js +20 -17
- package/lib/junit-adapter/csharp.js +14 -28
- package/lib/junit-adapter/index.js +25 -27
- package/lib/junit-adapter/java.js +53 -41
- package/lib/junit-adapter/javascript.js +27 -30
- package/lib/junit-adapter/python.js +37 -38
- package/lib/junit-adapter/ruby.js +8 -11
- package/lib/output.js +52 -44
- package/lib/pipe/bitbucket.js +230 -223
- package/lib/pipe/csv.js +126 -113
- package/lib/pipe/debug.js +99 -118
- package/lib/pipe/github.js +213 -218
- package/lib/pipe/gitlab.js +206 -183
- package/lib/pipe/html.js +321 -258
- package/lib/pipe/index.js +66 -94
- package/lib/pipe/testomatio.js +474 -429
- package/lib/reporter-functions.js +26 -28
- package/lib/reporter.js +29 -34
- package/lib/services/artifacts.js +51 -55
- package/lib/services/index.js +12 -14
- package/lib/services/key-values.js +53 -56
- package/lib/services/logger.js +245 -226
- package/lib/template/testomatio.hbs +1366 -1026
- package/lib/uploader.js +364 -295
- package/lib/utils/pipe_utils.js +85 -89
- package/lib/utils/utils.js +307 -398
- package/lib/xmlReader.js +532 -525
- package/package.json +21 -64
- package/lib/adapter/codecept.d.ts +0 -2
- package/lib/adapter/cucumber/current.d.ts +0 -14
- package/lib/adapter/cucumber/legacy.d.ts +0 -0
- package/lib/adapter/cucumber.d.ts +0 -2
- package/lib/adapter/cypress-plugin/index.d.ts +0 -2
- package/lib/adapter/jasmine.d.ts +0 -11
- package/lib/adapter/jest.d.ts +0 -13
- package/lib/adapter/mocha.d.ts +0 -2
- package/lib/adapter/nightwatch.d.ts +0 -4
- package/lib/adapter/nightwatch.js +0 -80
- package/lib/adapter/playwright.d.ts +0 -14
- package/lib/adapter/vitest.d.ts +0 -35
- package/lib/adapter/webdriver.d.ts +0 -24
- package/lib/bin/cli.d.ts +0 -2
- package/lib/bin/reportXml.d.ts +0 -2
- package/lib/bin/startTest.d.ts +0 -2
- package/lib/bin/uploadArtifacts.d.ts +0 -2
- package/lib/client.d.ts +0 -76
- package/lib/config.d.ts +0 -1
- package/lib/constants.d.ts +0 -25
- package/lib/data-storage.d.ts +0 -34
- package/lib/junit-adapter/adapter.d.ts +0 -9
- package/lib/junit-adapter/csharp.d.ts +0 -5
- package/lib/junit-adapter/index.d.ts +0 -3
- package/lib/junit-adapter/java.d.ts +0 -5
- package/lib/junit-adapter/javascript.d.ts +0 -4
- package/lib/junit-adapter/python.d.ts +0 -5
- package/lib/junit-adapter/ruby.d.ts +0 -4
- package/lib/output.d.ts +0 -11
- package/lib/package.json +0 -3
- package/lib/pipe/bitbucket.d.ts +0 -25
- package/lib/pipe/csv.d.ts +0 -47
- package/lib/pipe/debug.d.ts +0 -29
- package/lib/pipe/github.d.ts +0 -30
- package/lib/pipe/gitlab.d.ts +0 -25
- package/lib/pipe/html.d.ts +0 -35
- package/lib/pipe/index.d.ts +0 -1
- package/lib/pipe/testomatio.d.ts +0 -71
- package/lib/replay.d.ts +0 -31
- package/lib/replay.js +0 -237
- package/lib/reporter-functions.d.ts +0 -34
- package/lib/reporter.d.ts +0 -232
- package/lib/services/artifacts.d.ts +0 -33
- package/lib/services/index.d.ts +0 -9
- package/lib/services/key-values.d.ts +0 -27
- package/lib/services/logger.d.ts +0 -64
- package/lib/uploader.d.ts +0 -60
- package/lib/utils/pipe_utils.d.ts +0 -41
- package/lib/utils/utils.d.ts +0 -54
- package/lib/xmlReader.d.ts +0 -92
- package/src/adapter/codecept.js +0 -373
- package/src/adapter/cucumber/current.js +0 -228
- package/src/adapter/cucumber/legacy.js +0 -158
- package/src/adapter/cucumber.js +0 -4
- package/src/adapter/cypress-plugin/index.js +0 -110
- package/src/adapter/jasmine.js +0 -60
- package/src/adapter/jest.js +0 -107
- package/src/adapter/mocha.cjs +0 -2
- package/src/adapter/mocha.js +0 -156
- package/src/adapter/nightwatch.js +0 -88
- package/src/adapter/playwright.js +0 -254
- package/src/adapter/vitest.js +0 -183
- package/src/adapter/webdriver.js +0 -142
- package/src/bin/cli.js +0 -348
- package/src/bin/reportXml.js +0 -77
- package/src/bin/startTest.js +0 -124
- package/src/bin/uploadArtifacts.js +0 -91
- package/src/client.js +0 -508
- package/src/config.js +0 -30
- package/src/constants.js +0 -53
- package/src/data-storage.js +0 -204
- package/src/junit-adapter/adapter.js +0 -23
- package/src/junit-adapter/csharp.js +0 -28
- package/src/junit-adapter/index.js +0 -28
- package/src/junit-adapter/java.js +0 -58
- package/src/junit-adapter/javascript.js +0 -31
- package/src/junit-adapter/python.js +0 -42
- package/src/junit-adapter/ruby.js +0 -10
- package/src/output.js +0 -57
- package/src/pipe/bitbucket.js +0 -252
- package/src/pipe/csv.js +0 -140
- package/src/pipe/debug.js +0 -119
- package/src/pipe/github.js +0 -232
- package/src/pipe/gitlab.js +0 -247
- package/src/pipe/html.js +0 -373
- package/src/pipe/index.js +0 -71
- package/src/pipe/testomatio.js +0 -504
- package/src/replay.js +0 -245
- package/src/reporter-functions.js +0 -55
- package/src/reporter.cjs_decprecated +0 -21
- package/src/reporter.js +0 -33
- package/src/services/artifacts.js +0 -59
- package/src/services/index.js +0 -13
- package/src/services/key-values.js +0 -59
- package/src/services/logger.js +0 -315
- package/src/template/emptyData.svg +0 -23
- package/src/template/testomatio.hbs +0 -1081
- package/src/uploader.js +0 -376
- package/src/utils/pipe_utils.js +0 -119
- package/src/utils/utils.js +0 -416
- package/src/xmlReader.js +0 -614
package/lib/uploader.js
CHANGED
|
@@ -1,316 +1,385 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
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');
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:uploader');
|
|
2
|
+
const { S3 } = require('@aws-sdk/client-s3');
|
|
3
|
+
const { Upload } = require('@aws-sdk/lib-storage');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const promiseRetry = require('promise-retry');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
const { APP_PREFIX } = require('./constants');
|
|
10
|
+
const { filesize: prettyBytes } = require('filesize');
|
|
11
|
+
|
|
18
12
|
class S3Uploader {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
}
|
|
13
|
+
constructor() {
|
|
14
|
+
this.isEnabled = undefined;
|
|
15
|
+
this.storeEnabled = true;
|
|
16
|
+
this.config = undefined;
|
|
17
|
+
|
|
18
|
+
// counters
|
|
49
19
|
/**
|
|
50
|
-
*
|
|
51
|
-
* @returns {Record<string, string>}
|
|
20
|
+
* @type {{path: string, size: number}[]}
|
|
52
21
|
*/
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
22
|
+
this.skippedUploads = [];
|
|
23
|
+
this.failedUploads = [];
|
|
24
|
+
/**
|
|
25
|
+
* @type {{path: string, size: number, link: string}[]}
|
|
26
|
+
*/
|
|
27
|
+
this.successfulUploads = [];
|
|
28
|
+
|
|
29
|
+
this.configKeys = [
|
|
30
|
+
'S3_ENDPOINT',
|
|
31
|
+
'S3_REGION',
|
|
32
|
+
'S3_BUCKET',
|
|
33
|
+
'S3_ACCESS_KEY_ID',
|
|
34
|
+
'S3_SECRET_ACCESS_KEY',
|
|
35
|
+
'S3_SESSION_TOKEN',
|
|
36
|
+
'S3_FORCE_PATH_STYLE',
|
|
37
|
+
'TESTOMATIO_DISABLE_ARTIFACTS',
|
|
38
|
+
'TESTOMATIO_PRIVATE_ARTIFACTS',
|
|
39
|
+
'TESTOMATIO_ARTIFACT_MAX_SIZE_MB',
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
resetConfig() {
|
|
44
|
+
this.config = undefined;
|
|
45
|
+
this.isEnabled = undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getConfig() {
|
|
49
|
+
if (this.config) return this.config;
|
|
50
|
+
this.config = this.configKeys.reduce((acc, key) => {
|
|
51
|
+
acc[key] = process.env[key];
|
|
52
|
+
return acc;
|
|
53
|
+
}, {});
|
|
54
|
+
return this.config;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getMaskedConfig() {
|
|
58
|
+
return Object.fromEntries(
|
|
59
|
+
Object.entries(this.getConfig()).map(([key, value]) => [
|
|
60
|
+
key,
|
|
61
|
+
key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
|
|
62
|
+
]),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
checkEnabled() {
|
|
67
|
+
if (this.isEnabled !== undefined) return this.isEnabled;
|
|
68
|
+
|
|
69
|
+
const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = this.getConfig();
|
|
70
|
+
if (!S3_BUCKET) debug(`Artifacts uploading is disabled because S3_BUCKET is not set`);
|
|
71
|
+
this.isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
|
|
72
|
+
|
|
73
|
+
if (this.isEnabled) debug('S3 uploader is enabled');
|
|
74
|
+
debug(this.getMaskedConfig());
|
|
75
|
+
|
|
76
|
+
return this.isEnabled;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
enableLogStorage() {
|
|
80
|
+
this.storeEnabled = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
disableLogStorage() {
|
|
84
|
+
this.storeEnabled = false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
*
|
|
89
|
+
* @param {*} Body
|
|
90
|
+
* @param {*} Key
|
|
91
|
+
* @param {{path: string, size?: number}} file
|
|
92
|
+
* @returns
|
|
93
|
+
*/
|
|
94
|
+
async #uploadToS3(Body, Key, file) {
|
|
95
|
+
const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
|
|
96
|
+
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
97
|
+
|
|
98
|
+
if (!S3_BUCKET || !Body) {
|
|
99
|
+
console.log(
|
|
100
|
+
APP_PREFIX,
|
|
101
|
+
chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`),
|
|
102
|
+
this.getMaskedConfig(),
|
|
103
|
+
);
|
|
104
|
+
return;
|
|
61
105
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
106
|
+
|
|
107
|
+
debug('Uploading to S3:', Key);
|
|
108
|
+
|
|
109
|
+
const s3Config = this.#getS3Config();
|
|
110
|
+
const s3 = new S3(s3Config);
|
|
111
|
+
|
|
112
|
+
const params = {
|
|
113
|
+
Bucket: S3_BUCKET,
|
|
114
|
+
Key,
|
|
115
|
+
Body,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// disable ACL for I AM roles
|
|
119
|
+
if (!s3Config.credentials.sessionToken) {
|
|
120
|
+
params.ACL = ACL;
|
|
67
121
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const upload = new Upload({
|
|
125
|
+
client: s3,
|
|
126
|
+
params,
|
|
127
|
+
});
|
|
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 || 0)}, 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());
|
|
79
137
|
}
|
|
80
|
-
|
|
81
|
-
|
|
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 [];
|
|
82
153
|
}
|
|
83
|
-
|
|
84
|
-
|
|
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 [];
|
|
85
165
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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 || 0)}, 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
|
-
}
|
|
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, '');
|
|
124
178
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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));
|
|
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;
|
|
152
191
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
debug('Creating artifacts file:', tempFilePath);
|
|
157
|
-
fs_1.default.writeFileSync(tempFilePath, '');
|
|
158
|
-
}
|
|
159
|
-
return tempFilePath;
|
|
192
|
+
|
|
193
|
+
if (typeof filePath === 'string' && !path.isAbsolute(filePath)) {
|
|
194
|
+
filePath = path.join(process.cwd(), filePath);
|
|
160
195
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
196
|
+
|
|
197
|
+
const data = { rid, file: filePath, uploaded };
|
|
198
|
+
const jsonLine = `${JSON.stringify(data)}\n`;
|
|
199
|
+
fs.appendFileSync(tempFilePath, jsonLine);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
*
|
|
204
|
+
* @param {*} filePath
|
|
205
|
+
* @param {*} pathInS3 contains runId, rid and filename
|
|
206
|
+
* @returns
|
|
207
|
+
*/
|
|
208
|
+
async uploadFileByPath(filePath, pathInS3) {
|
|
209
|
+
/* WDIO: some artifacts uploading started before createRun function completion
|
|
210
|
+
probably, the reason is that run is NOT created in adapter (but via cli) */
|
|
211
|
+
this.isEnabled = this.isEnabled ?? this.checkEnabled();
|
|
212
|
+
|
|
213
|
+
const [runId, rid] = pathInS3;
|
|
214
|
+
|
|
215
|
+
if (!filePath) return;
|
|
216
|
+
|
|
217
|
+
let fileSize = null;
|
|
218
|
+
let fileSizeInMb = null;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// file may not exist
|
|
222
|
+
fileSize = fs.statSync(filePath).size || 0;
|
|
223
|
+
fileSizeInMb = Number((fileSize / (1024 * 1024)).toFixed(2));
|
|
224
|
+
} catch (e) {
|
|
225
|
+
debug(`File ${filePath} does not exist`);
|
|
176
226
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
async uploadFileByPath(filePath, pathInS3) {
|
|
183
|
-
/* WDIO: some artifacts uploading started before createRun function completion
|
|
184
|
-
probably, the reason is that run is NOT created in adapter (but via cli) */
|
|
185
|
-
this.isEnabled = this.isEnabled ?? this.checkEnabled();
|
|
186
|
-
const [runId, rid] = pathInS3;
|
|
187
|
-
if (!filePath)
|
|
188
|
-
return;
|
|
189
|
-
let fileSize = null;
|
|
190
|
-
let fileSizeInMb = null;
|
|
191
|
-
try {
|
|
192
|
-
// file may not exist
|
|
193
|
-
fileSize = fs_1.default.statSync(filePath).size || 0;
|
|
194
|
-
fileSizeInMb = Number((fileSize / (1024 * 1024)).toFixed(2));
|
|
195
|
-
}
|
|
196
|
-
catch (e) {
|
|
197
|
-
debug(`File ${filePath} does not exist`);
|
|
198
|
-
}
|
|
199
|
-
if (!this.isEnabled) {
|
|
200
|
-
this.storeUploadedFile(filePath, runId, rid, false);
|
|
201
|
-
this.skippedUploads.push({ path: filePath, size: fileSize });
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
const { S3_BUCKET, TESTOMATIO_ARTIFACT_MAX_SIZE_MB } = this.getConfig();
|
|
205
|
-
debug('Started upload', filePath, 'to', S3_BUCKET);
|
|
206
|
-
const isFileExist = await this.checkArtifactExistsInFileSystem(filePath, 20, 500);
|
|
207
|
-
if (!isFileExist) {
|
|
208
|
-
console.error(picocolors_1.default.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
// skipping artifact only if: 1. storing to file is enabled, 2. max size is set and 3. file size exceeds the limit
|
|
212
|
-
if (this.storeEnabled &&
|
|
213
|
-
TESTOMATIO_ARTIFACT_MAX_SIZE_MB &&
|
|
214
|
-
fileSizeInMb > parseFloat(TESTOMATIO_ARTIFACT_MAX_SIZE_MB)) {
|
|
215
|
-
const skippedArtifact = { path: filePath, size: fileSize };
|
|
216
|
-
this.storeUploadedFile(filePath, runId, rid, false);
|
|
217
|
-
this.skippedUploads.push(skippedArtifact);
|
|
218
|
-
debug(picocolors_1.default.yellow(`Artifacts file ${JSON.stringify(skippedArtifact)} exceeds the maximum allowed size. Skipping.`));
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
debug('File:', filePath, 'exists, size:', (0, filesize_1.filesize)(fileSize));
|
|
222
|
-
const fileStream = fs_1.default.createReadStream(filePath);
|
|
223
|
-
const Key = pathInS3.filter(p => !!p).join('/');
|
|
224
|
-
const link = await this.#uploadToS3(fileStream, Key, { path: filePath, size: fileSize });
|
|
225
|
-
this.storeUploadedFile(filePath, runId, rid, !!link);
|
|
226
|
-
return link;
|
|
227
|
+
|
|
228
|
+
if (!this.isEnabled) {
|
|
229
|
+
this.storeUploadedFile(filePath, runId, rid, false);
|
|
230
|
+
this.skippedUploads.push({ path: filePath, size: fileSize });
|
|
231
|
+
return;
|
|
227
232
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return;
|
|
239
|
-
let Key = pathInS3.filter(p => !!p).join('/');
|
|
240
|
-
const ext = this.#getFileExtBase64(buffer.toString('base64'));
|
|
241
|
-
if (ext) {
|
|
242
|
-
Key = `${Key}.${ext}`;
|
|
243
|
-
}
|
|
244
|
-
return this.#uploadToS3(buffer, Key, { path: Key });
|
|
233
|
+
|
|
234
|
+
const { S3_BUCKET, TESTOMATIO_ARTIFACT_MAX_SIZE_MB } = this.getConfig();
|
|
235
|
+
|
|
236
|
+
debug('Started upload', filePath, 'to', S3_BUCKET);
|
|
237
|
+
|
|
238
|
+
const isFileExist = await this.checkArtifactExistsInFileSystem(filePath, 20, 500);
|
|
239
|
+
|
|
240
|
+
if (!isFileExist) {
|
|
241
|
+
console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
242
|
+
return;
|
|
245
243
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
minTimeout: intervalMs,
|
|
265
|
-
maxTimeout: intervalMs,
|
|
266
|
-
});
|
|
244
|
+
|
|
245
|
+
// skipping artifact only if: 1. storing to file is enabled, 2. max size is set and 3. file size exceeds the limit
|
|
246
|
+
if (
|
|
247
|
+
this.storeEnabled &&
|
|
248
|
+
TESTOMATIO_ARTIFACT_MAX_SIZE_MB &&
|
|
249
|
+
fileSizeInMb > parseFloat(TESTOMATIO_ARTIFACT_MAX_SIZE_MB)
|
|
250
|
+
) {
|
|
251
|
+
const skippedArtifact = { path: filePath, size: fileSize };
|
|
252
|
+
this.storeUploadedFile(filePath, runId, rid, false);
|
|
253
|
+
this.skippedUploads.push(skippedArtifact);
|
|
254
|
+
debug(
|
|
255
|
+
chalk.yellow(
|
|
256
|
+
`Artifacts file ${JSON.stringify(skippedArtifact)} exceeds the maximum allowed size ${
|
|
257
|
+
TESTOMATIO_ARTIFACT_MAX_SIZE_MB
|
|
258
|
+
}MB. Skipping.`,
|
|
259
|
+
),
|
|
260
|
+
);
|
|
261
|
+
return;
|
|
267
262
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
263
|
+
debug('File:', filePath, 'exists, size:', prettyBytes(fileSize));
|
|
264
|
+
|
|
265
|
+
const fileStream = fs.createReadStream(filePath);
|
|
266
|
+
const Key = pathInS3.filter(p => !!p).join('/');
|
|
267
|
+
|
|
268
|
+
const link = await this.#uploadToS3(fileStream, Key, { path: filePath, size: fileSize });
|
|
269
|
+
|
|
270
|
+
this.storeUploadedFile(filePath, runId, rid, !!link);
|
|
271
|
+
|
|
272
|
+
return link;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @param {Buffer} buffer
|
|
277
|
+
* @param {string[]} pathInS3
|
|
278
|
+
* @returns
|
|
279
|
+
*/
|
|
280
|
+
async uploadFileAsBuffer(buffer, pathInS3) {
|
|
281
|
+
/* WDIO: some artifacts uploading started before createRun function completion
|
|
282
|
+
probably, the reason is that run is NOT created in adapter (but via cli) */
|
|
283
|
+
|
|
284
|
+
this.isEnabled = this.isEnabled ?? this.checkEnabled();
|
|
285
|
+
if (!this.isEnabled) return;
|
|
286
|
+
|
|
287
|
+
let Key = pathInS3.filter(p => !!p).join('/');
|
|
288
|
+
const ext = this.#getFileExtBase64(buffer.toString('base64'));
|
|
289
|
+
|
|
290
|
+
if (ext) {
|
|
291
|
+
Key = `${Key}.${ext}`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return this.#uploadToS3(buffer, Key, { path: Key });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async checkArtifactExistsInFileSystem(filePath, attempts = 5, intervalMs = 500) {
|
|
298
|
+
return promiseRetry(
|
|
299
|
+
async (retry, number) => {
|
|
300
|
+
try {
|
|
301
|
+
fs.accessSync(filePath);
|
|
302
|
+
return true;
|
|
303
|
+
} catch (err) {
|
|
304
|
+
if (number === attempts) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
debug(`File not found, retrying (attempt ${number}/${attempts})`);
|
|
308
|
+
await new Promise(resolve => {
|
|
309
|
+
setTimeout(resolve, intervalMs);
|
|
310
|
+
});
|
|
311
|
+
retry(err);
|
|
281
312
|
}
|
|
282
|
-
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
retries: attempts,
|
|
316
|
+
minTimeout: intervalMs,
|
|
317
|
+
maxTimeout: intervalMs,
|
|
318
|
+
},
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async getS3LocationLink(out) {
|
|
323
|
+
const response = await out.done();
|
|
324
|
+
|
|
325
|
+
let s3Location = response?.Location?.trim();
|
|
326
|
+
|
|
327
|
+
if (!s3Location) {
|
|
328
|
+
s3Location = out?.singleUploadResult?.Location;
|
|
329
|
+
debug('Uploaded singleUploadResult.Location', s3Location);
|
|
330
|
+
|
|
331
|
+
if (!s3Location) {
|
|
332
|
+
throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
|
|
333
|
+
}
|
|
283
334
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
i: 'png',
|
|
289
|
-
R: 'gif',
|
|
290
|
-
U: 'webp',
|
|
291
|
-
}[type] || '');
|
|
335
|
+
|
|
336
|
+
// Normalize the URL
|
|
337
|
+
if (!s3Location.startsWith('http')) {
|
|
338
|
+
s3Location = `https://${s3Location}`;
|
|
292
339
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
340
|
+
|
|
341
|
+
return s3Location;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
#getFileExtBase64(str) {
|
|
345
|
+
const type = str.charAt(0);
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
{
|
|
349
|
+
'/': 'jpg',
|
|
350
|
+
i: 'png',
|
|
351
|
+
R: 'gif',
|
|
352
|
+
U: 'webp',
|
|
353
|
+
}[type] || ''
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#getS3Config() {
|
|
358
|
+
const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
|
|
359
|
+
this.getConfig();
|
|
360
|
+
|
|
361
|
+
const cfg = {
|
|
362
|
+
region: S3_REGION,
|
|
363
|
+
credentials: {
|
|
364
|
+
accessKeyId: S3_ACCESS_KEY_ID,
|
|
365
|
+
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
if (S3_FORCE_PATH_STYLE) {
|
|
370
|
+
cfg.forcePathStyle = !['false', '0'].includes(String(S3_FORCE_PATH_STYLE || '').toLowerCase());
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (S3_SESSION_TOKEN) {
|
|
374
|
+
cfg.credentials.sessionToken = S3_SESSION_TOKEN;
|
|
312
375
|
}
|
|
376
|
+
|
|
377
|
+
if (S3_ENDPOINT) {
|
|
378
|
+
cfg.endpoint = S3_ENDPOINT;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return cfg;
|
|
382
|
+
}
|
|
313
383
|
}
|
|
314
|
-
exports.S3Uploader = S3Uploader;
|
|
315
384
|
|
|
316
|
-
module.exports
|
|
385
|
+
module.exports = S3Uploader;
|