monocart-reporter 2.7.0 → 2.8.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 CHANGED
@@ -74,7 +74,7 @@ module.exports = {
74
74
  ['list'],
75
75
  ['monocart-reporter', {
76
76
  name: "My Test Report",
77
- outputFile: './test-results/report.html'
77
+ outputFile: './monocart-report/index.html'
78
78
  }]
79
79
  ]
80
80
  };
@@ -89,86 +89,13 @@ Playwright Docs [https://playwright.dev/docs/test-reporters](https://playwright.
89
89
  ## Output
90
90
  - path-to/your-filename.html
91
91
  Single HTML file (data compressed), easy to transfer/deploy or open directly anywhere
92
- > Note: Test attachments (screenshots images/videos) are not included but linked with relative path in report. All attachments will be found in [playwrightConfig.outputDir](https://playwright.dev/docs/api/class-testconfig#test-config-output-dir)
93
- ```js
94
- // playwright.config.js
95
- // attachments outputDir and report outputFile used same folder
96
- const date = new Date().toISOString().slice(0, 10); //2022-10-10
97
- const outputDir = `./test-results/${date}`;
98
- module.exports = {
99
- outputDir: outputDir,
100
- reporter: [
101
- ['monocart-reporter', {
102
- name: `My Test Report ${date}`,
103
- outputFile: `${outputDir}/index.html`
104
- }]
105
- ]
106
- };
107
- // deploy the folder to your report site and easy checking online
108
- // http://your.report.site/test-results/2022-10-10
109
- ```
92
+ > Note: All attachments (screenshots images/videos) will be linked with relative path in report.
110
93
  - path-to/your-filename.json
111
- Separated metadata file (Already included in the above HTML and compressed, it can be deleted). Can be used for debugging or custom data collection.
94
+ Separated data file which can be used for debugging or data provider (It's included in the above HTML and compressed).
112
95
 
113
96
  ## Reporter Options
114
- ```js
115
- {
116
- // the report name
117
- name: '',
118
-
119
- // the output file path (relative process.cwd)
120
- outputFile: './test-results/report.html',
121
-
122
- // attachment path handler
123
- attachmentPath: null,
124
- // attachmentPath: (currentPath, extras) => `https://another-path/${currentPath}`,
125
-
126
- traceViewerUrl: 'https://trace.playwright.dev/?trace={traceUrl}',
127
-
128
- // logging levels: off, error, info, debug
129
- logging: 'info',
130
-
131
- // timezone offset in minutes, GMT+0800 = -480
132
- timezoneOffset: 0,
133
-
134
- // global coverage settings for addCoverageReport API
135
- coverage: null,
136
- // coverage: {
137
- // entryFilter: (entry) => true,
138
- // sourceFilter: (sourcePath) => sourcePath.search(/src\/.+/) !== -1,
139
- // },
140
-
141
- // trend data handler
142
- trend: null,
143
- // trend: () => './test-results/report.json',
144
-
145
- // custom tags style
146
- tags: null,
147
- // tags: {
148
- // smoke: {
149
- // 'background': '#6F9913'
150
- // },
151
- // sanity: {
152
- // 'background': '#178F43'
153
- // }
154
- // },
155
-
156
- // columns data handler
157
- columns: null,
158
- // columns: (defaultColumns) => {},
159
-
160
- // rows data handler (suite, case and step)
161
- visitor: null,
162
- // visitor: (data, metadata) => {},
163
-
164
- // enable/disable custom fields in comments. Defaults to true.
165
- customFieldsInComments: true,
166
-
167
- // onEnd hook
168
- onEnd: null
169
- // onEnd: async (reportData, helper) => {}
170
- }
171
- ```
97
+ - Default options: [lib/default/options.js](./lib/default/options.js)
98
+ - Options declaration see `MonocartReporterOptions` [lib/index.d.ts](./lib/index.d.ts)
172
99
 
173
100
  ## View Trace Online
174
101
  > The [Trace Viewer](https://trace.playwright.dev/) requires that the trace file must be loaded over the http:// or https:// protocols without [CORS](https://developer.mozilla.org/en-US/docs/Glossary/CORS) issue.
@@ -185,7 +112,7 @@ The server add the http header `Access-Control-Allow-Origin: *` to [allow reques
185
112
  ```sh
186
113
  npx monocart show-report <path-to-report> --ssl <path-to-key,path-to-cert>
187
114
  ```
188
- For example: `npx monocart show-report test-results/index.html --ssl ssl/key.pem,ssl/cert.pem`
115
+ For example: `npx monocart show-report monocart-report/index.html --ssl ssl/key.pem,ssl/cert.pem`
189
116
 
190
117
  You can create and install local CA with [mkcert](https://mkcert.dev)
191
118
  - Using your own trace viewer url with option `traceViewerUrl`:
@@ -193,7 +120,7 @@ You can create and install local CA with [mkcert](https://mkcert.dev)
193
120
  // reporter options
194
121
  {
195
122
  name: "My Test Report",
196
- outputFile: './test-results/report.html',
123
+ outputFile: './monocart-report/index.html',
197
124
  // defaults to 'https://trace.playwright.dev/?trace={traceUrl}'
198
125
  traceViewerUrl: 'https://your-own-trace-viewer-url/?trace={traceUrl}'
199
126
  }
@@ -228,7 +155,7 @@ module.exports = {
228
155
  reporter: [
229
156
  ['monocart-reporter', {
230
157
  name: "My Test Report",
231
- outputFile: './test-results/report.html',
158
+ outputFile: './monocart-report/index.html',
232
159
 
233
160
  // custom columns
234
161
  columns: (defaultColumns) => {
@@ -270,7 +197,7 @@ module.exports = {
270
197
  reporter: [
271
198
  ['monocart-reporter', {
272
199
  name: "My Test Report",
273
- outputFile: './test-results/report.html',
200
+ outputFile: './monocart-report/index.html',
274
201
  columns: (defaultColumns) => {
275
202
 
276
203
  // duration formatter
@@ -308,7 +235,7 @@ module.exports = {
308
235
  reporter: [
309
236
  ['monocart-reporter', {
310
237
  name: "My Test Report",
311
- outputFile: './test-results/report.html',
238
+ outputFile: './monocart-report/index.html',
312
239
  columns: (defaultColumns) => {
313
240
  const locationColumn = defaultColumns.find((column) => column.id === 'location');
314
241
  locationColumn.searchable = true;
@@ -413,7 +340,7 @@ module.exports = {
413
340
  reporter: [
414
341
  ['monocart-reporter', {
415
342
  name: "My Test Report",
416
- outputFile: './test-results/report.html',
343
+ outputFile: './monocart-report/index.html',
417
344
  mermaid: {
418
345
  // mermaid script url, using mermaid CDN: https://www.jsdelivr.com/package/npm/mermaid
419
346
  scriptSrc: 'https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js',
@@ -494,7 +421,7 @@ module.exports = {
494
421
  reporter: [
495
422
  ['monocart-reporter', {
496
423
  name: "My Test Report",
497
- outputFile: './test-results/report.html',
424
+ outputFile: './monocart-report/index.html',
498
425
  visitor: (data, metadata) => {
499
426
  // [MCR-123] collect data from the title
500
427
  const matchResult = metadata.title.match(/\[(.+)\]/);
@@ -524,7 +451,7 @@ module.exports = {
524
451
  reporter: [
525
452
  ['monocart-reporter', {
526
453
  name: "My Test Report",
527
- outputFile: './test-results/report.html',
454
+ outputFile: './monocart-report/index.html',
528
455
  visitor: (data, metadata) => {
529
456
  // collect data from the annotations
530
457
  if (metadata.annotations) {
@@ -547,7 +474,7 @@ module.exports = {
547
474
  reporter: [
548
475
  ['monocart-reporter', {
549
476
  name: "My Test Report",
550
- outputFile: './test-results/report.html',
477
+ outputFile: './monocart-report/index.html',
551
478
  visitor: (data, metadata) => {
552
479
  const mySecrets = [process.env.PASSWORD, process.env.TOKEN];
553
480
  mySecrets.forEach((secret) => {
@@ -588,7 +515,7 @@ module.exports = {
588
515
  reporter: [
589
516
  ['monocart-reporter', {
590
517
  name: "My Test Report",
591
- outputFile: './test-results/report.html',
518
+ outputFile: './monocart-report/index.html',
592
519
  tags: {
593
520
  smoke: {
594
521
  style: {
@@ -612,7 +539,7 @@ module.exports = {
612
539
  ['list'],
613
540
  ['monocart-reporter', {
614
541
  name: "My Test Report",
615
- outputFile: './test-results/report.html',
542
+ outputFile: './monocart-report/index.html',
616
543
  tags: {
617
544
  // ...
618
545
  },
@@ -656,7 +583,7 @@ module.exports = {
656
583
  reporter: [
657
584
  ['monocart-reporter', {
658
585
  name: "My Test Report",
659
- outputFile: './test-results/report.html'
586
+ outputFile: './monocart-report/index.html'
660
587
  }]
661
588
  ]
662
589
  };
@@ -723,9 +650,9 @@ module.exports = {
723
650
  reporter: [
724
651
  ['monocart-reporter', {
725
652
  name: "My Test Report",
726
- outputFile: './test-results/report.html',
653
+ outputFile: './monocart-report/index.html',
727
654
  // connect previous report data for trend chart
728
- trend: './test-results/report.json'
655
+ trend: './monocart-report/index.json'
729
656
  }]
730
657
  ]
731
658
  };
@@ -741,7 +668,7 @@ module.exports = {
741
668
  reporter: [
742
669
  ['monocart-reporter', {
743
670
  name: "My Test Report",
744
- outputFile: './test-results/report.html',
671
+ outputFile: './monocart-report/index.html',
745
672
  // connect previous report data for trend chart
746
673
  trend: async () => {
747
674
  const previousReportData = await readDataFromSomeWhere("path-to/report.json");
@@ -778,7 +705,7 @@ module.exports = {
778
705
  reporter: [
779
706
  ['monocart-reporter', {
780
707
  name: "My Test Report",
781
- outputFile: './test-results/report.html',
708
+ outputFile: './monocart-report/index.html',
782
709
  // global coverage report options
783
710
  coverage: {
784
711
  entryFilter: (entry) => true,
@@ -992,7 +919,7 @@ module.exports = {
992
919
  ['list'],
993
920
  ['monocart-reporter', {
994
921
  name: "My Test Report",
995
- outputFile: './test-results/report.html',
922
+ outputFile: './monocart-report/index.html',
996
923
  state: {
997
924
  data: {
998
925
  count: 0
@@ -1060,7 +987,7 @@ module.exports = {
1060
987
  ['list'],
1061
988
  ['monocart-reporter', {
1062
989
  name: "My Test Report",
1063
- outputFile: './test-results/report.html',
990
+ outputFile: './monocart-report/index.html',
1064
991
  state: {
1065
992
  onReceive: function(message) {
1066
993
  const test = this.getTest(message.testId);
@@ -1086,7 +1013,9 @@ npx playwright test --shard=1/3
1086
1013
  npx playwright test --shard=2/3
1087
1014
  npx playwright test --shard=3/3
1088
1015
  ```
1089
- There are 3 reports will be generated. Using `merge(reportDataList, options)` API to merge all reports into one.
1016
+ There are 3 reports will be generated.
1017
+
1018
+ ### Using `merge` API to merge all reports into one
1090
1019
  > Note: One more suite level "shard" will be added, its title will be the machine hostname, and the summary will be restated. All attachments will be copied to the merged output directory.
1091
1020
  ```js
1092
1021
  import { merge } from 'monocart-reporter';
@@ -1106,8 +1035,6 @@ await merge(reportDataList, {
1106
1035
  }
1107
1036
  });
1108
1037
  ```
1109
- see example [merge.js](https://github.com/cenfun/monocart-reporter-examples/blob/main/scripts/merge.js)
1110
-
1111
1038
  > Note: The coverage reports will be merged automatically if we specify the `raw` report in coverage options:
1112
1039
  ```js
1113
1040
  // global coverage options
@@ -1118,6 +1045,19 @@ coverage: {
1118
1045
  ]
1119
1046
  }
1120
1047
  ```
1048
+ see example [merge.js](https://github.com/cenfun/monocart-reporter-examples/blob/main/scripts/merge.js)
1049
+
1050
+ ### Using `merge` CLI
1051
+ ```sh
1052
+ npx monocart merge <glob-patterns>
1053
+
1054
+ # -o --outputFile
1055
+ npx monocart merge path-to/shard*/index.json -o merged-reports/index.html
1056
+
1057
+ # -c --config
1058
+ npx monocart merge path-to/shard*/index.json -c mr.config.js
1059
+ ```
1060
+
1121
1061
 
1122
1062
  ## onEnd Hook
1123
1063
  The `onEnd` function will be executed after report generated. Arguments:
@@ -1149,7 +1089,7 @@ module.exports = {
1149
1089
  reporter: [
1150
1090
  ['monocart-reporter', {
1151
1091
  name: "My Test Report",
1152
- outputFile: './test-results/report.html',
1092
+ outputFile: './monocart-report/index.html',
1153
1093
  // async hook after report data generated
1154
1094
  onEnd: async (reportData, helper) => {
1155
1095
  // console.log(reportData.summary);
package/lib/cli.js CHANGED
@@ -6,15 +6,27 @@ const http = require('http');
6
6
  const https = require('https');
7
7
  const net = require('net');
8
8
  const os = require('os');
9
+ const { pathToFileURL } = require('url');
9
10
  const EC = require('eight-colors');
10
11
  const KSR = require('koa-static-resolver');
11
12
  const Koa = require('koa');
12
13
  const CG = require('console-grid');
13
14
 
14
- const { program, open } = require('./packages/monocart-reporter-vendor.js');
15
+ const Util = require('./utils/util.js');
16
+ const {
17
+ program, open, glob, findUpSync, supportsColor
18
+ } = require('./packages/monocart-reporter-vendor.js');
15
19
  const getDefaultOptions = require('./default/options.js');
20
+ const merge = require('./merge-data.js');
16
21
  const version = require('../package.json').version;
17
22
 
23
+ // https://github.com/chalk/supports-color
24
+ // disabled color if Terminal stdout does not support color
25
+ if (!supportsColor.stdout) {
26
+ EC.disabled = true;
27
+ }
28
+
29
+
18
30
  const getInternalIps = () => {
19
31
  const n = os.networkInterfaces();
20
32
  // console.log(n);
@@ -153,6 +165,93 @@ const serveReport = async (p, options) => {
153
165
 
154
166
  };
155
167
 
168
+ const findUpConfig = (customConfigFile) => {
169
+ if (customConfigFile) {
170
+ if (fs.existsSync(customConfigFile)) {
171
+ return customConfigFile;
172
+ }
173
+ // custom config not found
174
+ return;
175
+ }
176
+
177
+ const defaultConfigList = [
178
+ 'mr.config.js',
179
+ 'mr.config.cjs',
180
+ 'mr.config.mjs',
181
+ 'mr.config.json',
182
+ 'mr.config.ts'
183
+ ];
184
+
185
+ const configPath = findUpSync(defaultConfigList);
186
+ if (configPath) {
187
+ return configPath;
188
+ }
189
+
190
+ // default config not found
191
+ };
192
+
193
+ const resolveConfigOptions = async (configPath) => {
194
+ // json format
195
+ const ext = path.extname(configPath);
196
+ if (ext === '.json' || configPath.slice(-2) === 'rc') {
197
+ return JSON.parse(Util.readFileSync(configPath));
198
+ }
199
+
200
+ let configOptions;
201
+ let err;
202
+ try {
203
+ configOptions = await import(pathToFileURL(configPath));
204
+ } catch (ee) {
205
+ err = ee;
206
+ }
207
+
208
+ if (err) {
209
+ Util.logError(`ERROR: failed to load config "${configPath}": ${err && err.message} `);
210
+ return;
211
+ }
212
+
213
+ // could be multiple level default
214
+ while (configOptions && configOptions.default) {
215
+ configOptions = configOptions.default;
216
+ }
217
+
218
+ return configOptions;
219
+ };
220
+
221
+ const mergeReports = async (str, cliOptions) => {
222
+
223
+ const customConfig = cliOptions.config;
224
+ const configPath = findUpConfig(customConfig);
225
+
226
+ let configOptions = {};
227
+ if (configPath) {
228
+ configOptions = await resolveConfigOptions(configPath);
229
+ } else {
230
+ if (customConfig) {
231
+ Util.logError(`ERROR: not found config file: ${customConfig}`);
232
+ return;
233
+ }
234
+ }
235
+
236
+ const options = {
237
+ ... getDefaultOptions(),
238
+ ... configOptions,
239
+ ... cliOptions
240
+ };
241
+
242
+ Util.initLoggingLevel(options.logging);
243
+
244
+ const files = await glob(str);
245
+ if (!Util.isList(files)) {
246
+ Util.logError(`ERROR: no files found with glob: ${str}`);
247
+ return;
248
+ }
249
+ // console.log(files, options);
250
+
251
+ await merge(files, options);
252
+
253
+ };
254
+
156
255
  program
157
256
  .name('monocart')
158
257
  .description('CLI to serve monocart reporter')
@@ -177,6 +276,19 @@ program.command('serve-report')
177
276
  serveReport(str, options);
178
277
  });
179
278
 
279
+ program.command('merge-reports')
280
+ .alias('merge')
281
+ .description('Merge reports')
282
+ .argument('<path>', 'path to report dirs')
283
+ .option('-c, --config <path>', 'config file')
284
+
285
+ .option('-n, --name <name>', 'report name for title')
286
+ .option('-o, --outputFile <path>', 'output file')
287
+
288
+ .action((str, options) => {
289
+ mergeReports(str, options);
290
+ });
291
+
180
292
  program.addHelpText('after', `
181
293
  Starts ${EC.cyan('https')} with option --ssl:
182
294
  npx monocart show-report <path-to-report> ${EC.cyan('--ssl <path-to-key,path-to-cert>')}
@@ -1,19 +1,24 @@
1
1
  module.exports = () => ({
2
+ // logging levels: off, error, info, debug
3
+ logging: 'info',
4
+
2
5
  // the report name
3
6
  name: '',
4
7
 
5
- // the output file path (relative process.cwd)
6
- outputFile: './test-results/report.html',
8
+ // the output html file path (relative process.cwd)
9
+ outputFile: './monocart-report/index.html',
10
+
11
+ // whether to copy attachments to the reporter output dir, defaults to true
12
+ // it is useful when there are multiple html reports being output.
13
+ copyAttachments: true,
7
14
 
8
15
  // attachment path handler
9
16
  attachmentPath: null,
10
17
  // attachmentPath: (currentPath, extras) => `https://cenfun.github.io/monocart-reporter/${currentPath}`,
11
18
 
19
+ // custom trace viewer url
12
20
  traceViewerUrl: 'https://trace.playwright.dev/?trace={traceUrl}',
13
21
 
14
- // logging levels: off, error, info, debug
15
- logging: 'info',
16
-
17
22
  // timezone offset in minutes, GMT+0800 = -480
18
23
  timezoneOffset: 0,
19
24
 
@@ -24,11 +29,12 @@ module.exports = () => ({
24
29
  // sourceFilter: (sourcePath) => sourcePath.search(/src\/.+/) !== -1,
25
30
  // },
26
31
 
32
+ // Global State Management
27
33
  state: null,
28
34
 
29
35
  // trend data handler
30
36
  trend: null,
31
- // trend: () => './test-results/report.json',
37
+ // trend: () => './monocart-report/index.json',
32
38
 
33
39
  // custom tags style
34
40
  tags: null,
@@ -76,21 +76,11 @@ const generateData = async (results) => {
76
76
  trends
77
77
  } = results;
78
78
 
79
- // console.log(config);
80
- const cwd = Util.formatPath(process.cwd());
81
-
82
- const outputFile = await Util.resolveOutputFile(options.outputFile);
83
- // init outputDir
84
- const outputDir = path.dirname(outputFile);
85
- if (!fs.existsSync(outputDir)) {
86
- fs.mkdirSync(outputDir, {
87
- recursive: true
88
- });
89
- }
90
- // for visitor relative path of attachments
91
- options.cwd = cwd;
92
- options.outputDir = outputDir;
79
+ const {
80
+ cwd, outputFile, outputDir
81
+ } = options;
93
82
 
83
+ // console.log(config);
94
84
  const visitor = new Visitor(root, options);
95
85
  await visitor.start();
96
86
 
@@ -183,8 +183,17 @@ const onEndHandler = async (reportData, options) => {
183
183
  await onEnd(reportData, helper);
184
184
  };
185
185
 
186
+ const onDataHandler = async (reportData, options, rawData) => {
187
+ // onData callback
188
+ const onData = options.onData;
189
+ if (typeof onData !== 'function') {
190
+ return;
191
+ }
192
+ await onData(reportData, rawData);
193
+ };
186
194
 
187
- const generateReport = async (reportData, options) => {
195
+
196
+ const generateReport = async (reportData, options, rawData) => {
188
197
 
189
198
  Util.logInfo('generating test report ...');
190
199
 
@@ -197,7 +206,9 @@ const generateReport = async (reportData, options) => {
197
206
  const g = report.global ? `${EC.magenta('(global)')}` : '';
198
207
  Util.logInfo(`${report.type}: ${EC.cyan(report.path)} ${report.name} ${g}`);
199
208
  // convert path to relative reporter
200
- report.path = Util.relativePath(report.path, outputDir);
209
+ if (report.path) {
210
+ report.path = Util.relativePath(report.path, outputDir);
211
+ }
201
212
  });
202
213
 
203
214
  reportData.summary.artifacts = {
@@ -207,6 +218,8 @@ const generateReport = async (reportData, options) => {
207
218
 
208
219
  }
209
220
 
221
+ await onDataHandler(reportData, options, rawData);
222
+
210
223
  // console.log(reportData);
211
224
  const htmlFile = path.basename(outputFile);
212
225