@testomatio/reporter 1.5.0-beta-vitest β 1.5.0
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 +39 -44
- package/lib/adapter/playwright.js +68 -43
- package/lib/bin/reportXml.js +4 -9
- package/lib/bin/startTest.js +2 -2
- package/lib/client.js +29 -2
- package/lib/pipe/bitbucket.js +254 -0
- package/lib/pipe/gitlab.js +4 -4
- package/lib/pipe/index.js +2 -0
- package/lib/pipe/testomatio.js +18 -2
- package/lib/xmlReader.js +18 -7
- package/package.json +6 -4
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
|
@@ -129,8 +129,8 @@ function CodeceptReporter(config) {
|
|
|
129
129
|
await Promise.all(reportTestPromises);
|
|
130
130
|
|
|
131
131
|
if (upload.isArtifactsEnabled()) {
|
|
132
|
-
uploadAttachments(client, videos, 'ποΈ
|
|
133
|
-
uploadAttachments(client, traces, 'π Uploading', 'trace');
|
|
132
|
+
await uploadAttachments(client, videos, 'ποΈ Uploading', 'video');
|
|
133
|
+
await uploadAttachments(client, traces, 'π Uploading', 'trace');
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
const status = failedTests.length === 0 ? STATUS.PASSED : STATUS.FAILED;
|
|
@@ -142,7 +142,6 @@ function CodeceptReporter(config) {
|
|
|
142
142
|
if (id && failedTests.includes(id)) {
|
|
143
143
|
failedTests = failedTests.filter(failed => id !== failed);
|
|
144
144
|
}
|
|
145
|
-
const testId = getTestomatIdFromTestTitle(`${title} ${tags?.join(' ')}`);
|
|
146
145
|
const testObj = getTestAndMessage(title);
|
|
147
146
|
|
|
148
147
|
const logs = getTestLogs(test);
|
|
@@ -152,11 +151,12 @@ function CodeceptReporter(config) {
|
|
|
152
151
|
|
|
153
152
|
client.addTestRun(STATUS.PASSED, {
|
|
154
153
|
...stripExampleFromTitle(title),
|
|
154
|
+
rid: id,
|
|
155
155
|
suite_title: test.parent && test.parent.title,
|
|
156
156
|
message: testObj.message,
|
|
157
157
|
time: getDuration(test),
|
|
158
158
|
steps: global.testomatioDataStore.steps.join('\n') || null,
|
|
159
|
-
test_id:
|
|
159
|
+
test_id: getTestomatIdFromTestTitle(`${title} ${tags?.join(' ')}`),
|
|
160
160
|
logs,
|
|
161
161
|
manuallyAttachedArtifacts,
|
|
162
162
|
meta: keyValues,
|
|
@@ -179,6 +179,7 @@ function CodeceptReporter(config) {
|
|
|
179
179
|
const testId = getTestomatIdFromTestTitle(`${title} ${tags?.join(' ')}`);
|
|
180
180
|
|
|
181
181
|
client.addTestRun(STATUS.FAILED, {
|
|
182
|
+
rid: id,
|
|
182
183
|
...stripExampleFromTitle(title),
|
|
183
184
|
suite_title: suite.title,
|
|
184
185
|
test_id: testId,
|
|
@@ -194,7 +195,6 @@ function CodeceptReporter(config) {
|
|
|
194
195
|
if (test.err) error = test.err;
|
|
195
196
|
const { id, tags, title, artifacts } = test;
|
|
196
197
|
failedTests.push(id || title);
|
|
197
|
-
let testId = getTestomatIdFromTestTitle(`${title} ${tags?.join(' ')}`);
|
|
198
198
|
const testObj = getTestAndMessage(title);
|
|
199
199
|
|
|
200
200
|
const files = [];
|
|
@@ -206,32 +206,27 @@ function CodeceptReporter(config) {
|
|
|
206
206
|
const keyValues = services.keyValues.get(test.fullTitle());
|
|
207
207
|
services.setContext(null);
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
debug('artifacts', artifacts);
|
|
227
|
-
|
|
228
|
-
for (const aid in artifacts) {
|
|
229
|
-
if (aid.startsWith('video')) videos.push({ testId, title, path: artifacts[aid], type: 'video/webm' });
|
|
230
|
-
if (aid.startsWith('trace')) traces.push({ testId, title, path: artifacts[aid], type: 'application/zip' });
|
|
231
|
-
}
|
|
232
|
-
});
|
|
209
|
+
client.addTestRun(STATUS.FAILED, {
|
|
210
|
+
...stripExampleFromTitle(title),
|
|
211
|
+
rid: id,
|
|
212
|
+
test_id: getTestomatIdFromTestTitle(`${title} ${tags?.join(' ')}`),
|
|
213
|
+
suite_title: test.parent && test.parent.title,
|
|
214
|
+
error,
|
|
215
|
+
message: testObj.message,
|
|
216
|
+
time: getDuration(test),
|
|
217
|
+
files,
|
|
218
|
+
steps: global.testomatioDataStore?.steps?.join('\n') || null,
|
|
219
|
+
logs,
|
|
220
|
+
manuallyAttachedArtifacts,
|
|
221
|
+
meta: keyValues,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
debug('artifacts', artifacts);
|
|
233
225
|
|
|
234
|
-
|
|
226
|
+
for (const aid in artifacts) {
|
|
227
|
+
if (aid.startsWith('video')) videos.push({ rid: id, title, path: artifacts[aid], type: 'video/webm' });
|
|
228
|
+
if (aid.startsWith('trace')) traces.push({ rid: id, title, path: artifacts[aid], type: 'application/zip' });
|
|
229
|
+
}
|
|
235
230
|
|
|
236
231
|
// output.stop();
|
|
237
232
|
});
|
|
@@ -240,11 +235,11 @@ function CodeceptReporter(config) {
|
|
|
240
235
|
const { id, tags, title } = test;
|
|
241
236
|
if (failedTests.includes(id || title)) return;
|
|
242
237
|
|
|
243
|
-
const testId = getTestomatIdFromTestTitle(`${title} ${tags?.join(' ')}`);
|
|
244
238
|
const testObj = getTestAndMessage(title);
|
|
245
239
|
client.addTestRun(STATUS.SKIPPED, {
|
|
240
|
+
rid: id,
|
|
246
241
|
...stripExampleFromTitle(title),
|
|
247
|
-
test_id:
|
|
242
|
+
test_id: getTestomatIdFromTestTitle(`${title} ${tags?.join(' ')}`),
|
|
248
243
|
suite_title: test.parent && test.parent.title,
|
|
249
244
|
message: testObj.message,
|
|
250
245
|
time: getDuration(test),
|
|
@@ -309,21 +304,21 @@ function CodeceptReporter(config) {
|
|
|
309
304
|
}
|
|
310
305
|
|
|
311
306
|
async function uploadAttachments(client, attachments, messagePrefix, attachmentType) {
|
|
312
|
-
if (attachments
|
|
313
|
-
console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType}/-s ...`);
|
|
307
|
+
if (!attachments?.length) return;
|
|
314
308
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
309
|
+
console.log(APP_PREFIX, `Attachments: ${messagePrefix} ${attachments.length} ${attachmentType} ...`);
|
|
310
|
+
|
|
311
|
+
const promises = attachments.map(async attachment => {
|
|
312
|
+
const { rid, title, path, type } = attachment;
|
|
313
|
+
const file = { path, type, title };
|
|
314
|
+
return client.addTestRun(undefined, {
|
|
315
|
+
...stripExampleFromTitle(title),
|
|
316
|
+
rid,
|
|
317
|
+
files: [file],
|
|
323
318
|
});
|
|
319
|
+
});
|
|
324
320
|
|
|
325
|
-
|
|
326
|
-
}
|
|
321
|
+
await Promise.all(promises);
|
|
327
322
|
}
|
|
328
323
|
|
|
329
324
|
function getTestAndMessage(title) {
|
|
@@ -2,6 +2,7 @@ const chalk = require('chalk');
|
|
|
2
2
|
const crypto = require('crypto');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const path = require('path');
|
|
5
|
+
const { v4: uuidv4 } = require('uuid');
|
|
5
6
|
const fs = require('fs');
|
|
6
7
|
const { APP_PREFIX, STATUS: Status, TESTOMAT_TMP_STORAGE_DIR } = require('../constants');
|
|
7
8
|
const TestomatioClient = require('../client');
|
|
@@ -38,16 +39,15 @@ class PlaywrightReporter {
|
|
|
38
39
|
if (!this.client) return;
|
|
39
40
|
|
|
40
41
|
const { title } = test;
|
|
41
|
-
|
|
42
|
-
let testId = getTestomatIdFromTestTitle(`${title} ${test.tags?.join(' ')}`);
|
|
43
|
-
|
|
44
42
|
const { error, duration } = result;
|
|
45
|
-
|
|
46
43
|
const suite_title = test.parent ? test.parent?.title : path.basename(test?.location?.file);
|
|
47
44
|
|
|
48
45
|
const steps = [];
|
|
49
46
|
for (const step of result.steps) {
|
|
50
|
-
appendStep(step
|
|
47
|
+
const appendedStep = appendStep(step);
|
|
48
|
+
if (appendedStep) {
|
|
49
|
+
steps.push(appendedStep);
|
|
50
|
+
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const fullTestTitle = getTestContextName(test);
|
|
@@ -57,33 +57,30 @@ class PlaywrightReporter {
|
|
|
57
57
|
}
|
|
58
58
|
const manuallyAttachedArtifacts = services.artifacts.get(fullTestTitle);
|
|
59
59
|
const keyValues = services.keyValues.get(fullTestTitle);
|
|
60
|
+
const rid = test.id || test.testId || uuidv4();
|
|
61
|
+
|
|
62
|
+
const reportTestPromise = this.client.addTestRun(checkStatus(result.status), {
|
|
63
|
+
rid,
|
|
64
|
+
error,
|
|
65
|
+
test_id: getTestomatIdFromTestTitle(`${title} ${test.tags?.join(' ')}`),
|
|
66
|
+
suite_title,
|
|
67
|
+
title,
|
|
68
|
+
steps: steps.length ? steps : undefined,
|
|
69
|
+
time: duration,
|
|
70
|
+
logs,
|
|
71
|
+
manuallyAttachedArtifacts,
|
|
72
|
+
meta: keyValues,
|
|
73
|
+
file: test.location?.file,
|
|
74
|
+
});
|
|
60
75
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
logs,
|
|
70
|
-
manuallyAttachedArtifacts,
|
|
71
|
-
meta: keyValues,
|
|
72
|
-
file: test.location?.file,
|
|
73
|
-
})
|
|
74
|
-
.then(pipes => {
|
|
75
|
-
testId = pipes?.filter(p => p.pipe.includes('Testomatio'))[0]?.result?.data?.test_id;
|
|
76
|
-
|
|
77
|
-
this.uploads.push({
|
|
78
|
-
testId,
|
|
79
|
-
title,
|
|
80
|
-
suite_title,
|
|
81
|
-
files: result.attachments.filter(a => a.body || a.path),
|
|
82
|
-
file: test.location?.file,
|
|
83
|
-
});
|
|
84
|
-
// remove empty uploads
|
|
85
|
-
this.uploads = this.uploads.filter(upload => upload.files.length);
|
|
86
|
-
});
|
|
76
|
+
this.uploads.push({
|
|
77
|
+
rid,
|
|
78
|
+
title: test.title,
|
|
79
|
+
files: result.attachments.filter(a => a.body || a.path),
|
|
80
|
+
file: test.location?.file,
|
|
81
|
+
});
|
|
82
|
+
// remove empty uploads
|
|
83
|
+
this.uploads = this.uploads.filter(upload => upload.files.length);
|
|
87
84
|
|
|
88
85
|
reportTestPromises.push(reportTestPromise);
|
|
89
86
|
}
|
|
@@ -115,7 +112,7 @@ class PlaywrightReporter {
|
|
|
115
112
|
const promises = [];
|
|
116
113
|
|
|
117
114
|
for (const upload of this.uploads) {
|
|
118
|
-
const {
|
|
115
|
+
const { rid, file, title } = upload;
|
|
119
116
|
|
|
120
117
|
const files = upload.files.map(attachment => ({
|
|
121
118
|
path: this.#getArtifactPath(attachment),
|
|
@@ -125,11 +122,10 @@ class PlaywrightReporter {
|
|
|
125
122
|
|
|
126
123
|
promises.push(
|
|
127
124
|
this.client.addTestRun(undefined, {
|
|
128
|
-
|
|
125
|
+
rid,
|
|
129
126
|
title,
|
|
130
|
-
suite_title,
|
|
131
127
|
files,
|
|
132
|
-
file
|
|
128
|
+
file,
|
|
133
129
|
}),
|
|
134
130
|
);
|
|
135
131
|
}
|
|
@@ -150,18 +146,47 @@ function checkStatus(status) {
|
|
|
150
146
|
);
|
|
151
147
|
}
|
|
152
148
|
|
|
153
|
-
function appendStep(step,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
149
|
+
function appendStep(step, shift = 0) {
|
|
150
|
+
// nesting too deep, ignore those steps
|
|
151
|
+
if (shift >= 10) return;
|
|
152
|
+
|
|
153
|
+
let newCategory = step.category;
|
|
154
|
+
switch (newCategory) {
|
|
155
|
+
case 'test.step':
|
|
156
|
+
newCategory = 'user';
|
|
157
|
+
break;
|
|
158
|
+
case 'hook':
|
|
159
|
+
newCategory = 'hook';
|
|
160
|
+
break;
|
|
161
|
+
case 'attach':
|
|
162
|
+
return null; // Skip steps with category 'attach'
|
|
163
|
+
default:
|
|
164
|
+
newCategory = 'framework';
|
|
160
165
|
}
|
|
161
166
|
|
|
167
|
+
const formattedSteps = [];
|
|
162
168
|
for (const child of step.steps || []) {
|
|
163
|
-
appendStep(child,
|
|
169
|
+
const appendedChild = appendStep(child, shift + 2);
|
|
170
|
+
if (appendedChild) {
|
|
171
|
+
formattedSteps.push(appendedChild);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const resultStep = {
|
|
176
|
+
category: newCategory,
|
|
177
|
+
title: step.title,
|
|
178
|
+
duration: step.duration,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (formattedSteps.length) {
|
|
182
|
+
resultStep.steps = formattedSteps.filter(s => !!s);
|
|
164
183
|
}
|
|
184
|
+
|
|
185
|
+
if (step.error !== undefined) {
|
|
186
|
+
resultStep.error = step.error;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return resultStep;
|
|
165
190
|
}
|
|
166
191
|
|
|
167
192
|
function tmpFile(prefix = 'tmp.') {
|
package/lib/bin/reportXml.js
CHANGED
|
@@ -44,15 +44,10 @@ program
|
|
|
44
44
|
|
|
45
45
|
let timeoutTimer;
|
|
46
46
|
if (opts.timelimit) {
|
|
47
|
-
timeoutTimer = setTimeout(
|
|
48
|
-
(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
);
|
|
52
|
-
process.exit(0);
|
|
53
|
-
},
|
|
54
|
-
parseInt(opts.timelimit, 10) * 1000,
|
|
55
|
-
);
|
|
47
|
+
timeoutTimer = setTimeout(() => {
|
|
48
|
+
console.log(`β οΈ Reached timeout of ${opts.timelimit}s. Exiting... (Exit code is 0 to not fail the pipeline)`);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}, parseInt(opts.timelimit, 10) * 1000);
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
try {
|
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
|
});
|
package/lib/client.js
CHANGED
|
@@ -137,6 +137,7 @@ class Client {
|
|
|
137
137
|
* @type {TestData}
|
|
138
138
|
*/
|
|
139
139
|
const {
|
|
140
|
+
rid,
|
|
140
141
|
error = null,
|
|
141
142
|
time = 0,
|
|
142
143
|
example = null,
|
|
@@ -187,6 +188,7 @@ class Client {
|
|
|
187
188
|
this.totalUploaded += artifacts.length;
|
|
188
189
|
|
|
189
190
|
const data = {
|
|
191
|
+
rid,
|
|
190
192
|
files,
|
|
191
193
|
steps,
|
|
192
194
|
status,
|
|
@@ -225,7 +227,7 @@ class Client {
|
|
|
225
227
|
/**
|
|
226
228
|
*
|
|
227
229
|
* Updates the status of the current test run and finishes the run.
|
|
228
|
-
* @param {'passed' | 'failed' | 'finished'} status - The status of the current test run.
|
|
230
|
+
* @param {'passed' | 'failed' | 'skipped' | 'finished'} status - The status of the current test run.
|
|
229
231
|
* Must be one of "passed", "failed", or "finished"
|
|
230
232
|
* @param {boolean} [isParallel] - Whether the current test run was executed in parallel with other tests.
|
|
231
233
|
* @returns {Promise<any>} - A Promise that resolves when finishes the run.
|
|
@@ -274,9 +276,15 @@ class Client {
|
|
|
274
276
|
*/
|
|
275
277
|
formatLogs({ error, steps, logs }) {
|
|
276
278
|
error = error?.trim();
|
|
277
|
-
steps = steps?.trim();
|
|
278
279
|
logs = logs?.trim();
|
|
279
280
|
|
|
281
|
+
if (Array.isArray(steps)) {
|
|
282
|
+
steps = steps
|
|
283
|
+
.map(step => formatStep(step))
|
|
284
|
+
.flat()
|
|
285
|
+
.join('\n');
|
|
286
|
+
}
|
|
287
|
+
|
|
280
288
|
let testLogs = '';
|
|
281
289
|
if (steps) testLogs += `${chalk.bold.blue('################[ Steps ]################')}\n${steps}\n\n`;
|
|
282
290
|
if (logs) testLogs += `${chalk.bold.gray('################[ Logs ]################')}\n${logs}\n\n`;
|
|
@@ -297,6 +305,7 @@ class Client {
|
|
|
297
305
|
stack += `${message}\n`;
|
|
298
306
|
|
|
299
307
|
if (error.diff) {
|
|
308
|
+
// diff for vitest
|
|
300
309
|
stack += error.diff;
|
|
301
310
|
stack += '\n\n';
|
|
302
311
|
} else if (error.actual && error.expected && error.actual !== error.expected) {
|
|
@@ -339,6 +348,24 @@ function isNotInternalFrame(frame) {
|
|
|
339
348
|
);
|
|
340
349
|
}
|
|
341
350
|
|
|
351
|
+
function formatStep(step, shift = 0) {
|
|
352
|
+
const prefix = ' '.repeat(shift);
|
|
353
|
+
|
|
354
|
+
const lines = [];
|
|
355
|
+
|
|
356
|
+
if (step.error) {
|
|
357
|
+
lines.push(`${prefix}${chalk.red(step.title)} ${chalk.gray(`${step.duration}ms`)}`);
|
|
358
|
+
} else {
|
|
359
|
+
lines.push(`${prefix}${step.title} ${chalk.gray(`${step.duration}ms`)}`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
for (const child of step.steps || []) {
|
|
363
|
+
lines.push(...formatStep(child, shift + 2));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return lines;
|
|
367
|
+
}
|
|
368
|
+
|
|
342
369
|
/**
|
|
343
370
|
*
|
|
344
371
|
* @param {TestData} testData
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
const debug = require('debug')('@testomatio/reporter:pipe:bitbucket');
|
|
2
|
+
const { default: axios } = require('axios');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const humanizeDuration = require('humanize-duration');
|
|
5
|
+
const merge = require('lodash.merge');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { APP_PREFIX, testomatLogoURL } = require('../constants');
|
|
8
|
+
const { ansiRegExp, isSameTest } = require('../utils/utils');
|
|
9
|
+
const { statusEmoji, fullName } = require('../utils/pipe_utils');
|
|
10
|
+
|
|
11
|
+
//! BITBUCKET_ACCESS_TOKEN environment variable is required for this functionality to work
|
|
12
|
+
//! and your pipeline trigger should be a pull request
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @class BitbucketPipe
|
|
16
|
+
* @typedef {import('../../types').Pipe} Pipe
|
|
17
|
+
* @typedef {import('../../types').TestData} TestData
|
|
18
|
+
*/
|
|
19
|
+
class BitbucketPipe {
|
|
20
|
+
constructor(params, store = {}) {
|
|
21
|
+
this.isEnabled = false;
|
|
22
|
+
this.ENV = process.env;
|
|
23
|
+
this.store = store;
|
|
24
|
+
this.tests = [];
|
|
25
|
+
// Bitbucket PAT looks like bbpat-*****
|
|
26
|
+
this.token = params.BITBUCKET_ACCESS_TOKEN || process.env.BITBUCKET_ACCESS_TOKEN || this.ENV.BITBUCKET_ACCESS_TOKEN;
|
|
27
|
+
this.hiddenCommentData = `Testomat.io report: ${process.env.BITBUCKET_BRANCH || ''}`;
|
|
28
|
+
|
|
29
|
+
debug(
|
|
30
|
+
chalk.yellow('Bitbucket Pipe:'),
|
|
31
|
+
this.token ? 'TOKEN passed' : '*no token*',
|
|
32
|
+
`Project key: ${this.ENV.BITBUCKET_PROJECT_KEY}, Pull request ID: ${this.ENV.BITBUCKET_PR_ID}`,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!this.token) {
|
|
36
|
+
debug(`Hint: Bitbucket CI variables are unavailable for unprotected branches by default.`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.isEnabled = true;
|
|
41
|
+
|
|
42
|
+
debug('Bitbucket Pipe: Enabled');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async cleanLog(log) {
|
|
46
|
+
const stripAnsi = (await import('strip-ansi')).default;
|
|
47
|
+
return stripAnsi(log);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Prepare the run (if needed)
|
|
51
|
+
async prepareRun() {}
|
|
52
|
+
|
|
53
|
+
// Create a new run (if needed)
|
|
54
|
+
async createRun() {}
|
|
55
|
+
|
|
56
|
+
addTest(test) {
|
|
57
|
+
if (!this.isEnabled) return;
|
|
58
|
+
|
|
59
|
+
const index = this.tests.findIndex(t => isSameTest(t, test));
|
|
60
|
+
// Update if they were already added
|
|
61
|
+
if (index >= 0) {
|
|
62
|
+
this.tests[index] = merge(this.tests[index], test);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.tests.push(test);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async finishRun(runParams) {
|
|
70
|
+
if (!this.isEnabled) return;
|
|
71
|
+
|
|
72
|
+
if (runParams.tests) runParams.tests.forEach(t => this.addTest(t));
|
|
73
|
+
|
|
74
|
+
// Clean up the logs from ANSI codes
|
|
75
|
+
for (let i = 0; i < this.tests.length; i++) {
|
|
76
|
+
this.tests[i].message = await this.cleanLog(this.tests[i].message || '');
|
|
77
|
+
this.tests[i].stack = await this.cleanLog(this.tests[i].stack || '');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Create a comment on Bitbucket
|
|
81
|
+
const passedCount = this.tests.filter(t => t.status === 'passed').length;
|
|
82
|
+
const failedCount = this.tests.filter(t => t.status === 'failed').length;
|
|
83
|
+
const skippedCount = this.tests.filter(t => t.status === 'skipped').length;
|
|
84
|
+
|
|
85
|
+
// Constructing the table
|
|
86
|
+
let summary = `${this.hiddenCommentData}
|
|
87
|
+
|
|
88
|
+
|  | ${statusEmoji(
|
|
89
|
+
runParams.status,
|
|
90
|
+
)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
91
|
+
| --- | --- |
|
|
92
|
+
| **Tests** | βοΈ **${this.tests.length}** tests run |
|
|
93
|
+
| **Summary** | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
94
|
+
'passed',
|
|
95
|
+
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
96
|
+
| **Duration** | π **${humanizeDuration(
|
|
97
|
+
parseInt(
|
|
98
|
+
this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
|
|
99
|
+
10,
|
|
100
|
+
),
|
|
101
|
+
{
|
|
102
|
+
maxDecimalPoints: 0,
|
|
103
|
+
},
|
|
104
|
+
)}** |
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
if (this.ENV.BITBUCKET_BRANCH && this.ENV.BITBUCKET_COMMIT) {
|
|
108
|
+
// eslint-disable-next-line max-len
|
|
109
|
+
summary += `| **Job** | π· [#${this.ENV.BITBUCKET_BUILD_NUMBER}](https://bitbucket.org/${this.ENV.BITBUCKET_REPO_FULL_NAME}/pipelines/results/${this.ENV.BITBUCKET_BUILD_NUMBER}") by commit: **${this.ENV.BITBUCKET_COMMIT}** |`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const failures = this.tests
|
|
113
|
+
.filter(t => t.status === 'failed')
|
|
114
|
+
.slice(0, 20)
|
|
115
|
+
.map(t => {
|
|
116
|
+
let text = `${statusEmoji('failed')} ${fullName(t)}\n`;
|
|
117
|
+
if (t.message) {
|
|
118
|
+
text += `> ${t.message
|
|
119
|
+
.replace(/[^\x20-\x7E]/g, '')
|
|
120
|
+
.replace(ansiRegExp(), '')
|
|
121
|
+
.trim()}\n`;
|
|
122
|
+
}
|
|
123
|
+
if (t.stack) {
|
|
124
|
+
text += `\n\`\`\`diff\n${t.stack
|
|
125
|
+
.replace(ansiRegExp(), '')
|
|
126
|
+
.replace(
|
|
127
|
+
/^[\s\S]*################\[ Failure \]################/g,
|
|
128
|
+
'################[ Failure ]################',
|
|
129
|
+
)
|
|
130
|
+
.trim()}\n\`\`\`\n`;
|
|
131
|
+
}
|
|
132
|
+
if (t.artifacts && t.artifacts.length && !this.ENV.TESTOMATIO_PRIVATE_ARTIFACTS) {
|
|
133
|
+
t.artifacts
|
|
134
|
+
.filter(f => !!f)
|
|
135
|
+
.forEach(f => {
|
|
136
|
+
if (f.endsWith('.png')) {
|
|
137
|
+
text += `\n`;
|
|
138
|
+
} else {
|
|
139
|
+
text += `[π ${path.basename(f)}](${f})\n`;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
text += `\n---\n`;
|
|
144
|
+
return text;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
let body = summary;
|
|
148
|
+
|
|
149
|
+
if (failures.length) {
|
|
150
|
+
body += `\nπ₯ **Failures (${failures.length})**\n\n* ${failures.join('\n* ')}\n`;
|
|
151
|
+
if (failures.length > 10) {
|
|
152
|
+
body += `\n> Notice: Only the first 10 failures are shown.`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (this.tests.length > 0) {
|
|
157
|
+
body += `\n\n**π’ Slowest Tests**\n\n`;
|
|
158
|
+
body += this.tests
|
|
159
|
+
.sort((a, b) => b.run_time - a.run_time)
|
|
160
|
+
.slice(0, 5)
|
|
161
|
+
.map(t => `* **${fullName(t)}** (${humanizeDuration(parseFloat(t.run_time))})`)
|
|
162
|
+
.join('\n');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Construct Bitbucket API URL for comments
|
|
166
|
+
// eslint-disable-next-line max-len
|
|
167
|
+
const commentsRequestURL = `https://api.bitbucket.org/2.0/repositories/${this.ENV.BITBUCKET_WORKSPACE}/${this.ENV.BITBUCKET_REPO_SLUG}/pullrequests/${this.ENV.BITBUCKET_PR_ID}/comments`;
|
|
168
|
+
|
|
169
|
+
// Delete previous report
|
|
170
|
+
await deletePreviousReport(axios, commentsRequestURL, this.hiddenCommentData, this.token);
|
|
171
|
+
|
|
172
|
+
// Add current report
|
|
173
|
+
debug(`Adding comment via URL: ${commentsRequestURL}`);
|
|
174
|
+
debug(`Final Bitbucket API call body: ${body}`);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const addCommentResponse = await axios.post(
|
|
178
|
+
commentsRequestURL,
|
|
179
|
+
{ content: { raw: body } },
|
|
180
|
+
{
|
|
181
|
+
headers: {
|
|
182
|
+
Authorization: `Bearer ${this.token}`,
|
|
183
|
+
'Content-Type': 'application/json',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const commentID = addCommentResponse.data.id;
|
|
189
|
+
// eslint-disable-next-line max-len
|
|
190
|
+
const commentURL = `https://bitbucket.org/${this.ENV.BITBUCKET_WORKSPACE}/${this.ENV.BITBUCKET_REPO_SLUG}/pull-requests/${this.ENV.BITBUCKET_PR_ID}#comment-${commentID}`;
|
|
191
|
+
|
|
192
|
+
console.log(APP_PREFIX, chalk.yellow('Bitbucket'), `Report created: ${chalk.magenta(commentURL)}`);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
console.error(
|
|
195
|
+
APP_PREFIX,
|
|
196
|
+
chalk.yellow('Bitbucket'),
|
|
197
|
+
`Couldn't create Bitbucket report\n${err}.
|
|
198
|
+
Request URL: ${commentsRequestURL}
|
|
199
|
+
Request data: ${body}`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
toString() {
|
|
205
|
+
return 'Bitbucket Reporter';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
updateRun() {}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function deletePreviousReport(axiosInstance, commentsRequestURL, hiddenCommentData, token) {
|
|
212
|
+
if (process.env.BITBUCKET_KEEP_OUTDATED_REPORTS) return;
|
|
213
|
+
|
|
214
|
+
// Get comments
|
|
215
|
+
let comments = [];
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const response = await axiosInstance.get(commentsRequestURL, {
|
|
219
|
+
headers: {
|
|
220
|
+
Authorization: `Bearer ${token}`,
|
|
221
|
+
'Content-Type': 'application/json',
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
comments = response.data.values;
|
|
225
|
+
} catch (e) {
|
|
226
|
+
console.error('Error while attempting to retrieve comments on Bitbucket Pull Request:\n', e);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!comments.length) return;
|
|
230
|
+
|
|
231
|
+
for (const comment of comments) {
|
|
232
|
+
// If comment was left by the same workflow
|
|
233
|
+
if (comment.content.raw.includes(hiddenCommentData)) {
|
|
234
|
+
try {
|
|
235
|
+
// Delete previous comment
|
|
236
|
+
const deleteCommentURL = `${commentsRequestURL}/${comment.id}`;
|
|
237
|
+
await axiosInstance.delete(deleteCommentURL, {
|
|
238
|
+
headers: {
|
|
239
|
+
Authorization: `Bearer ${token}`,
|
|
240
|
+
'Content-Type': 'application/json',
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
} catch (e) {
|
|
244
|
+
console.warn(`Can't delete previously added comment with testomat.io report. Ignored.`);
|
|
245
|
+
}
|
|
246
|
+
// Pass next env var if need to clear all previous reports;
|
|
247
|
+
// only the last one is removed by default
|
|
248
|
+
if (!process.env.BITBUCKET_REMOVE_ALL_OUTDATED_REPORTS) break;
|
|
249
|
+
// TODO: in case of many reports should implement pagination
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = BitbucketPipe;
|
package/lib/pipe/gitlab.js
CHANGED
|
@@ -79,13 +79,13 @@ class GitLabPipe {
|
|
|
79
79
|
let summary = `${this.hiddenCommentData}
|
|
80
80
|
|
|
81
81
|
| [](https://testomat.io) | ${statusEmoji(
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
runParams.status,
|
|
83
|
+
)} ${runParams.status.toUpperCase()} ${statusEmoji(runParams.status)} |
|
|
84
84
|
| --- | --- |
|
|
85
85
|
| Tests | βοΈ **${this.tests.length}** tests run |
|
|
86
86
|
| Summary | ${statusEmoji('failed')} **${failedCount}** failed; ${statusEmoji(
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
'passed',
|
|
88
|
+
)} **${passedCount}** passed; **${statusEmoji('skipped')}** ${skippedCount} skipped |
|
|
89
89
|
| Duration | π **${humanizeDuration(
|
|
90
90
|
parseInt(
|
|
91
91
|
this.tests.reduce((a, t) => a + (t.run_time || 0), 0),
|
package/lib/pipe/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const GitHubPipe = require('./github');
|
|
|
7
7
|
const GitLabPipe = require('./gitlab');
|
|
8
8
|
const CsvPipe = require('./csv');
|
|
9
9
|
const HtmlPipe = require('./html');
|
|
10
|
+
const BitbucketPipe = require('./bitbucket');
|
|
10
11
|
|
|
11
12
|
function PipeFactory(params, opts) {
|
|
12
13
|
const extraPipes = [];
|
|
@@ -45,6 +46,7 @@ function PipeFactory(params, opts) {
|
|
|
45
46
|
new GitLabPipe(params, opts),
|
|
46
47
|
new CsvPipe(params, opts),
|
|
47
48
|
new HtmlPipe(params, opts),
|
|
49
|
+
new BitbucketPipe(params, opts),
|
|
48
50
|
...extraPipes,
|
|
49
51
|
];
|
|
50
52
|
|
package/lib/pipe/testomatio.js
CHANGED
|
@@ -42,10 +42,15 @@ class TestomatioPipe {
|
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
debug('Testomatio Pipe: Enabled');
|
|
45
|
+
|
|
46
|
+
const proxyUrl = process.env.HTTP_PROXY || process.env.HTTPS_PROXY;
|
|
47
|
+
const proxy = proxyUrl ? new URL(proxyUrl) : null;
|
|
48
|
+
|
|
45
49
|
this.parallel = params.parallel;
|
|
46
50
|
this.store = store || {};
|
|
47
51
|
this.title = params.title || process.env.TESTOMATIO_TITLE;
|
|
48
52
|
this.sharedRun = !!process.env.TESTOMATIO_SHARED_RUN;
|
|
53
|
+
this.sharedRunTimeout = !!process.env.TESTOMATIO_SHARED_RUN_TIMEOUT;
|
|
49
54
|
this.groupTitle = params.groupTitle || process.env.TESTOMATIO_RUNGROUP_TITLE;
|
|
50
55
|
this.env = process.env.TESTOMATIO_ENV;
|
|
51
56
|
this.label = process.env.TESTOMATIO_LABEL;
|
|
@@ -53,6 +58,11 @@ class TestomatioPipe {
|
|
|
53
58
|
this.axios = axios.create({
|
|
54
59
|
baseURL: `${this.url.trim()}`,
|
|
55
60
|
timeout: AXIOS_TIMEOUT,
|
|
61
|
+
proxy: proxy ? {
|
|
62
|
+
host: proxy.hostname,
|
|
63
|
+
port: proxy.port,
|
|
64
|
+
protocol: proxy.protocol,
|
|
65
|
+
} : false,
|
|
56
66
|
});
|
|
57
67
|
|
|
58
68
|
// Pass the axios instance to the retry function
|
|
@@ -175,6 +185,7 @@ class TestomatioPipe {
|
|
|
175
185
|
title: this.title,
|
|
176
186
|
label: this.label,
|
|
177
187
|
shared_run: this.sharedRun,
|
|
188
|
+
shared_run_timeout: this.sharedRunTimeout,
|
|
178
189
|
}).filter(([, value]) => !!value),
|
|
179
190
|
);
|
|
180
191
|
debug('Run params', JSON.stringify(runParams, null, 2));
|
|
@@ -287,7 +298,7 @@ class TestomatioPipe {
|
|
|
287
298
|
}
|
|
288
299
|
});
|
|
289
300
|
};
|
|
290
|
-
|
|
301
|
+
|
|
291
302
|
|
|
292
303
|
/**
|
|
293
304
|
* Uploads tests as a batch (multiple tests at once). Intended to be used with a setInterval
|
|
@@ -301,6 +312,8 @@ class TestomatioPipe {
|
|
|
301
312
|
const testsToSend = this.batch.tests.splice(0);
|
|
302
313
|
debug('π¨ Batch upload', testsToSend.length, 'tests');
|
|
303
314
|
|
|
315
|
+
testsToSend.forEach(debug);
|
|
316
|
+
|
|
304
317
|
return this.axios
|
|
305
318
|
.post(
|
|
306
319
|
`/api/reporter/${this.runId}/testrun`,
|
|
@@ -339,6 +352,9 @@ class TestomatioPipe {
|
|
|
339
352
|
addTest(data) {
|
|
340
353
|
if (!this.isEnabled) return;
|
|
341
354
|
if (!this.runId) return;
|
|
355
|
+
|
|
356
|
+
// add test ID + run ID
|
|
357
|
+
if (data.rid) data.rid = `${this.runId}-${data.rid}`;
|
|
342
358
|
data.api_key = this.apiKey;
|
|
343
359
|
data.create = this.createNewTests;
|
|
344
360
|
|
|
@@ -355,7 +371,7 @@ class TestomatioPipe {
|
|
|
355
371
|
*/
|
|
356
372
|
async finishRun(params) {
|
|
357
373
|
if (!this.isEnabled) return;
|
|
358
|
-
|
|
374
|
+
|
|
359
375
|
await this.#batchUpload();
|
|
360
376
|
if (this.batch.intervalFunction) clearInterval(this.batch.intervalFunction);
|
|
361
377
|
|
package/lib/xmlReader.js
CHANGED
|
@@ -2,6 +2,7 @@ const debug = require('debug')('@testomatio/reporter:xml');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const fs = require('fs');
|
|
5
|
+
const { randomUUID } = require('crypto');
|
|
5
6
|
const { XMLParser } = require('fast-xml-parser');
|
|
6
7
|
const { APP_PREFIX, STATUS } = require('./constants');
|
|
7
8
|
const {
|
|
@@ -17,6 +18,8 @@ const pipesFactory = require('./pipe');
|
|
|
17
18
|
const adapterFactory = require('./junit-adapter');
|
|
18
19
|
const config = require('./config');
|
|
19
20
|
|
|
21
|
+
const ridRunId = randomUUID();
|
|
22
|
+
|
|
20
23
|
const TESTOMATIO_URL = process.env.TESTOMATIO_URL || 'https://app.testomat.io';
|
|
21
24
|
const { TESTOMATIO_RUNGROUP_TITLE, TESTOMATIO_TITLE, TESTOMATIO_ENV, TESTOMATIO_RUN } = process.env;
|
|
22
25
|
|
|
@@ -457,16 +460,20 @@ function reduceTestCases(prev, item) {
|
|
|
457
460
|
if (testCaseItem.error && testCaseItem.error['#text']) stack = testCaseItem.error['#text'];
|
|
458
461
|
if (!message) message = stack.trim().split('\n')[0];
|
|
459
462
|
|
|
463
|
+
const isParametrized = item.type === 'ParameterizedMethod';
|
|
464
|
+
const preferClassname = reduceOptions.preferClassname || isParametrized;
|
|
465
|
+
|
|
460
466
|
// SpecFlow config
|
|
461
|
-
let { title, tags } = fetchProperties(
|
|
467
|
+
let { title, tags } = fetchProperties(isParametrized ? item : testCaseItem);
|
|
462
468
|
let example = null;
|
|
469
|
+
const suiteTitle = preferClassname ? testCaseItem.classname : item.name || testCaseItem.classname;
|
|
463
470
|
|
|
464
471
|
title ||= testCaseItem.name || testCaseItem.methodname || testCaseItem.classname;
|
|
465
472
|
tags ||= [];
|
|
466
473
|
|
|
467
|
-
const exampleMatches =
|
|
474
|
+
const exampleMatches = testCaseItem.name?.match(/\S\((.*?)\)/);
|
|
468
475
|
if (exampleMatches) {
|
|
469
|
-
example = { ...exampleMatches[1].split(',').map(v => v.replace(
|
|
476
|
+
example = { ...exampleMatches[1].split(',').map(v => v.trim().replace(/[^\w\s-]/g, '')) };
|
|
470
477
|
title = title.replace(/\(.*?\)/, '').trim();
|
|
471
478
|
}
|
|
472
479
|
|
|
@@ -480,12 +487,16 @@ function reduceTestCases(prev, item) {
|
|
|
480
487
|
if ('failure' in testCaseItem || 'error' in testCaseItem) status = STATUS.FAILED;
|
|
481
488
|
if ('skipped' in testCaseItem) status = STATUS.SKIPPED;
|
|
482
489
|
|
|
490
|
+
let rid = null;
|
|
491
|
+
if (testCaseItem.id) rid = `${ridRunId}-${testCaseItem.id}`;
|
|
492
|
+
|
|
483
493
|
prev.push({
|
|
484
|
-
|
|
494
|
+
rid,
|
|
485
495
|
file,
|
|
486
496
|
stack,
|
|
487
497
|
example,
|
|
488
498
|
tags,
|
|
499
|
+
create: true,
|
|
489
500
|
test_id: testId,
|
|
490
501
|
message,
|
|
491
502
|
line: testCaseItem.lineno,
|
|
@@ -493,7 +504,7 @@ function reduceTestCases(prev, item) {
|
|
|
493
504
|
run_time: parseFloat(testCaseItem.time || testCaseItem.duration) * 1000,
|
|
494
505
|
status,
|
|
495
506
|
title,
|
|
496
|
-
suite_title:
|
|
507
|
+
suite_title: suiteTitle,
|
|
497
508
|
});
|
|
498
509
|
});
|
|
499
510
|
return prev;
|
|
@@ -509,9 +520,9 @@ function processTestSuite(testsuite) {
|
|
|
509
520
|
suites = [testsuite];
|
|
510
521
|
}
|
|
511
522
|
|
|
512
|
-
const
|
|
523
|
+
const subSuites = suites.filter(s => s['test-suite'] && !testsuite['test-case']);
|
|
513
524
|
|
|
514
|
-
return
|
|
525
|
+
return [...subSuites.map(s => processTestSuite(s['test-suite'])), ...suites.reduce(reduceTestCases, [])].flat();
|
|
515
526
|
}
|
|
516
527
|
|
|
517
528
|
function fetchProperties(item) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@testomatio/reporter",
|
|
3
|
-
"version": "1.5.0
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Testomatio Reporter Client",
|
|
5
5
|
"main": "./lib/reporter.js",
|
|
6
6
|
"typings": "typings/index.d.ts",
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
"callsite-record": "^4.1.4",
|
|
18
18
|
"chalk": "^4.1.0",
|
|
19
19
|
"commander": "^4.1.1",
|
|
20
|
+
"cross-spawn": "^7.0.3",
|
|
20
21
|
"csv-writer": "^1.6.0",
|
|
21
22
|
"debug": "^4.3.4",
|
|
22
23
|
"dotenv": "^16.0.1",
|
|
23
|
-
"fast-xml-parser": "^4.
|
|
24
|
+
"fast-xml-parser": "^4.4.1",
|
|
24
25
|
"file-url": "3.0.0",
|
|
25
26
|
"glob": "^10.3",
|
|
26
27
|
"handlebars": "^4.7.8",
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
"lodash.merge": "^4.6.2",
|
|
33
34
|
"minimatch": "^9.0.3",
|
|
34
35
|
"promise-retry": "^2.0.1",
|
|
36
|
+
"strip-ansi": "^7.1.0",
|
|
35
37
|
"uuid": "^9.0.0"
|
|
36
38
|
},
|
|
37
39
|
"files": [
|
|
@@ -63,7 +65,7 @@
|
|
|
63
65
|
"@redocly/cli": "^1.0.0-beta.125",
|
|
64
66
|
"@wdio/reporter": "^7.16.13",
|
|
65
67
|
"chai": "^4.3.6",
|
|
66
|
-
"codeceptjs": "
|
|
68
|
+
"codeceptjs": "^3.5.11",
|
|
67
69
|
"cucumber": "^6.0.7",
|
|
68
70
|
"eslint": "^8.7.0",
|
|
69
71
|
"eslint-config-airbnb-base": "^15.0.0",
|
|
@@ -76,7 +78,7 @@
|
|
|
76
78
|
"mock-http-server": "^1.4.5",
|
|
77
79
|
"pino": "^8.15.0",
|
|
78
80
|
"prettier": "^3.2.5",
|
|
79
|
-
"puppeteer": "^
|
|
81
|
+
"puppeteer": "^22.15.0"
|
|
80
82
|
},
|
|
81
83
|
"bin": {
|
|
82
84
|
"report-xml": "./lib/bin/reportXml.js",
|