monocart-reporter 1.6.32 → 1.6.34

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
@@ -451,18 +451,6 @@ test.describe('suite title', () => {
451
451
  */
452
452
  const { test, expect } = require('@playwright/test');
453
453
  ```
454
- ```js
455
- // for project (Can't use comments but use project level `metadata`)
456
- // playwright.config.js
457
- module.exports = {
458
- projects: [{
459
- name: 'Desktop Chromium',
460
- metadata: {
461
- owner: 'PO'
462
- }
463
- }]
464
- };
465
- ```
466
454
 
467
455
  ### Remove Secrets and Sensitive Data
468
456
  > The report may hosted outside of the organization’s internal boundaries, security becomes a big issue. Any secrets or sensitive data, such as usernames, passwords, tokens and API keys, should be handled with extreme care. The following example is removing the password and token from the report data with the string replacement in `visitor` function.
@@ -639,6 +627,7 @@ Attach a code coverage report with API `attachCoverageReport(data, testInfo, opt
639
627
  - Istanbul only:
640
628
  - `watermarks` (Object) Istanbul watermarks, see [here](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-lib-report)
641
629
  - `lcov` (Boolean) Whether to create `lcov.info`
630
+ - `sourcePath` (Function) source path handler, return a new source path
642
631
  - V8 only:
643
632
  - `toIstanbul` (Boolean) Whether to convert to Istanbul report
644
633
  - `watermarks` (Array) Defaults to `[50, 80]`
@@ -654,30 +643,27 @@ Attach a code coverage report with API `attachCoverageReport(data, testInfo, opt
654
643
  ```js
655
644
  import { test, expect } from '@playwright/test';
656
645
  import { attachCoverageReport } from 'monocart-reporter';
657
- test.describe.configure({
658
- mode: 'serial'
659
- });
660
- let page;
661
- test.describe('take Istanbul coverage', () => {
662
- test('first, open page', async ({ browser }) => {
663
- page = await browser.newPage();
664
- await page.goto(pageUrl);
665
- });
666
646
 
667
- test('next, run test cases', async () => {
668
- await new Promise((resolve) => {
669
- setTimeout(resolve, 500);
670
- });
647
+ test('Take Istanbul coverage report', async ({ page }) => {
648
+
649
+ await page.goto('http://localhost:8090/coverage/istanbul.html');
650
+
651
+ // delay for mock code execution
652
+ await new Promise((resolve) => {
653
+ setTimeout(resolve, 500);
671
654
  });
672
655
 
673
- test('finally, take coverage', async () => {
674
- // take Istanbul coverage
675
- const coverageData = await page.evaluate(() => window.__coverage__);
676
- expect(coverageData, 'expect found Istanbul data: __coverage__').toBeTruthy();
677
- // coverage report
678
- const report = await attachCoverageReport(coverageData, test.info());
679
- console.log(report.summary);
656
+ // take Istanbul coverage
657
+ const coverageData = await page.evaluate(() => window.__coverage__);
658
+ await page.close();
659
+ expect(coverageData, 'expect found Istanbul data: __coverage__').toBeTruthy();
660
+
661
+ // coverage report
662
+ const report = await attachCoverageReport(coverageData, test.info(), {
663
+ lcov: true
680
664
  });
665
+ console.log(report.summary);
666
+
681
667
  });
682
668
  ```
683
669
 
@@ -687,43 +673,38 @@ test.describe('take Istanbul coverage', () => {
687
673
  ```js
688
674
  import { test, expect } from '@playwright/test';
689
675
  import { attachCoverageReport } from 'monocart-reporter';
690
- test.describe.configure({
691
- mode: 'serial'
692
- });
693
- let page;
694
- test.describe('take V8 coverage', () => {
695
- test('first, open page', async ({ browser }) => {
696
- page = await browser.newPage();
697
- await Promise.all([
698
- page.coverage.startJSCoverage(),
699
- page.coverage.startCSSCoverage()
700
- ]);
701
- await page.goto(pageUrl);
702
- });
703
676
 
704
- test('next, run test cases', async () => {
705
- await new Promise((resolve) => {
706
- setTimeout(resolve, 500);
707
- });
677
+ test('Take V8 and Istanbul coverage report', async ({ page }) => {
678
+
679
+ await Promise.all([
680
+ page.coverage.startJSCoverage({
681
+ resetOnNavigation: false
682
+ }),
683
+ page.coverage.startCSSCoverage({
684
+ resetOnNavigation: false
685
+ })
686
+ ]);
687
+
688
+ await page.goto('http://localhost:8090/coverage/v8.html');
689
+
690
+ // delay for mock code execution
691
+ await new Promise((resolve) => {
692
+ setTimeout(resolve, 500);
708
693
  });
709
694
 
710
- test('finally, take coverage', async () => {
711
- const [jsCoverage, cssCoverage] = await Promise.all([
712
- page.coverage.stopJSCoverage(),
713
- page.coverage.stopCSSCoverage()
714
- ]);
715
- const coverageList = [... jsCoverage, ... cssCoverage];
716
- // filter file list
717
- // coverageList = coverageList.filter((item) => {
718
- // if (item.url.endsWith('.js') || item.url.endsWith('.css')) {
719
- // return true;
720
- // }
721
- // });
722
- expect(coverageList.length).toBeGreaterThan(0);
723
- // coverage report
724
- const report = await attachCoverageReport(coverageList, test.info());
725
- console.log(report.summary);
695
+ const [jsCoverage, cssCoverage] = await Promise.all([
696
+ page.coverage.stopJSCoverage(),
697
+ page.coverage.stopCSSCoverage()
698
+ ]);
699
+ await page.close();
700
+
701
+ const coverageList = [... jsCoverage, ... cssCoverage];
702
+
703
+ const v8 = await attachCoverageReport(coverageList, test.info(), {
704
+ excludeDistFile: false
726
705
  });
706
+ console.log(v8.summary);
707
+
727
708
  });
728
709
  ```
729
710
 
package/lib/common.js CHANGED
@@ -39,7 +39,8 @@ const stepHandler = (item, summary) => {
39
39
  const tagHandler = (item, tags, tagOptions) => {
40
40
  const matches = item.title.matchAll(Util.tagPattern);
41
41
  for (const match of matches) {
42
- const tag = match[1];
42
+ // all, before, key, after
43
+ const tag = match[2];
43
44
  let tagItem = tags[tag];
44
45
  if (!tagItem) {
45
46
  tagItem = {};
@@ -9,7 +9,7 @@ module.exports = [{
9
9
  id: 'title',
10
10
  name: 'Title',
11
11
  searchable: true,
12
- width: 300,
12
+ width: 350,
13
13
  maxWidth: 1230
14
14
  }, {
15
15
  id: 'type',
@@ -4,8 +4,8 @@ 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
9
 
10
10
  const getReportName = (options, config, metadata) => {
11
11
  const reportName = options.name || config.name || metadata.name;
@@ -20,11 +20,11 @@ const artifactsHandler = async (visitor, options) => {
20
20
  // global artifacts
21
21
  const { coverage, network } = visitor.artifactDataMap;
22
22
  if (coverage) {
23
- const report = await generateCoverageReport(coverage, options);
23
+ const report = await generateGlobalCoverageReport(coverage, options);
24
24
  artifacts.push(report);
25
25
  }
26
26
  if (network) {
27
- const report = await generateNetworkReport(coverage, options);
27
+ const report = await generateGlobalNetworkReport(coverage, options);
28
28
  artifacts.push(report);
29
29
  }
30
30
  return artifacts;
@@ -81,16 +81,6 @@ const generateData = async (results) => {
81
81
  // global metadata
82
82
  const metadata = config.metadata || {};
83
83
 
84
- // merge project row metadata
85
- config.projects.forEach((p, i) => {
86
- // merge project metadata to project row
87
- const pm = p.metadata;
88
- const row = data.rows[i];
89
- if (pm && row) {
90
- Object.assign(row, pm);
91
- }
92
- });
93
-
94
84
  const reportName = getReportName(options, config, metadata);
95
85
  options.name = reportName;
96
86
 
@@ -1,7 +1,7 @@
1
1
  const Util = {
2
2
 
3
3
  // definition
4
- tagPattern: /@([^@\s]+)/g,
4
+ tagPattern: /(\s*)@([^@\s]+)(\s*)/g,
5
5
  lineBreakPattern: /\r\n|[\r\n\u2028\u2029]/gu,
6
6
 
7
7
  attachments: {
@@ -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
 
@@ -145,12 +149,17 @@ const attachCoverageReport = (coverageInput, testInfo, options = {}) => {
145
149
  ... options
146
150
  };
147
151
 
148
- const htmlDir = path.resolve(options.outputDir, options.outputName);
149
- if (!fs.existsSync(htmlDir)) {
150
- fs.mkdirSync(htmlDir, {
151
- recursive: true
152
- });
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,34 @@ 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
 
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;
202
+
202
203
  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);
204
+ const filename = `coverage-${id}.json`;
205
+ const jsonPath = path.resolve(artifactsDir, filename);
206
206
 
207
- const data = await generateArtifactData(coverageInput, options);
207
+ // init v8list and unpack sourcemap
208
+ const inlineSourceMap = false;
209
+ v8list = await initV8ListAndSourcemap(v8list, options, inlineSourceMap);
208
210
 
209
- // console.log('addCoverageReport keys', Object.keys(data.coverageData));
211
+ // console.log('addCoverageReport', coverageInput.map((it) => it.url));
210
212
 
211
213
  const report = {
212
- id,
213
- outputFile: Util.relativePath(jsonPath),
214
- data
214
+ // title for debug
215
+ title: testInfo.title,
216
+ // merge with data
217
+ data: v8list
215
218
  };
216
219
 
217
220
  const artifactContent = JSON.stringify({
@@ -233,34 +236,43 @@ const addCoverageReport = async (coverageInput, testInfo) => {
233
236
 
234
237
  // ========================================================================================================
235
238
 
236
- const generateGlobalCoverageReport = async (dataList, options) => {
239
+ const getGlobalCoverageData = async (dataList, options) => {
237
240
 
238
241
  options = {
239
242
  ... defaultV8Options,
240
243
  ... options
241
244
  };
242
245
 
246
+ // merge v8list first
247
+ const v8list = await mergeV8Coverage(dataList, options);
248
+ // console.log('after merge', v8list.map((it) => it.url));
249
+
243
250
  if (options.toIstanbul) {
251
+
244
252
  options = {
245
253
  ... defaultIstanbulOptions,
246
254
  ... options
247
255
  };
248
- const istanbulList = dataList.map((it) => it.data);
249
- const { coverageData, fileSources } = mergeIstanbulList(istanbulList);
250
- return saveIstanbulReport(coverageData, fileSources, options);
256
+
257
+ const { coverageData, fileSources } = await convertV8ToIstanbul(v8list, options);
258
+
259
+ const report = await saveIstanbulReport(coverageData, fileSources, options);
260
+
261
+ // console.log(report);
262
+
263
+ return report;
251
264
  }
252
265
 
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);
266
+ // functions to ranges, and unpack source maps
267
+ await unpackV8List(v8list, options);
268
+
269
+ const report = await saveV8Report(v8list, options);
270
+
271
+ return report;
260
272
  };
261
273
 
262
274
  // global coverage report, run different process with addCoverageReport
263
- const generateCoverageReport = async (dataList, reporterOptions) => {
275
+ const generateGlobalCoverageReport = async (dataList, reporterOptions) => {
264
276
 
265
277
  // reporter outputFile
266
278
  const outputFile = await Util.resolveOutputFile(reporterOptions.outputFile);
@@ -274,15 +286,27 @@ const generateCoverageReport = async (dataList, reporterOptions) => {
274
286
  ... coverageOptions
275
287
  };
276
288
 
277
- const htmlDir = path.resolve(options.outputDir, options.outputName);
278
- if (!fs.existsSync(htmlDir)) {
279
- fs.mkdirSync(htmlDir, {
289
+ const reportDir = path.resolve(options.outputDir, options.outputName);
290
+ if (!fs.existsSync(reportDir)) {
291
+ fs.mkdirSync(reportDir, {
280
292
  recursive: true
281
293
  });
282
294
  }
283
- options.htmlDir = htmlDir;
295
+ options.htmlDir = reportDir;
296
+
297
+ // artifactsDir for temp artifact files
298
+ const artifactsDir = path.resolve(reportDir, '.artifacts');
299
+ options.artifactsDir = artifactsDir;
284
300
 
285
- const report = await generateGlobalCoverageReport(dataList, options);
301
+ const report = await getGlobalCoverageData(dataList, options);
302
+ // console.log(report);
303
+
304
+ // =============================================================
305
+ // remove artifacts dir after report generated
306
+ if (!options.debug) {
307
+ Util.rmSync(artifactsDir);
308
+ }
309
+ // =============================================================
286
310
 
287
311
  return {
288
312
  global: true,
@@ -295,6 +319,6 @@ const generateCoverageReport = async (dataList, reporterOptions) => {
295
319
 
296
320
  module.exports = {
297
321
  addCoverageReport,
298
- generateCoverageReport,
322
+ generateGlobalCoverageReport,
299
323
  attachCoverageReport
300
324
  };