@testomatio/reporter 1.6.0-beta-2-artifacts → 2.0.0-beta-esm
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 +288 -330
- package/lib/adapter/cucumber/current.js +195 -203
- package/lib/adapter/cucumber/legacy.js +130 -155
- package/lib/adapter/cucumber.js +5 -16
- package/lib/adapter/cypress-plugin/index.js +91 -105
- package/lib/adapter/jasmine/jasmine.js +63 -0
- package/lib/adapter/jasmine.js +54 -53
- package/lib/adapter/jest.js +97 -99
- package/lib/adapter/mocha/mocha.js +125 -0
- package/lib/adapter/mocha.js +111 -140
- package/lib/adapter/playwright.js +168 -200
- package/lib/adapter/vitest.js +144 -143
- package/lib/adapter/webdriver.js +113 -97
- package/lib/bin/reportXml.js +49 -49
- package/lib/bin/startTest.js +80 -97
- package/lib/client.js +344 -385
- package/lib/config.js +16 -21
- package/lib/constants.js +49 -43
- package/lib/data-storage.js +206 -188
- package/lib/fileUploader.js +245 -0
- package/lib/junit-adapter/adapter.js +17 -20
- package/lib/junit-adapter/csharp.js +18 -14
- package/lib/junit-adapter/index.js +27 -25
- package/lib/junit-adapter/java.js +41 -53
- package/lib/junit-adapter/javascript.js +30 -27
- package/lib/junit-adapter/python.js +38 -37
- package/lib/junit-adapter/ruby.js +11 -8
- package/lib/output.js +44 -52
- package/lib/package.json +1 -0
- package/lib/pipe/bitbucket.js +208 -227
- package/lib/pipe/csv.js +111 -124
- package/lib/pipe/github.js +184 -211
- package/lib/pipe/gitlab.js +164 -205
- package/lib/pipe/html.js +253 -312
- package/lib/pipe/index.js +83 -63
- package/lib/pipe/testomatio.js +391 -454
- package/lib/reporter-functions.js +16 -20
- package/lib/reporter.js +47 -17
- package/lib/services/artifacts.js +55 -51
- package/lib/services/index.js +14 -12
- package/lib/services/key-values.js +56 -53
- package/lib/services/logger.js +227 -245
- package/lib/utils/chalk.js +10 -0
- package/lib/utils/pipe_utils.js +91 -84
- package/lib/utils/utils.js +289 -273
- package/lib/xmlReader.js +480 -519
- package/package.json +57 -19
- package/src/adapter/codecept.js +369 -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/playwright.js +222 -0
- package/src/adapter/vitest.js +183 -0
- package/src/adapter/webdriver.js +111 -0
- package/src/bin/reportXml.js +67 -0
- package/src/bin/startTest.js +119 -0
- package/src/client.js +423 -0
- package/src/config.js +30 -0
- package/src/constants.js +49 -0
- package/src/data-storage.js +204 -0
- package/src/fileUploader.js +307 -0
- package/src/junit-adapter/adapter.js +23 -0
- package/src/junit-adapter/csharp.js +16 -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 +254 -0
- package/src/pipe/csv.js +140 -0
- package/src/pipe/github.js +234 -0
- package/src/pipe/gitlab.js +229 -0
- package/src/pipe/html.js +366 -0
- package/src/pipe/index.js +73 -0
- package/src/pipe/testomatio.js +498 -0
- package/src/reporter-functions.js +44 -0
- package/src/reporter.cjs +22 -0
- package/src/reporter.js +24 -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 +314 -0
- package/src/template/emptyData.svg +23 -0
- package/src/template/testomatio.hbs +1421 -0
- package/src/utils/chalk.js +13 -0
- package/src/utils/pipe_utils.js +127 -0
- package/src/utils/utils.js +341 -0
- package/src/xmlReader.js +551 -0
- package/lib/bin/cli.js +0 -216
- package/lib/bin/uploadArtifacts.js +0 -86
- package/lib/uploader.js +0 -312
package/lib/bin/cli.js
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const { program } = require('commander');
|
|
3
|
-
const spawn = require('cross-spawn');
|
|
4
|
-
const chalk = require('chalk');
|
|
5
|
-
const glob = require('glob');
|
|
6
|
-
const debug = require('debug')('@testomatio/reporter:cli');
|
|
7
|
-
const TestomatClient = require('../client');
|
|
8
|
-
const XmlReader = require('../xmlReader');
|
|
9
|
-
const { APP_PREFIX, STATUS } = require('../constants');
|
|
10
|
-
const { version } = require('../../package.json');
|
|
11
|
-
const config = require('../config');
|
|
12
|
-
|
|
13
|
-
console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
|
|
14
|
-
|
|
15
|
-
program
|
|
16
|
-
.version(version)
|
|
17
|
-
.option('--env-file <envfile>', 'Load environment variables from env file')
|
|
18
|
-
.hook('preAction', (thisCommand) => {
|
|
19
|
-
const opts = thisCommand.opts();
|
|
20
|
-
if (opts.envFile) {
|
|
21
|
-
require('dotenv').config({ path: opts.envFile });
|
|
22
|
-
} else {
|
|
23
|
-
require('dotenv').config();
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
program
|
|
28
|
-
.command('start')
|
|
29
|
-
.description('Start a new run and return its ID')
|
|
30
|
-
.action(async () => {
|
|
31
|
-
console.log('Starting a new Run on Testomat.io...');
|
|
32
|
-
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
|
|
33
|
-
const client = new TestomatClient({ apiKey });
|
|
34
|
-
|
|
35
|
-
client.createRun().then(() => {
|
|
36
|
-
console.log(process.env.runId);
|
|
37
|
-
process.exit(0);
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
program
|
|
42
|
-
.command('finish')
|
|
43
|
-
.description('Finish Run by its ID')
|
|
44
|
-
.action(async () => {
|
|
45
|
-
if (!process.env.TESTOMATIO_RUN) {
|
|
46
|
-
console.log('TESTOMATIO_RUN environment variable must be set.');
|
|
47
|
-
return process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
console.log('Finishing Run on Testomat.io...');
|
|
51
|
-
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
|
|
52
|
-
const client = new TestomatClient({ apiKey });
|
|
53
|
-
|
|
54
|
-
client.updateRunStatus(STATUS.FINISHED).then(() => {
|
|
55
|
-
console.log(chalk.yellow(`Run ${process.env.TESTOMATIO_RUN} was finished`));
|
|
56
|
-
process.exit(0);
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
program
|
|
61
|
-
.command('run')
|
|
62
|
-
.description('Run tests with the specified command')
|
|
63
|
-
.argument('<command>', 'Test runner command')
|
|
64
|
-
.option('--filter <filter>', 'Additional execution filter')
|
|
65
|
-
.action(async (command, opts) => {
|
|
66
|
-
const apiKey = process.env['INPUT_TESTOMATIO-KEY'] || config.TESTOMATIO;
|
|
67
|
-
const title = process.env.TESTOMATIO_TITLE;
|
|
68
|
-
|
|
69
|
-
if (!command || !command.split) {
|
|
70
|
-
console.log(APP_PREFIX, `No command provided. Use -c option to launch a test runner.`);
|
|
71
|
-
return process.exit(255);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const client = new TestomatClient({ apiKey, title, parallel: true });
|
|
75
|
-
|
|
76
|
-
if (opts.filter) {
|
|
77
|
-
const [pipe, ...optsArray] = opts.filter.split(':');
|
|
78
|
-
const pipeOptions = optsArray.join(':');
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
const tests = await client.prepareRun({ pipe, pipeOptions });
|
|
82
|
-
if (tests && tests.length > 0) {
|
|
83
|
-
command += ` --grep (${tests.join('|')})`;
|
|
84
|
-
}
|
|
85
|
-
} catch (err) {
|
|
86
|
-
console.log(APP_PREFIX, err);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
console.log(APP_PREFIX, `🚀 Running`, chalk.green(command));
|
|
91
|
-
|
|
92
|
-
const runTests = () => {
|
|
93
|
-
const testCmds = command.split(' ');
|
|
94
|
-
const cmd = spawn(testCmds[0], testCmds.slice(1), { stdio: 'inherit' });
|
|
95
|
-
|
|
96
|
-
cmd.on('close', code => {
|
|
97
|
-
const emoji = code === 0 ? '🟢' : '🔴';
|
|
98
|
-
console.log(APP_PREFIX, emoji, `Runner exited with ${chalk.bold(code)}`);
|
|
99
|
-
if (apiKey) {
|
|
100
|
-
const status = code === 0 ? 'passed' : 'failed';
|
|
101
|
-
client.updateRunStatus(status, true);
|
|
102
|
-
}
|
|
103
|
-
process.exit(code);
|
|
104
|
-
});
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
if (apiKey) {
|
|
108
|
-
client.createRun().then(runTests);
|
|
109
|
-
} else {
|
|
110
|
-
runTests();
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
program
|
|
115
|
-
.command('xml')
|
|
116
|
-
.description('Parse XML reports and upload to Testomat.io')
|
|
117
|
-
.argument('<pattern>', 'XML file pattern')
|
|
118
|
-
.option('-d, --dir <dir>', 'Project directory')
|
|
119
|
-
.option('--java-tests [java-path]', 'Load Java tests from path, by default: src/test/java')
|
|
120
|
-
.option('--lang <lang>', 'Language used (python, ruby, java)')
|
|
121
|
-
.option('--timelimit <time>', 'default time limit in seconds to kill a stuck process')
|
|
122
|
-
.action(async (pattern, opts) => {
|
|
123
|
-
if (!pattern.endsWith('.xml')) {
|
|
124
|
-
pattern += '.xml';
|
|
125
|
-
}
|
|
126
|
-
let { javaTests, lang } = opts;
|
|
127
|
-
if (javaTests === true) javaTests = 'src/test/java';
|
|
128
|
-
lang = lang?.toLowerCase();
|
|
129
|
-
const runReader = new XmlReader({ javaTests, lang });
|
|
130
|
-
const files = glob.sync(pattern, { cwd: opts.dir || process.cwd() });
|
|
131
|
-
if (!files.length) {
|
|
132
|
-
console.log(APP_PREFIX, `Report can't be created. No XML files found 😥`);
|
|
133
|
-
process.exit(1);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
for (const file of files) {
|
|
137
|
-
console.log(APP_PREFIX, `Parsed ${file}`);
|
|
138
|
-
runReader.parse(file);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
let timeoutTimer;
|
|
142
|
-
if (opts.timelimit) {
|
|
143
|
-
timeoutTimer = setTimeout(() => {
|
|
144
|
-
console.log(`⚠️ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`);
|
|
145
|
-
process.exit(0);
|
|
146
|
-
}, parseInt(opts.timelimit, 10) * 1000);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
await runReader.createRun();
|
|
151
|
-
await runReader.uploadData();
|
|
152
|
-
} catch (err) {
|
|
153
|
-
console.log(APP_PREFIX, 'Error updating status, skipping...', err);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
program
|
|
160
|
-
.command('upload-artifacts')
|
|
161
|
-
.description('Upload artifacts to Testomat.io')
|
|
162
|
-
.option('--force', 'Re-upload artifacts even if they were uploaded before')
|
|
163
|
-
.action(async (opts) => {
|
|
164
|
-
const apiKey = config.TESTOMATIO;
|
|
165
|
-
|
|
166
|
-
process.env.TESTOMATIO_DISABLE_ARTIFACTS = '';
|
|
167
|
-
|
|
168
|
-
const client = new TestomatClient({
|
|
169
|
-
apiKey,
|
|
170
|
-
isBatchEnabled: false,
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
|
|
174
|
-
const numTotalArtifacts = testruns.length;
|
|
175
|
-
|
|
176
|
-
debug('Found testruns:', testruns);
|
|
177
|
-
|
|
178
|
-
if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
|
|
179
|
-
|
|
180
|
-
if (!testruns.length) {
|
|
181
|
-
console.log(APP_PREFIX, 'Total artifacts:', numTotalArtifacts);
|
|
182
|
-
if (numTotalArtifacts) {
|
|
183
|
-
console.log(APP_PREFIX, 'No new artifacts to upload');
|
|
184
|
-
console.log(APP_PREFIX, 'To re-upload artifacts run this command with --force flag');
|
|
185
|
-
}
|
|
186
|
-
process.exit(0);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const testrunsByRid = testruns.reduce((acc, { rid, file }) => {
|
|
190
|
-
if (!acc[rid]) {
|
|
191
|
-
acc[rid] = [];
|
|
192
|
-
}
|
|
193
|
-
if (!acc[rid].includes(file)) acc[rid].push(file);
|
|
194
|
-
return acc;
|
|
195
|
-
}, {});
|
|
196
|
-
|
|
197
|
-
await client.createRun();
|
|
198
|
-
client.uploader.checkEnabled();
|
|
199
|
-
client.uploader.disbleLogStorage();
|
|
200
|
-
|
|
201
|
-
for (const rid in testrunsByRid) {
|
|
202
|
-
const files = testrunsByRid[rid];
|
|
203
|
-
await client.addTestRun(undefined, { rid, files });
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
console.log(APP_PREFIX, client.uploader.totalUploaded, 'artifacts uploaded');
|
|
207
|
-
if (client.uploader.failedUpload) {
|
|
208
|
-
console.log(APP_PREFIX, client.uploader.failedUpload, 'artifacts failed to upload');
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
program.parse(process.argv);
|
|
213
|
-
|
|
214
|
-
if (!process.argv.slice(2).length) {
|
|
215
|
-
program.outputHelp();
|
|
216
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const program = require('commander');
|
|
3
|
-
const chalk = require('chalk');
|
|
4
|
-
const debug = require('debug')('@testomatio/reporter:upload-cli');
|
|
5
|
-
const TestomatClient = require('../client');
|
|
6
|
-
const { APP_PREFIX } = require('../constants');
|
|
7
|
-
const { version } = require('../../package.json');
|
|
8
|
-
const config = require('../config');
|
|
9
|
-
|
|
10
|
-
console.log(chalk.cyan.bold(` 🤩 Testomat.io Reporter v${version}`));
|
|
11
|
-
|
|
12
|
-
program
|
|
13
|
-
.option('--env-file <envfile>', 'Load environment variables from env file')
|
|
14
|
-
.option('--force', 'Re-upload artifacts even if they were uploaded before')
|
|
15
|
-
.action(async opts => {
|
|
16
|
-
|
|
17
|
-
if (opts.envFile) {
|
|
18
|
-
require('dotenv').config(opts.envFile); // eslint-disable-line
|
|
19
|
-
} else {
|
|
20
|
-
// try to load from env file
|
|
21
|
-
require('dotenv').config(); // eslint-disable-line
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const apiKey = config.TESTOMATIO;
|
|
25
|
-
|
|
26
|
-
process.env.TESTOMATIO_DISABLE_ARTIFACTS = '';
|
|
27
|
-
// process.env.TESTOMATIO_ARTIFACTS_SIZE = null;
|
|
28
|
-
|
|
29
|
-
const client = new TestomatClient({
|
|
30
|
-
apiKey,
|
|
31
|
-
isBatchEnabled: false,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
|
|
35
|
-
|
|
36
|
-
const numTotalArtifacts = testruns.length;
|
|
37
|
-
|
|
38
|
-
debug('Found testruns:', testruns);
|
|
39
|
-
|
|
40
|
-
if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
|
|
41
|
-
|
|
42
|
-
if (!testruns.length) {
|
|
43
|
-
console.log(APP_PREFIX, 'Total artifacts:', numTotalArtifacts);
|
|
44
|
-
if (numTotalArtifacts) {
|
|
45
|
-
console.log(APP_PREFIX, 'No new artifacts to upload');
|
|
46
|
-
console.log(APP_PREFIX, 'To re-upload artifacts run this command with --force flag');
|
|
47
|
-
}
|
|
48
|
-
process.exit(0);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const testrunsByRid = testruns.reduce((acc, { rid, file }) => {
|
|
52
|
-
if (!acc[rid]) {
|
|
53
|
-
acc[rid] = [];
|
|
54
|
-
}
|
|
55
|
-
if (!acc[rid].includes(file)) acc[rid].push(file);
|
|
56
|
-
return acc;
|
|
57
|
-
}, {});
|
|
58
|
-
|
|
59
|
-
let numArtifacts = 0;
|
|
60
|
-
|
|
61
|
-
// we need to obtain S3 credentials
|
|
62
|
-
await client.createRun();
|
|
63
|
-
|
|
64
|
-
client.uploader.checkEnabled();
|
|
65
|
-
client.uploader.disbleLogStorage();
|
|
66
|
-
|
|
67
|
-
for (const rid in testrunsByRid) {
|
|
68
|
-
const files = testrunsByRid[rid];
|
|
69
|
-
numArtifacts += files.length;
|
|
70
|
-
await client.addTestRun(undefined, {
|
|
71
|
-
rid,
|
|
72
|
-
files,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
console.log(APP_PREFIX, client.uploader.totalUploaded, 'artifacts uploaded');
|
|
77
|
-
if (client.uploader.failedUpload) {
|
|
78
|
-
console.log(APP_PREFIX, client.uploader.failedUpload, 'artifacts failed to upload');
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
if (process.argv.length <= 1) {
|
|
83
|
-
program.outputHelp();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
program.parse(process.argv);
|
package/lib/uploader.js
DELETED
|
@@ -1,312 +0,0 @@
|
|
|
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
|
-
|
|
11
|
-
class S3Uploader {
|
|
12
|
-
constructor() {
|
|
13
|
-
this.isEnabled = undefined;
|
|
14
|
-
this.storeEnabled = true;
|
|
15
|
-
this.config = undefined;
|
|
16
|
-
|
|
17
|
-
// counters
|
|
18
|
-
this.skippedUpload = 0;
|
|
19
|
-
this.failedUpload = 0;
|
|
20
|
-
this.totalUploaded = 0;
|
|
21
|
-
|
|
22
|
-
this.succesfulUploads = {};
|
|
23
|
-
|
|
24
|
-
this.configKeys = [
|
|
25
|
-
'S3_ENDPOINT',
|
|
26
|
-
'S3_REGION',
|
|
27
|
-
'S3_BUCKET',
|
|
28
|
-
'S3_ACCESS_KEY_ID',
|
|
29
|
-
'S3_SECRET_ACCESS_KEY',
|
|
30
|
-
'S3_SESSION_TOKEN',
|
|
31
|
-
'S3_FORCE_PATH_STYLE',
|
|
32
|
-
'TESTOMATIO_DISABLE_ARTIFACTS',
|
|
33
|
-
'TESTOMATIO_PRIVATE_ARTIFACTS',
|
|
34
|
-
'TESTOMATIO_ARTIFACTS_SIZE'
|
|
35
|
-
];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
resetConfig() {
|
|
39
|
-
this.config = undefined;
|
|
40
|
-
this.isEnabled = undefined;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
getConfig() {
|
|
44
|
-
if (this.config) return this.config;
|
|
45
|
-
this.config = this.configKeys.reduce((acc, key) => {
|
|
46
|
-
acc[key] = process.env[key];
|
|
47
|
-
return acc;
|
|
48
|
-
}, {});
|
|
49
|
-
return this.config;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
getMaskedConfig() {
|
|
53
|
-
return Object.fromEntries(
|
|
54
|
-
Object.entries(this.getConfig()).map(([key, value]) => [
|
|
55
|
-
key,
|
|
56
|
-
key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value,
|
|
57
|
-
]),
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
checkEnabled() {
|
|
62
|
-
if (this.isEnabled !== undefined) return this.isEnabled;
|
|
63
|
-
|
|
64
|
-
const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = this.getConfig();
|
|
65
|
-
if (!S3_BUCKET) debug(`Upload is disabled because S3_BUCKET is not set`);
|
|
66
|
-
this.isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
|
|
67
|
-
|
|
68
|
-
if (this.isEnabled) debug('S3 uploader is enabled');
|
|
69
|
-
debug(this.getMaskedConfig());
|
|
70
|
-
|
|
71
|
-
return this.isEnabled;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
enableLogStorage() {
|
|
75
|
-
this.storeEnabled = true;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
disbleLogStorage() {
|
|
79
|
-
this.storeEnabled = false;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async uploadToS3(Body, Key) {
|
|
83
|
-
const { S3_BUCKET, TESTOMATIO_PRIVATE_ARTIFACTS } = this.getConfig();
|
|
84
|
-
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
85
|
-
|
|
86
|
-
if (!S3_BUCKET || !Body) {
|
|
87
|
-
console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), this.getMaskedConfig());
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
debug('Uploading to S3:', Key);
|
|
92
|
-
|
|
93
|
-
const s3 = new S3(this.getS3Config());
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
const upload = new Upload({
|
|
97
|
-
client: s3,
|
|
98
|
-
params: {
|
|
99
|
-
Bucket: S3_BUCKET,
|
|
100
|
-
Key,
|
|
101
|
-
Body,
|
|
102
|
-
ACL,
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const link = await this.getS3LocationLink(upload);
|
|
107
|
-
this.totalUploaded++;
|
|
108
|
-
this.succesfulUploads[Key] = link;
|
|
109
|
-
return link;
|
|
110
|
-
} catch (e) {
|
|
111
|
-
this.failedUpload++;
|
|
112
|
-
debug('S3 uploading error:', e);
|
|
113
|
-
console.log(APP_PREFIX, "Upload failed:", e.message, this.getMaskedConfig());
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
readUploadedFiles(runId = null) {
|
|
118
|
-
const tempFilePath = this.#getUploadFilePath(runId);
|
|
119
|
-
|
|
120
|
-
debug('Reading file', tempFilePath);
|
|
121
|
-
|
|
122
|
-
if (!fs.existsSync(tempFilePath)) {
|
|
123
|
-
debug('File not found:', tempFilePath);
|
|
124
|
-
return [];
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const stats = fs.statSync(tempFilePath);
|
|
128
|
-
const diff = (+new Date()) - (+stats.mtime);
|
|
129
|
-
const diffHours = diff / 1000 / 60 / 60;
|
|
130
|
-
if (diffHours > 3) {
|
|
131
|
-
console.log(APP_PREFIX, 'Artifacts file is too old, can\'t process artifacts. Please re-run the tests.');
|
|
132
|
-
return [];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const data = fs.readFileSync(tempFilePath, 'utf8');
|
|
136
|
-
const lines = data.split('\n').filter(Boolean);
|
|
137
|
-
return lines.map(line => JSON.parse(line));
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
#getUploadFilePath(runId = null, forceCreate = false) {
|
|
141
|
-
if (!runId && !forceCreate) {
|
|
142
|
-
return path.join(os.tmpdir(), 'testomatio.run.latest.jsonl');
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const tempFilePath = path.join(os.tmpdir(), `testomatio.run.${runId}.jsonl`);
|
|
146
|
-
if (!fs.existsSync(tempFilePath) || forceCreate) {
|
|
147
|
-
debug('Creating artifacts file:', tempFilePath);
|
|
148
|
-
fs.writeFileSync(tempFilePath, '');
|
|
149
|
-
// make symlink to 'testomatio.run.latest.jsonl' file
|
|
150
|
-
const latestFilePath = path.join(os.tmpdir(), 'testomatio.run.latest.jsonl');
|
|
151
|
-
if (fs.existsSync(latestFilePath)) {
|
|
152
|
-
fs.unlinkSync(latestFilePath);
|
|
153
|
-
}
|
|
154
|
-
fs.symlinkSync(tempFilePath, latestFilePath);
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
return tempFilePath;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
storeUploadedFile(filePath, runId, rid, uploaded = false) {
|
|
161
|
-
if (!this.storeEnabled) return;
|
|
162
|
-
|
|
163
|
-
if (!filePath || !runId || !rid ) return;
|
|
164
|
-
|
|
165
|
-
const tempFilePath = this.#getUploadFilePath(runId);
|
|
166
|
-
|
|
167
|
-
const data = { rid, file: filePath, uploaded };
|
|
168
|
-
const jsonLine = JSON.stringify(data) + '\n';
|
|
169
|
-
|
|
170
|
-
fs.appendFileSync(tempFilePath, jsonLine);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
getskippedUpload() {
|
|
174
|
-
return this.skippedUpload;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async uploadFileByPath(filePath, pathInS3) {
|
|
178
|
-
const [runId, rid] = pathInS3;
|
|
179
|
-
|
|
180
|
-
if (!this.isEnabled) {
|
|
181
|
-
this.storeUploadedFile(filePath, runId, rid, false);
|
|
182
|
-
this.skippedUpload++;
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const {
|
|
187
|
-
S3_BUCKET,
|
|
188
|
-
TESTOMATIO_ARTIFACTS_SIZE,
|
|
189
|
-
} = this.getConfig();
|
|
190
|
-
|
|
191
|
-
debug('Started upload', filePath, 'to', S3_BUCKET);
|
|
192
|
-
|
|
193
|
-
const isFileExist = await this.checkFileExists(filePath, 20, 500);
|
|
194
|
-
|
|
195
|
-
if (!isFileExist) {
|
|
196
|
-
this.failedUpload++;
|
|
197
|
-
console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const fileSize = fs.statSync(filePath).size;
|
|
202
|
-
const fileSizeInMb = fileSize / (1024 * 1024);
|
|
203
|
-
|
|
204
|
-
if (TESTOMATIO_ARTIFACTS_SIZE && fileSizeInMb > parseInt(TESTOMATIO_ARTIFACTS_SIZE)) {
|
|
205
|
-
this.skippedUpload++;
|
|
206
|
-
console.error(chalk.yellow(`Artifacts file ${filePath} exceeds the maximum allowed size. Skipping...`));
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
debug('File:', filePath, 'exists, size:', fileSizeInMb.toFixed(2), 'MB');
|
|
210
|
-
|
|
211
|
-
const fileStream = fs.createReadStream(filePath);
|
|
212
|
-
const Key = pathInS3.join('/');
|
|
213
|
-
|
|
214
|
-
const link = await this.uploadToS3(fileStream, Key);
|
|
215
|
-
|
|
216
|
-
this.storeUploadedFile(filePath, runId, rid, !!link);
|
|
217
|
-
|
|
218
|
-
return link;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async uploadFileAsBuffer(buffer, pathInS3) {
|
|
222
|
-
if (!this.isEnabled) return;
|
|
223
|
-
|
|
224
|
-
let Key = pathInS3.join('/');
|
|
225
|
-
const ext = this.#getFileExtBase64(buffer);
|
|
226
|
-
|
|
227
|
-
if (ext) {
|
|
228
|
-
Key = `${Key}.${ext}`;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return this.uploadToS3(buffer, Key);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async checkFileExists(filePath, attempts = 5, intervalMs = 500) {
|
|
235
|
-
return promiseRetry(
|
|
236
|
-
async (retry, number) => {
|
|
237
|
-
try {
|
|
238
|
-
fs.accessSync(filePath);
|
|
239
|
-
return true;
|
|
240
|
-
} catch (err) {
|
|
241
|
-
if (number === attempts) {
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
debug(`File not found, retrying (attempt ${number}/${attempts})`);
|
|
245
|
-
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
246
|
-
retry(err);
|
|
247
|
-
}
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
retries: attempts,
|
|
251
|
-
minTimeout: intervalMs,
|
|
252
|
-
maxTimeout: intervalMs,
|
|
253
|
-
}
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
async getS3LocationLink(out) {
|
|
258
|
-
const response = await out.done();
|
|
259
|
-
|
|
260
|
-
let s3Location = response?.Location;
|
|
261
|
-
|
|
262
|
-
if (!s3Location) {
|
|
263
|
-
s3Location = out?.singleUploadResult?.Location;
|
|
264
|
-
debug('Uploaded singleUploadResult.Location', s3Location);
|
|
265
|
-
|
|
266
|
-
if (!s3Location) {
|
|
267
|
-
throw new Error("Problems getting the S3 artifact's link. Please check S3 permissions!");
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
return s3Location;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
#getFileExtBase64(str) {
|
|
275
|
-
const type = str.charAt(0);
|
|
276
|
-
|
|
277
|
-
return (
|
|
278
|
-
{
|
|
279
|
-
'/': 'jpg',
|
|
280
|
-
i: 'png',
|
|
281
|
-
R: 'gif',
|
|
282
|
-
U: 'webp',
|
|
283
|
-
}[type] || ''
|
|
284
|
-
);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
getS3Config() {
|
|
288
|
-
const { S3_REGION, S3_SESSION_TOKEN, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } =
|
|
289
|
-
this.getConfig();
|
|
290
|
-
|
|
291
|
-
const cfg = {
|
|
292
|
-
region: S3_REGION,
|
|
293
|
-
credentials: {
|
|
294
|
-
accessKeyId: S3_ACCESS_KEY_ID,
|
|
295
|
-
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
296
|
-
s3ForcePathStyle: S3_FORCE_PATH_STYLE,
|
|
297
|
-
},
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
if (S3_SESSION_TOKEN) {
|
|
301
|
-
cfg.credentials.sessionToken = S3_SESSION_TOKEN;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
if (S3_ENDPOINT) {
|
|
305
|
-
cfg.endpoint = S3_ENDPOINT;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return cfg;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
module.exports = S3Uploader;
|