monocart-reporter 1.7.1 → 1.7.2

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.
Files changed (38) hide show
  1. package/README.md +7 -5
  2. package/lib/cli.js +1 -1
  3. package/lib/common.js +2 -3
  4. package/lib/default/options.js +4 -1
  5. package/lib/generate-data.js +2 -7
  6. package/lib/generate-report.js +10 -11
  7. package/lib/index.d.ts +2 -4
  8. package/lib/index.js +6 -0
  9. package/lib/merge-data.js +7 -6
  10. package/lib/plugins/audit/audit.js +4 -2
  11. package/lib/plugins/comments.js +2 -3
  12. package/lib/plugins/coverage/converter/collect-source-maps.js +194 -0
  13. package/lib/plugins/coverage/converter/converter.js +547 -0
  14. package/lib/plugins/coverage/converter/decode-mappings.js +49 -0
  15. package/lib/plugins/coverage/{v8 → converter}/dedupe.js +8 -1
  16. package/lib/plugins/coverage/converter/find-original-range.js +576 -0
  17. package/lib/plugins/coverage/converter/info-branch.js +30 -0
  18. package/lib/plugins/coverage/converter/info-function.js +29 -0
  19. package/lib/plugins/coverage/converter/info-line.js +20 -0
  20. package/lib/plugins/coverage/converter/position-mapping.js +183 -0
  21. package/lib/plugins/coverage/{coverage-utils.js → converter/source-path.js} +26 -42
  22. package/lib/plugins/coverage/coverage.js +51 -67
  23. package/lib/plugins/coverage/istanbul/istanbul.js +15 -174
  24. package/lib/plugins/coverage/v8/v8.js +22 -13
  25. package/lib/plugins/network/network.js +4 -13
  26. package/lib/plugins/state/client.js +3 -4
  27. package/lib/plugins/state/state.js +6 -3
  28. package/lib/runtime/monocart-code-viewer.js +1 -1
  29. package/lib/runtime/monocart-coverage.js +13 -14
  30. package/lib/runtime/monocart-formatter.js +1 -1
  31. package/lib/runtime/monocart-network.js +1 -1
  32. package/lib/runtime/monocart-reporter.js +1 -1
  33. package/lib/runtime/monocart-v8.js +1 -1
  34. package/lib/runtime/monocart-vendor.js +13 -13
  35. package/lib/utils/util.js +97 -3
  36. package/package.json +4 -5
  37. package/lib/plugins/coverage/v8/position-mapping.js +0 -92
  38. package/lib/plugins/coverage/v8/source-map.js +0 -451
