@testomatio/reporter 1.5.1-beta β 1.6.0-beta-1-artifacts
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 +2 -1
- package/lib/adapter/codecept.js +8 -7
- package/lib/adapter/cucumber/current.js +2 -4
- package/lib/adapter/playwright.js +30 -22
- package/lib/bin/cli.js +220 -0
- package/lib/bin/startTest.js +2 -2
- package/lib/bin/uploadArtifacts.js +91 -0
- package/lib/client.js +50 -35
- package/lib/pipe/bitbucket.js +254 -0
- package/lib/pipe/index.js +2 -0
- package/lib/pipe/testomatio.js +10 -4
- package/lib/uploader.js +308 -0
- package/lib/utils/pipe_utils.js +1 -1
- package/lib/xmlReader.js +6 -4
- package/package.json +7 -3
- package/lib/fileUploader.js +0 -306
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Testomat.io Reporter (this npm package) supports:
|
|
|
11
11
|
- π Integarion with all popular [JavaScript/TypeScript frameworks](./docs/frameworks.md)
|
|
12
12
|
- ποΈ Screenshots, videos, traces [uploaded into S3 bucket](./docs/artifacts.md)
|
|
13
13
|
- π [Stack traces](./docs/stacktrace.md) and error messages
|
|
14
|
-
- π [GitHub](./docs/pipes/github.md)
|
|
14
|
+
- π [GitHub](./docs/pipes/github.md), [GitLab](./docs/pipes/gitlab.md) & [Bitbucket](./docs/pipes/bitbucket.md) integration
|
|
15
15
|
- π
Realtime reports
|
|
16
16
|
- ποΈ Other test frameworks supported via [JUNit XML](./docs/junit.md)
|
|
17
17
|
- πΆββοΈ Steps _(work in progress)_
|
|
@@ -128,6 +128,7 @@ Bring this reporter on CI and never lose test results again!
|
|
|
128
128
|
- [Gitlab](./docs/pipes/gitlab.md)
|
|
129
129
|
- [CSV](./docs/pipes/csv.md)
|
|
130
130
|
- [HTML report](./docs/pipes/html.md)
|
|
131
|
+
- [Bitbucket](./docs/pipes/bitbucket.md)
|
|
131
132
|
- π [JUnit](./docs/junit.md)
|
|
132
133
|
- ποΈ [Artifacts](./docs/artifacts.md)
|
|
133
134
|
- π [Workflows](./docs/workflows.md)
|
package/lib/adapter/codecept.js
CHANGED
|
@@ -2,7 +2,6 @@ const debug = require('debug')('@testomatio/reporter:adapter:codeceptjs');
|
|
|
2
2
|
const chalk = require('chalk');
|
|
3
3
|
const TestomatClient = require('../client');
|
|
4
4
|
const { STATUS, APP_PREFIX, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
5
|
-
const upload = require('../fileUploader');
|
|
6
5
|
const { getTestomatIdFromTestTitle, fileSystem } = require('../utils/utils');
|
|
7
6
|
const { services } = require('../services');
|
|
8
7
|
|
|
@@ -128,10 +127,8 @@ function CodeceptReporter(config) {
|
|
|
128
127
|
// all tests were reported and we can upload videos
|
|
129
128
|
await Promise.all(reportTestPromises);
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
await uploadAttachments(client, traces, 'π Uploading', 'trace');
|
|
134
|
-
}
|
|
130
|
+
await uploadAttachments(client, videos, 'ποΈ Uploading', 'video');
|
|
131
|
+
await uploadAttachments(client, traces, 'π Uploading', 'trace');
|
|
135
132
|
|
|
136
133
|
const status = failedTests.length === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
137
134
|
client.updateRunStatus(status);
|
|
@@ -304,13 +301,17 @@ function CodeceptReporter(config) {
|
|
|
304
301
|
}
|
|
305
302
|
|
|
306
303
|
async function uploadAttachments(client, attachments, messagePrefix, attachmentType) {
|
|
307
|
-
if (!attachments?.length) return;
|
|
304
|
+
if (!attachments?.length) return;
|
|
308
305
|
|
|
309
|
-
console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType} ...`);
|
|
306
|
+
if (client.uploader.isEnabled) console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType} ...`);
|
|
310
307
|
|
|
311
308
|
const promises = attachments.map(async attachment => {
|
|
312
309
|
const { rid, title, path, type } = attachment;
|
|
313
310
|
const file = { path, type, title };
|
|
311
|
+
|
|
312
|
+
// we are storing file if upload is disabled
|
|
313
|
+
if (!client.uploader.isEnabled) return client.uploader.storeUploadedFile(path, client.runId, rid, false);
|
|
314
|
+
|
|
314
315
|
return client.addTestRun(undefined, {
|
|
315
316
|
...stripExampleFromTitle(title),
|
|
316
317
|
rid,
|
|
@@ -32,16 +32,14 @@ class CucumberReporter extends Formatter {
|
|
|
32
32
|
|
|
33
33
|
this.client = new TestomatClient({ apiKey: options.apiKey || config.TESTOMATIO });
|
|
34
34
|
this.status = STATUS.PASSED;
|
|
35
|
+
this.client.createRun();
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
parseEnvelope(envelope) {
|
|
38
39
|
if (envelope.testRunStarted) {
|
|
39
40
|
fileSystem.clearDir(TESTOMAT_TMP_STORAGE_DIR);
|
|
40
41
|
}
|
|
41
|
-
if (envelope.testCaseStarted && this.client)
|
|
42
|
-
this.client.createRun();
|
|
43
|
-
this.onTestCaseStarted(envelope.testCaseStarted);
|
|
44
|
-
}
|
|
42
|
+
if (envelope.testCaseStarted && this.client) this.onTestCaseStarted(envelope.testCaseStarted);
|
|
45
43
|
if (envelope.testCaseFinished) this.onTestCaseFinished(envelope.testCaseFinished);
|
|
46
44
|
if (envelope.testRunFinished) this.onTestRunFinished(envelope);
|
|
47
45
|
}
|
|
@@ -6,7 +6,6 @@ const { v4: uuidv4 } = require('uuid');
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
8
8
|
const TestomatioClient = require('../client');
|
|
9
|
-
const { isArtifactsEnabled } = require('../fileUploader');
|
|
10
9
|
const { getTestomatIdFromTestTitle, fileSystem } = require('../utils/utils');
|
|
11
10
|
// const debug = require('debug')('@testomatio/reporter:adapter:playwright');
|
|
12
11
|
const { services } = require('../services');
|
|
@@ -106,31 +105,37 @@ class PlaywrightReporter {
|
|
|
106
105
|
|
|
107
106
|
await Promise.all(reportTestPromises);
|
|
108
107
|
|
|
109
|
-
if (this.uploads.length
|
|
110
|
-
|
|
108
|
+
if (!this.uploads.length) return;
|
|
109
|
+
|
|
110
|
+
if (this.client.uploader.isEnabled) console.log(APP_PREFIX, `ποΈ Uploading ${this.uploads.length} files...`);
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
const promises = [];
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
for (const upload of this.uploads) {
|
|
115
|
+
const { rid, file, title } = upload;
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
title,
|
|
127
|
-
files,
|
|
128
|
-
file,
|
|
129
|
-
}),
|
|
130
|
-
);
|
|
117
|
+
const files = upload.files.map(attachment => ({
|
|
118
|
+
path: this.#getArtifactPath(attachment),
|
|
119
|
+
title,
|
|
120
|
+
type: attachment.contentType,
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
if (!this.client.uploader.isEnabled) {
|
|
124
|
+
files.forEach(f => this.client.uploader.storeUploadedFile(f, this.client.runId, rid, false));
|
|
125
|
+
continue;
|
|
131
126
|
}
|
|
132
|
-
|
|
127
|
+
|
|
128
|
+
promises.push(
|
|
129
|
+
this.client.addTestRun(undefined, {
|
|
130
|
+
rid,
|
|
131
|
+
title,
|
|
132
|
+
files,
|
|
133
|
+
file,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
133
136
|
}
|
|
137
|
+
await Promise.all(promises);
|
|
138
|
+
|
|
134
139
|
|
|
135
140
|
await this.client.updateRunStatus(checkStatus(result.status));
|
|
136
141
|
}
|
|
@@ -147,6 +152,9 @@ function checkStatus(status) {
|
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
function appendStep(step, shift = 0) {
|
|
155
|
+
// nesting too deep, ignore those steps
|
|
156
|
+
if (shift >= 10) return;
|
|
157
|
+
|
|
150
158
|
let newCategory = step.category;
|
|
151
159
|
switch (newCategory) {
|
|
152
160
|
case 'test.step':
|
|
@@ -176,7 +184,7 @@ function appendStep(step, shift = 0) {
|
|
|
176
184
|
};
|
|
177
185
|
|
|
178
186
|
if (formattedSteps.length) {
|
|
179
|
-
resultStep.steps = formattedSteps;
|
|
187
|
+
resultStep.steps = formattedSteps.filter(s => !!s);
|
|
180
188
|
}
|
|
181
189
|
|
|
182
190
|
if (step.error !== undefined) {
|
package/lib/bin/cli.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
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
|
+
process.env.TESTOMATIO_DISABLE_ARTIFACTS = '';
|
|
166
|
+
|
|
167
|
+
const client = new TestomatClient({
|
|
168
|
+
apiKey,
|
|
169
|
+
isBatchEnabled: false,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (!process.env.TESTOMATIO_RUN) {
|
|
173
|
+
console.log(APP_PREFIX, 'No run ID provided');
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
|
|
178
|
+
const numTotalArtifacts = testruns.length;
|
|
179
|
+
|
|
180
|
+
debug('Found testruns:', testruns);
|
|
181
|
+
|
|
182
|
+
if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
|
|
183
|
+
|
|
184
|
+
if (!testruns.length) {
|
|
185
|
+
console.log(APP_PREFIX, 'Total artifacts:', numTotalArtifacts);
|
|
186
|
+
if (numTotalArtifacts) {
|
|
187
|
+
console.log(APP_PREFIX, 'No new artifacts to upload');
|
|
188
|
+
console.log(APP_PREFIX, 'To re-upload artifacts run this command with --force flag');
|
|
189
|
+
}
|
|
190
|
+
process.exit(0);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const testrunsByRid = testruns.reduce((acc, { rid, file }) => {
|
|
194
|
+
if (!acc[rid]) {
|
|
195
|
+
acc[rid] = [];
|
|
196
|
+
}
|
|
197
|
+
if (!acc[rid].includes(file)) acc[rid].push(file);
|
|
198
|
+
return acc;
|
|
199
|
+
}, {});
|
|
200
|
+
|
|
201
|
+
await client.createRun();
|
|
202
|
+
client.uploader.checkEnabled();
|
|
203
|
+
client.uploader.disbleLogStorage();
|
|
204
|
+
|
|
205
|
+
for (const rid in testrunsByRid) {
|
|
206
|
+
const files = testrunsByRid[rid];
|
|
207
|
+
await client.addTestRun(undefined, { rid, files });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(APP_PREFIX, client.uploader.totalUploaded, 'artifacts uploaded');
|
|
211
|
+
if (client.uploader.failedUpload) {
|
|
212
|
+
console.log(APP_PREFIX, client.uploader.failedUpload, 'artifacts failed to upload');
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
program.parse(process.argv);
|
|
217
|
+
|
|
218
|
+
if (!process.argv.slice(2).length) {
|
|
219
|
+
program.outputHelp();
|
|
220
|
+
}
|
package/lib/bin/startTest.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const
|
|
2
|
+
const spawn = require('cross-spawn');
|
|
3
3
|
const program = require('commander');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const TestomatClient = require('../client');
|
|
@@ -45,7 +45,7 @@ program
|
|
|
45
45
|
|
|
46
46
|
const client = new TestomatClient({ apiKey });
|
|
47
47
|
|
|
48
|
-
client.updateRunStatus(STATUS.FINISHED
|
|
48
|
+
client.updateRunStatus(STATUS.FINISHED).then(() => {
|
|
49
49
|
console.log(chalk.yellow(`Run ${process.env.TESTOMATIO_RUN} was finished`));
|
|
50
50
|
process.exit(0);
|
|
51
51
|
});
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
if (!process.env.TESTOMATIO_RUN) {
|
|
35
|
+
console.log(APP_PREFIX, 'No run ID provided');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let testruns = client.uploader.readUploadedFiles(process.env.TESTOMATIO_RUN);
|
|
40
|
+
|
|
41
|
+
const numTotalArtifacts = testruns.length;
|
|
42
|
+
|
|
43
|
+
debug('Found testruns:', testruns);
|
|
44
|
+
|
|
45
|
+
if (!opts.force) testruns = testruns.filter(tr => !tr.uploaded);
|
|
46
|
+
|
|
47
|
+
if (!testruns.length) {
|
|
48
|
+
console.log(APP_PREFIX, 'Total artifacts:', numTotalArtifacts);
|
|
49
|
+
if (numTotalArtifacts) {
|
|
50
|
+
console.log(APP_PREFIX, 'No new artifacts to upload');
|
|
51
|
+
console.log(APP_PREFIX, 'To re-upload artifacts run this command with --force flag');
|
|
52
|
+
}
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const testrunsByRid = testruns.reduce((acc, { rid, file }) => {
|
|
57
|
+
if (!acc[rid]) {
|
|
58
|
+
acc[rid] = [];
|
|
59
|
+
}
|
|
60
|
+
if (!acc[rid].includes(file)) acc[rid].push(file);
|
|
61
|
+
return acc;
|
|
62
|
+
}, {});
|
|
63
|
+
|
|
64
|
+
let numArtifacts = 0;
|
|
65
|
+
|
|
66
|
+
// we need to obtain S3 credentials
|
|
67
|
+
await client.createRun();
|
|
68
|
+
|
|
69
|
+
client.uploader.checkEnabled();
|
|
70
|
+
client.uploader.disbleLogStorage();
|
|
71
|
+
|
|
72
|
+
for (const rid in testrunsByRid) {
|
|
73
|
+
const files = testrunsByRid[rid];
|
|
74
|
+
numArtifacts += files.length;
|
|
75
|
+
await client.addTestRun(undefined, {
|
|
76
|
+
rid,
|
|
77
|
+
files,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(APP_PREFIX, client.uploader.totalUploaded, 'artifacts uploaded');
|
|
82
|
+
if (client.uploader.failedUpload) {
|
|
83
|
+
console.log(APP_PREFIX, client.uploader.failedUpload, 'artifacts failed to upload');
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (process.argv.length <= 1) {
|
|
88
|
+
program.outputHelp();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
program.parse(process.argv);
|
package/lib/client.js
CHANGED
|
@@ -5,7 +5,7 @@ const { minimatch } = require('minimatch');
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const chalk = require('chalk');
|
|
7
7
|
const { randomUUID } = require('crypto');
|
|
8
|
-
const
|
|
8
|
+
const S3Uploader = require('./uploader');
|
|
9
9
|
const { APP_PREFIX, STATUS } = require('./constants');
|
|
10
10
|
const pipesFactory = require('./pipe');
|
|
11
11
|
const { glob } = require('glob');
|
|
@@ -26,14 +26,14 @@ class Client {
|
|
|
26
26
|
* @param {*} params
|
|
27
27
|
*/
|
|
28
28
|
constructor(params = {}) {
|
|
29
|
-
|
|
30
|
-
this.
|
|
31
|
-
this.pipes = pipesFactory(params,
|
|
29
|
+
this.pipeStore = {};
|
|
30
|
+
this.runId = randomUUID(); // will be replaced by real run id
|
|
31
|
+
this.pipes = pipesFactory(params, this.pipeStore);
|
|
32
32
|
this.queue = Promise.resolve();
|
|
33
|
-
this.totalUploaded = 0;
|
|
34
|
-
this.failedToUpload = 0;
|
|
35
33
|
this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
|
|
36
34
|
this.executionList = Promise.resolve();
|
|
35
|
+
this.uploader = new S3Uploader();
|
|
36
|
+
this.uploader.checkEnabled();
|
|
37
37
|
|
|
38
38
|
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
39
39
|
}
|
|
@@ -104,7 +104,13 @@ class Client {
|
|
|
104
104
|
this.queue = this.queue
|
|
105
105
|
.then(() => Promise.all(this.pipes.map(p => p.createRun())))
|
|
106
106
|
.catch(err => console.log(APP_PREFIX, err))
|
|
107
|
+
.then(() => {
|
|
108
|
+
const runId = this.pipeStore?.runId;
|
|
109
|
+
if (runId) this.runId = runId;
|
|
110
|
+
this.uploader.checkEnabled();
|
|
111
|
+
})
|
|
107
112
|
.then(() => undefined); // fixes return type
|
|
113
|
+
|
|
108
114
|
// debug('Run', this.queue);
|
|
109
115
|
return this.queue;
|
|
110
116
|
}
|
|
@@ -169,24 +175,28 @@ class Client {
|
|
|
169
175
|
|
|
170
176
|
const uploadedFiles = [];
|
|
171
177
|
|
|
172
|
-
for (
|
|
173
|
-
|
|
178
|
+
for (let f of files) {
|
|
179
|
+
if (typeof f === 'object') {
|
|
180
|
+
if (!f.path) continue;
|
|
181
|
+
|
|
182
|
+
f = f.path;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
uploadedFiles.push(this.uploader.uploadFileByPath(f, [
|
|
186
|
+
this.runId,
|
|
187
|
+
rid,
|
|
188
|
+
path.basename(f)
|
|
189
|
+
]));
|
|
190
|
+
|
|
174
191
|
}
|
|
175
192
|
|
|
176
193
|
for (const [idx, buffer] of filesBuffers.entries()) {
|
|
177
194
|
const fileName = `${idx + 1}-${title.replace(/\s+/g, '-')}`;
|
|
178
|
-
uploadedFiles.push(
|
|
195
|
+
uploadedFiles.push(this.uploader.uploadFileAsBuffer(buffer, [this.runId, rid, fileName]));
|
|
179
196
|
}
|
|
180
197
|
|
|
181
198
|
const artifacts = (await Promise.all(uploadedFiles)).filter(n => !!n);
|
|
182
199
|
|
|
183
|
-
if (artifacts.length < uploadedFiles.length) {
|
|
184
|
-
const failedUploading = uploadedFiles.length - artifacts.length;
|
|
185
|
-
this.failedToUpload += failedUploading;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
this.totalUploaded += artifacts.length;
|
|
189
|
-
|
|
190
200
|
const data = {
|
|
191
201
|
rid,
|
|
192
202
|
files,
|
|
@@ -242,28 +252,30 @@ class Client {
|
|
|
242
252
|
this.queue = this.queue
|
|
243
253
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
244
254
|
.then(() => {
|
|
245
|
-
debug('TOTAL artifacts', this.totalUploaded);
|
|
246
|
-
|
|
247
|
-
debug(`${this.totalUploaded} artifacts are not uploaded, because artifacts uploading is not enabled`);
|
|
255
|
+
debug('TOTAL artifacts', this.uploader.totalUploaded);
|
|
256
|
+
debug(`${this.uploader.skippedUpload} artifacts skipped`);
|
|
248
257
|
|
|
249
|
-
if (this.totalUploaded &&
|
|
258
|
+
if (this.uploader.totalUploaded && this.uploader.isEnabled) {
|
|
250
259
|
console.log(
|
|
251
260
|
APP_PREFIX,
|
|
252
|
-
`ποΈ ${this.totalUploaded} artifacts ${
|
|
261
|
+
`ποΈ ${this.uploader.totalUploaded} artifacts ${
|
|
253
262
|
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
254
263
|
} uploaded to S3 bucket`,
|
|
255
|
-
);
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
);
|
|
265
|
-
}
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (this.uploader.failedUpload) {
|
|
268
|
+
console.log(APP_PREFIX, `${this.uploader.failedUpload} artifacts failed to upload`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (this.uploader.isEnabled && this.uploader.skippedUpload) {
|
|
272
|
+
console.log(APP_PREFIX, `${chalk.bold(this.uploader.skippedUpload)} artifacts skipped to upload`);
|
|
266
273
|
}
|
|
274
|
+
|
|
275
|
+
if (this.uploader.skippedUpload || this.uploader.failedUpload) {
|
|
276
|
+
console.log(APP_PREFIX, `Run "${chalk.magenta(`TESTOMATIO_RUN=${this.runId} npx upload-artifacts`)}" with valid S3 credentials to upload skipped & failed artifacts`);
|
|
277
|
+
}
|
|
278
|
+
|
|
267
279
|
})
|
|
268
280
|
.catch(err => console.log(APP_PREFIX, err));
|
|
269
281
|
|
|
@@ -279,7 +291,10 @@ class Client {
|
|
|
279
291
|
logs = logs?.trim();
|
|
280
292
|
|
|
281
293
|
if (Array.isArray(steps)) {
|
|
282
|
-
steps = steps
|
|
294
|
+
steps = steps
|
|
295
|
+
.map(step => formatStep(step))
|
|
296
|
+
.flat()
|
|
297
|
+
.join('\n');
|
|
283
298
|
}
|
|
284
299
|
|
|
285
300
|
let testLogs = '';
|
|
@@ -358,8 +373,8 @@ function formatStep(step, shift = 0) {
|
|
|
358
373
|
|
|
359
374
|
for (const child of step.steps || []) {
|
|
360
375
|
lines.push(...formatStep(child, shift + 2));
|
|
361
|
-
}
|
|
362
|
-
|
|
376
|
+
}
|
|
377
|
+
|
|
363
378
|
return lines;
|
|
364
379
|
}
|
|
365
380
|
|