@testomatio/reporter 2.0.1-beta.5-timestamp → 2.0.1-beta.6
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 +1 -0
- package/lib/adapter/codecept.d.ts +2 -0
- package/lib/adapter/codecept.js +293 -335
- package/lib/adapter/cucumber/current.d.ts +14 -0
- package/lib/adapter/cucumber/current.js +195 -203
- package/lib/adapter/cucumber/legacy.d.ts +0 -0
- package/lib/adapter/cucumber/legacy.js +130 -155
- package/lib/adapter/cucumber.d.ts +2 -0
- package/lib/adapter/cucumber.js +5 -16
- package/lib/adapter/cypress-plugin/index.d.ts +2 -0
- package/lib/adapter/cypress-plugin/index.js +91 -105
- package/lib/adapter/jasmine.d.ts +11 -0
- package/lib/adapter/jasmine.js +54 -53
- package/lib/adapter/jest.d.ts +13 -0
- package/lib/adapter/jest.js +97 -99
- package/lib/adapter/mocha.d.ts +2 -0
- package/lib/adapter/mocha.js +112 -141
- 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 +199 -231
- package/lib/adapter/vitest.d.ts +35 -0
- package/lib/adapter/vitest.js +150 -149
- package/lib/adapter/webdriver.d.ts +24 -0
- package/lib/adapter/webdriver.js +144 -121
- package/lib/bin/cli.d.ts +2 -0
- package/lib/bin/cli.js +229 -211
- package/lib/bin/reportXml.d.ts +2 -0
- package/lib/bin/reportXml.js +51 -52
- package/lib/bin/startTest.d.ts +2 -0
- package/lib/bin/startTest.js +83 -95
- package/lib/bin/uploadArtifacts.d.ts +2 -0
- package/lib/bin/uploadArtifacts.js +56 -61
- package/lib/client.d.ts +76 -0
- package/lib/client.js +429 -465
- package/lib/config.d.ts +1 -0
- package/lib/config.js +18 -23
- package/lib/constants.d.ts +25 -0
- package/lib/constants.js +50 -44
- package/lib/data-storage.d.ts +34 -0
- package/lib/data-storage.js +216 -188
- package/lib/junit-adapter/adapter.d.ts +9 -0
- package/lib/junit-adapter/adapter.js +17 -20
- package/lib/junit-adapter/csharp.d.ts +5 -0
- package/lib/junit-adapter/csharp.js +28 -14
- package/lib/junit-adapter/index.d.ts +3 -0
- package/lib/junit-adapter/index.js +27 -25
- package/lib/junit-adapter/java.d.ts +5 -0
- package/lib/junit-adapter/java.js +41 -53
- package/lib/junit-adapter/javascript.d.ts +4 -0
- package/lib/junit-adapter/javascript.js +30 -27
- package/lib/junit-adapter/python.d.ts +5 -0
- package/lib/junit-adapter/python.js +38 -37
- package/lib/junit-adapter/ruby.d.ts +4 -0
- package/lib/junit-adapter/ruby.js +11 -8
- package/lib/output.d.ts +11 -0
- package/lib/output.js +44 -52
- package/lib/package.json +3 -0
- package/lib/pipe/bitbucket.d.ts +25 -0
- package/lib/pipe/bitbucket.js +223 -230
- package/lib/pipe/csv.d.ts +47 -0
- package/lib/pipe/csv.js +113 -126
- package/lib/pipe/debug.d.ts +29 -0
- package/lib/pipe/debug.js +125 -99
- package/lib/pipe/github.d.ts +30 -0
- package/lib/pipe/github.js +218 -213
- package/lib/pipe/gitlab.d.ts +25 -0
- package/lib/pipe/gitlab.js +183 -206
- package/lib/pipe/html.d.ts +35 -0
- package/lib/pipe/html.js +258 -321
- package/lib/pipe/index.d.ts +1 -0
- package/lib/pipe/index.js +94 -66
- package/lib/pipe/testomatio.d.ts +71 -0
- package/lib/pipe/testomatio.js +429 -474
- package/lib/replay.d.ts +31 -0
- package/lib/replay.js +255 -0
- package/lib/reporter-functions.d.ts +34 -0
- package/lib/reporter-functions.js +28 -26
- package/lib/reporter.d.ts +232 -0
- package/lib/reporter.js +34 -29
- package/lib/services/artifacts.d.ts +33 -0
- package/lib/services/artifacts.js +55 -51
- package/lib/services/index.d.ts +9 -0
- package/lib/services/index.js +14 -12
- package/lib/services/key-values.d.ts +27 -0
- package/lib/services/key-values.js +56 -53
- package/lib/services/logger.d.ts +64 -0
- package/lib/services/logger.js +226 -245
- package/lib/template/testomatio.hbs +1026 -1366
- package/lib/uploader.d.ts +60 -0
- package/lib/uploader.js +295 -364
- package/lib/utils/pipe_utils.d.ts +41 -0
- package/lib/utils/pipe_utils.js +89 -85
- package/lib/utils/utils.d.ts +54 -0
- package/lib/utils/utils.js +398 -307
- package/lib/xmlReader.d.ts +92 -0
- package/lib/xmlReader.js +525 -532
- package/package.json +64 -21
- package/src/adapter/codecept.js +373 -0
- package/src/adapter/cucumber/current.js +228 -0
- package/src/adapter/cucumber/legacy.js +158 -0
- package/src/adapter/cucumber.js +4 -0
- package/src/adapter/cypress-plugin/index.js +110 -0
- package/src/adapter/jasmine.js +60 -0
- package/src/adapter/jest.js +107 -0
- package/src/adapter/mocha.cjs +2 -0
- package/src/adapter/mocha.js +156 -0
- package/src/adapter/nightwatch.js +88 -0
- package/src/adapter/playwright.js +254 -0
- package/src/adapter/vitest.js +183 -0
- package/src/adapter/webdriver.js +142 -0
- package/src/bin/cli.js +348 -0
- package/src/bin/reportXml.js +77 -0
- package/src/bin/startTest.js +124 -0
- package/src/bin/uploadArtifacts.js +91 -0
- package/src/client.js +515 -0
- package/src/config.js +30 -0
- package/src/constants.js +53 -0
- package/src/data-storage.js +204 -0
- package/src/junit-adapter/adapter.js +23 -0
- package/src/junit-adapter/csharp.js +28 -0
- package/src/junit-adapter/index.js +28 -0
- package/src/junit-adapter/java.js +58 -0
- package/src/junit-adapter/javascript.js +31 -0
- package/src/junit-adapter/python.js +42 -0
- package/src/junit-adapter/ruby.js +10 -0
- package/src/output.js +57 -0
- package/src/pipe/bitbucket.js +252 -0
- package/src/pipe/csv.js +140 -0
- package/src/pipe/debug.js +125 -0
- package/src/pipe/github.js +232 -0
- package/src/pipe/gitlab.js +247 -0
- package/src/pipe/html.js +373 -0
- package/src/pipe/index.js +71 -0
- package/src/pipe/testomatio.js +504 -0
- package/src/replay.js +262 -0
- package/src/reporter-functions.js +55 -0
- package/src/reporter.cjs_decprecated +21 -0
- package/src/reporter.js +33 -0
- package/src/services/artifacts.js +59 -0
- package/src/services/index.js +13 -0
- package/src/services/key-values.js +59 -0
- package/src/services/logger.js +315 -0
- package/src/template/emptyData.svg +23 -0
- package/src/template/testomatio.hbs +1081 -0
- package/src/uploader.js +376 -0
- package/src/utils/pipe_utils.js +119 -0
- package/src/utils/utils.js +416 -0
- package/src/xmlReader.js +614 -0
package/lib/uploader.js
CHANGED
|
@@ -1,385 +1,316 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
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');
|
|
12
18
|
class S3Uploader {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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;
|
|
105
|
-
}
|
|
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;
|
|
121
|
-
}
|
|
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());
|
|
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
|
+
];
|
|
137
44
|
}
|
|
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 [];
|
|
45
|
+
resetConfig() {
|
|
46
|
+
this.config = undefined;
|
|
47
|
+
this.isEnabled = undefined;
|
|
153
48
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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;
|
|
165
61
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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, '');
|
|
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
|
+
]));
|
|
178
67
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
filePath = filePath.path;
|
|
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;
|
|
191
79
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
filePath = path.join(process.cwd(), filePath);
|
|
80
|
+
enableLogStorage() {
|
|
81
|
+
this.storeEnabled = true;
|
|
195
82
|
}
|
|
196
|
-
|
|
197
|
-
|
|
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`);
|
|
83
|
+
disableLogStorage() {
|
|
84
|
+
this.storeEnabled = false;
|
|
226
85
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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 || 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
|
+
}
|
|
232
124
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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));
|
|
243
152
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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;
|
|
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;
|
|
262
160
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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}`;
|
|
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);
|
|
292
176
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
177
|
+
/**
|
|
178
|
+
* @param {*} filePath
|
|
179
|
+
* @param {*} pathInS3 contains runId, rid and filename
|
|
180
|
+
* @returns
|
|
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;
|
|
300
191
|
try {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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);
|
|
192
|
+
// file may not exist
|
|
193
|
+
fileSize = fs_1.default.statSync(filePath).size || 0;
|
|
194
|
+
fileSizeInMb = Number((fileSize / (1024 * 1024)).toFixed(2));
|
|
312
195
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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;
|
|
334
227
|
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
228
|
+
/**
|
|
229
|
+
* @param {Buffer} buffer
|
|
230
|
+
* @param {string[]} pathInS3
|
|
231
|
+
* @returns
|
|
232
|
+
*/
|
|
233
|
+
async uploadFileAsBuffer(buffer, pathInS3) {
|
|
234
|
+
/* WDIO: some artifacts uploading started before createRun function completion
|
|
235
|
+
probably, the reason is that run is NOT created in adapter (but via cli) */
|
|
236
|
+
this.isEnabled = this.isEnabled ?? this.checkEnabled();
|
|
237
|
+
if (!this.isEnabled)
|
|
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 });
|
|
339
245
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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());
|
|
246
|
+
async checkArtifactExistsInFileSystem(filePath, attempts = 5, intervalMs = 500) {
|
|
247
|
+
return (0, promise_retry_1.default)(async (retry, number) => {
|
|
248
|
+
try {
|
|
249
|
+
fs_1.default.accessSync(filePath);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
if (number === attempts) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
debug(`File not found, retrying (attempt ${number}/${attempts})`);
|
|
257
|
+
await new Promise(resolve => {
|
|
258
|
+
setTimeout(resolve, intervalMs);
|
|
259
|
+
});
|
|
260
|
+
retry(err);
|
|
261
|
+
}
|
|
262
|
+
}, {
|
|
263
|
+
retries: attempts,
|
|
264
|
+
minTimeout: intervalMs,
|
|
265
|
+
maxTimeout: intervalMs,
|
|
266
|
+
});
|
|
371
267
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
268
|
+
async getS3LocationLink(out) {
|
|
269
|
+
const response = await out.done();
|
|
270
|
+
let s3Location = response?.Location?.trim();
|
|
271
|
+
if (!s3Location) {
|
|
272
|
+
s3Location = out?.singleUploadResult?.Location;
|
|
273
|
+
debug('Uploaded singleUploadResult.Location', s3Location);
|
|
274
|
+
if (!s3Location) {
|
|
275
|
+
throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Normalize the URL
|
|
279
|
+
if (!s3Location.startsWith('http')) {
|
|
280
|
+
s3Location = `https://${s3Location}`;
|
|
281
|
+
}
|
|
282
|
+
return s3Location;
|
|
375
283
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
284
|
+
#getFileExtBase64(str) {
|
|
285
|
+
const type = str.charAt(0);
|
|
286
|
+
return ({
|
|
287
|
+
'/': 'jpg',
|
|
288
|
+
i: 'png',
|
|
289
|
+
R: 'gif',
|
|
290
|
+
U: 'webp',
|
|
291
|
+
}[type] || '');
|
|
292
|
+
}
|
|
293
|
+
#getS3Config() {
|
|
294
|
+
const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } = this.getConfig();
|
|
295
|
+
const cfg = {
|
|
296
|
+
region: S3_REGION,
|
|
297
|
+
credentials: {
|
|
298
|
+
accessKeyId: S3_ACCESS_KEY_ID,
|
|
299
|
+
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
if (S3_FORCE_PATH_STYLE) {
|
|
303
|
+
cfg.forcePathStyle = !['false', '0'].includes(String(S3_FORCE_PATH_STYLE || '').toLowerCase());
|
|
304
|
+
}
|
|
305
|
+
if (S3_SESSION_TOKEN) {
|
|
306
|
+
cfg.credentials.sessionToken = S3_SESSION_TOKEN;
|
|
307
|
+
}
|
|
308
|
+
if (S3_ENDPOINT) {
|
|
309
|
+
cfg.endpoint = S3_ENDPOINT;
|
|
310
|
+
}
|
|
311
|
+
return cfg;
|
|
379
312
|
}
|
|
380
|
-
|
|
381
|
-
return cfg;
|
|
382
|
-
}
|
|
383
313
|
}
|
|
314
|
+
exports.S3Uploader = S3Uploader;
|
|
384
315
|
|
|
385
|
-
module.exports = S3Uploader;
|
|
316
|
+
module.exports.S3Uploader = S3Uploader;
|