monocart-reporter 2.9.6 → 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.
@@ -1,360 +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
- return reportData;
358
- };
359
-
360
- module.exports = generateReport;
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;