@testomatio/reporter 0.8.0-beta.30 → 0.8.0-beta.31
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 +16 -9
- package/lib/ArtifactStorage.js +132 -0
- package/lib/adapter/mocha.js +49 -15
- package/lib/client.js +23 -10
- package/lib/constants.js +3 -1
- package/lib/fileUploader.js +7 -3
- package/lib/pipe/csv.js +2 -0
- package/lib/pipe/github.js +8 -8
- package/lib/pipe/gitlab.js +16 -20
- package/lib/pipe/testomatio.js +24 -33
- package/lib/reporter.js +2 -0
- package/lib/util.js +20 -2
- package/lib/xmlReader.js +4 -1
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@ You can verify installed packages via `npm list` or `npm list -g`.
|
|
|
37
37
|
|
|
38
38
|
### CodeceptJS
|
|
39
39
|
|
|
40
|
-
Make sure you load all your tests using [check-
|
|
40
|
+
Make sure you load all your tests using [check-tests](https://github.com/testomatio/check-tests#cli).
|
|
41
41
|
|
|
42
42
|
Add plugin to [codecept conf](https://github.com/testomatio/reporter/blob/master/example/codecept/codecept.conf.js#L23):
|
|
43
43
|
|
|
@@ -94,7 +94,7 @@ TESTOMATIO={API_KEY} npx playwright test
|
|
|
94
94
|
|
|
95
95
|
### Mocha
|
|
96
96
|
|
|
97
|
-
Load the test using using `check-
|
|
97
|
+
Load the test using using `check-tests` if not done already. Get the test id from testomat account and add it to your mocha test like in this [example](https://github.com/testomatio/reporter/blob/master/example/mocha/test/index.test.js#L4).
|
|
98
98
|
|
|
99
99
|
Run the following command from you project folder:
|
|
100
100
|
|
|
@@ -104,7 +104,7 @@ mocha --reporter ./node_modules/testomat-reporter/lib/adapter/mocha.js --reporte
|
|
|
104
104
|
|
|
105
105
|
### Jest
|
|
106
106
|
|
|
107
|
-
Load the test using using `check-
|
|
107
|
+
Load the test using using `check-tests`. Add the test id to your tests like in this [example](https://github.com/testomatio/reporter/blob/master/example/jest/index.test.js#L1).
|
|
108
108
|
|
|
109
109
|
Add the following line to [jest.config.js](https://github.com/testomatio/reporter/blob/master/example/jest/jest.config.js#L100):
|
|
110
110
|
|
|
@@ -126,7 +126,7 @@ TESTOMATIO={API_KEY} ./node_modules/.bin/cucumber-js --format ./node_modules/@te
|
|
|
126
126
|
|
|
127
127
|
### TestCafe
|
|
128
128
|
|
|
129
|
-
Load the test using using `check-
|
|
129
|
+
Load the test using using `check-tests`.
|
|
130
130
|
|
|
131
131
|
Run the following command from you project folder:
|
|
132
132
|
|
|
@@ -138,14 +138,16 @@ TESTOMATIO={API_KEY} npx testcafe chrome -r testomatio
|
|
|
138
138
|
|
|
139
139
|
Run collection and specify `testomatio` as reporter:
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
```bash
|
|
142
|
+
TESTOMATIO={API_KEY} npx newman run {collection_name.json} -r testomatio
|
|
143
|
+
```
|
|
142
144
|
|
|
143
|
-
> _`check-
|
|
145
|
+
> _`check-tests` not supported for newman for now, tests will be created on testomatio by default_
|
|
144
146
|
|
|
145
147
|
|
|
146
148
|
### Cypress
|
|
147
149
|
|
|
148
|
-
Load the test using using `check-
|
|
150
|
+
Load the test using using `check-tests`.
|
|
149
151
|
|
|
150
152
|
Register our `cypress-plugin` in `cypress/plugins/index.js`:
|
|
151
153
|
|
|
@@ -173,7 +175,7 @@ TESTOMATIO={API_KEY} npx cypress run
|
|
|
173
175
|
|
|
174
176
|
### Protractor
|
|
175
177
|
|
|
176
|
-
Load the test using using `check-
|
|
178
|
+
Load the test using using `check-tests`.
|
|
177
179
|
|
|
178
180
|
Add the following lines to [conf.js](https://github.com/angular/protractor/blob/5.4.1/example/conf.js):
|
|
179
181
|
|
|
@@ -195,7 +197,7 @@ TESTOMATIO={API_KEY} npx start-test-run -c 'npx protractor conf.js'
|
|
|
195
197
|
|
|
196
198
|
### WebdriverIO
|
|
197
199
|
|
|
198
|
-
Load the test using using `check-
|
|
200
|
+
Load the test using using `check-tests`.
|
|
199
201
|
|
|
200
202
|
Add the following lines to [wdio.conf.js](https://webdriver.io/docs/configurationfile/):
|
|
201
203
|
|
|
@@ -583,3 +585,8 @@ If you want to finish a run started by `--launch` use `--finish` option. `TESTOM
|
|
|
583
585
|
```bash
|
|
584
586
|
TESTOMATIO={API_KEY} TESTOMATIO_RUN={RUN_ID} npx start-test-run --finish
|
|
585
587
|
```
|
|
588
|
+
|
|
589
|
+
### Debug logs
|
|
590
|
+
Pass `DEBUG` variable with module name e.g. `DEBUG=@testomatio/reporter:pipe:github`.
|
|
591
|
+
(Module name could be taken directly from the required module code).
|
|
592
|
+
To log all debug info pass `DEBUG=*`.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:storage');
|
|
2
|
+
const { join, resolve } = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const uuid = require('uuid');
|
|
6
|
+
|
|
7
|
+
const { TESTOMAT_ARTIFACT_SUFFIX } = require('./constants');
|
|
8
|
+
const { specificTestInfo } = require('./util');
|
|
9
|
+
|
|
10
|
+
class ArtifactStorage {
|
|
11
|
+
_tmpPrefix = "tsmt_reporter";
|
|
12
|
+
|
|
13
|
+
constructor(params) {
|
|
14
|
+
this.isFile = false;
|
|
15
|
+
this.isMemory = true;
|
|
16
|
+
this.storage = params?.toFile;
|
|
17
|
+
|
|
18
|
+
if (this.storage) {
|
|
19
|
+
this.isFile = true;
|
|
20
|
+
this.isMemory = false;
|
|
21
|
+
this.tmpDirFullpath = this.createTestomatTmpDir(this._tmpPrefix);
|
|
22
|
+
debug('SAVE to tmp folder mode enabled!');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static async storeToFile(tmpDirName, artifact, testSuffix = "test") {
|
|
27
|
+
const suffix = TESTOMAT_ARTIFACT_SUFFIX + uuid.v4() + testSuffix;
|
|
28
|
+
const dirpath = join(os.tmpdir(), tmpDirName);
|
|
29
|
+
const filepath = resolve(dirpath, suffix + ".json");
|
|
30
|
+
|
|
31
|
+
return await fs.promises.appendFile(filepath, JSON.stringify(artifact));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static async artifact(artifact, context) {
|
|
35
|
+
//TODO: mode for different pre-hooks. As variant - "all-tests" || "only_fail"
|
|
36
|
+
// const mode = process.env.TESTOMAT_ARTIFACTS || "only_fail";
|
|
37
|
+
|
|
38
|
+
if (Array.isArray(global.testomatioArtifacts)) {
|
|
39
|
+
debug("Saving artifacts to global storage");
|
|
40
|
+
|
|
41
|
+
global.testomatioArtifacts.push(artifact);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (global?.testomatioArtifacts === undefined) {
|
|
45
|
+
const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
|
|
46
|
+
|
|
47
|
+
const testSuffix = specificTestInfo(context.test);
|
|
48
|
+
|
|
49
|
+
if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
|
|
50
|
+
debug("Saving artifacts to memory tmp folder");
|
|
51
|
+
|
|
52
|
+
return await ArtifactStorage.storeToFile(tmpDirNames[tmpDirNames.length - 1], artifact, testSuffix);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async artifactByTestName(test) {
|
|
58
|
+
const list = [];
|
|
59
|
+
|
|
60
|
+
if (this.isFile && this.tmpDirFullpath) {
|
|
61
|
+
const files = fs.readdirSync(this.tmpDirFullpath);
|
|
62
|
+
|
|
63
|
+
for (const file of files) {
|
|
64
|
+
if (file.includes(test)) {
|
|
65
|
+
const buff = await fs.promises.readFile(this.tmpDirFullpath + "/" + file);
|
|
66
|
+
|
|
67
|
+
list.push(JSON.parse(buff.toString()));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return list;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async tmpContents() {
|
|
76
|
+
const contents = [];
|
|
77
|
+
|
|
78
|
+
if (this.isFile && this.tmpDirFullpath) {
|
|
79
|
+
const files = fs.readdirSync(this.tmpDirFullpath);
|
|
80
|
+
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
const buff = await fs.promises.readFile(this.tmpDirFullpath + "/" + file);
|
|
83
|
+
const content = buff.toString();
|
|
84
|
+
|
|
85
|
+
contents.push(content);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return contents;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
createTestomatTmpDir(clientPrefix) {
|
|
93
|
+
return fs.mkdtempSync(join(os.tmpdir(), clientPrefix));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
clearTmpDirByName(name) {
|
|
97
|
+
const tmpDirPath = join(os.tmpdir(), name);
|
|
98
|
+
|
|
99
|
+
if (fs.existsSync(tmpDirPath)) {
|
|
100
|
+
fs.rmSync(tmpDirPath, { recursive: true });
|
|
101
|
+
debug(` Testomat tmpDir = ${tmpDirPath} was deleted successfully!`);
|
|
102
|
+
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
static tmpTestomatDirNames() {
|
|
108
|
+
const subname = this._tmpPrefix || "tsmt_reporter";
|
|
109
|
+
|
|
110
|
+
return fs.readdirSync(os.tmpdir(), { withFileTypes: true })
|
|
111
|
+
.filter((item) => item.isDirectory())
|
|
112
|
+
.map((item) => item.name)
|
|
113
|
+
.filter((name) => name.includes(subname));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
cleanup() {
|
|
117
|
+
if (this.isFile && this.tmpDirFullpath) {
|
|
118
|
+
const tmpDirNames = ArtifactStorage.tmpTestomatDirNames();
|
|
119
|
+
|
|
120
|
+
if (Array.isArray(tmpDirNames) && tmpDirNames.length) {
|
|
121
|
+
for (const name of tmpDirNames) {
|
|
122
|
+
this.clearTmpDirByName(name);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
debug("The tmp folder has not been created! Nothing to delete");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = ArtifactStorage;
|
package/lib/adapter/mocha.js
CHANGED
|
@@ -1,36 +1,58 @@
|
|
|
1
1
|
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
|
|
2
|
+
const debug = require('debug')('@testomatio/reporter:adapter:mocha');
|
|
2
3
|
const Mocha = require('mocha');
|
|
3
4
|
const chalk = require('chalk');
|
|
4
5
|
const TestomatClient = require('../client');
|
|
5
6
|
const TRConstants = require('../constants');
|
|
6
|
-
const { parseTest } = require('../util');
|
|
7
|
+
const { parseTest, specificTestInfo } = require('../util');
|
|
8
|
+
const ArtifactStorage = require('../ArtifactStorage');
|
|
7
9
|
|
|
8
10
|
const { EVENT_RUN_BEGIN, EVENT_RUN_END, EVENT_TEST_FAIL, EVENT_TEST_PASS, EVENT_TEST_PENDING } = Mocha.Runner.constants;
|
|
9
11
|
|
|
10
12
|
function MochaReporter(runner, opts) {
|
|
11
13
|
Mocha.reporters.Base.call(this, runner);
|
|
12
|
-
let passes = 0; let failures = 0; let skipped = 0;
|
|
14
|
+
let passes = 0; let failures = 0; let skipped = 0;
|
|
15
|
+
let artifactStore;
|
|
13
16
|
|
|
14
17
|
const apiKey = opts?.reporterOptions?.apiKey || process.env.TESTOMATIO;
|
|
15
18
|
|
|
16
19
|
if (!apiKey) {
|
|
17
|
-
|
|
20
|
+
debug('TESTOMATIO key is empty, ignoring reports');
|
|
18
21
|
return;
|
|
19
22
|
}
|
|
20
23
|
const client = new TestomatClient({ apiKey });
|
|
21
24
|
|
|
22
25
|
runner.on(EVENT_RUN_BEGIN, () => {
|
|
23
26
|
client.createRun();
|
|
27
|
+
|
|
28
|
+
const params = {
|
|
29
|
+
"toFile": true
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
(runner._workerReporter !== undefined)
|
|
33
|
+
? artifactStore = new ArtifactStorage(params)
|
|
34
|
+
: artifactStore = new ArtifactStorage();
|
|
24
35
|
});
|
|
25
36
|
|
|
26
|
-
runner.on(EVENT_TEST_PASS, test => {
|
|
37
|
+
runner.on(EVENT_TEST_PASS, async(test) => {
|
|
27
38
|
passes += 1;
|
|
28
39
|
console.log(chalk.bold.green('✔'), test.fullTitle());
|
|
29
40
|
const testId = parseTest(test.title);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
|
|
42
|
+
const specificTest = specificTestInfo(test);
|
|
43
|
+
const content = await artifactStore.artifactByTestName(specificTest);
|
|
44
|
+
|
|
45
|
+
debug(`test=${specificTest} content = `, content);
|
|
46
|
+
|
|
47
|
+
client.addTestRun(
|
|
48
|
+
testId,
|
|
49
|
+
TRConstants.PASSED,
|
|
50
|
+
{
|
|
51
|
+
title: test.title,
|
|
52
|
+
time: test.duration,
|
|
53
|
+
},
|
|
54
|
+
content
|
|
55
|
+
);
|
|
34
56
|
});
|
|
35
57
|
|
|
36
58
|
runner.on(EVENT_TEST_PENDING, test => {
|
|
@@ -43,21 +65,33 @@ function MochaReporter(runner, opts) {
|
|
|
43
65
|
});
|
|
44
66
|
});
|
|
45
67
|
|
|
46
|
-
runner.on(EVENT_TEST_FAIL, (test, err) => {
|
|
68
|
+
runner.on(EVENT_TEST_FAIL, async(test, err) => {
|
|
47
69
|
failures += 1;
|
|
48
70
|
console.log(chalk.bold.red('✖'), test.fullTitle(), chalk.gray(err.message));
|
|
49
71
|
const testId = parseTest(test.title);
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
});
|
|
72
|
+
|
|
73
|
+
const specificTest = specificTestInfo(test);
|
|
74
|
+
const content = await artifactStore.artifactByTestName(specificTest);
|
|
75
|
+
|
|
76
|
+
debug(`fail test=${specificTest} content = `, content);
|
|
77
|
+
|
|
78
|
+
client.addTestRun(
|
|
79
|
+
testId,
|
|
80
|
+
TRConstants.FAILED, {
|
|
81
|
+
error: err,
|
|
82
|
+
title: test.title,
|
|
83
|
+
time: test.duration,
|
|
84
|
+
},
|
|
85
|
+
content
|
|
86
|
+
);
|
|
55
87
|
});
|
|
56
88
|
|
|
57
89
|
runner.on(EVENT_RUN_END, () => {
|
|
58
90
|
const status = failures === 0 ? TRConstants.PASSED : TRConstants.FAILED;
|
|
59
|
-
console.log(chalk.bold(status), `${passes} passed, ${failures} failed`);
|
|
91
|
+
console.log(chalk.bold(status), `${passes} passed, ${failures} failed, ${skipped} skipped`);
|
|
60
92
|
client.updateRunStatus(status);
|
|
93
|
+
|
|
94
|
+
artifactStore.cleanup();
|
|
61
95
|
});
|
|
62
96
|
}
|
|
63
97
|
|
package/lib/client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:client');
|
|
1
2
|
const createCallsiteRecord = require('callsite-record');
|
|
2
3
|
const { sep, join } = require('path');
|
|
3
4
|
const fs = require('fs');
|
|
@@ -48,7 +49,7 @@ class TestomatClient {
|
|
|
48
49
|
this.queue = this.queue
|
|
49
50
|
.then(() => Promise.all(this.pipes.map(p => p.createRun(runParams))))
|
|
50
51
|
.catch(err => console.log(APP_PREFIX, err));
|
|
51
|
-
|
|
52
|
+
debug('Run', this.queue);
|
|
52
53
|
return this.queue;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -57,7 +58,7 @@ class TestomatClient {
|
|
|
57
58
|
*
|
|
58
59
|
* @returns {Promise}
|
|
59
60
|
*/
|
|
60
|
-
async addTestRun(testId, status, testData = {}) {
|
|
61
|
+
async addTestRun(testId, status, testData = {}, storeArtifacts = []) {
|
|
61
62
|
// all pipes disabled, skipping
|
|
62
63
|
if (!this.pipes.filter(p => p.isEnabled).length) return;
|
|
63
64
|
|
|
@@ -68,6 +69,7 @@ class TestomatClient {
|
|
|
68
69
|
files = [],
|
|
69
70
|
filesBuffers = [],
|
|
70
71
|
steps,
|
|
72
|
+
code = null,
|
|
71
73
|
title,
|
|
72
74
|
suite_title,
|
|
73
75
|
suite_id,
|
|
@@ -89,7 +91,13 @@ class TestomatClient {
|
|
|
89
91
|
stack = this.formatSteps(stack, steps);
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
if (Array.isArray(storeArtifacts) && storeArtifacts.length) {
|
|
95
|
+
debug("CLIENT storeArtefact", storeArtifacts);
|
|
96
|
+
files.push(...storeArtifacts);
|
|
97
|
+
}
|
|
98
|
+
|
|
92
99
|
if (Array.isArray(global.testomatioArtifacts)) {
|
|
100
|
+
debug("CLIENT global[testomatioArtifacts]", global.testomatioArtifacts);
|
|
93
101
|
files.push(...global.testomatioArtifacts);
|
|
94
102
|
global.testomatioArtifacts = [];
|
|
95
103
|
}
|
|
@@ -115,6 +123,7 @@ class TestomatClient {
|
|
|
115
123
|
status,
|
|
116
124
|
stack,
|
|
117
125
|
example,
|
|
126
|
+
code,
|
|
118
127
|
title,
|
|
119
128
|
suite_title,
|
|
120
129
|
suite_id,
|
|
@@ -148,16 +157,20 @@ class TestomatClient {
|
|
|
148
157
|
|
|
149
158
|
this.queue = this.queue
|
|
150
159
|
.then(() => Promise.all(this.pipes.map(p => p.finishRun(runParams))))
|
|
160
|
+
.then(() => {
|
|
161
|
+
debug("TOTAL uploaded files", this.totalUploaded);
|
|
162
|
+
|
|
163
|
+
if (upload.isArtifactsEnabled() && this.totalUploaded > 0) {
|
|
164
|
+
console.log(
|
|
165
|
+
APP_PREFIX,
|
|
166
|
+
`🗄️ Total ${this.totalUploaded} artifacts ${
|
|
167
|
+
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
168
|
+
} uploaded to S3 bucket `,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
})
|
|
151
172
|
.catch(err => console.log(APP_PREFIX, err));
|
|
152
173
|
|
|
153
|
-
if (upload.isArtifactsEnabled() && this.totalUploaded > 0) {
|
|
154
|
-
console.log(
|
|
155
|
-
APP_PREFIX,
|
|
156
|
-
`🗄️ Total ${this.totalUploaded} artifacts ${
|
|
157
|
-
process.env.TESTOMATIO_PRIVATE_ARTIFACTS ? 'privately' : chalk.bold('publicly')
|
|
158
|
-
} uploaded to S3 bucket `,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
161
174
|
return this.queue;
|
|
162
175
|
}
|
|
163
176
|
|
package/lib/constants.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
|
|
3
3
|
const APP_PREFIX = chalk.gray('[TESTOMATIO]');
|
|
4
|
+
const TESTOMAT_ARTIFACT_SUFFIX = "testomatio_artifact_";
|
|
4
5
|
|
|
5
6
|
const CSV_HEADERS = [
|
|
6
7
|
{ id: 'suite_title', title: 'Suite_title' },
|
|
@@ -16,5 +17,6 @@ module.exports = Object.freeze({
|
|
|
16
17
|
SKIPPED: 'skipped',
|
|
17
18
|
FINISHED: 'finished',
|
|
18
19
|
APP_PREFIX,
|
|
19
|
-
CSV_HEADERS
|
|
20
|
+
CSV_HEADERS,
|
|
21
|
+
TESTOMAT_ARTIFACT_SUFFIX
|
|
20
22
|
});
|
package/lib/fileUploader.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const debug = require('debug')('@testomatio/reporter:
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:file-uploader');
|
|
2
2
|
const { S3 } = require('@aws-sdk/client-s3');
|
|
3
3
|
const { Upload } = require("@aws-sdk/lib-storage");
|
|
4
4
|
const fs = require('fs');
|
|
@@ -88,7 +88,9 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
const {
|
|
91
|
+
const {
|
|
92
|
+
S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
|
|
93
|
+
} = getConfig();
|
|
92
94
|
|
|
93
95
|
const file = fs.readFileSync(filePath);
|
|
94
96
|
|
|
@@ -139,7 +141,9 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
139
141
|
|
|
140
142
|
const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
|
141
143
|
|
|
142
|
-
const {
|
|
144
|
+
const {
|
|
145
|
+
S3_REGION, S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY, S3_ENDPOINT, TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
|
|
146
|
+
} = getConfig();
|
|
143
147
|
|
|
144
148
|
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
145
149
|
|
package/lib/pipe/csv.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:pipe:csv');
|
|
1
2
|
const path = require('path');
|
|
2
3
|
const fs = require('fs');
|
|
3
4
|
const csvWriter = require('csv-writer');
|
|
@@ -54,6 +55,7 @@ class CsvPipe {
|
|
|
54
55
|
* @param {Object} headers - csv file headers. Example: [{ id: 'suite_title', title: 'Suite_title' }]
|
|
55
56
|
*/
|
|
56
57
|
async saveToCsv(data, headers) {
|
|
58
|
+
debug('Data', data);
|
|
57
59
|
// First, we check whether the export directory exists: if yes - OK, no - create it.
|
|
58
60
|
this.checkExportDir();
|
|
59
61
|
|
package/lib/pipe/github.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:pipe:github');
|
|
1
2
|
const path = require('path');
|
|
2
3
|
const chalk = require('chalk');
|
|
3
4
|
const humanizeDuration = require('humanize-duration');
|
|
@@ -16,9 +17,7 @@ class GitHubPipe {
|
|
|
16
17
|
this.repo = process.env.GITHUB_REPOSITORY;
|
|
17
18
|
this.hiddenCommentData = `<!--- testomat.io report ${process.env.GITHUB_WORKFLOW || ''} -->`;
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
console.log(APP_PREFIX, 'GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', this.ref, this.repo);
|
|
21
|
-
}
|
|
20
|
+
debug('GitHub Pipe: ', this.token ? 'TOKEN' : '*no token*', 'Ref:', this.ref, 'Repo:', this.repo);
|
|
22
21
|
|
|
23
22
|
if (!this.token || !this.ref || !this.repo) return;
|
|
24
23
|
this.isEnabled = true;
|
|
@@ -27,9 +26,7 @@ class GitHubPipe {
|
|
|
27
26
|
this.issue = matchedIssue[1];
|
|
28
27
|
this.start = new Date();
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
console.log(APP_PREFIX, 'GitHub Pipe: Enabled');
|
|
32
|
-
}
|
|
29
|
+
debug('GitHub Pipe: Enabled');
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
async createRun() {}
|
|
@@ -37,6 +34,7 @@ class GitHubPipe {
|
|
|
37
34
|
updateRun() {}
|
|
38
35
|
|
|
39
36
|
addTest(test) {
|
|
37
|
+
debug('Adding test:', test);
|
|
40
38
|
if (!this.isEnabled) return;
|
|
41
39
|
|
|
42
40
|
const index = this.tests.findIndex(t => isSameTest(t, test));
|
|
@@ -69,7 +67,7 @@ class GitHubPipe {
|
|
|
69
67
|
let summary = `${this.hiddenCommentData}
|
|
70
68
|
|
|
71
69
|
| [](https://testomat.io) | ${
|
|
72
|
-
statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
70
|
+
statusEmoji(runParams.status,)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
73
71
|
| --- | --- |
|
|
74
72
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
75
73
|
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
@@ -155,6 +153,7 @@ class GitHubPipe {
|
|
|
155
153
|
|
|
156
154
|
// add report as comment
|
|
157
155
|
try {
|
|
156
|
+
debug('Adding comment\n', body);
|
|
158
157
|
const resp = await this.octokit.rest.issues.createComment({
|
|
159
158
|
owner,
|
|
160
159
|
repo,
|
|
@@ -163,6 +162,7 @@ class GitHubPipe {
|
|
|
163
162
|
});
|
|
164
163
|
|
|
165
164
|
const url = resp.data?.html_url;
|
|
165
|
+
debug('Comment URL:', url);
|
|
166
166
|
this.store.githubUrl = url;
|
|
167
167
|
|
|
168
168
|
console.log(APP_PREFIX, chalk.yellow('GitHub'), `Report created: ${chalk.magenta(url)}`);
|
|
@@ -221,7 +221,7 @@ async function deletePreviousReport(octokit, owner, repo, issue, hiddenCommentDa
|
|
|
221
221
|
comment_id: comment.id,
|
|
222
222
|
});
|
|
223
223
|
} catch (e) {
|
|
224
|
-
console.
|
|
224
|
+
console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);
|
|
225
225
|
}
|
|
226
226
|
|
|
227
227
|
// pass next env var if need to clear all previous reports;
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:pipe:gitlab');
|
|
1
2
|
const axios = require('axios');
|
|
2
3
|
const chalk = require('chalk');
|
|
3
4
|
const humanizeDuration = require('humanize-duration');
|
|
@@ -20,30 +21,25 @@ class GitLabPipe {
|
|
|
20
21
|
this.hiddenCommentData = `<!--- testomat.io report ${process.env.CI_JOB_NAME || ''} -->`;
|
|
21
22
|
this.axios = axios.create();
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
`Project id: ${this.ENV.CI_PROJECT_ID}, MR id: ${this.ENV.CI_MERGE_REQUEST_IID}`,
|
|
29
|
-
);
|
|
30
|
-
}
|
|
24
|
+
debug(
|
|
25
|
+
chalk.yellow('GitLab Pipe:'),
|
|
26
|
+
this.token ? 'TOKEN passed' : '*no token*',
|
|
27
|
+
`Project id: ${this.ENV.CI_PROJECT_ID}, MR id: ${this.ENV.CI_MERGE_REQUEST_IID}`,
|
|
28
|
+
);
|
|
31
29
|
|
|
32
30
|
if (!this.ENV.CI_PROJECT_ID || !this.ENV.CI_MERGE_REQUEST_IID) {
|
|
33
|
-
|
|
31
|
+
debug(`CI pipeline should be run in Merge Request to have ability to add the report comment.`);
|
|
34
32
|
return;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
35
|
if (!this.token) {
|
|
38
|
-
|
|
36
|
+
debug(`Hint: GitLab CI variables are unavailable for unprotected branches by default.`);
|
|
39
37
|
return;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
this.isEnabled = true;
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
console.log(APP_PREFIX, chalk.yellow('GitLab Pipe: Enabled'));
|
|
46
|
-
}
|
|
42
|
+
debug('GitLab Pipe: Enabled');
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
async createRun() {}
|
|
@@ -75,8 +71,7 @@ class GitLabPipe {
|
|
|
75
71
|
let summary = `${this.hiddenCommentData}
|
|
76
72
|
|
|
77
73
|
| [](https://testomat.io) | ${
|
|
78
|
-
statusEmoji(runParams.status
|
|
79
|
-
)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
74
|
+
statusEmoji(runParams.status)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
80
75
|
| --- | --- |
|
|
81
76
|
| Tests | ✔️ **${this.tests.length}** tests run |
|
|
82
77
|
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
@@ -86,8 +81,8 @@ class GitLabPipe {
|
|
|
86
81
|
`;
|
|
87
82
|
|
|
88
83
|
if (this.ENV.CI_JOB_NAME && this.ENV.CI_JOB_ID) {
|
|
89
|
-
|
|
90
|
-
|
|
84
|
+
// eslint-disable-next-line max-len
|
|
85
|
+
summary += `| Job | 👷 [${this.ENV.CI_JOB_ID}](${this.ENV.CI_JOB_URL})<br>Name: **${this.ENV.CI_JOB_NAME}**<br>Stage: **${this.ENV.CI_JOB_STAGE}** | `;
|
|
91
86
|
}
|
|
92
87
|
|
|
93
88
|
const failures = this.tests
|
|
@@ -150,7 +145,8 @@ class GitLabPipe {
|
|
|
150
145
|
await deletePreviousReport(this.axios, commentsRequestURL, this.hiddenCommentData, this.token);
|
|
151
146
|
|
|
152
147
|
// add current report
|
|
153
|
-
|
|
148
|
+
debug(`Adding comment via url: ${commentsRequestURL}`);
|
|
149
|
+
|
|
154
150
|
try {
|
|
155
151
|
const addCommentResponse = await this.axios.post(`${commentsRequestURL}?access_token=${this.token}`, { body });
|
|
156
152
|
|
|
@@ -160,7 +156,7 @@ class GitLabPipe {
|
|
|
160
156
|
|
|
161
157
|
console.log(APP_PREFIX, chalk.yellow('GitLab'), `Report created: ${chalk.magenta(commentURL)}`);
|
|
162
158
|
} catch (err) {
|
|
163
|
-
console.
|
|
159
|
+
console.error(
|
|
164
160
|
APP_PREFIX,
|
|
165
161
|
chalk.yellow('GitLab'),
|
|
166
162
|
`Couldn't create GitLab report\n${err}.
|
|
@@ -215,7 +211,7 @@ async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCom
|
|
|
215
211
|
const deleteCommentURL = `${commentsRequestURL}/${comment.id}?access_token=${token}`;
|
|
216
212
|
await axiosInstance.delete(deleteCommentURL);
|
|
217
213
|
} catch (e) {
|
|
218
|
-
console.
|
|
214
|
+
console.warn(`Can't delete previously added comment with testomat.io report. Ignore.`);
|
|
219
215
|
}
|
|
220
216
|
|
|
221
217
|
// pass next env var if need to clear all previous reports;
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:pipe:testomatio');
|
|
1
2
|
const chalk = require('chalk');
|
|
2
3
|
const axios = require('axios');
|
|
3
4
|
const JsonCycle = require('json-cycle');
|
|
@@ -6,25 +7,21 @@ const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_RUN } = process.env;
|
|
|
6
7
|
const { APP_PREFIX } = require('../constants');
|
|
7
8
|
const { isValidUrl } = require('../util');
|
|
8
9
|
|
|
10
|
+
|
|
9
11
|
if (TESTOMATIO_RUN) {
|
|
10
12
|
process.env.runId = TESTOMATIO_RUN;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
class TestomatioPipe {
|
|
14
|
-
|
|
15
|
-
constructor(params, store = {}) {
|
|
16
|
+
constructor(params, store = {}) {
|
|
16
17
|
this.isEnabled = false;
|
|
17
18
|
this.url = params.testomatioUrl || process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
18
19
|
this.apiKey = params.apiKey || process.env.TESTOMATIO;
|
|
19
|
-
|
|
20
|
-
console.log(APP_PREFIX, 'Testomatio Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
|
|
21
|
-
}
|
|
20
|
+
debug('Testomatio Pipe: ', this.apiKey ? 'API KEY' : '*no api key*');
|
|
22
21
|
if (!this.apiKey) {
|
|
23
22
|
return;
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
console.log(APP_PREFIX, 'Testomatio Pipe: Enabled');
|
|
27
|
-
}
|
|
24
|
+
debug('Testomatio Pipe: Enabled');
|
|
28
25
|
this.store = store;
|
|
29
26
|
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
30
27
|
this.axios = axios.create();
|
|
@@ -35,11 +32,7 @@ class TestomatioPipe {
|
|
|
35
32
|
|
|
36
33
|
if (!isValidUrl(this.url.trim())) {
|
|
37
34
|
this.isEnabled = false;
|
|
38
|
-
console.
|
|
39
|
-
APP_PREFIX,
|
|
40
|
-
chalk.red(`Error creating report on Testomat.io, report url '${this.url}' is invalid`),
|
|
41
|
-
);
|
|
42
|
-
|
|
35
|
+
console.error(APP_PREFIX, chalk.red(`Error creating report on Testomat.io, report url '${this.url}' is invalid`));
|
|
43
36
|
}
|
|
44
37
|
}
|
|
45
38
|
|
|
@@ -48,47 +41,46 @@ class TestomatioPipe {
|
|
|
48
41
|
|
|
49
42
|
runParams.api_key = this.apiKey.trim();
|
|
50
43
|
runParams.group_title = TESTOMATIO_RUNGROUP_TITLE;
|
|
51
|
-
|
|
44
|
+
|
|
52
45
|
if (this.runId) {
|
|
53
|
-
return this.axios.put(`${this.url.trim()}/api/reporter/${this.runId}`, runParams)
|
|
46
|
+
return this.axios.put(`${this.url.trim()}/api/reporter/${this.runId}`, runParams);
|
|
54
47
|
}
|
|
55
|
-
|
|
48
|
+
|
|
56
49
|
try {
|
|
57
50
|
const resp = await this.axios.post(`${this.url.trim()}/api/reporter`, runParams, {
|
|
58
51
|
maxContentLength: Infinity,
|
|
59
52
|
maxBodyLength: Infinity,
|
|
60
|
-
})
|
|
53
|
+
});
|
|
61
54
|
this.runId = resp.data.uid;
|
|
62
55
|
this.runUrl = `${this.url}/${resp.data.url.split('/').splice(3).join('/')}`;
|
|
63
56
|
this.store.runUrl = this.runUrl;
|
|
64
|
-
this.store.runId = this.runId;
|
|
57
|
+
this.store.runId = this.runId;
|
|
65
58
|
console.log(APP_PREFIX, '📊 Report created. Report ID:', this.runId);
|
|
66
59
|
process.env.runId = this.runId;
|
|
67
60
|
} catch (err) {
|
|
68
|
-
console.
|
|
61
|
+
console.error(
|
|
69
62
|
APP_PREFIX,
|
|
70
63
|
'Error creating Testomat.io report, please check if your API key is valid. Skipping report',
|
|
71
64
|
);
|
|
72
65
|
}
|
|
73
66
|
}
|
|
74
67
|
|
|
75
|
-
|
|
68
|
+
addTest(data) {
|
|
76
69
|
if (!this.isEnabled) return;
|
|
77
70
|
if (!this.runId) return;
|
|
78
71
|
data.api_key = this.apiKey;
|
|
79
72
|
data.create = this.createNewTests;
|
|
80
73
|
const json = JsonCycle.stringify(data);
|
|
81
74
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
} catch (err) {
|
|
75
|
+
return this.axios.post(`${this.url}/api/reporter/${this.runId}/testrun`, json, {
|
|
76
|
+
maxContentLength: Infinity,
|
|
77
|
+
maxBodyLength: Infinity,
|
|
78
|
+
headers: {
|
|
79
|
+
// Overwrite Axios's automatically set Content-Type
|
|
80
|
+
'Content-Type': 'application/json',
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
.catch((err) => {
|
|
92
84
|
if (err.response) {
|
|
93
85
|
if (err.response.status >= 400) {
|
|
94
86
|
const responseData = err.response.data || { message: '' };
|
|
@@ -103,8 +95,7 @@ class TestomatioPipe {
|
|
|
103
95
|
} else {
|
|
104
96
|
console.log(APP_PREFIX, chalk.blue(this.title), "Report couldn't be processed", err);
|
|
105
97
|
}
|
|
106
|
-
}
|
|
107
|
-
|
|
98
|
+
});
|
|
108
99
|
}
|
|
109
100
|
|
|
110
101
|
async finishRun(params) {
|
|
@@ -136,4 +127,4 @@ class TestomatioPipe {
|
|
|
136
127
|
}
|
|
137
128
|
}
|
|
138
129
|
|
|
139
|
-
module.exports = TestomatioPipe;
|
|
130
|
+
module.exports = TestomatioPipe;
|
package/lib/reporter.js
CHANGED
package/lib/util.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { URL } = require('url');
|
|
2
|
-
const { sep } = require('path');
|
|
2
|
+
const { sep, basename } = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const isValid = require('is-valid-path');
|
|
@@ -153,6 +153,23 @@ const getCurrentDateTime = () => {
|
|
|
153
153
|
today.getHours() }_${ today.getMinutes() }_${ today.getSeconds()}`;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* @param {String} test - Test adapter object
|
|
158
|
+
*
|
|
159
|
+
* @returns {String} testInfo as one string
|
|
160
|
+
*/
|
|
161
|
+
const specificTestInfo = test => {
|
|
162
|
+
//TODO: afterEach has another context.... need to add specific handler, maybe...
|
|
163
|
+
if (test?.title && test?.file) {
|
|
164
|
+
|
|
165
|
+
return basename(test.file).split(".").join("#")
|
|
166
|
+
+ "#"
|
|
167
|
+
+ test.title.split(" ").join("#");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return null;
|
|
171
|
+
};
|
|
172
|
+
|
|
156
173
|
module.exports = {
|
|
157
174
|
parseTest,
|
|
158
175
|
parseSuite,
|
|
@@ -162,5 +179,6 @@ module.exports = {
|
|
|
162
179
|
fetchSourceCode,
|
|
163
180
|
fetchSourceCodeFromStackTrace,
|
|
164
181
|
fetchFilesFromStackTrace,
|
|
165
|
-
getCurrentDateTime
|
|
182
|
+
getCurrentDateTime,
|
|
183
|
+
specificTestInfo
|
|
166
184
|
};
|
package/lib/xmlReader.js
CHANGED
|
@@ -127,12 +127,13 @@ class XmlReader {
|
|
|
127
127
|
const title = td.name.replace(/\(.*?\)/, '').trim();
|
|
128
128
|
let example = td.name.match(/\((.*?)\)/);
|
|
129
129
|
if (example) example = { ...example[1].split(',')};
|
|
130
|
-
const suite = td.TestMethod.className.split('.');
|
|
130
|
+
const suite = td.TestMethod.className.split(', ')[0].split('.');
|
|
131
131
|
const suite_title = suite.pop();
|
|
132
132
|
return {
|
|
133
133
|
title,
|
|
134
134
|
example,
|
|
135
135
|
file: suite.join('/'),
|
|
136
|
+
description: td.Description,
|
|
136
137
|
suite_title,
|
|
137
138
|
id: td.Execution.id,
|
|
138
139
|
}
|
|
@@ -154,6 +155,8 @@ class XmlReader {
|
|
|
154
155
|
const test = tests.find(t => t.id === r.id) || { };
|
|
155
156
|
r.suite_title = test.suite_title;
|
|
156
157
|
r.title = test.title?.trim();
|
|
158
|
+
if (test.code) r.code = test.code;
|
|
159
|
+
if (test.description) r.description = test.description;
|
|
157
160
|
if (test.example) r.example = test.example;
|
|
158
161
|
if (test.file) r.file = test.file;
|
|
159
162
|
r.create = true;
|
package/package.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "0.8.0-beta.
|
|
3
|
+
"version": "0.8.0-beta.31",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"repository": "git@github.com:testomatio/reporter.git",
|
|
7
7
|
"author": "Michael Bodnarchuk <davert@testomat.io>,Koushik Mohan <koushikmohan1996@gmail.com>",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@octokit/rest": "^19.0.5",
|
|
11
|
-
"aws-sdk": "^2.1072.0",
|
|
12
10
|
"@aws-sdk/client-s3": "^3.279.0",
|
|
13
11
|
"@aws-sdk/lib-storage": "^3.279.0",
|
|
12
|
+
"@octokit/rest": "^19.0.5",
|
|
13
|
+
"aws-sdk": "^2.1072.0",
|
|
14
14
|
"axios": "^0.25.0",
|
|
15
15
|
"callsite-record": "^4.1.4",
|
|
16
16
|
"chalk": "^4.1.0",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"json-cycle": "^1.3.0",
|
|
25
25
|
"lodash.memoize": "^4.1.2",
|
|
26
26
|
"lodash.merge": "^4.6.2",
|
|
27
|
-
"csv-writer": "^1.6.0"
|
|
27
|
+
"csv-writer": "^1.6.0",
|
|
28
|
+
"uuid": "^9.0.0"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
30
31
|
"bin",
|