monocart-reporter 2.9.5 → 2.9.7
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/LICENSE +21 -21
- package/README.md +1180 -1177
- package/lib/cli.js +371 -358
- package/lib/common.js +244 -244
- package/lib/default/columns.js +79 -79
- package/lib/default/options.js +95 -95
- package/lib/default/summary.js +80 -80
- package/lib/default/template.html +47 -47
- package/lib/generate-data.js +174 -174
- package/lib/generate-report.js +360 -359
- package/lib/index.d.ts +268 -268
- package/lib/index.js +253 -253
- package/lib/index.mjs +19 -19
- package/lib/merge-data.js +405 -405
- package/lib/packages/monocart-reporter-assets.js +3 -3
- package/lib/platform/concurrency.js +74 -74
- package/lib/platform/share.js +369 -369
- package/lib/plugins/audit/audit.js +119 -119
- package/lib/plugins/comments.js +124 -124
- package/lib/plugins/coverage/coverage.js +169 -169
- package/lib/plugins/email.js +76 -76
- package/lib/plugins/metadata/metadata.js +25 -25
- package/lib/plugins/network/network.js +186 -186
- package/lib/plugins/state/client.js +152 -152
- package/lib/plugins/state/state.js +194 -194
- package/lib/utils/pie.js +148 -148
- package/lib/utils/system.js +145 -145
- package/lib/utils/util.js +511 -511
- package/lib/visitor.js +915 -915
- package/package.json +89 -89
package/lib/generate-report.js
CHANGED
|
@@ -1,359 +1,360 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const EC = require('eight-colors');
|
|
4
|
-
const nodemailer = require('nodemailer');
|
|
5
|
-
const Util = require('./utils/util.js');
|
|
6
|
-
const emailPlugin = require('./plugins/email.js');
|
|
7
|
-
const Assets = require('./assets.js');
|
|
8
|
-
const { ZipFile } = require('./packages/monocart-reporter-vendor.js');
|
|
9
|
-
// ===========================================================================
|
|
10
|
-
|
|
11
|
-
const generateJson = (outputDir, filename, reportData, options) => {
|
|
12
|
-
if (!options.json) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const jsonPath = path.resolve(outputDir, `${filename}.json`);
|
|
17
|
-
Util.writeJSONSync(jsonPath, reportData);
|
|
18
|
-
reportData.jsonPath = Util.relativePath(jsonPath);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const initZipOptions = (outputDir, filename, zipOptions) => {
|
|
22
|
-
let clean = false;
|
|
23
|
-
let outputFile;
|
|
24
|
-
if (typeof zipOptions === 'string') {
|
|
25
|
-
outputFile = zipOptions;
|
|
26
|
-
} else if (typeof zipOptions === 'object') {
|
|
27
|
-
if (zipOptions.outputFile) {
|
|
28
|
-
outputFile = zipOptions.outputFile;
|
|
29
|
-
}
|
|
30
|
-
if (zipOptions.clean) {
|
|
31
|
-
clean = true;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (outputFile) {
|
|
36
|
-
if (!outputFile.endsWith('.zip')) {
|
|
37
|
-
outputFile += '.zip';
|
|
38
|
-
}
|
|
39
|
-
const zipDir = path.dirname(outputFile);
|
|
40
|
-
if (!fs.existsSync(zipDir)) {
|
|
41
|
-
fs.mkdirSync(zipDir, {
|
|
42
|
-
recursive: true
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
} else {
|
|
47
|
-
outputFile = path.resolve(outputDir, `${filename}.zip`);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
outputFile,
|
|
53
|
-
clean
|
|
54
|
-
};
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
const generateZip = (outputDir, filename, reportData, options) => {
|
|
58
|
-
|
|
59
|
-
const zipOptions = options.zip;
|
|
60
|
-
if (!zipOptions) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const { outputFile, clean } = initZipOptions(outputDir, filename, zipOptions);
|
|
65
|
-
|
|
66
|
-
const reportFiles = [];
|
|
67
|
-
return new Promise((resolve) => {
|
|
68
|
-
const zipFile = new ZipFile();
|
|
69
|
-
zipFile.outputStream.pipe(fs.createWriteStream(outputFile)).on('close', function() {
|
|
70
|
-
|
|
71
|
-
reportData.reportFiles = reportFiles;
|
|
72
|
-
|
|
73
|
-
// whether to clean the files after zipped
|
|
74
|
-
if (clean) {
|
|
75
|
-
// console.log('clean', reportFiles);
|
|
76
|
-
const dirSet = new Set();
|
|
77
|
-
reportFiles.forEach((f) => {
|
|
78
|
-
const fp = path.resolve(outputDir, f);
|
|
79
|
-
dirSet.add(path.dirname(fp));
|
|
80
|
-
Util.rmSync(fp);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// clean empty dirs
|
|
84
|
-
const dirList = Array.from(dirSet).reverse();
|
|
85
|
-
dirList.forEach((dir) => {
|
|
86
|
-
const files = fs.readdirSync(dir);
|
|
87
|
-
if (files.length === 0) {
|
|
88
|
-
Util.rmSync(dir);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
reportData.zipPath = Util.relativePath(outputFile);
|
|
95
|
-
|
|
96
|
-
resolve();
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
Util.forEachFile(outputDir, (name, dir) => {
|
|
100
|
-
const absPath = path.resolve(dir, name);
|
|
101
|
-
const relPath = Util.relativePath(absPath, outputDir);
|
|
102
|
-
reportFiles.push(relPath);
|
|
103
|
-
// console.log(relPath);
|
|
104
|
-
zipFile.addFile(absPath, relPath);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
zipFile.end();
|
|
108
|
-
});
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const generateHtml = async (outputDir, filename, reportData, options) => {
|
|
112
|
-
|
|
113
|
-
// generate html
|
|
114
|
-
let inline = true;
|
|
115
|
-
if (typeof options.inline === 'boolean') {
|
|
116
|
-
inline = options.inline;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// deps
|
|
120
|
-
const jsFiles = ['monocart-reporter-app'];
|
|
121
|
-
const htmlFile = `${filename}.html`;
|
|
122
|
-
|
|
123
|
-
const htmlPath = await Assets.saveHtmlReport({
|
|
124
|
-
inline,
|
|
125
|
-
reportData,
|
|
126
|
-
jsFiles,
|
|
127
|
-
assetsPath: './assets',
|
|
128
|
-
outputDir,
|
|
129
|
-
htmlFile,
|
|
130
|
-
|
|
131
|
-
reportDataFile: 'report-data.js'
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
reportData.htmlPath = htmlPath;
|
|
135
|
-
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const showTestResults = (reportData) => {
|
|
139
|
-
Util.logInfo(EC.cyan(reportData.name));
|
|
140
|
-
|
|
141
|
-
const summary = reportData.summary;
|
|
142
|
-
|
|
143
|
-
const colorHandler = (item, row) => {
|
|
144
|
-
|
|
145
|
-
// do not show 'errors' in red
|
|
146
|
-
if (['failed'].includes(item.id) && item.value > 0) {
|
|
147
|
-
row.name = EC.red(row.name);
|
|
148
|
-
row.value = EC.red(row.value);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (['flaky'].includes(item.id) && item.value > 0) {
|
|
153
|
-
row.name = EC.yellow(row.name);
|
|
154
|
-
row.value = EC.yellow(row.value);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if (item.id === 'passed') {
|
|
159
|
-
if (summary.failed.value === 0 && summary.passed.value > 0) {
|
|
160
|
-
row.name = EC.green(row.name);
|
|
161
|
-
row.value = EC.green(row.value);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
let rows = [];
|
|
167
|
-
|
|
168
|
-
const caseTypes = reportData.caseTypes;
|
|
169
|
-
const suiteSubs = reportData.suiteTypes.map((item) => `${item}s`);
|
|
170
|
-
|
|
171
|
-
Object.values(summary).forEach((item) => {
|
|
172
|
-
if (caseTypes.includes(item.id) || suiteSubs.includes(item.id)) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
const row = {
|
|
177
|
-
... item
|
|
178
|
-
};
|
|
179
|
-
colorHandler(item, row);
|
|
180
|
-
rows.push(row);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
const tests = rows.find((it) => it.id === 'tests');
|
|
184
|
-
tests.subs = caseTypes.map((k) => {
|
|
185
|
-
const item = {
|
|
186
|
-
... summary[k]
|
|
187
|
-
};
|
|
188
|
-
const value = `${item.value}`.padEnd(`${tests.value}`.length, ' ');
|
|
189
|
-
const percent = `(${item.percent})`;
|
|
190
|
-
const row = {
|
|
191
|
-
name: item.name,
|
|
192
|
-
value: `${value} ${percent}`
|
|
193
|
-
};
|
|
194
|
-
colorHandler(item, row);
|
|
195
|
-
return row;
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
const suites = rows.find((it) => it.id === 'suites');
|
|
199
|
-
suites.subs = suiteSubs.map((k) => {
|
|
200
|
-
return {
|
|
201
|
-
... summary[k]
|
|
202
|
-
};
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// for shards
|
|
206
|
-
const system = Array.isArray(reportData.system) ? reportData.system[0] : reportData.system;
|
|
207
|
-
|
|
208
|
-
rows = rows.concat([{
|
|
209
|
-
name: 'Playwright',
|
|
210
|
-
value: `v${system.playwright}`
|
|
211
|
-
}, {
|
|
212
|
-
name: 'Date',
|
|
213
|
-
value: new Date(reportData.date).toLocaleString()
|
|
214
|
-
}, {
|
|
215
|
-
name: 'Duration',
|
|
216
|
-
value: Util.TF(reportData.duration)
|
|
217
|
-
}]);
|
|
218
|
-
|
|
219
|
-
Util.logGrid({
|
|
220
|
-
options: {
|
|
221
|
-
headerVisible: false
|
|
222
|
-
},
|
|
223
|
-
columns: [{
|
|
224
|
-
id: 'name'
|
|
225
|
-
}, {
|
|
226
|
-
id: 'value'
|
|
227
|
-
}],
|
|
228
|
-
rows
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
const onEndHandler = async (reportData, options) => {
|
|
234
|
-
// onEnd callback
|
|
235
|
-
const onEnd = options.onEnd;
|
|
236
|
-
if (typeof onEnd !== 'function') {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// generate email data
|
|
241
|
-
emailPlugin(reportData);
|
|
242
|
-
|
|
243
|
-
// helper APIs
|
|
244
|
-
const helper = {
|
|
245
|
-
|
|
246
|
-
find: (callback) => {
|
|
247
|
-
let foundItem;
|
|
248
|
-
Util.forEach(reportData.rows, (item, parent) => {
|
|
249
|
-
if (callback(item, parent)) {
|
|
250
|
-
foundItem = item;
|
|
251
|
-
return 'break';
|
|
252
|
-
}
|
|
253
|
-
});
|
|
254
|
-
return foundItem;
|
|
255
|
-
},
|
|
256
|
-
|
|
257
|
-
filter: (callback) => {
|
|
258
|
-
const list = [];
|
|
259
|
-
Util.forEach(reportData.rows, (item, parent) => {
|
|
260
|
-
if (callback(item, parent)) {
|
|
261
|
-
list.push(item);
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
return list;
|
|
265
|
-
},
|
|
266
|
-
|
|
267
|
-
forEach: (callback) => {
|
|
268
|
-
Util.forEach(reportData.rows, callback);
|
|
269
|
-
},
|
|
270
|
-
|
|
271
|
-
sendEmail: (emailOptions) => {
|
|
272
|
-
if (!emailOptions.transport || !emailOptions.message) {
|
|
273
|
-
Util.logError('invalid email options, transport and message are required (https://nodemailer.com/)');
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
Util.logInfo('sending email ...');
|
|
277
|
-
const transporter = nodemailer.createTransport(emailOptions.transport);
|
|
278
|
-
return transporter.sendMail(emailOptions.message);
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
await onEnd(reportData, helper);
|
|
283
|
-
};
|
|
284
|
-
|
|
285
|
-
const onDataHandler = async (reportData, options, rawData) => {
|
|
286
|
-
// onData callback
|
|
287
|
-
const onData = options.onData;
|
|
288
|
-
if (typeof onData !== 'function') {
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
await onData(reportData, rawData);
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const generateReport = async (reportData, options, rawData) => {
|
|
296
|
-
|
|
297
|
-
Util.logInfo('generating test report ...');
|
|
298
|
-
|
|
299
|
-
const {
|
|
300
|
-
outputFile, outputDir, artifacts
|
|
301
|
-
} = reportData;
|
|
302
|
-
|
|
303
|
-
if (artifacts) {
|
|
304
|
-
artifacts.forEach((report) => {
|
|
305
|
-
const g = report.global ? `${EC.magenta('(global)')}` : '';
|
|
306
|
-
Util.logInfo(`${report.type}: ${EC.cyan(report.path)} ${report.name} ${g}`);
|
|
307
|
-
// convert path to relative reporter
|
|
308
|
-
if (report.path) {
|
|
309
|
-
report.path = Util.relativePath(report.path, outputDir);
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
reportData.summary.artifacts = {
|
|
314
|
-
name: 'Artifacts',
|
|
315
|
-
value: artifacts.length
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
await onDataHandler(reportData, options, rawData);
|
|
321
|
-
|
|
322
|
-
// console.log(reportData);
|
|
323
|
-
const filename = path.basename(outputFile, '.html');
|
|
324
|
-
|
|
325
|
-
await generateHtml(outputDir, filename, reportData, options);
|
|
326
|
-
await generateJson(outputDir, filename, reportData, options);
|
|
327
|
-
await generateZip(outputDir, filename, reportData, options);
|
|
328
|
-
|
|
329
|
-
await onEndHandler(reportData, options);
|
|
330
|
-
|
|
331
|
-
// after onEnd for summary changes
|
|
332
|
-
showTestResults(reportData);
|
|
333
|
-
|
|
334
|
-
// clean .cache for merge
|
|
335
|
-
if (options.cacheDir) {
|
|
336
|
-
Util.rmSync(options.cacheDir);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const {
|
|
340
|
-
htmlPath, jsonPath, zipPath
|
|
341
|
-
} = reportData;
|
|
342
|
-
|
|
343
|
-
const assets = [];
|
|
344
|
-
if (jsonPath) {
|
|
345
|
-
assets.push(`json: ${EC.cyan(jsonPath)}`);
|
|
346
|
-
}
|
|
347
|
-
if (zipPath) {
|
|
348
|
-
assets.push(`zip: ${EC.cyan(zipPath)}`);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
if (assets.length) {
|
|
352
|
-
Util.logInfo(assets.join(' '));
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
Util.logInfo(`view report: ${EC.cyan(`npx monocart show-report ${htmlPath}`)}`);
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const EC = require('eight-colors');
|
|
4
|
+
const nodemailer = require('nodemailer');
|
|
5
|
+
const Util = require('./utils/util.js');
|
|
6
|
+
const emailPlugin = require('./plugins/email.js');
|
|
7
|
+
const Assets = require('./assets.js');
|
|
8
|
+
const { ZipFile } = require('./packages/monocart-reporter-vendor.js');
|
|
9
|
+
// ===========================================================================
|
|
10
|
+
|
|
11
|
+
const generateJson = (outputDir, filename, reportData, options) => {
|
|
12
|
+
if (!options.json) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const jsonPath = path.resolve(outputDir, `${filename}.json`);
|
|
17
|
+
Util.writeJSONSync(jsonPath, reportData);
|
|
18
|
+
reportData.jsonPath = Util.relativePath(jsonPath);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const initZipOptions = (outputDir, filename, zipOptions) => {
|
|
22
|
+
let clean = false;
|
|
23
|
+
let outputFile;
|
|
24
|
+
if (typeof zipOptions === 'string') {
|
|
25
|
+
outputFile = zipOptions;
|
|
26
|
+
} else if (typeof zipOptions === 'object') {
|
|
27
|
+
if (zipOptions.outputFile) {
|
|
28
|
+
outputFile = zipOptions.outputFile;
|
|
29
|
+
}
|
|
30
|
+
if (zipOptions.clean) {
|
|
31
|
+
clean = true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (outputFile) {
|
|
36
|
+
if (!outputFile.endsWith('.zip')) {
|
|
37
|
+
outputFile += '.zip';
|
|
38
|
+
}
|
|
39
|
+
const zipDir = path.dirname(outputFile);
|
|
40
|
+
if (!fs.existsSync(zipDir)) {
|
|
41
|
+
fs.mkdirSync(zipDir, {
|
|
42
|
+
recursive: true
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
} else {
|
|
47
|
+
outputFile = path.resolve(outputDir, `${filename}.zip`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
outputFile,
|
|
53
|
+
clean
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const generateZip = (outputDir, filename, reportData, options) => {
|
|
58
|
+
|
|
59
|
+
const zipOptions = options.zip;
|
|
60
|
+
if (!zipOptions) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { outputFile, clean } = initZipOptions(outputDir, filename, zipOptions);
|
|
65
|
+
|
|
66
|
+
const reportFiles = [];
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
const zipFile = new ZipFile();
|
|
69
|
+
zipFile.outputStream.pipe(fs.createWriteStream(outputFile)).on('close', function() {
|
|
70
|
+
|
|
71
|
+
reportData.reportFiles = reportFiles;
|
|
72
|
+
|
|
73
|
+
// whether to clean the files after zipped
|
|
74
|
+
if (clean) {
|
|
75
|
+
// console.log('clean', reportFiles);
|
|
76
|
+
const dirSet = new Set();
|
|
77
|
+
reportFiles.forEach((f) => {
|
|
78
|
+
const fp = path.resolve(outputDir, f);
|
|
79
|
+
dirSet.add(path.dirname(fp));
|
|
80
|
+
Util.rmSync(fp);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// clean empty dirs
|
|
84
|
+
const dirList = Array.from(dirSet).reverse();
|
|
85
|
+
dirList.forEach((dir) => {
|
|
86
|
+
const files = fs.readdirSync(dir);
|
|
87
|
+
if (files.length === 0) {
|
|
88
|
+
Util.rmSync(dir);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
reportData.zipPath = Util.relativePath(outputFile);
|
|
95
|
+
|
|
96
|
+
resolve();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
Util.forEachFile(outputDir, (name, dir) => {
|
|
100
|
+
const absPath = path.resolve(dir, name);
|
|
101
|
+
const relPath = Util.relativePath(absPath, outputDir);
|
|
102
|
+
reportFiles.push(relPath);
|
|
103
|
+
// console.log(relPath);
|
|
104
|
+
zipFile.addFile(absPath, relPath);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
zipFile.end();
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const generateHtml = async (outputDir, filename, reportData, options) => {
|
|
112
|
+
|
|
113
|
+
// generate html
|
|
114
|
+
let inline = true;
|
|
115
|
+
if (typeof options.inline === 'boolean') {
|
|
116
|
+
inline = options.inline;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// deps
|
|
120
|
+
const jsFiles = ['monocart-reporter-app'];
|
|
121
|
+
const htmlFile = `${filename}.html`;
|
|
122
|
+
|
|
123
|
+
const htmlPath = await Assets.saveHtmlReport({
|
|
124
|
+
inline,
|
|
125
|
+
reportData,
|
|
126
|
+
jsFiles,
|
|
127
|
+
assetsPath: './assets',
|
|
128
|
+
outputDir,
|
|
129
|
+
htmlFile,
|
|
130
|
+
|
|
131
|
+
reportDataFile: 'report-data.js'
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
reportData.htmlPath = htmlPath;
|
|
135
|
+
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const showTestResults = (reportData) => {
|
|
139
|
+
Util.logInfo(EC.cyan(reportData.name));
|
|
140
|
+
|
|
141
|
+
const summary = reportData.summary;
|
|
142
|
+
|
|
143
|
+
const colorHandler = (item, row) => {
|
|
144
|
+
|
|
145
|
+
// do not show 'errors' in red
|
|
146
|
+
if (['failed'].includes(item.id) && item.value > 0) {
|
|
147
|
+
row.name = EC.red(row.name);
|
|
148
|
+
row.value = EC.red(row.value);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (['flaky'].includes(item.id) && item.value > 0) {
|
|
153
|
+
row.name = EC.yellow(row.name);
|
|
154
|
+
row.value = EC.yellow(row.value);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (item.id === 'passed') {
|
|
159
|
+
if (summary.failed.value === 0 && summary.passed.value > 0) {
|
|
160
|
+
row.name = EC.green(row.name);
|
|
161
|
+
row.value = EC.green(row.value);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
let rows = [];
|
|
167
|
+
|
|
168
|
+
const caseTypes = reportData.caseTypes;
|
|
169
|
+
const suiteSubs = reportData.suiteTypes.map((item) => `${item}s`);
|
|
170
|
+
|
|
171
|
+
Object.values(summary).forEach((item) => {
|
|
172
|
+
if (caseTypes.includes(item.id) || suiteSubs.includes(item.id)) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const row = {
|
|
177
|
+
... item
|
|
178
|
+
};
|
|
179
|
+
colorHandler(item, row);
|
|
180
|
+
rows.push(row);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const tests = rows.find((it) => it.id === 'tests');
|
|
184
|
+
tests.subs = caseTypes.map((k) => {
|
|
185
|
+
const item = {
|
|
186
|
+
... summary[k]
|
|
187
|
+
};
|
|
188
|
+
const value = `${item.value}`.padEnd(`${tests.value}`.length, ' ');
|
|
189
|
+
const percent = `(${item.percent})`;
|
|
190
|
+
const row = {
|
|
191
|
+
name: item.name,
|
|
192
|
+
value: `${value} ${percent}`
|
|
193
|
+
};
|
|
194
|
+
colorHandler(item, row);
|
|
195
|
+
return row;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const suites = rows.find((it) => it.id === 'suites');
|
|
199
|
+
suites.subs = suiteSubs.map((k) => {
|
|
200
|
+
return {
|
|
201
|
+
... summary[k]
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// for shards
|
|
206
|
+
const system = Array.isArray(reportData.system) ? reportData.system[0] : reportData.system;
|
|
207
|
+
|
|
208
|
+
rows = rows.concat([{
|
|
209
|
+
name: 'Playwright',
|
|
210
|
+
value: `v${system.playwright}`
|
|
211
|
+
}, {
|
|
212
|
+
name: 'Date',
|
|
213
|
+
value: new Date(reportData.date).toLocaleString()
|
|
214
|
+
}, {
|
|
215
|
+
name: 'Duration',
|
|
216
|
+
value: Util.TF(reportData.duration)
|
|
217
|
+
}]);
|
|
218
|
+
|
|
219
|
+
Util.logGrid({
|
|
220
|
+
options: {
|
|
221
|
+
headerVisible: false
|
|
222
|
+
},
|
|
223
|
+
columns: [{
|
|
224
|
+
id: 'name'
|
|
225
|
+
}, {
|
|
226
|
+
id: 'value'
|
|
227
|
+
}],
|
|
228
|
+
rows
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const onEndHandler = async (reportData, options) => {
|
|
234
|
+
// onEnd callback
|
|
235
|
+
const onEnd = options.onEnd;
|
|
236
|
+
if (typeof onEnd !== 'function') {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// generate email data
|
|
241
|
+
emailPlugin(reportData);
|
|
242
|
+
|
|
243
|
+
// helper APIs
|
|
244
|
+
const helper = {
|
|
245
|
+
|
|
246
|
+
find: (callback) => {
|
|
247
|
+
let foundItem;
|
|
248
|
+
Util.forEach(reportData.rows, (item, parent) => {
|
|
249
|
+
if (callback(item, parent)) {
|
|
250
|
+
foundItem = item;
|
|
251
|
+
return 'break';
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
return foundItem;
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
filter: (callback) => {
|
|
258
|
+
const list = [];
|
|
259
|
+
Util.forEach(reportData.rows, (item, parent) => {
|
|
260
|
+
if (callback(item, parent)) {
|
|
261
|
+
list.push(item);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
return list;
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
forEach: (callback) => {
|
|
268
|
+
Util.forEach(reportData.rows, callback);
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
sendEmail: (emailOptions) => {
|
|
272
|
+
if (!emailOptions.transport || !emailOptions.message) {
|
|
273
|
+
Util.logError('invalid email options, transport and message are required (https://nodemailer.com/)');
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
Util.logInfo('sending email ...');
|
|
277
|
+
const transporter = nodemailer.createTransport(emailOptions.transport);
|
|
278
|
+
return transporter.sendMail(emailOptions.message);
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
await onEnd(reportData, helper);
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const onDataHandler = async (reportData, options, rawData) => {
|
|
286
|
+
// onData callback
|
|
287
|
+
const onData = options.onData;
|
|
288
|
+
if (typeof onData !== 'function') {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
await onData(reportData, rawData);
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
const generateReport = async (reportData, options, rawData) => {
|
|
296
|
+
|
|
297
|
+
Util.logInfo('generating test report ...');
|
|
298
|
+
|
|
299
|
+
const {
|
|
300
|
+
outputFile, outputDir, artifacts
|
|
301
|
+
} = reportData;
|
|
302
|
+
|
|
303
|
+
if (artifacts) {
|
|
304
|
+
artifacts.forEach((report) => {
|
|
305
|
+
const g = report.global ? `${EC.magenta('(global)')}` : '';
|
|
306
|
+
Util.logInfo(`${report.type}: ${EC.cyan(report.path)} ${report.name} ${g}`);
|
|
307
|
+
// convert path to relative reporter
|
|
308
|
+
if (report.path) {
|
|
309
|
+
report.path = Util.relativePath(report.path, outputDir);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
reportData.summary.artifacts = {
|
|
314
|
+
name: 'Artifacts',
|
|
315
|
+
value: artifacts.length
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
await onDataHandler(reportData, options, rawData);
|
|
321
|
+
|
|
322
|
+
// console.log(reportData);
|
|
323
|
+
const filename = path.basename(outputFile, '.html');
|
|
324
|
+
|
|
325
|
+
await generateHtml(outputDir, filename, reportData, options);
|
|
326
|
+
await generateJson(outputDir, filename, reportData, options);
|
|
327
|
+
await generateZip(outputDir, filename, reportData, options);
|
|
328
|
+
|
|
329
|
+
await onEndHandler(reportData, options);
|
|
330
|
+
|
|
331
|
+
// after onEnd for summary changes
|
|
332
|
+
showTestResults(reportData);
|
|
333
|
+
|
|
334
|
+
// clean .cache for merge
|
|
335
|
+
if (options.cacheDir) {
|
|
336
|
+
Util.rmSync(options.cacheDir);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const {
|
|
340
|
+
htmlPath, jsonPath, zipPath
|
|
341
|
+
} = reportData;
|
|
342
|
+
|
|
343
|
+
const assets = [];
|
|
344
|
+
if (jsonPath) {
|
|
345
|
+
assets.push(`json: ${EC.cyan(jsonPath)}`);
|
|
346
|
+
}
|
|
347
|
+
if (zipPath) {
|
|
348
|
+
assets.push(`zip: ${EC.cyan(zipPath)}`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (assets.length) {
|
|
352
|
+
Util.logInfo(assets.join(' '));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
Util.logInfo(`view report: ${EC.cyan(`npx monocart show-report ${htmlPath}`)}`);
|
|
356
|
+
|
|
357
|
+
return reportData;
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
module.exports = generateReport;
|