@testomatio/reporter 0.8.1 → 0.8.3
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/playwright.js +22 -28
- package/lib/fileUploader.js +23 -20
- package/lib/pipe/csv.js +1 -1
- package/lib/pipe/github.js +3 -2
- package/lib/pipe/gitlab.js +3 -1
- package/lib/xmlReader.js +6 -4
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ const path = require('path');
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const { APP_PREFIX, STATUS: Status } = require('../constants');
|
|
7
7
|
const TestomatioClient = require('../client');
|
|
8
|
-
const
|
|
8
|
+
const { isArtifactsEnabled } = require('../fileUploader');
|
|
9
9
|
const { parseTest } = require('../util');
|
|
10
10
|
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ class TestomatioReporter {
|
|
|
13
13
|
constructor(config = {}) {
|
|
14
14
|
this.client = new TestomatioClient({ apiKey: config?.apiKey });
|
|
15
15
|
|
|
16
|
-
this.
|
|
16
|
+
this.uploads = [];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
onBegin(_config, suite) {
|
|
@@ -37,32 +37,15 @@ class TestomatioReporter {
|
|
|
37
37
|
appendStep(step, steps);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (!attachment.body && !attachment.path) {
|
|
44
|
-
continue;
|
|
45
|
-
}
|
|
46
|
-
if (attachment.contentType && attachment.contentType.startsWith('video')) {
|
|
47
|
-
// video is post-processed
|
|
48
|
-
this.videos.push({ testId, attachment, title, suite_title });
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let fileName = attachment.path;
|
|
53
|
-
if (attachment.body) {
|
|
54
|
-
fileName = tmpFile();
|
|
55
|
-
fs.writeFileSync(fileName, attachment.body);
|
|
56
|
-
}
|
|
57
|
-
files.push({ path: fileName, type: attachment.contentType });
|
|
58
|
-
}
|
|
40
|
+
this.uploads.push({
|
|
41
|
+
testId, title, suite_title, files: result.attachments.filter((a) => a.body || a.path)
|
|
42
|
+
})
|
|
59
43
|
|
|
60
44
|
this.client.addTestRun(checkStatus(result.status), {
|
|
61
45
|
error,
|
|
62
46
|
test_id: testId,
|
|
63
47
|
suite_title,
|
|
64
48
|
title,
|
|
65
|
-
files,
|
|
66
49
|
steps: steps.join('\n'),
|
|
67
50
|
time: duration,
|
|
68
51
|
});
|
|
@@ -71,19 +54,30 @@ class TestomatioReporter {
|
|
|
71
54
|
async onEnd(result) {
|
|
72
55
|
if (!this.client) return;
|
|
73
56
|
|
|
74
|
-
if (this.
|
|
75
|
-
console.log(APP_PREFIX, `🎞️ Uploading ${this.
|
|
57
|
+
if (this.uploads.length && isArtifactsEnabled()) {
|
|
58
|
+
console.log(APP_PREFIX, `🎞️ Uploading ${this.uploads.length} files...`);
|
|
76
59
|
|
|
77
60
|
const promises = [];
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
61
|
+
|
|
62
|
+
for (const upload of this.uploads) {
|
|
63
|
+
|
|
64
|
+
const { title, testId, suite_title } = upload;
|
|
65
|
+
|
|
66
|
+
const files = upload.files.map((attachment) => {
|
|
67
|
+
if (attachment.body) {
|
|
68
|
+
const fileName = tmpFile();
|
|
69
|
+
fs.writeFileSync(fileName, attachment.body);
|
|
70
|
+
}
|
|
71
|
+
return { path: attachment.path, title, type: attachment.contentType };
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
|
|
81
75
|
promises.push(
|
|
82
76
|
this.client.addTestRun(undefined, {
|
|
83
77
|
test_id: testId,
|
|
84
78
|
title,
|
|
85
79
|
suite_title,
|
|
86
|
-
files
|
|
80
|
+
files,
|
|
87
81
|
}),
|
|
88
82
|
);
|
|
89
83
|
}
|
package/lib/fileUploader.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
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
5
|
const fs = require('fs');
|
|
5
6
|
const path = require('path');
|
|
6
7
|
const chalk = require('chalk');
|
|
@@ -27,10 +28,14 @@ function getConfig() {
|
|
|
27
28
|
acc[key] = process.env[key];
|
|
28
29
|
return acc;
|
|
29
30
|
}, {});
|
|
30
|
-
debug('Config', config);
|
|
31
31
|
return config;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function getMaskedConfig() {
|
|
35
|
+
return Object.fromEntries(Object.entries(getConfig())
|
|
36
|
+
.map(([key, value]) => [key, key === 'S3_SECRET_ACCESS_KEY' || key === 'S3_ACCESS_KEY_ID' ? '***' : value]));
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
let isEnabled;
|
|
35
40
|
|
|
36
41
|
const isArtifactsEnabled = () => {
|
|
@@ -89,27 +94,31 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
const {
|
|
92
|
-
|
|
97
|
+
TESTOMATIO_PRIVATE_ARTIFACTS, S3_BUCKET
|
|
93
98
|
} = getConfig();
|
|
99
|
+
|
|
100
|
+
debug('S3 config', getMaskedConfig());
|
|
101
|
+
debug('Uploading', filePath, 'to', S3_BUCKET);
|
|
94
102
|
|
|
95
|
-
|
|
103
|
+
const fileData = fs.readFileSync(filePath);
|
|
96
104
|
|
|
97
105
|
Key = `${runId}/${randomUUID()}-${Key || path.basename(filePath)}`;
|
|
98
106
|
const ACL = TESTOMATIO_PRIVATE_ARTIFACTS ? 'private' : 'public-read';
|
|
99
107
|
|
|
100
108
|
const s3 = new S3(_getS3Config());
|
|
101
109
|
|
|
110
|
+
const params = {
|
|
111
|
+
Bucket: S3_BUCKET,
|
|
112
|
+
Key,
|
|
113
|
+
Body: fileData,
|
|
114
|
+
ContentType,
|
|
115
|
+
ACL,
|
|
116
|
+
};
|
|
117
|
+
|
|
102
118
|
try {
|
|
103
119
|
const out = new Upload({
|
|
104
120
|
client: s3,
|
|
105
|
-
|
|
106
|
-
params: {
|
|
107
|
-
Bucket: S3_BUCKET,
|
|
108
|
-
Key,
|
|
109
|
-
Body: file,
|
|
110
|
-
ContentType,
|
|
111
|
-
ACL,
|
|
112
|
-
}
|
|
121
|
+
params
|
|
113
122
|
});
|
|
114
123
|
|
|
115
124
|
await out.done();
|
|
@@ -117,14 +126,8 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
117
126
|
|
|
118
127
|
return out.singleUploadResult.Location;
|
|
119
128
|
} catch (e) {
|
|
120
|
-
console.log(
|
|
121
|
-
|
|
122
|
-
secretAccessKey: S3_SECRET_ACCESS_KEY ? '**** (hidden) ***' : '(empty)',
|
|
123
|
-
region: S3_REGION,
|
|
124
|
-
bucket: S3_BUCKET,
|
|
125
|
-
acl: ACL,
|
|
126
|
-
endpoint: S3_ENDPOINT,
|
|
127
|
-
});
|
|
129
|
+
console.log(e);
|
|
130
|
+
console.log(APP_PREFIX, chalk.red(`Failed uploading '${Key}'. Please check S3 credentials`), getMaskedConfig());
|
|
128
131
|
|
|
129
132
|
console.log(APP_PREFIX, `To ${chalk.bold('disable')} artifact uploads set: TESTOMATIO_DISABLE_ARTIFACTS=1`);
|
|
130
133
|
if (!TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
@@ -136,7 +139,7 @@ const uploadUsingS3 = async (filePath, runId) => {
|
|
|
136
139
|
);
|
|
137
140
|
}
|
|
138
141
|
console.log(APP_PREFIX, '---------------');
|
|
139
|
-
}
|
|
142
|
+
}
|
|
140
143
|
};
|
|
141
144
|
|
|
142
145
|
const uploadUsingS3AsBuffer = async (buffer, fileName, runId) => {
|
package/lib/pipe/csv.js
CHANGED
|
@@ -18,11 +18,11 @@ class CsvPipe {
|
|
|
18
18
|
constructor(params, store) {
|
|
19
19
|
this.store = store || {};
|
|
20
20
|
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
21
|
-
this.isEnabled = true;
|
|
22
21
|
this.results = [];
|
|
23
22
|
|
|
24
23
|
this.outputDir = 'export';
|
|
25
24
|
this.csvFilename = process.env.TESTOMATIO_CSV_FILENAME;
|
|
25
|
+
this.isEnabled = !!this.csvFilename
|
|
26
26
|
this.isCsvSave = false;
|
|
27
27
|
|
|
28
28
|
if (this.csvFilename !== undefined && this.csvFilename.split('.').length > 0) {
|
package/lib/pipe/github.js
CHANGED
|
@@ -79,7 +79,9 @@ class GitHubPipe {
|
|
|
79
79
|
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
80
80
|
'passed',
|
|
81
81
|
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
82
|
-
| Duration | 🕐 **${humanizeDuration(
|
|
82
|
+
| Duration | 🕐 **${humanizeDuration(parseInt(this.tests.reduce((a, t) => a + (t.run_time || 0), 0), 10), {
|
|
83
|
+
maxDecimalPoints: 0,
|
|
84
|
+
})}** |
|
|
83
85
|
`;
|
|
84
86
|
if (this.store.runUrl) {
|
|
85
87
|
summary += `| Testomat.io Report | 📊 [Run #${this.store.runId}](${this.store.runUrl}) | `;
|
|
@@ -154,7 +156,6 @@ class GitHubPipe {
|
|
|
154
156
|
.join('\n');
|
|
155
157
|
body += '\n</details>';
|
|
156
158
|
}
|
|
157
|
-
|
|
158
159
|
|
|
159
160
|
await deletePreviousReport(this.octokit, owner, repo, this.issue, this.hiddenCommentData);
|
|
160
161
|
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -81,7 +81,9 @@ class GitLabPipe {
|
|
|
81
81
|
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
82
82
|
'passed',
|
|
83
83
|
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
84
|
-
| Duration | 🕐 **${humanizeDuration(
|
|
84
|
+
| Duration | 🕐 **${humanizeDuration(parseInt(this.tests.reduce((a, t) => a + (t.run_time || 0), 0), 10), {
|
|
85
|
+
maxDecimalPoints: 0,
|
|
86
|
+
})}** |
|
|
85
87
|
`;
|
|
86
88
|
|
|
87
89
|
if (this.ENV.CI_JOB_NAME && this.ENV.CI_JOB_ID) {
|
package/lib/xmlReader.js
CHANGED
|
@@ -144,8 +144,9 @@ class XmlReader {
|
|
|
144
144
|
if (!Array.isArray(result)) result = [result].filter(d => !!d);
|
|
145
145
|
|
|
146
146
|
const results = result.map(td => ({
|
|
147
|
-
id: td.executionId,
|
|
148
|
-
|
|
147
|
+
id: td.executionId,
|
|
148
|
+
// seconds are used in junit reports, but ms are used by testomatio
|
|
149
|
+
run_time: parseFloat(td.duration) * 1000,
|
|
149
150
|
status: td.outcome,
|
|
150
151
|
stack: td.Output.StdOut,
|
|
151
152
|
files: td?.ResultFiles?.ResultFile?.map(rf => rf.path)
|
|
@@ -278,7 +279,7 @@ class XmlReader {
|
|
|
278
279
|
async uploadArtifacts() {
|
|
279
280
|
for (const test of this.tests.filter(t => !!t.stack)) {
|
|
280
281
|
let files = [];
|
|
281
|
-
if (test.files
|
|
282
|
+
if (test.files?.length) files = test.files.map(f => path.join(process.cwd(), f))
|
|
282
283
|
files = [...files, ...fetchFilesFromStackTrace(test.stack)];
|
|
283
284
|
debug('Uploading files', files)
|
|
284
285
|
test.artifacts = await Promise.all(files.map(f => upload.uploadFileByPath(f, this.runId)));
|
|
@@ -361,7 +362,8 @@ function reduceTestCases(prev, item) {
|
|
|
361
362
|
stack,
|
|
362
363
|
message,
|
|
363
364
|
line: testCaseItem.lineno,
|
|
364
|
-
|
|
365
|
+
// seconds are used in junit reports, but ms are used by testomatio
|
|
366
|
+
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
365
367
|
status,
|
|
366
368
|
title: testCaseItem.name,
|
|
367
369
|
suite_title: reduceOptions.preferClassname ? testCaseItem.classname : (item.name || testCaseItem.classname),
|