package/README.md CHANGED
@@ -119,11 +119,14 @@ Separated metadata file (Already included in the above HTML and compressed, it c
119
119
 
120
120
  traceViewerUrl: 'https://trace.playwright.dev/?trace={traceUrl}',
121
121
 
122
+ // logging levels: off, error, info, debug
123
+ logging: 'info',
124
+
122
125
  // global coverage settings for addCoverageReport API
123
126
  coverage: null,
124
127
  // coverage: {
125
128
  // entryFilter: (entry) => true,
126
- // sourceFilter: (sourceName) => sourceName.search(/src\/.+/) !== -1,
129
+ // sourceFilter: (sourcePath) => sourcePath.search(/src\/.+/) !== -1,
127
130
  // },
128
131
 
129
132
  // trend data handler
@@ -628,14 +631,13 @@ Attach a code coverage report with API `attachCoverageReport(data, testInfo, opt
628
631
  - `testInfo` see [TestInfo](https://playwright.dev/docs/api/class-testinfo)
629
632
  - `options` (Object)
630
633
  - `title` (String) report title.
631
- - `toIstanbul` (Boolean) Whether to convert to Istanbul report from V8 list.
634
+ - `toIstanbul` (Boolean) Whether to convert to Istanbul report from V8 list. Defaults to `html-spa` report | (String) or using `html` report
632
635
  - `entryFilter` (Function) A filter function to execute for each element in the V8 list.
633
636
  - `sourceFilter` (Function) A filter function to execute for each element in the sources which unpacked from the source map.
634
637
  - `watermarks` (Object) Istanbul watermarks, see [here](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-lib-report) | (Array) V8 watermarks, Defaults to `[50, 80]`.
635
638
  - `lcov` (Boolean) Whether to create `lcov.info`. Istanbul only.
636
639
  - `sourcePath` (Function) source path handler, return a new source path. Istanbul only.
637
640
  - `inline` (Boolean) Whether inline all scripts to the single HTML file. V8 only.
638
- - `debug` (Boolean) The dist file which has source map will be included, and the temporary artifacts will not be removed.
639
641
 
640
642
  (see example: [report-coverage.spec.js](https://github.com/cenfun/monocart-reporter/blob/main/tests/report-coverage/report-coverage.spec.js))
641
643
 
@@ -701,7 +703,7 @@ test('Take V8 and Istanbul coverage report', async ({ page }) => {
701
703
  const coverageList = [... jsCoverage, ... cssCoverage];
702
704
 
703
705
  const v8 = await attachCoverageReport(coverageList, test.info(), {
704
- debug: true
706
+
705
707
  });
706
708
  console.log(v8.summary);
707
709
 
@@ -741,7 +743,7 @@ module.exports = {
741
743
  // global coverage report options
742
744
  coverage: {
743
745
  entryFilter: (entry) => true,
744
- sourceFilter: (sourceName) => sourceName.search(/src\/.+/) !== -1,
746
+ sourceFilter: (sourcePath) => sourcePath.search(/src\/.+/) !== -1,
745
747
  }
746
748
  }]
747
749
  ]
package/lib/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
package/lib/common.js CHANGED
@@ -1,4 +1,3 @@
1
- const EC = require('eight-colors');
2
1
  const Util = require('./utils/util.js');
3
2
  const defaultSummary = require('./default/summary.js');
4
3
  const generatePieChart = require('./utils/pie.js');
@@ -205,12 +204,12 @@ const getTrends = async (input) => {
205
204
  }
206
205
  const data = await getTrendData(input);
207
206
  if (!data) {
208
- EC.logRed('[MCR] failed to load trend data');
207
+ Util.logError('failed to load trend data');
209
208
  return [];
210
209
  }
211
210
 
212
211
  if (!isTrendData(data)) {
213
- EC.logRed('[MCR] trend data requires properties: date, duration and summary');
212
+ Util.logError('trend data requires properties: date, duration and summary');
214
213
  return [];
215
214
  }
216
215
 
@@ -11,11 +11,14 @@ module.exports = {
11
11
 
12
12
  traceViewerUrl: 'https://trace.playwright.dev/?trace={traceUrl}',
13
13
 
14
+ // logging levels: off, error, info, debug
15
+ logging: 'info',
16
+
14
17
  // global coverage settings for addCoverageReport API
15
18
  coverage: null,
16
19
  // coverage: {
17
20
  // entryFilter: (entry) => true,
18
- // sourceFilter: (sourceName) => sourceName.search(/src\/.+/) !== -1,
21
+ // sourceFilter: (sourcePath) => sourcePath.search(/src\/.+/) !== -1,
19
22
  // },
20
23
 
21
24
  state: null,
@@ -5,7 +5,6 @@ const { getTickInfo } = require('./utils/system.js');
5
5
  const Visitor = require('./visitor.js');
6
6
  const { calculateSummary } = require('./common.js');
7
7
  const { generateGlobalCoverageReport } = require('./plugins/coverage/coverage.js');
8
- const { generateGlobalNetworkReport } = require('./plugins/network/network.js');
9
8
  const version = require('../package.json').version;
10
9
 
11
10
  const getReportName = (options, config, metadata) => {
@@ -19,21 +18,17 @@ const getReportName = (options, config, metadata) => {
19
18
  const artifactsHandler = async (visitor, options) => {
20
19
  const artifacts = [].concat(visitor.artifacts);
21
20
  // global artifacts
22
- const { coverage, network } = visitor.artifactDataMap;
21
+ const { coverage } = visitor.artifactDataMap;
23
22
  if (coverage) {
24
23
  const report = await generateGlobalCoverageReport(coverage, options);
25
24
  artifacts.push(report);
26
25
  }
27
- if (network) {
28
- const report = await generateGlobalNetworkReport(coverage, options);
29
- artifacts.push(report);
30
- }
31
26
  return artifacts;
32
27
  };
33
28
 
34
29
  const generateData = async (results) => {
35
30
 
36
- console.log('[MCR] generating report data ...');
31
+ Util.logInfo('generating report data ...');
37
32
 
38
33
  const {
39
34
  config,
@@ -1,6 +1,5 @@
1
1
  const path = require('path');
2
2
  const EC = require('eight-colors');
3
- const CG = require('console-grid');
4
3
  const nodemailer = require('nodemailer');
5
4
  const Util = require('./utils/util.js');
6
5
  const emailPlugin = require('./plugins/email.js');
@@ -12,7 +11,7 @@ const generateJson = (outputDir, htmlFile, reportData) => {
12
11
  let jsonPath = path.resolve(outputDir, `${filename}.json`);
13
12
  Util.writeJSONSync(jsonPath, reportData);
14
13
  jsonPath = Util.relativePath(jsonPath);
15
- console.log(`[MCR] json report: ${EC.cyan(jsonPath)}`);
14
+ Util.logInfo(`json report: ${EC.cyan(jsonPath)}`);
16
15
  return jsonPath;
17
16
  };
18
17
 
@@ -27,31 +26,30 @@ const generateHtml = async (outputDir, htmlFile, reportData, inline) => {
27
26
 
28
27
  outputDir,
29
28
  reportDataFile: 'report-data.js',
30
- assetsName: 'assets',
31
29
  assetsRelative: ''
32
30
  };
33
31
 
34
32
  const htmlPath = await Util.saveHtmlReport(options);
35
- console.log(`[MCR] html report: ${EC.cyan(htmlPath)}`);
33
+ Util.logInfo(`html report: ${EC.cyan(htmlPath)}`);
36
34
 
37
35
  const cmd = `npx monocart show-report ${htmlPath}`;
38
- console.log(`[MCR] view report: ${EC.cyan(cmd)}`);
36
+ Util.logInfo(`view report: ${EC.cyan(cmd)}`);
39
37
 
40
38
  return htmlPath;
41
39
  };
42
40
 
43
41
  const sendEmail = (emailOptions) => {
44
42
  if (!emailOptions.transport || !emailOptions.message) {
45
- EC.logRed('[MCR] invalid email options, transport and message are required (https://nodemailer.com/)');
43
+ Util.logError('invalid email options, transport and message are required (https://nodemailer.com/)');
46
44
  return;
47
45
  }
48
- console.log('[MCR] sending email ...');
46
+ Util.logInfo('sending email ...');
49
47
  const transporter = nodemailer.createTransport(emailOptions.transport);
50
48
  return transporter.sendMail(emailOptions.message);
51
49
  };
52
50
 
53
51
  const showTestResults = (reportData) => {
54
- console.log(`[MCR] test results: ${EC.cyan(reportData.name)}`);
52
+ Util.logInfo(`test results: ${EC.cyan(reportData.name)}`);
55
53
 
56
54
  const summary = reportData.summary;
57
55
 
@@ -127,7 +125,7 @@ const showTestResults = (reportData) => {
127
125
  value: Util.TF(reportData.duration)
128
126
  }]);
129
127
 
130
- CG({
128
+ Util.logGrid({
131
129
  options: {
132
130
  headerVisible: false
133
131
  },
@@ -138,12 +136,13 @@ const showTestResults = (reportData) => {
138
136
  }],
139
137
  rows
140
138
  });
139
+
141
140
  };
142
141
 
143
142
 
144
143
  const generateReport = async (reportData, options) => {
145
144
 
146
- console.log('[MCR] generating test report ...');
145
+ Util.logInfo('generating test report ...');
147
146
  showTestResults(reportData);
148
147
 
149
148
  const {
@@ -153,7 +152,7 @@ const generateReport = async (reportData, options) => {
153
152
  if (artifacts) {
154
153
  artifacts.forEach((report) => {
155
154
  const g = report.global ? `${EC.magenta('(global)')}` : '';
156
- console.log(`[MCR] ${report.name}: ${EC.cyan(report.path)} ${report.title} ${g}`);
155
+ Util.logInfo(`${report.name}: ${EC.cyan(report.path)} ${report.title} ${g}`);
157
156
  // convert path to relative reporter
158
157
  report.path = Util.relativePath(report.path, outputDir);
159
158
  });
package/lib/index.d.ts CHANGED
@@ -39,7 +39,7 @@ export type CoverageReportOptions = {
39
39
  outputName?: string,
40
40
 
41
41
  // Whether to convert to Istanbul report
42
- toIstanbul?: boolean,
42
+ toIstanbul?: boolean | string,
43
43
 
44
44
  // A filter function to execute for each element in the V8 list.
45
45
  entryFilter?: (entry: any) => boolean,
@@ -62,9 +62,7 @@ export type CoverageReportOptions = {
62
62
  },
63
63
 
64
64
  // Whether inline all scripts to the single HTML file.
65
- inline?: boolean,
66
-
67
- debug?: boolean
65
+ inline?: boolean
68
66
  };
69
67
 
70
68
  export function attachCoverageReport(
package/lib/index.js CHANGED
@@ -11,6 +11,7 @@ const { addCoverageReport, attachCoverageReport } = require('./plugins/coverage/
11
11
  const { attachNetworkReport } = require('./plugins/network/network.js');
12
12
 
13
13
  const { createStateServer, useState } = require('./plugins/state/state.js');
14
+ const Util = require('./utils/util.js');
14
15
 
15
16
  // custom reporter
16
17
  // https://playwright.dev/docs/test-reporters#custom-reporters
@@ -36,6 +37,8 @@ class Reporter {
36
37
  ... userOptions
37
38
  };
38
39
 
40
+ Util.initLoggingLevel(this.options.logging, 'reporter');
41
+
39
42
  this.system = getSystemInfo();
40
43
  this.system.timestampStart = timestampStart;
41
44
  this.system.ticks = [];
@@ -47,6 +50,9 @@ class Reporter {
47
50
  this.tickTime = this.options.tickTime || 1000;
48
51
  this.tickStart();
49
52
 
53
+ // clean assets dir
54
+ Util.initAssetsDir(this.options);
55
+
50
56
  const stateOptions = this.options.state;
51
57
  if (stateOptions) {
52
58
  this.stateServer = createStateServer(stateOptions);
package/lib/merge-data.js CHANGED
@@ -1,6 +1,5 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const EC = require('eight-colors');
4
3
  const Util = require('./utils/util.js');
5
4
  const generateReport = require('./generate-report.js');
6
5
  const defaultOptions = require('./default/options.js');
@@ -16,7 +15,7 @@ const checkReportData = (item) => {
16
15
 
17
16
  const initDataList = (reportDataList) => {
18
17
  if (!Util.isList(reportDataList)) {
19
- EC.logRed(`[MCR] invalid report data list: ${reportDataList}`);
18
+ Util.logError(`invalid report data list: ${reportDataList}`);
20
19
  return;
21
20
  }
22
21
 
@@ -28,16 +27,16 @@ const initDataList = (reportDataList) => {
28
27
  const data = Util.readJSONSync(item);
29
28
  if (!data) {
30
29
  hasError = true;
31
- EC.logRed(`[MCR] failed to read report data file: ${item}`);
30
+ Util.logError(`failed to read report data file: ${item}`);
32
31
  continue;
33
32
  }
34
33
  list.push(data);
35
- EC.logGreen(`[MCR] report data loaded: ${item}`);
34
+ Util.logInfo(`report data loaded: ${item}`);
36
35
  continue;
37
36
  }
38
37
 
39
38
  if (!checkReportData(item)) {
40
- EC.logRed(`[MCR] unmatched report data format: ${item}`);
39
+ Util.logError(`unmatched report data format: ${item}`);
41
40
  return;
42
41
  }
43
42
  list.push(item);
@@ -164,7 +163,9 @@ const mergeDataList = async (dataList, options) => {
164
163
 
165
164
  module.exports = async (reportDataList, userOptions = {}) => {
166
165
 
167
- console.log('[MCR] merging report data ...');
166
+ Util.initLoggingLevel(userOptions.logging, 'merge');
167
+
168
+ Util.logInfo('merging report data ...');
168
169
 
169
170
  const dataList = await initDataList(reportDataList);
170
171
  if (!dataList) {
@@ -1,6 +1,5 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const EC = require('eight-colors');
4
3
  const Util = require('../../utils/util.js');
5
4
 
6
5
  const getStatus = (s) => {
@@ -65,8 +64,11 @@ const getSummaryReport = (lhr) => {
65
64
 
66
65
  const attachAuditReport = async (runnerResult, testInfo, options = {}) => {
67
66
 
67
+ const reporterOptions = Util.resolveReporterOptions(testInfo);
68
+ Util.initLoggingLevel(reporterOptions.logging, 'audit');
69
+
68
70
  if (!runnerResult || !runnerResult.lhr || !runnerResult.report) {
69
- EC.logRed('[MCR] invalid lighthouse runner result');
71
+ Util.logError('invalid lighthouse runner result');
70
72
  return;
71
73
  }
72
74
 
@@ -1,5 +1,4 @@
1
1
  const fs = require('fs');
2
- const EC = require('eight-colors');
3
2
  const { parse } = require('../runtime/monocart-vendor.js');
4
3
  const Util = require('../utils/util.js');
5
4
 
@@ -32,8 +31,8 @@ const getFileComments = (filePath, parserOptions = {}) => {
32
31
  try {
33
32
  ast = parse(source, parserOptions);
34
33
  } catch (e) {
35
- EC.logRed(`[MCR] ${e.message}`);
36
- EC.logRed(`[MCR] failed to collect comments from file: "${Util.relativePath(filePath)}" please try different parser options like: sourceType: "module", plugins: ["typescript"] and more https://babeljs.io/docs/babel-parser`);
34
+ Util.logError(e.message);
35
+ Util.logError(`failed to collect comments from the file: ${Util.relativePath(filePath)}`);
37
36
  return map;
38
37
  }
