monocart-reporter 1.6.33 → 1.6.35

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
@@ -159,7 +159,11 @@ Separated metadata file (Already included in the above HTML and compressed, it c
159
159
  ## View Trace Online
160
160
  > 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, try following start a local web server:
161
161
  ```sh
162
+ # serve and open report
162
163
  npx monocart show-report <your-outputFile-path>
164
+
165
+ # serve report
166
+ npx monocart serve-report <your-outputFile-path>
163
167
  ```
164
168
  Or customize your own trace viewer url with option `traceViewerUrl` defaults to `https://trace.playwright.dev/?trace={traceUrl}`
165
169
 
@@ -627,6 +631,7 @@ Attach a code coverage report with API `attachCoverageReport(data, testInfo, opt
627
631
  - Istanbul only:
628
632
  - `watermarks` (Object) Istanbul watermarks, see [here](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-lib-report)
629
633
  - `lcov` (Boolean) Whether to create `lcov.info`
634
+ - `sourcePath` (Function) source path handler, return a new source path
630
635
  - V8 only:
631
636
  - `toIstanbul` (Boolean) Whether to convert to Istanbul report
632
637
  - `watermarks` (Array) Defaults to `[50, 80]`
@@ -642,30 +647,27 @@ Attach a code coverage report with API `attachCoverageReport(data, testInfo, opt
642
647
  ```js
643
648
  import { test, expect } from '@playwright/test';
644
649
  import { attachCoverageReport } from 'monocart-reporter';
645
- test.describe.configure({
646
- mode: 'serial'
647
- });
648
- let page;
649
- test.describe('take Istanbul coverage', () => {
650
- test('first, open page', async ({ browser }) => {
651
- page = await browser.newPage();
652
- await page.goto(pageUrl);
653
- });
654
650
 
655
- test('next, run test cases', async () => {
656
- await new Promise((resolve) => {
657
- setTimeout(resolve, 500);
658
- });
651
+ test('Take Istanbul coverage report', async ({ page }) => {
652
+
653
+ await page.goto('http://localhost:8090/coverage/istanbul.html');
654
+
655
+ // delay for mock code execution
656
+ await new Promise((resolve) => {
657
+ setTimeout(resolve, 500);
659
658
  });
660
659
 
661
- test('finally, take coverage', async () => {
662
- // take Istanbul coverage
663
- const coverageData = await page.evaluate(() => window.__coverage__);
664
- expect(coverageData, 'expect found Istanbul data: __coverage__').toBeTruthy();
665
- // coverage report
666
- const report = await attachCoverageReport(coverageData, test.info());
667
- console.log(report.summary);
660
+ // take Istanbul coverage
661
+ const coverageData = await page.evaluate(() => window.__coverage__);
662
+ await page.close();
663
+ expect(coverageData, 'expect found Istanbul data: __coverage__').toBeTruthy();
664
+
665
+ // coverage report
666
+ const report = await attachCoverageReport(coverageData, test.info(), {
667
+ lcov: true
668
668
  });
669
+ console.log(report.summary);
670
+
669
671
  });
670
672
  ```
671
673
 
@@ -675,43 +677,38 @@ test.describe('take Istanbul coverage', () => {
675
677
  ```js
676
678
  import { test, expect } from '@playwright/test';
677
679
  import { attachCoverageReport } from 'monocart-reporter';
678
- test.describe.configure({
679
- mode: 'serial'
680
- });
681
- let page;
682
- test.describe('take V8 coverage', () => {
683
- test('first, open page', async ({ browser }) => {
684
- page = await browser.newPage();
685
- await Promise.all([
686
- page.coverage.startJSCoverage(),
687
- page.coverage.startCSSCoverage()
688
- ]);
689
- await page.goto(pageUrl);
690
- });
691
680
 
692
- test('next, run test cases', async () => {
693
- await new Promise((resolve) => {
694
- setTimeout(resolve, 500);
695
- });
681
+ test('Take V8 and Istanbul coverage report', async ({ page }) => {
682
+
683
+ await Promise.all([
684
+ page.coverage.startJSCoverage({
685
+ resetOnNavigation: false
686
+ }),
687
+ page.coverage.startCSSCoverage({
688
+ resetOnNavigation: false
689
+ })
690
+ ]);
691
+
692
+ await page.goto('http://localhost:8090/coverage/v8.html');
693
+
694
+ // delay for mock code execution
695
+ await new Promise((resolve) => {
696
+ setTimeout(resolve, 500);
696
697
  });
697
698
 
698
- test('finally, take coverage', async () => {
699
- const [jsCoverage, cssCoverage] = await Promise.all([
700
- page.coverage.stopJSCoverage(),
701
- page.coverage.stopCSSCoverage()
702
- ]);
703
- const coverageList = [... jsCoverage, ... cssCoverage];
704
- // filter file list
705
- // coverageList = coverageList.filter((item) => {
706
- // if (item.url.endsWith('.js') || item.url.endsWith('.css')) {
707
- // return true;
708
- // }
709
- // });
710
- expect(coverageList.length).toBeGreaterThan(0);
711
- // coverage report
712
- const report = await attachCoverageReport(coverageList, test.info());
713
- console.log(report.summary);
699
+ const [jsCoverage, cssCoverage] = await Promise.all([
700
+ page.coverage.stopJSCoverage(),
701
+ page.coverage.stopCSSCoverage()
702
+ ]);
703
+ await page.close();
704
+
705
+ const coverageList = [... jsCoverage, ... cssCoverage];
706
+
707
+ const v8 = await attachCoverageReport(coverageList, test.info(), {
708
+ excludeDistFile: false
714
709
  });
710
+ console.log(v8.summary);
711
+
715
712
  });
716
713
  ```
717
714
 
package/lib/cli.js CHANGED
@@ -35,7 +35,7 @@ const openUrl = async (p) => {
35
35
  };
36
36
 
37
37
 
38
- const showReport = async (list) => {
38
+ const serveReport = async (list, openReport) => {
39
39
  if (!list.length) {
40
40
  list.push(defaultOptions.outputFile);
41
41
  }
@@ -83,7 +83,9 @@ const showReport = async (list) => {
83
83
 
84
84
  server.listen(port, function() {
85
85
  EC.logCyan(`${new Date().toLocaleString()} server listening on ${url}`);
86
- openUrl(url);
86
+ if (openReport) {
87
+ openUrl(url);
88
+ }
87
89
  });
88
90
 
89
91
  };
@@ -93,7 +95,12 @@ const start = function() {
93
95
  const command = args.shift();
94
96
  // console.log(command, args);
95
97
  if (command === 'show-report') {
96
- showReport(args);
98
+ serveReport(args, true);
99
+ return;
100
+ }
101
+
102
+ if (command === 'serve-report') {
103
+ serveReport(args, false);
97
104
  }
98
105
  };
99
106
 
@@ -4,8 +4,9 @@ const Util = require('./utils/util.js');
4
4
  const { getTickInfo } = require('./utils/system.js');
5
5
  const Visitor = require('./visitor.js');
6
6
  const { calculateSummary } = require('./common.js');
7
- const { generateCoverageReport } = require('./plugins/coverage/coverage.js');
8
- const { generateNetworkReport } = require('./plugins/network/network.js');
7
+ const { generateGlobalCoverageReport } = require('./plugins/coverage/coverage.js');
8
+ const { generateGlobalNetworkReport } = require('./plugins/network/network.js');
9
+ const version = require('../package.json').version;
9
10
 
10
11
  const getReportName = (options, config, metadata) => {
11
12
  const reportName = options.name || config.name || metadata.name;
@@ -20,11 +21,11 @@ const artifactsHandler = async (visitor, options) => {
20
21
  // global artifacts
21
22
  const { coverage, network } = visitor.artifactDataMap;
22
23
  if (coverage) {
23
- const report = await generateCoverageReport(coverage, options);
24
+ const report = await generateGlobalCoverageReport(coverage, options);
24
25
  artifacts.push(report);
25
26
  }
26
27
  if (network) {
27
- const report = await generateNetworkReport(coverage, options);
28
+ const report = await generateGlobalNetworkReport(coverage, options);
28
29
  artifacts.push(report);
29
30
  }
30
31
  return artifacts;
@@ -92,6 +93,8 @@ const generateData = async (results) => {
92
93
  system.configFile = Util.relativePath(config.configFile);
93
94
  // playwright version
94
95
  system.playwright = config.version;
96
+ // monocart version
97
+ system.monocart = version;
95
98
 
96
99
  // test root dir
97
100
  system.testDir = Util.relativePath(config.rootDir);
@@ -70,13 +70,10 @@ const attachAuditReport = async (runnerResult, testInfo, options = {}) => {
70
70
  return;
71
71
  }
72
72
 
73
- const title = options.title || `Lighthouse Report - ${testInfo.title}`;
74
-
75
73
  options = {
76
- title,
74
+ title: `Lighthouse Report - ${testInfo.title}`,
77
75
  outputDir: Util.resolveOutputDir(testInfo),
78
- outputName: `audit-${Util.shortTestId(testInfo.testId)}`,
79
-
76
+ outputName: `audit-${Util.resolveTestIdWithRetry(testInfo)}`,
80
77
  ... options
81
78
  };
82
79
 
@@ -90,7 +87,7 @@ const attachAuditReport = async (runnerResult, testInfo, options = {}) => {
90
87
 
91
88
  // `.lhr` is the Lighthouse Result as a JS object
92
89
  const report = {
93
- title,
90
+ title: options.title,
94
91
  ... getSummaryReport(runnerResult.lhr)
95
92
  };
96
93
 
@@ -1,6 +1,4 @@
1
1
  const Util = require('../../utils/util.js');
2
- const Concurrency = require('../../platform/concurrency.js');
3
- const { convertSourceMap, axios } = require('../../runtime/monocart-coverage.js');
4
2
 
5
3
  const sortRanges = (ranges) => {
6
4
  ranges.sort((a, b) => {
@@ -67,6 +65,8 @@ const getSourcePath = (url, index = '', type = '') => {
67
65
  return filterPath(relPath);
68
66
  };
69
67
 
68
+ // ========================================================================================================
69
+
70
70
  const mergeSourceRoot = (sourceRoot, sourceName) => {
71
71
  if (sourceName.startsWith(sourceRoot)) {
72
72
  return sourceName;
@@ -74,89 +74,51 @@ const mergeSourceRoot = (sourceRoot, sourceName) => {
74
74
  return sourceRoot + sourceName;
75
75
  };
76
76
 
77
- // ================================================================================================
78
-
79
- const request = async (options) => {
80
- if (typeof options === 'string') {
81
- options = {
82
- url: options
83
- };
84
- }
85
- let err;
86
- const res = await axios(options).catch((e) => {
87
- err = e;
77
+ const initSourceMapRootAndUrl = (sourceMap, fileUrls, fileSources) => {
78
+ // reset sourceRoot
79
+ const sourceRoot = sourceMap.sourceRoot || '';
80
+ sourceMap.sourceRoot = '';
81
+
82
+ sourceMap.sources = sourceMap.sources.map((sourceName, i) => {
83
+ const sourceUrl = mergeSourceRoot(sourceRoot, sourceName);
84
+ const sourcePath = getSourcePath(sourceUrl, i + 1);
85
+ fileUrls[sourcePath] = sourceUrl;
86
+ fileSources[sourcePath] = sourceMap.sourcesContent[i];
87
+ return sourcePath;
88
88
  });
89
- return [err, res];
90
- };
91
-
92
- const getSourceMapUrl = (content, url) => {
93
89
 
94
- const m = content.match(convertSourceMap.mapFileCommentRegex);
95
- if (!m) {
96
- return;
97
- }
90
+ };
98
91
 
99
- const comment = m.pop();
100
- const r = convertSourceMap.mapFileCommentRegex.exec(comment);
101
- // for some odd reason //# .. captures in 1 and /* .. */ in 2
102
- const filename = r[1] || r[2];
92
+ // ================================================================================================
103
93
 
104
- let mapUrl;
94
+ const convertFunctionsToRanges = (functions) => {
105
95
 
106
- try {
107
- mapUrl = new URL(filename, url);
108
- } catch (e) {
109
- // console.log(e)
110
- }
111
- if (mapUrl) {
112
- return mapUrl.toString();
96
+ if (!Util.isList(functions)) {
97
+ return [];
113
98
  }
114
- };
115
99
 
116
- const resolveSourceMap = (data) => {
117
- if (data) {
118
- const { sources, sourcesContent } = data;
119
- if (!sources || !sourcesContent) {
120
- return;
100
+ const ranges = [];
101
+ for (const fun of functions) {
102
+ if (fun.ranges) {
103
+ for (const range of fun.ranges) {
104
+ // rename startOffset/endOffset to start/end, keep count
105
+ ranges.push({
106
+ start: range.startOffset,
107
+ end: range.endOffset,
108
+ count: range.count
109
+ });
110
+ }
121
111
  }
122
- return data;
123
112
  }
124
- };
125
113
 
126
- const collectSourceMaps = async (v8list) => {
127
- const concurrency = new Concurrency();
128
- for (const item of v8list) {
129
- // useless for css
130
- if (item.type === 'css') {
131
- continue;
132
- }
114
+ sortRanges(ranges);
133
115
 
134
- const source = item.source;
135
- const converter = convertSourceMap.fromSource(source);
136
- if (converter) {
137
- item.sourceMap = resolveSourceMap(converter.sourcemap);
138
- continue;
139
- }
140
- const sourceMapUrl = getSourceMapUrl(source, item.url);
141
- if (sourceMapUrl) {
142
- item.sourceMapUrl = sourceMapUrl;
143
- concurrency.addItem(item);
144
- }
145
- }
146
- await concurrency.start(async (item) => {
147
- const [err, res] = await request({
148
- url: item.sourceMapUrl
149
- });
150
- if (!err && res) {
151
- item.sourceMap = resolveSourceMap(res.data);
152
- }
153
- });
116
+ return ranges;
154
117
  };
155
118
 
156
-
157
119
  module.exports = {
158
120
  sortRanges,
159
121
  getSourcePath,
160
- mergeSourceRoot,
161
- collectSourceMaps
122
+ initSourceMapRootAndUrl,
123
+ convertFunctionsToRanges
162
124
  };
@@ -3,13 +3,9 @@ const path = require('path');
3
3
  const EC = require('eight-colors');
4
4
 
5
5
  const Util = require('../../utils/util.js');
6
-
7
- const {
8
- convertV8ToIstanbul, mergeIstanbulList, saveIstanbulReport
9
- } = require('./istanbul/istanbul.js');
10
-
6
+ const { convertV8ToIstanbul, saveIstanbulReport } = require('./istanbul/istanbul.js');
11
7
  const {
12
- initV8List, mergeV8List, saveV8Report
8
+ initV8ListAndSourcemap, unpackV8List, mergeV8Coverage, saveV8Report
13
9
  } = require('./v8/v8.js');
14
10
 
15
11
  // ========================================================================================================
@@ -48,6 +44,9 @@ const defaultIstanbulOptions = {
48
44
  unpackSourceMap: true,
49
45
  sourceFilter: null,
50
46
 
47
+ // source path handler
48
+ sourcePath: null,
49
+
51
50
  // (usually not used) source finder for Istanbul HTML report
52
51
  sourceFinder: null,
53
52
 
@@ -101,6 +100,10 @@ const generateV8Coverage = async (v8list, testInfo, options) => {
101
100
  ... options
102
101
  };
103
102
 
103
+ // init v8list and unpack sourcemap
104
+ const inlineSourceMap = true;
105
+ v8list = await initV8ListAndSourcemap(v8list, options, inlineSourceMap);
106
+
104
107
  // ================================================================
105
108
 
106
109
  if (options.toIstanbul) {
@@ -120,7 +123,8 @@ const generateV8Coverage = async (v8list, testInfo, options) => {
120
123
 
121
124
  // ================================================================
122
125
 
123
- v8list = await initV8List(v8list, options);
126
+ // functions to ranges, and unpack source maps
127
+ await unpackV8List(v8list, options);
124
128
 
125
129
  const report = await saveV8Report(v8list, options);
126
130
 
@@ -136,21 +140,26 @@ const attachCoverageReport = (coverageInput, testInfo, options = {}) => {
136
140
  return;
137
141
  }
138
142
 
139
- const title = options.title || `Coverage Report - ${testInfo.title}`;
140
-
141
143
  options = {
142
- title,
144
+ // default title
145
+ title: `Coverage Report - ${testInfo.title}`,
143
146
  outputDir: Util.resolveOutputDir(testInfo),
144
- outputName: `coverage-${Util.shortTestId(testInfo.testId)}`,
147
+ outputName: `coverage-${Util.resolveTestIdWithRetry(testInfo)}`,
145
148
  ... options
146
149
  };
147
150
 
148
- const htmlDir = path.resolve(options.outputDir, options.outputName);
149
- if (!fs.existsSync(htmlDir)) {
150
- fs.mkdirSync(htmlDir, {
151
- recursive: true
152
- });
151
+ // support multiple calls
152
+ let htmlDir = path.resolve(options.outputDir, options.outputName);
153
+ let i = 1;
154
+ while (fs.existsSync(htmlDir)) {
155
+ const outputName = `${options.outputName}-${i}`;
156
+ htmlDir = path.resolve(options.outputDir, outputName);
157
+ i += 1;
153
158
  }
159
+
160
+ fs.mkdirSync(htmlDir, {
161
+ recursive: true
162
+ });
154
163
  options.htmlDir = htmlDir;
155
164
 
156
165
  if (Array.isArray(coverageInput)) {
@@ -162,26 +171,11 @@ const attachCoverageReport = (coverageInput, testInfo, options = {}) => {
162
171
 
163
172
  // ========================================================================================================
164
173
 
165
- const generateArtifactData = (v8list, options) => {
166
- options = {
167
- ... defaultV8Options,
168
- ... options
169
- };
170
- if (options.toIstanbul) {
171
- options = {
172
- ... defaultIstanbulOptions,
173
- ... options
174
- };
175
- return convertV8ToIstanbul(v8list, options);
176
- }
177
- return initV8List(v8list, options);
178
- };
179
-
180
174
  // add coverage report to global, v8list only
181
- const addCoverageReport = async (coverageInput, testInfo) => {
175
+ const addCoverageReport = async (v8list, testInfo) => {
182
176
 
183
- if (!coverageInput) {
184
- EC.logRed('[MCR] invalid coverage input');
177
+ if (!Util.isList(v8list)) {
178
+ EC.logRed(`[MCR] invalid v8 list for test: ${testInfo.title}`);
185
179
  return;
186
180
  }
187
181
 
@@ -193,25 +187,33 @@ const addCoverageReport = async (coverageInput, testInfo) => {
193
187
  const outputDir = path.dirname(outputFile);
194
188
 
195
189
  const options = {
190
+ ... defaultV8Options,
196
191
  // use reporter dir as output dir, NOT test output dir
197
192
  outputDir,
198
193
  outputName: 'coverage',
199
194
  ... coverageOptions
200
195
  };
201
196
 
202
- const id = Util.shortTestId(testInfo.testId);
203
- const filename = `artifact-${id}.json`;
204
- const jsonDir = path.resolve(options.outputDir, options.outputName);
205
- const jsonPath = path.resolve(jsonDir, filename);
197
+ const reportDir = path.resolve(options.outputDir, options.outputName);
198
+
199
+ // artifactsDir for temp artifact files
200
+ const artifactsDir = path.resolve(reportDir, '.artifacts');
201
+ options.artifactsDir = artifactsDir;
206
202
 
207
- const data = await generateArtifactData(coverageInput, options);
203
+ const filename = `coverage-${Util.resolveTestIdWithRetry(testInfo)}.json`;
204
+ const jsonPath = path.resolve(artifactsDir, filename);
208
205
 
209
- // console.log('addCoverageReport keys', Object.keys(data.coverageData));
206
+ // init v8list and unpack sourcemap
207
+ const inlineSourceMap = false;
208
+ v8list = await initV8ListAndSourcemap(v8list, options, inlineSourceMap);
209
+
210
+ // console.log('addCoverageReport', coverageInput.map((it) => it.url));
210
211
 
211
212
  const report = {
212
- id,
213
- outputFile: Util.relativePath(jsonPath),
214
- data
213
+ // title for debug
214
+ title: testInfo.title,
215
+ // merge with data
216
+ data: v8list
215
217
  };
216
218
 
217
219
  const artifactContent = JSON.stringify({
@@ -233,34 +235,43 @@ const addCoverageReport = async (coverageInput, testInfo) => {
233
235
 
234
236
  // ========================================================================================================
235
237
 
236
- const generateGlobalCoverageReport = async (dataList, options) => {
238
+ const getGlobalCoverageData = async (dataList, options) => {
237
239
 
238
240
  options = {
239
241
  ... defaultV8Options,
240
242
  ... options
241
243
  };
242
244
 
245
+ // merge v8list first
246
+ const v8list = await mergeV8Coverage(dataList, options);
247
+ // console.log('after merge', v8list.map((it) => it.url));
248
+
243
249
  if (options.toIstanbul) {
250
+
244
251
  options = {
245
252
  ... defaultIstanbulOptions,
246
253
  ... options
247
254
  };
248
- const istanbulList = dataList.map((it) => it.data);
249
- const { coverageData, fileSources } = mergeIstanbulList(istanbulList);
250
- return saveIstanbulReport(coverageData, fileSources, options);
255
+
256
+ const { coverageData, fileSources } = await convertV8ToIstanbul(v8list, options);
257
+
258
+ const report = await saveIstanbulReport(coverageData, fileSources, options);
259
+
260
+ // console.log(report);
261
+
262
+ return report;
251
263
  }
252
264
 
253
- let v8list = [];
254
- dataList.forEach((item) => {
255
- v8list = v8list.concat(item.data);
256
- });
257
- // merge list again for multiple v8list, maybe collected multiple times
258
- v8list = await mergeV8List(v8list, options);
259
- return saveV8Report(v8list, options);
265
+ // functions to ranges, and unpack source maps
266
+ await unpackV8List(v8list, options);
267
+
268
+ const report = await saveV8Report(v8list, options);
269
+
270
+ return report;
260
271
  };
261
272
 
262
273
  // global coverage report, run different process with addCoverageReport
263
- const generateCoverageReport = async (dataList, reporterOptions) => {
274
+ const generateGlobalCoverageReport = async (dataList, reporterOptions) => {
264
275
 
265
276
  // reporter outputFile
266
277
  const outputFile = await Util.resolveOutputFile(reporterOptions.outputFile);
@@ -274,15 +285,27 @@ const generateCoverageReport = async (dataList, reporterOptions) => {
274
285
  ... coverageOptions
275
286
  };
276
287
 
277
- const htmlDir = path.resolve(options.outputDir, options.outputName);
278
- if (!fs.existsSync(htmlDir)) {
279
- fs.mkdirSync(htmlDir, {
288
+ const reportDir = path.resolve(options.outputDir, options.outputName);
289
+ if (!fs.existsSync(reportDir)) {
290
+ fs.mkdirSync(reportDir, {
280
291
  recursive: true
281
292
  });
282
293
  }
283
- options.htmlDir = htmlDir;
294
+ options.htmlDir = reportDir;
295
+
296
+ // artifactsDir for temp artifact files
297
+ const artifactsDir = path.resolve(reportDir, '.artifacts');
298
+ options.artifactsDir = artifactsDir;
284
299
 
285
- const report = await generateGlobalCoverageReport(dataList, options);
300
+ const report = await getGlobalCoverageData(dataList, options);
301
+ // console.log(report);
302
+
303
+ // =============================================================
304
+ // remove artifacts dir after report generated
305
+ if (!options.debug) {
306
+ Util.rmSync(artifactsDir);
307
+ }
308
+ // =============================================================
286
309
 
287
310
  return {
288
311
  global: true,
@@ -295,6 +318,6 @@ const generateCoverageReport = async (dataList, reporterOptions) => {
295
318
 
296
319
  module.exports = {
297
320
  addCoverageReport,
298
- generateCoverageReport,
321
+ generateGlobalCoverageReport,
299
322
  attachCoverageReport
300
323
  };