@testomatio/reporter 0.8.0-beta.3 → 0.8.0-beta.30
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/Changelog.md +12 -0
- package/README.md +63 -0
- package/lib/adapter/codecept.js +1 -6
- package/lib/adapter/cucumber/current.js +1 -7
- package/lib/adapter/jasmine.js +1 -8
- package/lib/adapter/jest.js +1 -7
- package/lib/adapter/mocha.js +23 -7
- package/lib/adapter/playwright.js +2 -8
- package/lib/adapter/webdriver.js +1 -4
- package/lib/bin/reportXml.js +6 -2
- package/lib/client.js +29 -15
- package/lib/constants.js +9 -0
- package/lib/fileUploader.js +82 -49
- package/lib/pipe/csv.js +119 -0
- package/lib/pipe/github.js +135 -63
- package/lib/pipe/gitlab.js +229 -0
- package/lib/pipe/index.js +29 -13
- package/lib/pipe/testomatio.js +19 -8
- package/lib/util.js +15 -2
- package/lib/xmlReader.js +30 -17
- package/package.json +9 -4
- package/testcafe/index.js +0 -61
- package/testcafe/package-lock.json +0 -863
- package/testcafe/package.json +0 -14
package/Changelog.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# 0.7.6
|
|
2
|
+
|
|
3
|
+
* Updated to use AWS S3 3.0 SDK for uploading
|
|
4
|
+
|
|
5
|
+
# 0.7.5
|
|
6
|
+
|
|
7
|
+
* Fixed reporting skipped tests in mocha
|
|
8
|
+
|
|
9
|
+
# 0.7.4
|
|
10
|
+
|
|
11
|
+
* Fixed parsing source code in JUnit files
|
|
12
|
+
|
|
1
13
|
# 0.7.3
|
|
2
14
|
|
|
3
15
|
* CodeceptJS: Upload all traces and videos from artifacts
|
package/README.md
CHANGED
|
@@ -20,6 +20,19 @@ For testcafe use testcafe reporter:
|
|
|
20
20
|
npm i testcafe-reporter-testomatio
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
For newman use:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm i newman-reporter-testomatio --save-dev
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
>`newman` and `newman-reporter-testomatio` should be installed in the same directory.
|
|
30
|
+
\
|
|
31
|
+
If you run your tests using globally installed newman (`newman run ...`), intall `newman-reporter-testomatio` globally too (`npm i newman-reporter-testomatio -g`).
|
|
32
|
+
\
|
|
33
|
+
If you use locally installed newman (within the project) (`npx newman run ...`), install `newman-reporter-testomatio` locally (`npm i newman-reporter-testomatio`).
|
|
34
|
+
You can verify installed packages via `npm list` or `npm list -g`.
|
|
35
|
+
|
|
23
36
|
## Usage
|
|
24
37
|
|
|
25
38
|
### CodeceptJS
|
|
@@ -121,6 +134,15 @@ Run the following command from you project folder:
|
|
|
121
134
|
TESTOMATIO={API_KEY} npx testcafe chrome -r testomatio
|
|
122
135
|
```
|
|
123
136
|
|
|
137
|
+
### Newman (Postman)
|
|
138
|
+
|
|
139
|
+
Run collection and specify `testomatio` as reporter:
|
|
140
|
+
|
|
141
|
+
`TESTOMATIO={API_KEY} npx newman run {collection_name.json} -r testomatio`
|
|
142
|
+
|
|
143
|
+
> _`check-test` not supported for newman for now, tests will be created on testomatio by default_
|
|
144
|
+
|
|
145
|
+
|
|
124
146
|
### Cypress
|
|
125
147
|
|
|
126
148
|
Load the test using using `check-test`.
|
|
@@ -208,6 +230,30 @@ Run the following command from you project folder:
|
|
|
208
230
|
TESTOMATIO={API_KEY} npx start-test-run -c 'npx wdio wdio.conf.js'
|
|
209
231
|
```
|
|
210
232
|
|
|
233
|
+
## Pipes
|
|
234
|
+
Pipes allow you to get report inside different systems (e.g. Pull request comment, database etc)
|
|
235
|
+
For now next pipes available:
|
|
236
|
+
|
|
237
|
+
### GitHub
|
|
238
|
+
This pipe adds comment with run report to GitHub Pull Request.
|
|
239
|
+
|
|
240
|
+
To use it:
|
|
241
|
+
1. run your tests using github actions in Pull Request
|
|
242
|
+
2. pass `GH_PAT` (GitHub Personal Access Token) as environment variable.
|
|
243
|
+
|
|
244
|
+
> Last report (comment) will be replaced with the new one.
|
|
245
|
+
To leave previous report pass `GITHUB_KEEP_OUTDATED_REPORTS=1` env variable.
|
|
246
|
+
|
|
247
|
+
### GitLab
|
|
248
|
+
This pipe adds comment with run report to GitLab Merge Request.
|
|
249
|
+
|
|
250
|
+
To use it:
|
|
251
|
+
1. run your tests in Merge Request (pipeline trigger should be `merge_request`)
|
|
252
|
+
2. pass `GITLAB_PAT` (GitLab Personal Access Token) as environment variable.
|
|
253
|
+
|
|
254
|
+
> Last report (comment) will be replaced with the new one.
|
|
255
|
+
To leave previous report pass `GITLAB_KEEP_OUTDATED_REPORTS=1` env variable.
|
|
256
|
+
|
|
211
257
|
## JUnit Reports
|
|
212
258
|
|
|
213
259
|
> **Notice** JUnit reports are supported since 0.6.0
|
|
@@ -408,6 +454,23 @@ Add environments to run by providing `TESTOMATIO_ENV` as comma seperated values:
|
|
|
408
454
|
TESTOMATIO={API_KEY} TESTOMATIO_ENV="Windows, Chrome" <actual run command>
|
|
409
455
|
```
|
|
410
456
|
|
|
457
|
+
### Save test results to .csv file
|
|
458
|
+
Add an env to run by specifying the `TESTOMATIO_CSV_FILENAME` variable.
|
|
459
|
+
|
|
460
|
+
1) using default report name:
|
|
461
|
+
|
|
462
|
+
```bash
|
|
463
|
+
TESTOMATIO={API_KEY} TESTOMATIO_CSV_FILENAME="report.csv" <actual run command>
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
2) using unique report name:
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
TESTOMATIO={API_KEY} TESTOMATIO_CSV_FILENAME="test.csv" <actual run command>
|
|
470
|
+
```
|
|
471
|
+
_It's create a new /export folder with csv files_
|
|
472
|
+
|
|
473
|
+
|
|
411
474
|
### Attaching Test Artifacts
|
|
412
475
|
|
|
413
476
|
To save a test artifacts (screenshots and videos) of a failed test use S3 storage.
|
package/lib/adapter/codecept.js
CHANGED
|
@@ -35,11 +35,6 @@ function CodeceptReporter(config) {
|
|
|
35
35
|
const testTimeMap = {};
|
|
36
36
|
const { apiKey } = config;
|
|
37
37
|
|
|
38
|
-
if (!apiKey) {
|
|
39
|
-
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
38
|
const getDuration = test => {
|
|
44
39
|
if (testTimeMap[test.id]) {
|
|
45
40
|
return Date.now() - testTimeMap[test.id];
|
|
@@ -72,7 +67,7 @@ function CodeceptReporter(config) {
|
|
|
72
67
|
});
|
|
73
68
|
|
|
74
69
|
event.dispatcher.on(event.all.result, async () => {
|
|
75
|
-
if (videos.length && upload.isArtifactsEnabled) {
|
|
70
|
+
if (videos.length && upload.isArtifactsEnabled()) {
|
|
76
71
|
console.log(TRConstants.APP_PREFIX, `🎞️ Uploading ${videos.length} videos...`);
|
|
77
72
|
|
|
78
73
|
const promises = [];
|
|
@@ -18,13 +18,7 @@ class CucumberReporter extends Formatter {
|
|
|
18
18
|
this.failures = [];
|
|
19
19
|
this.cases = [];
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
if (!apiKey || apiKey === '') {
|
|
23
|
-
console.log(chalk.red('TESTOMATIO key is empty, ignoring reports'));
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
this.client = new TestomatClient({ apiKey });
|
|
21
|
+
this.client = new TestomatClient();
|
|
28
22
|
this.status = TRConstants.PASSED;
|
|
29
23
|
|
|
30
24
|
}
|
package/lib/adapter/jasmine.js
CHANGED
|
@@ -5,14 +5,7 @@ const { PASSED, FAILED } = require('../constants');
|
|
|
5
5
|
class JasmineReporter {
|
|
6
6
|
constructor(options) {
|
|
7
7
|
this.testTimeMap = {};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
if (!apiKey) {
|
|
11
|
-
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
this.client = new TestomatClient({ apiKey });
|
|
8
|
+
this.client = new TestomatClient({ apiKey: options?.apiKey });
|
|
16
9
|
this.client.createRun();
|
|
17
10
|
}
|
|
18
11
|
|
package/lib/adapter/jest.js
CHANGED
|
@@ -6,14 +6,8 @@ class JestReporter {
|
|
|
6
6
|
constructor(globalConfig, options) {
|
|
7
7
|
this._globalConfig = globalConfig;
|
|
8
8
|
this._options = options;
|
|
9
|
-
const { apiKey } = options;
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
this.client = new TestomatClient({ apiKey });
|
|
10
|
+
this.client = new TestomatClient({ apiKey: options?.apiKey });
|
|
17
11
|
this.client.createRun();
|
|
18
12
|
}
|
|
19
13
|
|
package/lib/adapter/mocha.js
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
2
2
|
const Mocha = require('mocha');
|
|
3
|
+
const chalk = require('chalk');
|
|
3
4
|
const TestomatClient = require('../client');
|
|
4
5
|
const TRConstants = require('../constants');
|
|
5
6
|
const { parseTest } = require('../util');
|
|
6
7
|
|
|
7
|
-
const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS } = Mocha.Runner.constants;
|
|
8
|
+
const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } = Mocha.Runner.constants;
|
|
8
9
|
|
|
9
10
|
function MochaReporter(runner, opts) {
|
|
10
11
|
Mocha.reporters.Base.call(this, runner);
|
|
11
|
-
let passes = 0;
|
|
12
|
-
let failures = 0;
|
|
12
|
+
let passes = 0; let failures = 0; let skipped = 0; // eslint-disable-line no-unused-vars
|
|
13
13
|
|
|
14
|
-
const
|
|
14
|
+
const apiKey = opts?.reporterOptions?.apiKey || process.env.TESTOMATIO;
|
|
15
|
+
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
console.log('TESTOMATIO key is empty, ignoring reports');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const client = new TestomatClient({ apiKey });
|
|
15
21
|
|
|
16
22
|
runner.on(EVENT_RUN_BEGIN, () => {
|
|
17
23
|
client.createRun();
|
|
@@ -19,7 +25,7 @@ function MochaReporter(runner, opts) {
|
|
|
19
25
|
|
|
20
26
|
runner.on(EVENT_TEST_PASS, test => {
|
|
21
27
|
passes += 1;
|
|
22
|
-
console.log('
|
|
28
|
+
console.log(chalk.bold.green('✔'), test.fullTitle());
|
|
23
29
|
const testId = parseTest(test.title);
|
|
24
30
|
client.addTestRun(testId, TRConstants.PASSED, {
|
|
25
31
|
title: test.title,
|
|
@@ -27,9 +33,19 @@ function MochaReporter(runner, opts) {
|
|
|
27
33
|
});
|
|
28
34
|
});
|
|
29
35
|
|
|
36
|
+
runner.on(EVENT_TEST_PENDING, test => {
|
|
37
|
+
skipped += 1;
|
|
38
|
+
console.log('skip: %s', test.fullTitle());
|
|
39
|
+
const testId = parseTest(test.title);
|
|
40
|
+
client.addTestRun(testId, TRConstants.SKIPPED, {
|
|
41
|
+
title: test.title,
|
|
42
|
+
time: test.duration,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
30
46
|
runner.on(EVENT_TEST_FAIL, (test, err) => {
|
|
31
47
|
failures += 1;
|
|
32
|
-
console.log('
|
|
48
|
+
console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
|
|
33
49
|
const testId = parseTest(test.title);
|
|
34
50
|
client.addTestRun(testId, TRConstants.FAILED, {
|
|
35
51
|
error: err,
|
|
@@ -39,8 +55,8 @@ function MochaReporter(runner, opts) {
|
|
|
39
55
|
});
|
|
40
56
|
|
|
41
57
|
runner.on(EVENT_RUN_END, () => {
|
|
42
|
-
console.log('end: %d/%d', passes, passes + failures);
|
|
43
58
|
const status = failures === 0 ? TRConstants.PASSED : TRConstants.FAILED;
|
|
59
|
+
console.log(chalk.bold(status), `${passes} passed, ${failures} failed`);
|
|
44
60
|
client.updateRunStatus(status);
|
|
45
61
|
});
|
|
46
62
|
}
|
|
@@ -10,13 +10,7 @@ const { parseTest } = require('../util');
|
|
|
10
10
|
|
|
11
11
|
class TestomatioReporter {
|
|
12
12
|
constructor(config = {}) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
if (!apiKey) {
|
|
16
|
-
console.log('API Key is not provided. Testomat.io report is disabled');
|
|
17
|
-
} else {
|
|
18
|
-
this.client = new TestomatioClient({ apiKey });
|
|
19
|
-
}
|
|
13
|
+
this.client = new TestomatioClient({ apiKey: config?.apiKey });
|
|
20
14
|
|
|
21
15
|
this.videos = [];
|
|
22
16
|
}
|
|
@@ -75,7 +69,7 @@ class TestomatioReporter {
|
|
|
75
69
|
async onEnd(result) {
|
|
76
70
|
if (!this.client) return;
|
|
77
71
|
|
|
78
|
-
if (this.videos.length && upload.isArtifactsEnabled) {
|
|
72
|
+
if (this.videos.length && upload.isArtifactsEnabled()) {
|
|
79
73
|
console.log(Status.APP_PREFIX, `🎞️ Uploading ${this.videos.length} videos...`);
|
|
80
74
|
|
|
81
75
|
const promises = [];
|
package/lib/adapter/webdriver.js
CHANGED
|
@@ -7,10 +7,7 @@ class WebdriverReporter extends WDIOReporter {
|
|
|
7
7
|
constructor(options) {
|
|
8
8
|
super(options);
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
if (!apiKey) return;
|
|
12
|
-
|
|
13
|
-
this.client = new TestomatClient({ apiKey });
|
|
10
|
+
this.client = new TestomatClient({ apiKey: options?.apiKey });
|
|
14
11
|
options = Object.assign(options, { stdout: true });
|
|
15
12
|
|
|
16
13
|
this._addTestPromises = [];
|
package/lib/bin/reportXml.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
2
3
|
const program = require("commander");
|
|
3
4
|
const chalk = require("chalk");
|
|
4
5
|
const glob = require('glob');
|
|
5
|
-
|
|
6
|
+
const debug = require('debug')('@testomatio/reporter:xml-cli');
|
|
6
7
|
const { APP_PREFIX } = require('../constants');
|
|
7
8
|
const XmlReader = require("../xmlReader");
|
|
8
9
|
|
|
@@ -22,7 +23,10 @@ program
|
|
|
22
23
|
pattern += '.xml';
|
|
23
24
|
}
|
|
24
25
|
let { javaTests, lang } = opts;
|
|
25
|
-
if (opts.envFile)
|
|
26
|
+
if (opts.envFile) {
|
|
27
|
+
debug('Loading env file: %s', opts.envFile)
|
|
28
|
+
require('dotenv').config({ path: opts.envFile }); // eslint-disable-line
|
|
29
|
+
}
|
|
26
30
|
if (javaTests === true) javaTests = 'src/test/java';
|
|
27
31
|
lang = lang?.toLowerCase();
|
|
28
32
|
const runReader = new XmlReader({ javaTests, lang });
|
package/lib/client.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const axios = require('axios');
|
|
2
1
|
const createCallsiteRecord = require('callsite-record');
|
|
3
2
|
const { sep, join } = require('path');
|
|
4
3
|
const fs = require('fs');
|
|
@@ -6,8 +5,8 @@ const chalk = require('chalk');
|
|
|
6
5
|
const upload = require('./fileUploader');
|
|
7
6
|
const { PASSED, FAILED, FINISHED, APP_PREFIX } = require('./constants');
|
|
8
7
|
const pipesFactory = require('./pipe');
|
|
9
|
-
const { TESTOMATIO_ENV } = process.env;
|
|
10
8
|
|
|
9
|
+
const { TESTOMATIO_ENV } = process.env;
|
|
11
10
|
|
|
12
11
|
class TestomatClient {
|
|
13
12
|
/**
|
|
@@ -15,17 +14,18 @@ class TestomatClient {
|
|
|
15
14
|
*
|
|
16
15
|
* @param {*} params
|
|
17
16
|
*/
|
|
18
|
-
constructor(params) {
|
|
19
|
-
this.apiKey = params.apiKey || process.env.TESTOMATIO;
|
|
17
|
+
constructor(params = {}) {
|
|
20
18
|
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
21
|
-
this.
|
|
19
|
+
this.createNewTests = params.createNewTests ?? !!process.env.TESTOMATIO_CREATE;
|
|
20
|
+
this.proceed = process.env.TESTOMATIO_PROCEED;
|
|
21
|
+
this.env = TESTOMATIO_ENV;
|
|
22
22
|
this.parallel = params.parallel;
|
|
23
|
-
const store = {}
|
|
23
|
+
const store = {};
|
|
24
24
|
this.pipes = pipesFactory(params, store);
|
|
25
25
|
this.queue = Promise.resolve();
|
|
26
|
-
this.axios = axios.create();
|
|
27
26
|
this.totalUploaded = 0;
|
|
28
27
|
this.version = JSON.parse(fs.readFileSync(join(__dirname, '..', 'package.json')).toString()).version;
|
|
28
|
+
console.log(APP_PREFIX, `Testomatio Reporter v${this.version}`);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -34,7 +34,9 @@ class TestomatClient {
|
|
|
34
34
|
* @returns {Promise} - resolves to Run id which should be used to update / add test
|
|
35
35
|
*/
|
|
36
36
|
createRun() {
|
|
37
|
-
|
|
37
|
+
// all pipes disabled, skipping
|
|
38
|
+
if (!this.pipes.filter(p => p.isEnabled).length) return;
|
|
39
|
+
|
|
38
40
|
const runParams = {
|
|
39
41
|
title: this.title,
|
|
40
42
|
parallel: this.parallel,
|
|
@@ -43,7 +45,9 @@ class TestomatClient {
|
|
|
43
45
|
|
|
44
46
|
global.testomatioArtifacts = [];
|
|
45
47
|
|
|
46
|
-
this.queue = this.queue
|
|
48
|
+
this.queue = this.queue
|
|
49
|
+
.then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))))
|
|
50
|
+
.catch(err => console.log(APP_PREFIX, err));
|
|
47
51
|
|
|
48
52
|
return this.queue;
|
|
49
53
|
}
|
|
@@ -54,6 +58,9 @@ class TestomatClient {
|
|
|
54
58
|
* @returns {Promise}
|
|
55
59
|
*/
|
|
56
60
|
async addTestRun(testId, status, testData = {}) {
|
|
61
|
+
// all pipes disabled, skipping
|
|
62
|
+
if (!this.pipes.filter(p => p.isEnabled).length) return;
|
|
63
|
+
|
|
57
64
|
const {
|
|
58
65
|
error = '',
|
|
59
66
|
time = '',
|
|
@@ -96,7 +103,7 @@ class TestomatClient {
|
|
|
96
103
|
uploadedFiles.push(upload.uploadFileAsBuffer(buffer, fileName, this.runId));
|
|
97
104
|
}
|
|
98
105
|
|
|
99
|
-
const artifacts = await Promise.all(uploadedFiles)
|
|
106
|
+
const artifacts = await Promise.all(uploadedFiles);
|
|
100
107
|
|
|
101
108
|
global.testomatioArtifacts = [];
|
|
102
109
|
|
|
@@ -108,7 +115,7 @@ class TestomatClient {
|
|
|
108
115
|
status,
|
|
109
116
|
stack,
|
|
110
117
|
example,
|
|
111
|
-
title,
|
|
118
|
+
title,
|
|
112
119
|
suite_title,
|
|
113
120
|
suite_id,
|
|
114
121
|
test_id,
|
|
@@ -117,7 +124,9 @@ class TestomatClient {
|
|
|
117
124
|
artifacts,
|
|
118
125
|
};
|
|
119
126
|
|
|
120
|
-
this.queue = this.queue
|
|
127
|
+
this.queue = this.queue
|
|
128
|
+
.then(() => Promise.all(this.pipes.map(p => p.addTest(data))))
|
|
129
|
+
.catch(err => console.log(APP_PREFIX, err));
|
|
121
130
|
return this.queue;
|
|
122
131
|
}
|
|
123
132
|
|
|
@@ -127,16 +136,21 @@ class TestomatClient {
|
|
|
127
136
|
* @returns {Promise}
|
|
128
137
|
*/
|
|
129
138
|
updateRunStatus(status, isParallel) {
|
|
139
|
+
// all pipes disabled, skipping
|
|
140
|
+
if (!this.pipes.filter(p => p.isEnabled).length) return;
|
|
141
|
+
|
|
130
142
|
let statusEvent;
|
|
131
143
|
if (status === FINISHED) statusEvent = 'finish';
|
|
132
144
|
if (status === PASSED) statusEvent = 'pass';
|
|
133
145
|
if (status === FAILED) statusEvent = 'fail';
|
|
134
146
|
if (isParallel) statusEvent += '_parallel';
|
|
135
|
-
const runParams = { status, statusEvent }
|
|
147
|
+
const runParams = { status, statusEvent };
|
|
136
148
|
|
|
137
|
-
this.queue = this.queue
|
|
149
|
+
this.queue = this.queue
|
|
150
|
+
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
151
|
+
.catch(err => console.log(APP_PREFIX, err));
|
|
138
152
|
|
|
139
|
-
if (upload.isArtifactsEnabled && this.totalUploaded > 0) {
|
|
153
|
+
if (upload.isArtifactsEnabled() && this.totalUploaded > 0) {
|
|
140
154
|
console.log(
|
|
141
155
|
APP_PREFIX,
|
|
142
156
|
`🗄️ Total ${this.totalUploaded} artifacts ${
|
package/lib/constants.js
CHANGED
|
@@ -2,10 +2,19 @@ const chalk = require('chalk');
|
|
|
2
2
|
|
|
3
3
|
const APP_PREFIX = chalk.gray('[TESTOMATIO]');
|
|
4
4
|
|
|
5
|
+
const CSV_HEADERS = [
|
|
6
|
+
{ id: 'suite_title', title: 'Suite_title' },
|
|
7
|
+
{ id: 'title', title: 'Title' },
|
|
8
|
+
{ id: 'status', title: 'Status' },
|
|
9
|
+
{ id: 'message', title: 'Message' },
|
|
10
|
+
{ id: 'stack', title: 'Stack' },
|
|
11
|
+
];
|
|
12
|
+
|
|
5
13
|
module.exports = Object.freeze({
|
|
6
14
|
PASSED: 'passed',
|
|
7
15
|
FAILED: 'failed',
|
|
8
16
|
SKIPPED: 'skipped',
|
|
9
17
|
FINISHED: 'finished',
|
|
10
18
|
APP_PREFIX,
|
|
19
|
+
CSV_HEADERS
|
|
11
20
|
});
|
package/lib/fileUploader.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:upload');
|
|
2
|
+
const { S3 } = require('@aws-sdk/client-s3');
|
|
3
|
+
const { Upload } = require("@aws-sdk/lib-storage");
|
|
2
4
|
const fs = require('fs');
|
|
3
5
|
const path = require('path');
|
|
4
6
|
const chalk = require('chalk');
|
|
@@ -6,18 +8,38 @@ const { randomUUID } = require('crypto');
|
|
|
6
8
|
const memoize = require('lodash.memoize');
|
|
7
9
|
const { APP_PREFIX } = require('./constants');
|
|
8
10
|
|
|
9
|
-
const
|
|
10
|
-
S3_ENDPOINT,
|
|
11
|
-
S3_REGION,
|
|
12
|
-
S3_BUCKET,
|
|
13
|
-
S3_ACCESS_KEY_ID,
|
|
14
|
-
S3_SECRET_ACCESS_KEY,
|
|
15
|
-
TESTOMATIO_DISABLE_ARTIFACTS,
|
|
16
|
-
TESTOMATIO_PRIVATE_ARTIFACTS,
|
|
17
|
-
S3_FORCE_PATH_STYLE,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
const keys = [
|
|
12
|
+
'S3_ENDPOINT',
|
|
13
|
+
'S3_REGION',
|
|
14
|
+
'S3_BUCKET',
|
|
15
|
+
'S3_ACCESS_KEY_ID',
|
|
16
|
+
'S3_SECRET_ACCESS_KEY',
|
|
17
|
+
'TESTOMATIO_DISABLE_ARTIFACTS',
|
|
18
|
+
'TESTOMATIO_PRIVATE_ARTIFACTS',
|
|
19
|
+
'S3_FORCE_PATH_STYLE',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
let config;
|
|
23
|
+
|
|
24
|
+
function getConfig() {
|
|
25
|
+
if (config) return config;
|
|
26
|
+
config = keys.reduce((acc, key) => {
|
|
27
|
+
acc[key] = process.env[key];
|
|
28
|
+
return acc;
|
|
29
|
+
}, {});
|
|
30
|
+
debug('Config', config);
|
|
31
|
+
return config;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let isEnabled;
|
|
35
|
+
|
|
36
|
+
const isArtifactsEnabled = () => {
|
|
37
|
+
if (isEnabled !== undefined) return isEnabled;
|
|
38
|
+
const { S3_BUCKET, TESTOMATIO_DISABLE_ARTIFACTS } = getConfig();
|
|
39
|
+
isEnabled = !!(S3_BUCKET && !TESTOMATIO_DISABLE_ARTIFACTS);
|
|
40
|
+
debug(`Upload is ${isEnabled ? 'enabled' : 'disabled'}`);
|
|
41
|
+
return isEnabled;
|
|
42
|
+
};
|
|
21
43
|
|
|
22
44
|
const _getFileExtBase64 = str => {
|
|
23
45
|
const type = str.charAt(0);
|
|
@@ -32,6 +54,25 @@ const _getFileExtBase64 = str => {
|
|
|
32
54
|
);
|
|
33
55
|
};
|
|
34
56
|
|
|
57
|
+
const _getS3Config = () => {
|
|
58
|
+
const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_FORCE_PATH_STYLE, S3_ENDPOINT } = getConfig();
|
|
59
|
+
|
|
60
|
+
const cfg = {
|
|
61
|
+
region: S3_REGION,
|
|
62
|
+
credentials: {
|
|
63
|
+
accessKeyId: S3_ACCESS_KEY_ID,
|
|
64
|
+
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
65
|
+
s3ForcePathStyle: S3_FORCE_PATH_STYLE,
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
if (S3_ENDPOINT) {
|
|
70
|
+
cfg.endpoint = S3_ENDPOINT;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return cfg;
|
|
74
|
+
}
|
|
75
|
+
|
|
35
76
|
const uploadUsingS3 = async (filePath, runId) => {
|
|
36
77
|
let ContentType;
|
|
37
78
|
let Key;
|
|
@@ -42,40 +83,37 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
42
83
|
Key = filePath.name;
|
|
43
84
|
}
|
|
44
85
|
|
|
45
|
-
const config = {
|
|
46
|
-
region: S3_REGION,
|
|
47
|
-
accessKeyId: S3_ACCESS_KEY_ID,
|
|
48
|
-
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
49
|
-
s3ForcePathStyle: S3_FORCE_PATH_STYLE,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
if (S3_ENDPOINT) {
|
|
53
|
-
config.endpoint = new AWS.Endpoint(S3_ENDPOINT);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const s3 = new AWS.S3(config);
|
|
57
86
|
if (!fs.existsSync(filePath)) {
|
|
58
87
|
console.error(chalk.yellow(`Artifacts file ${filePath} does not exist. Skipping...`));
|
|
59
88
|
return;
|
|
60
89
|
}
|
|
90
|
+
|
|
91
|
+
const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } = getConfig();
|
|
61
92
|
|
|
62
93
|
const file = fs.readFileSync(filePath);
|
|
63
94
|
|
|
64
95
|
Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
|
|
65
96
|
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
66
97
|
|
|
98
|
+
const s3 = new S3(_getS3Config());
|
|
99
|
+
|
|
67
100
|
try {
|
|
68
|
-
const out =
|
|
69
|
-
|
|
101
|
+
const out = new Upload({
|
|
102
|
+
client: s3,
|
|
103
|
+
|
|
104
|
+
params: {
|
|
70
105
|
Bucket: S3_BUCKET,
|
|
71
106
|
Key,
|
|
72
107
|
Body: file,
|
|
73
108
|
ContentType,
|
|
74
109
|
ACL,
|
|
75
|
-
}
|
|
76
|
-
|
|
110
|
+
}
|
|
111
|
+
});
|
|
77
112
|
|
|
78
|
-
|
|
113
|
+
await out.done();
|
|
114
|
+
debug('Uploaded', out.singleUploadResult.Location)
|
|
115
|
+
|
|
116
|
+
return out.singleUploadResult.Location;
|
|
79
117
|
} catch (e) {
|
|
80
118
|
console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
|
|
81
119
|
accessKeyId: S3_ACCESS_KEY_ID,
|
|
@@ -100,35 +138,30 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
100
138
|
};
|
|
101
139
|
|
|
102
140
|
const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
103
|
-
const config = {
|
|
104
|
-
region: S3_REGION,
|
|
105
|
-
accessKeyId: S3_ACCESS_KEY_ID,
|
|
106
|
-
secretAccessKey: S3_SECRET_ACCESS_KEY,
|
|
107
|
-
s3ForcePathStyle: S3_FORCE_PATH_STYLE,
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
if (S3_ENDPOINT) {
|
|
111
|
-
config.endpoint = new AWS.Endpoint(S3_ENDPOINT);
|
|
112
|
-
}
|
|
113
141
|
|
|
114
|
-
const
|
|
142
|
+
const { S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET } = getConfig();
|
|
115
143
|
|
|
116
144
|
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
117
145
|
|
|
118
146
|
const fileExtension = _getFileExtBase64(buffer.toString('base64'));
|
|
119
147
|
const Key = `${runId}/${fileName}${fileExtension}`;
|
|
120
148
|
|
|
149
|
+
const s3 = new S3(_getS3Config());
|
|
150
|
+
|
|
121
151
|
try {
|
|
122
|
-
const out =
|
|
123
|
-
|
|
152
|
+
const out = new Upload({
|
|
153
|
+
client: s3,
|
|
154
|
+
|
|
155
|
+
params: {
|
|
124
156
|
Bucket: S3_BUCKET,
|
|
125
157
|
Key,
|
|
126
158
|
Body: buffer,
|
|
127
159
|
ACL,
|
|
128
|
-
}
|
|
129
|
-
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
await out.done();
|
|
130
163
|
|
|
131
|
-
return out.Location;
|
|
164
|
+
return out.singleUploadResult.Location;
|
|
132
165
|
} catch (e) {
|
|
133
166
|
console.log(APP_PREFIX, chalk.bold.red(`Failed uploading '${Key}'. Please check S3 credentials`), {
|
|
134
167
|
accessKeyId: S3_ACCESS_KEY_ID,
|
|
@@ -154,7 +187,7 @@ const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
|
154
187
|
|
|
155
188
|
const uploadFileByPath = async (filePath, runId) => {
|
|
156
189
|
try {
|
|
157
|
-
if (isArtifactsEnabled) {
|
|
190
|
+
if (isArtifactsEnabled()) {
|
|
158
191
|
return uploadUsingS3(filePath, runId);
|
|
159
192
|
}
|
|
160
193
|
} catch (e) {
|
|
@@ -164,7 +197,7 @@ const uploadFileByPath = async (filePath, runId) => {
|
|
|
164
197
|
|
|
165
198
|
const uploadFileAsBuffer = async (buffer, fileName, runId) => {
|
|
166
199
|
try {
|
|
167
|
-
if (isArtifactsEnabled) {
|
|
200
|
+
if (isArtifactsEnabled()) {
|
|
168
201
|
return uploadUsingS3AsBuffer(buffer, fileName, runId);
|
|
169
202
|
}
|
|
170
203
|
} catch (e) {
|
|
@@ -175,5 +208,5 @@ const uploadFileAsBuffer = async (buffer, fileName, runId) => {
|
|
|
175
208
|
module.exports = {
|
|
176
209
|
uploadFileByPath: memoize(uploadFileByPath),
|
|
177
210
|
uploadFileAsBuffer: memoize(uploadFileAsBuffer),
|
|
178
|
-
isArtifactsEnabled,
|
|
211
|
+
isArtifactsEnabled: memoize(isArtifactsEnabled),
|
|
179
212
|
};
|