39
38
 
@@ -0,0 +1,194 @@
1
+ const fs = require('fs');
2
+ const EC = require('eight-colors');
3
+ const { fileURLToPath } = require('url');
4
+ const Concurrency = require('../../../platform/concurrency.js');
5
+ const { convertSourceMap, axios } = require('../../../runtime/monocart-coverage.js');
6
+
7
+ const Util = require('../../../utils/util.js');
8
+
9
+ const request = async (options) => {
10
+ if (typeof options === 'string') {
11
+ options = {
12
+ url: options
13
+ };
14
+ }
15
+ let err;
16
+ const res = await axios(options).catch((e) => {
17
+ err = e;
18
+ });
19
+ return [err, res];
20
+ };
21
+
22
+ const loadSourceMap = async (url = '') => {
23
+
24
+ if (url.startsWith('file://')) {
25
+ const p = fileURLToPath(url);
26
+ const json = Util.readJSONSync(p);
27
+ if (!json) {
28
+ Util.logDebug(EC.red(`failed to load sourcemap ${p}`));
29
+ return;
30
+ }
31
+ return json;
32
+ }
33
+
34
+ const [err, res] = await request({
35
+ url
36
+ });
37
+
38
+ if (err) {
39
+ Util.logDebug(EC.red(`${err.message} ${url}`));
40
+ return;
41
+ }
42
+
43
+ return res.data;
44
+ };
45
+
46
+ const getSourceMapUrl = (content, url) => {
47
+
48
+ const m = content.match(convertSourceMap.mapFileCommentRegex);
49
+ if (!m) {
50
+ return;
51
+ }
52
+
53
+ const comment = m.pop();
54
+ const r = convertSourceMap.mapFileCommentRegex.exec(comment);
55
+ // for some odd reason //# .. captures in 1 and /* .. */ in 2
56
+ const filename = r[1] || r[2];
57
+
58
+ let mapUrl;
59
+
60
+ try {
61
+ mapUrl = new URL(filename, url);
62
+ } catch (e) {
63
+ Util.logDebug(EC.red(`${e.message} ${filename} ${url}`));
64
+ }
65
+ if (mapUrl) {
66
+ return mapUrl.toString();
67
+ }
68
+ };
69
+
70
+ const resolveSourceMap = (data) => {
71
+ if (data) {
72
+ const {
73
+ sources, sourcesContent, mappings
74
+ } = data;
75
+ if (!sources || !sourcesContent || !mappings) {
76
+ return;
77
+ }
78
+ return data;
79
+ }
80
+ };
81
+
82
+ const collectInlineSourceMaps = async (v8list) => {
83
+ let count = 0;
84
+ const concurrency = new Concurrency();
85
+ for (const item of v8list) {
86
+
87
+ const { type, source } = item;
88
+
89
+ // only for js
90
+ if (type === 'js') {
91
+ const converter = convertSourceMap.fromSource(source);
92
+ if (converter) {
93
+ item.sourceMap = resolveSourceMap(converter.sourcemap);
94
+ count += 1;
95
+ // source map comments is inline source map, remove it because it is big
96
+ item.source = convertSourceMap.removeComments(source);
97
+
98
+ continue;
99
+ }
100
+ const sourceMapUrl = getSourceMapUrl(source, item.url);
101
+ if (sourceMapUrl) {
102
+ // console.log('========================', sourceMapUrl);
103
+ item.sourceMapUrl = sourceMapUrl;
104
+ concurrency.addItem(item);
105
+ }
106
+ }
107
+ }
108
+ await concurrency.start(async (item) => {
109
+ const data = await loadSourceMap(item.sourceMapUrl);
110
+ if (data) {
111
+ item.sourceMap = resolveSourceMap(data);
112
+ count += 1;
113
+ // source map comments is a link, no need remove
114
+ }
115
+ });
116
+
117
+ return count;
118
+ };
119
+
120
+ const collectFileSourceMaps = async (v8list, options) => {
121
+ let count = 0;
122
+ const concurrency = new Concurrency();
123
+ for (const item of v8list) {
124
+
125
+ const {
126
+ type, url, source, id
127
+ } = item;
128
+
129
+ // remove source just keep functions to reduce artifacts size
130
+ delete item.source;
131
+
132
+ const sourcePath = Util.resolveArtifactSourcePath(options.artifactsDir, id);
133
+ if (fs.existsSync(sourcePath)) {
134
+ continue;
135
+ }
136
+
137
+ const sourceData = {
138
+ url,
139
+ id,
140
+ source: convertSourceMap.removeComments(source)
141
+ };
142
+
143
+ // only for js
144
+ if (type === 'js') {
145
+ const converter = convertSourceMap.fromSource(source);
146
+ if (converter) {
147
+ sourceData.sourceMap = resolveSourceMap(converter.sourcemap);
148
+ count += 1;
149
+ await saveSourceFile(sourcePath, sourceData);
150
+ continue;
151
+ }
152
+ const sourceMapUrl = getSourceMapUrl(source, item.url);
153
+ if (sourceMapUrl) {
154
+ concurrency.addItem({
155
+ sourceMapUrl,
156
+ sourcePath,
157
+ sourceData
158
+ });
159
+ continue;
160
+ }
161
+ }
162
+
163
+ await saveSourceFile(sourcePath, sourceData);
164
+
165
+ }
166
+
167
+ await concurrency.start(async (item) => {
168
+ const sourceData = item.sourceData;
169
+ const data = await loadSourceMap(item.sourceMapUrl);
170
+ if (data) {
171
+ sourceData.sourceMap = resolveSourceMap(data);
172
+ count += 1;
173
+ }
174
+ await saveSourceFile(item.sourcePath, sourceData);
175
+ });
176
+
177
+ return count;
178
+ };
179
+
180
+ const saveSourceFile = async (filePath, data) => {
181
+ await Util.writeFile(filePath, JSON.stringify(data));
182
+ };
183
+
184
+ const collectSourceMaps = (v8list, options, inlineSourceMap) => {
185
+
186
+ if (inlineSourceMap) {
187
+ return collectInlineSourceMaps(v8list);
188
+ }
189
+
190
+ return collectFileSourceMaps(v8list, options);
191
+
192
+ };
193
+
194
+ module.exports = collectSourceMaps;