monocart-coverage-reports 2.7.6 → 2.7.8

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
@@ -16,7 +16,7 @@
16
16
  * [Collecting V8 Coverage Data](#collecting-v8-coverage-data)
17
17
  - [Collecting V8 Coverage Data with Playwright](#collecting-v8-coverage-data-with-playwright)
18
18
  - [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
19
- - [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
19
+ - [Collecting V8 Coverage Data from Node.js](#collecting-v8-coverage-data-from-nodejs)
20
20
  - [Collecting V8 Coverage Data with `CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
21
21
  - [V8 Coverage Data API](#v8-coverage-data-api)
22
22
  * [Using `entryFilter` and `sourceFilter` to filter the results for V8 report](#using-entryfilter-and-sourcefilter-to-filter-the-results-for-v8-report)
@@ -42,6 +42,7 @@
42
42
  - [WebdriverIO](#webdriverio)
43
43
  - [Storybook Test Runner](#storybook-test-runner)
44
44
  - [TestCafe](#testcafe)
45
+ - [Selenium Webdriver](#selenium-webdriver)
45
46
  - [Mocha](#mocha)
46
47
  - [tsx](#tsx)
47
48
  - [ts-node](#ts-node)
@@ -49,34 +50,37 @@
49
50
  - [Codecov](#codecov)
50
51
  - [Coveralls](#coveralls)
51
52
  - [Sonar Cloud](#sonar-cloud)
53
+ * [Contributing](#contributing)
54
+ * [Changelog](CHANGELOG.md)
52
55
  * [Thanks](#thanks)
53
56
 
54
57
  ## Usage
55
58
  > It's recommended to use [Node.js 20+](https://nodejs.org/).
56
- - [API](#multiprocessing-support)
59
+ - API
57
60
  ```js
58
61
  const MCR = require('monocart-coverage-reports');
59
- const coverageOptions = {
62
+ const mcr = MCR({
60
63
  name: 'My Coverage Report - 2024-02-28',
61
64
  outputDir: './coverage-reports',
62
- reports: ["v8", "console-details"]
63
- }
64
- const mcr = MCR(coverageOptions);
65
- mcr.cleanCache();
66
-
67
- await mcr.add(coverageData1);
68
- await mcr.add(coverageData2);
69
-
65
+ reports: ["v8", "console-details"],
66
+ cleanCache: true
67
+ });
68
+ await mcr.add(coverageData);
70
69
  await mcr.generate();
71
-
72
- // Or
73
- // const { CoverageReport } = require('monocart-coverage-reports');
74
- // const mcr = new CoverageReport(coverageOptions);
75
70
  ```
76
- - [CLI](#command-line)
71
+ Using `import` and load options from [config file](#config-file)
72
+ ```js
73
+ import { CoverageReport } from 'monocart-coverage-reports';
74
+ const mcr = new CoverageReport();
75
+ await mcr.loadConfig();
76
+ ```
77
+ For more information, see [Multiprocessing Support](#multiprocessing-support)
78
+
79
+ - CLI
77
80
  ```sh
78
81
  mcr node my-app.js -r v8,console-details
79
82
  ```
83
+ For more information, see [Command Line](#command-line)
80
84
 
81
85
  ## Options
82
86
  - Default Options: [lib/default/options.js](./lib/default/options.js)
@@ -109,7 +113,7 @@ mcr node my-app.js -r v8,console-details
109
113
  - `v8-json`
110
114
  - [V8 coverage-report.json](https://cenfun.github.io/monocart-coverage-reports/v8-and-istanbul/coverage-report.json)
111
115
 
112
- > Istanbul build-in reports (both V8 and istanbul data):
116
+ > Istanbul build-in reports (both V8 and Istanbul data):
113
117
 
114
118
  - `clover`
115
119
  - `cobertura`
@@ -129,7 +133,7 @@ mcr node my-app.js -r v8,console-details
129
133
  - `text-lcov`
130
134
  - `text-summary`
131
135
 
132
- > Other build-in reports (both V8 and istanbul data):
136
+ > Other build-in reports (both V8 and Istanbul data):
133
137
 
134
138
  - `codecov`
135
139
  - coverage data for [Codecov](https://docs.codecov.com/docs/codecov-custom-coverage-format), see [example](https://app.codecov.io/github/cenfun/monocart-coverage-reports)
@@ -163,9 +167,9 @@ mcr node my-app.js -r v8,console-details
163
167
  ]
164
168
  }
165
169
  ```
166
- - istanbul custom reporter
170
+ - Istanbul custom reporter
167
171
  > example: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference.
168
- - v8 custom reporter
172
+ - V8 custom reporter
169
173
  > example: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
170
174
 
171
175
  ### Multiple Reports:
@@ -194,11 +198,9 @@ const coverageOptions = {
194
198
  }],
195
199
  // Specify reporter name with local path
196
200
  ['/absolute/path/to/custom-reporter.js']
197
-
198
201
  ]
199
202
  }
200
203
  const mcr = MCR(coverageOptions);
201
- mcr.cleanCache();
202
204
  ```
203
205
 
204
206
  ## Compare Reports
@@ -216,29 +218,35 @@ mcr.cleanCache();
216
218
  | Minified code | ❌ | ✅ | ❌ |
217
219
 
218
220
  ## Collecting Istanbul Coverage Data
219
- - Instrumenting source code
220
- > Before collecting Istanbul coverage data, It requires your source code is instrumented with Istanbul
221
- - webpack: [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul), example: [webpack.config-istanbul.js](./test/webpack.config-istanbul.js)
222
- - rollup: [rollup-plugin-istanbul](https://github.com/artberri/rollup-plugin-istanbul)
221
+ - Before coverage collection: Instrumenting source code with Istanbul
222
+ - webpack with babel loader: [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul), see example: [webpack.config-istanbul.js](./test/build/webpack.config-istanbul.js)
223
+ - CLI: [nyc instrument](https://github.com/istanbuljs/nyc/blob/master/docs/instrument.md) or API: [istanbul-lib-instrument](https://github.com/istanbuljs/istanbuljs/blob/main/packages/istanbul-lib-instrument/api.md)
223
224
  - vite: [vite-plugin-istanbul](https://github.com/ifaxity/vite-plugin-istanbul)
225
+ - rollup: [rollup-plugin-istanbul](https://github.com/artberri/rollup-plugin-istanbul)
226
+ - swc: [swc-plugin-coverage-instrument](https://github.com/kwonoj/swc-plugin-coverage-instrument)
227
+
224
228
  - Browser
225
229
  - Collecting coverage data from `window.__coverage__`, example: [test-istanbul.js](./test/test-istanbul.js)
230
+
226
231
  - Node.js
227
232
  - Collecting coverage data from `global.__coverage__`
228
233
 
234
+ - CDP
235
+ - `getIstanbulCoverage()` see [`CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
236
+
229
237
  ## Collecting V8 Coverage Data
230
- - Sourcemap for source code: enable `sourcemap` and do not compress/minify:
231
- - [webpack](https://webpack.js.org/configuration/): `devtool: source-map` and `mode: development`, example [webpack.config-v8.js](./test/webpack.config-v8.js)
232
- - [rollup](https://rollupjs.org/configuration-options/): `sourcemap: true`
238
+ - Before coverage collection: Enabling `sourcemap` for source code
239
+ - [webpack](https://webpack.js.org/configuration/): `devtool: source-map` and `mode: development`, example [webpack.config-v8.js](./test/build/webpack.config-v8.js)
240
+ - [rollup](https://rollupjs.org/configuration-options/): `sourcemap: true` and `treeshake: false`
241
+ - [esbuild](https://esbuild.github.io/api/): `sourcemap: true`, `treeShaking: false` and `minify: false`
233
242
  - [vite](https://vitejs.dev/config/build-options.html): `sourcemap: true` and `minify: false`
234
- - [esbuild](https://esbuild.github.io/api/): `sourcemap: true` and `minify: false`
235
243
 
236
- - Browser (Chromium Only)
244
+ - Browser (Chromium-based Only)
237
245
  - [Collecting V8 Coverage Data with Playwright](#collecting-v8-coverage-data-with-playwright)
238
246
  - [Collecting Raw V8 Coverage Data with Puppeteer](#collecting-raw-v8-coverage-data-with-puppeteer)
239
247
 
240
248
  - Node.js
241
- - [Node.js V8 Coverage Report for Server Side](#nodejs-v8-coverage-report-for-server-side)
249
+ - [Collecting V8 Coverage Data from Node.js](#collecting-v8-coverage-data-from-nodejs)
242
250
 
243
251
  - CDP
244
252
  - [Collecting V8 Coverage Data with `CDPClient` API](#collecting-v8-coverage-data-with-cdpclient-api)
@@ -264,7 +272,7 @@ const [jsCoverage, cssCoverage] = await Promise.all([
264
272
  const coverageData = [... jsCoverage, ... cssCoverage];
265
273
 
266
274
  ```
267
- see [./test/test-v8.js](./test/test-v8.js), and [anonymous](./test/test-anonymous.js), [css](./test/test-css.js)
275
+ For more examples, see [./test/test-v8.js](./test/test-v8.js), and [anonymous](./test/test-anonymous.js), [css](./test/test-css.js)
268
276
 
269
277
  ### Collecting Raw V8 Coverage Data with Puppeteer
270
278
  ```js
@@ -294,9 +302,9 @@ const coverageData = [... jsCoverage.map((it) => {
294
302
  };
295
303
  }), ... cssCoverage];
296
304
  ```
297
- see example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
305
+ Example: [./test/test-puppeteer.js](./test/test-puppeteer.js)
298
306
 
299
- ### Node.js V8 Coverage Report for Server Side
307
+ ### Collecting V8 Coverage Data from Node.js
300
308
  Possible solutions:
301
309
  - [NODE_V8_COVERAGE](https://nodejs.org/docs/latest/api/cli.html#node_v8_coveragedir)=`dir`
302
310
  - Sets Node.js env `NODE_V8_COVERAGE`=`dir` before the program running, the coverage data will be saved to the `dir` after the program exits gracefully.
@@ -330,7 +338,27 @@ Possible solutions:
330
338
  - see [Command Line](#command-line)
331
339
 
332
340
  ### Collecting V8 Coverage Data with `CDPClient` API
333
- - Work with node debugger `--inspect=9229`
341
+ - `CDPClient` available APIs
342
+ ```js
343
+ startJSCoverage: () => Promise<void>;
344
+ stopJSCoverage: () => Promise<V8CoverageEntry[]>;
345
+
346
+ startCSSCoverage: () => Promise<void>;
347
+ stopCSSCoverage: () => Promise<V8CoverageEntry[]>;
348
+
349
+ /** start both js and css coverage */
350
+ startCoverage: () => Promise<void>;
351
+ /** stop and return both js and css coverage */
352
+ stopCoverage: () => Promise<V8CoverageEntry[]>;
353
+
354
+ /** write the coverage started by NODE_V8_COVERAGE to disk on demand, returns v8 coverage dir */
355
+ writeCoverage: () => Promise<string>;
356
+
357
+ /** get istanbul coverage data */
358
+ getIstanbulCoverage: (coverageKey?: string) => Promise<any>;
359
+ ```
360
+
361
+ - Work with node debugger port `--inspect=9229` or browser debugging port `--remote-debugging-port=9229`
334
362
  ```js
335
363
  const MCR = require('monocart-coverage-reports');
336
364
  const client = await MCR.CDPClient({
@@ -340,6 +368,7 @@ await client.startJSCoverage();
340
368
  // run your test here
341
369
  const coverageData = await client.stopJSCoverage();
342
370
  ```
371
+
343
372
  - Work with [Playwright CDPSession](https://playwright.dev/docs/api/class-cdpsession)
344
373
  ```js
345
374
  const { chromium } = require('playwright');
@@ -356,6 +385,7 @@ await client.startCoverage();
356
385
  await page.goto("your page url");
357
386
  const coverageData = await client.stopCoverage();
358
387
  ```
388
+
359
389
  - Work with [Puppeteer CDPSession](https://pptr.dev/api/puppeteer.cdpsession)
360
390
  ```js
361
391
  const puppeteer = require('puppeteer');
@@ -373,11 +403,23 @@ await page.goto("your page url");
373
403
  const coverageData = await client.stopCoverage();
374
404
  ```
375
405
 
406
+ - Work with [Selenium Webdriver](https://www.selenium.dev/documentation/webdriver/) WebSocket (Chrome/Edge Browser)
407
+ ```js
408
+ const { Builder, Browser } = require('selenium-webdriver');
409
+ const MCR = require('monocart-coverage-reports');
410
+ const driver = await new Builder().forBrowser(Browser.CHROME).build();
411
+ const pageCdpConnection = await driver.createCDPConnection('page');
412
+ const session = new MCR.WSSession(pageCdpConnection._wsConnection);
413
+ const client = await MCR.CDPClient({
414
+ session
415
+ })
416
+ ```
417
+
376
418
  ### V8 Coverage Data API
377
- - [V8 coverage report](https://v8.dev/blog/javascript-code-coverage) - Native support for JavaScript code coverage to V8. (Chromium only)
419
+ - [JavaScript code coverage in V8](https://v8.dev/blog/javascript-code-coverage)
378
420
  - [Playwright Coverage Class](https://playwright.dev/docs/api/class-coverage)
379
- - [Puppeteer Coverage class](https://pptr.dev/api/puppeteer.coverage)
380
- - [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage) see devtools-protocol [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
421
+ - [Puppeteer Coverage Class](https://pptr.dev/api/puppeteer.coverage)
422
+ - [DevTools Protocol for Coverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#method-startPreciseCoverage) see [ScriptCoverage](https://chromedevtools.github.io/devtools-protocol/tot/Profiler/#type-ScriptCoverage) and [v8-coverage](https://github.com/bcoe/v8-coverage)
381
423
  ```js
382
424
  // Coverage data for a source range.
383
425
  export interface CoverageRange {
@@ -388,7 +430,6 @@ export interface CoverageRange {
388
430
  // Collected execution count of the source range.
389
431
  count: integer;
390
432
  }
391
-
392
433
  // Coverage data for a JavaScript function.
393
434
  /**
394
435
  * @functionName can be an empty string.
@@ -405,7 +446,6 @@ export interface FunctionCoverage {
405
446
  // Whether coverage data for this function has block granularity.
406
447
  isBlockCoverage: boolean;
407
448
  }
408
-
409
449
  // Coverage data for a JavaScript script.
410
450
  export interface ScriptCoverage {
411
451
  // JavaScript script id.
@@ -415,32 +455,31 @@ export interface ScriptCoverage {
415
455
  // Functions contained in the script that has coverage data.
416
456
  functions: FunctionCoverage[];
417
457
  }
418
-
419
458
  export type V8CoverageData = ScriptCoverage[];
420
459
  ```
421
460
 
422
461
  ## Using `entryFilter` and `sourceFilter` to filter the results for V8 report
423
462
  When V8 coverage data collected, it actually contains the data of all entry files, for example:
424
- ```
425
- dist/main.js
426
- dist/vendor.js
427
- dist/something-else.js
428
- ```
463
+
464
+ - *dist/main.js*
465
+ - *dist/vendor.js*
466
+ - *dist/something-else.js*
467
+
429
468
  We can use `entryFilter` to filter the entry files. For example, we should remove `vendor.js` and `something-else.js` if they are not in our coverage scope.
430
- ```
431
- dist/main.js
432
- ```
469
+
470
+ - *dist/main.js*
471
+
433
472
  When inline or linked sourcemap exists to the entry file, the source files will be extracted from the sourcemap for the entry file, and the entry file will be removed if `logging` is not `debug`.
434
- ```
435
- > src/index.js
436
- > src/components/app.js
437
- > node_modules/dependency/dist/dependency.js
438
- ```
473
+
474
+ - *src/index.js*
475
+ - *src/components/app.js*
476
+ - *node_modules/dependency/dist/dependency.js*
477
+
439
478
  We can use `sourceFilter` to filter the source files. For example, we should remove `dependency.js` if it is not in our coverage scope.
440
- ```
441
- > src/index.js
442
- > src/components/app.js
443
- ```
479
+
480
+ - *src/index.js*
481
+ - *src/components/app.js*
482
+
444
483
  For example:
445
484
  ```js
446
485
  const coverageOptions = {
@@ -600,7 +639,6 @@ mcr.cleanCache();
600
639
  const MCR = require('monocart-coverage-reports');
601
640
  const coverageOptions = require('path-to/same-options.js');
602
641
  const mcr = MCR(coverageOptions);
603
- // do not clean cache in the stage
604
642
  await mcr.add(coverageData1);
605
643
  ```
606
644
 
@@ -609,7 +647,6 @@ await mcr.add(coverageData1);
609
647
  const MCR = require('monocart-coverage-reports');
610
648
  const coverageOptions = require('path-to/same-options.js');
611
649
  const mcr = MCR(coverageOptions);
612
- // do not clean cache in the stage
613
650
  await mcr.add(coverageData2);
614
651
  ```
615
652
 
@@ -619,7 +656,6 @@ await mcr.add(coverageData2);
619
656
  const MCR = require('monocart-coverage-reports');
620
657
  const coverageOptions = require('path-to/same-options.js');
621
658
  const mcr = MCR(coverageOptions);
622
- // do not clean cache before generating reports
623
659
  await mcr.generate();
624
660
  ```
625
661
 
@@ -629,13 +665,13 @@ await mcr.generate();
629
665
  - Installing globally
630
666
  ```sh
631
667
  npm i monocart-coverage-reports -g
632
- mcr node ./test/specs/node.test.js -r v8,console-summary --lcov
668
+ mcr node ./test/specs/node.test.js -r v8,console-details --lcov
633
669
  ```
634
670
 
635
671
  - Locally in your project
636
672
  ```sh
637
673
  npm i monocart-coverage-reports
638
- npx mcr node ./test/specs/node.test.js -r v8,console-summary --lcov
674
+ npx mcr node ./test/specs/node.test.js -r v8,console-details --lcov
639
675
  ```
640
676
 
641
677
  - CLI Options
@@ -671,6 +707,12 @@ Options:
671
707
  mcr -c mcr.config.js -- sub-cli -c sub-cli.config.js
672
708
  ```
673
709
 
710
+ - Examples
711
+ - [Mocha](#mocha)
712
+ - [tsx](#tsx)
713
+ - [ts-node](#ts-node)
714
+ - [AVA](#ava)
715
+
674
716
  ## Config File
675
717
  Loading config file by priority:
676
718
  - Custom config file:
@@ -692,6 +734,7 @@ The following usage scenarios may require merging coverage reports:
692
734
 
693
735
  If the reports cannot be merged automatically, then here is how to manually merge the reports.
694
736
  First, using the `raw` report to export the original coverage data to the specified directory.
737
+ For example, we have `raw` coverage data from unit tests:
695
738
  ```js
696
739
  const coverageOptions = {
697
740
  name: 'My Unit Test Coverage Report',
@@ -699,15 +742,18 @@ const coverageOptions = {
699
742
  reports: [
700
743
  ['raw', {
701
744
  // relative path will be "./coverage-reports/unit/raw"
745
+ // defaults to raw
702
746
  outputDir: "raw"
703
747
  }],
704
748
  ['v8'],
705
- ['console-summary']
749
+ ['console-details']
706
750
  ]
707
751
  };
708
752
  ```
709
- Then, after all the tests are completed, generate a merged report with option `inputDir`:
753
+ We also have `raw` coverage data from e2e tests, which is output to `./coverage-reports/e2e/raw`.
754
+ After all the tests are completed, generate a merged report with option `inputDir`:
710
755
  ```js
756
+ // merge-coverage.js
711
757
  const fs = require('fs');
712
758
  const { CoverageReport } = require('monocart-coverage-reports');
713
759
  const inputDir = [
@@ -731,6 +777,10 @@ const coverageOptions = {
731
777
 
732
778
  sourcePath: (filePath, info) => {
733
779
  // Unify the file path for the same files
780
+ // For example, the file index.js has different paths:
781
+ // unit: unit-dist/src/index.js
782
+ // e2e: e2e-dist/src/index.js
783
+ // return filePath.replace("unit-dist/", "").replace("e2e-dist/", "")
734
784
  return filePath;
735
785
  },
736
786
 
@@ -741,12 +791,12 @@ const coverageOptions = {
741
791
 
742
792
  onEnd: () => {
743
793
  // remove the raw files if it useless
744
- inputDir.forEach((p) => {
745
- fs.rmSync(p, {
746
- recursive: true,
747
- force: true
748
- });
749
- });
794
+ // inputDir.forEach((p) => {
795
+ // fs.rmSync(p, {
796
+ // recursive: true,
797
+ // force: true
798
+ // });
799
+ // });
750
800
  }
751
801
  };
752
802
  await new CoverageReport(coverageOptions).generate();
@@ -763,7 +813,7 @@ const a = tf ? 'true' : 'false';
763
813
  `m1` and `m2` are two consecutive mappings, `p` is the position we looking for. However, we can only get the position of the `m1` or `m2` if we don't fix it to `p`. Especially the generated code is different from the original code, such as the code was minified, compressed or converted, it is difficult to find the exact position. You can try [Debug for Coverage and Sourcemap](#debug-for-coverage-and-sourcemap)
764
814
 
765
815
  How `MCR` Works:
766
- - 1, Trying to fix the middle position if not found the exact mapping for the position. However, for non-JS code, such as Vue template, JSX, etc., it might be hard to find a perfect solution.
816
+ - 1, Trying to fix the middle original position with string comparison and [`diff-sequences`](https://github.com/jestjs/jest/tree/main/packages/diff-sequences). However, for non-JS code, such as Vue template, JSX, etc., it might be hard to find a perfect solution.
767
817
  - 2, Finding all functions, statements and branches by parsing the source code [AST](https://github.com/acornjs/acorn). However, there's a small issue, which is the V8 cannot provide effective branch coverage information for `AssignmentPattern`.
768
818
 
769
819
 
@@ -795,7 +845,7 @@ const coverageOptions = {
795
845
  logging: 'debug',
796
846
  reports: [
797
847
  ['v8'],
798
- ['console-summary']
848
+ ['console-details']
799
849
  ]
800
850
  };
801
851
  ```
@@ -818,6 +868,7 @@ When `logging` is `debug`, the raw report data will be preserved in `[outputDir]
818
868
  ## Integration Examples
819
869
 
820
870
  ### [Playwright](https://github.com/microsoft/playwright)
871
+ - [playwright-coverage](https://github.com/cenfun/playwright-coverage) - A Example for Playwright Coverage Reports
821
872
  - [monocart-reporter](https://github.com/cenfun/monocart-reporter) - A Playwright custom reporter, supports generating [Code Coverage Report](https://github.com/cenfun/monocart-reporter?#code-coverage-report)
822
873
  - Coverage for component testing:
823
874
  - [playwright-ct-vue](https://github.com/cenfun/playwright-ct-vue)
@@ -829,6 +880,7 @@ When `logging` is `debug`, the raw report data will be preserved in `[outputDir]
829
880
 
830
881
  ### [Jest](https://github.com/jestjs/jest/)
831
882
  - [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage) - A Jest custom reporter for coverage reports
883
+ - [jest-puppeteer-coverage](https://github.com/cenfun/jest-puppeteer-coverage) - A Jest Puppeteer Coverage Example
832
884
  - Example for Jest (unit) + Puppeteer (e2e) + Codecov: [maplibre-gl-js](https://github.com/maplibre/maplibre-gl-js)
833
885
 
834
886
  ### [Vitest](https://github.com/vitest-dev/vitest)
@@ -850,6 +902,9 @@ When `logging` is `debug`, the raw report data will be preserved in `[outputDir]
850
902
  ### [TestCafe](https://github.com/DevExpress/testcafe)
851
903
  - [testcafe-reporter-coverage](https://github.com/cenfun/testcafe-reporter-coverage) - A TestCafe custom reporter for coverage reports
852
904
 
905
+ ### [Selenium Webdriver](https://github.com/seleniumhq/selenium)
906
+ - [selenium-webdriver-coverage](https://github.com/cenfun/selenium-webdriver-coverage) - Selenium Webdriver V8 Coverage Example
907
+
853
908
  ### [Mocha](https://github.com/mochajs/mocha)
854
909
  ```sh
855
910
  mcr mocha ./test/**/*.js
@@ -927,6 +982,18 @@ const coverageOptions = {
927
982
  -Dsonar.exclusions=dist/*,packages/*
928
983
  ```
929
984
 
985
+ ## Contributing
986
+ ```sh
987
+ # Node.js 20+
988
+ npm install starfall-cli -g
989
+ npm install
990
+
991
+ npm run build
992
+ npm run test
993
+
994
+ npm run dev
995
+ ```
996
+
930
997
  ### VSCode Extension
931
998
  - [Coverage Gutters](https://github.com/ryanluker/vscode-coverage-gutters) - Display test coverage generated by lcov or xml in VSCode editor.
932
999
 
package/lib/cli.js CHANGED
@@ -21,25 +21,12 @@ const getRegisterPath = (filename) => {
21
21
  return `./${rel}`;
22
22
  };
23
23
 
24
- const isFeatureSupported = (nv) => {
25
- // module register added in Node.js: v20.6.0
26
- const [major, minor] = nv.split('.').map((s) => parseInt(s));
27
- if (major < 20) {
28
- return false;
29
- }
30
- if (major > 20) {
31
- return true;
32
- }
33
- if (minor < 6) {
34
- return false;
35
- }
36
- return true;
37
- };
38
-
39
24
  const initNodeOptions = async (cliOptions) => {
25
+
26
+ // module register added in Node.js: v20.6.0
40
27
  const nv = process.versions.node;
41
- if (!isFeatureSupported(nv)) {
42
- Util.logInfo(EC.yellow(`The current Node.js version "${nv}" does NOT support "module.register", it requires "20.6.0" or higher.`));
28
+ if (Util.cmpVersion(nv, '20.6.0') < 0) {
29
+ Util.logInfo(`The current Node.js version "${nv}" does NOT support "module.register", it requires "20.6.0" or higher.`);
43
30
  return;
44
31
  }
45
32
 
@@ -12,10 +12,7 @@ const getDebuggerUrl = async (options) => {
12
12
  const protocol = options.secure ? 'https' : 'http';
13
13
 
14
14
  const url = `${protocol}://${options.host}:${options.port}/json/list`;
15
- const [err, res] = await Util.request({
16
- url,
17
- timeout: options.timeout
18
- });
15
+ const [err, res] = await Util.request(url);
19
16
  if (err) {
20
17
  return [err];
21
18
  }
@@ -131,19 +131,22 @@ class CoverageClient {
131
131
  unbindEvents(this.session, this.jsEvents);
132
132
  this.jsEvents = null;
133
133
 
134
- const jsCoverage = profileResponse.result;
135
- // console.log('coverageList', coverageList);
134
+ const jsCoverage = [];
136
135
 
137
- jsCoverage.forEach((entry) => {
138
- // anonymous url
139
- entry.url = entry.url || '';
140
- // add source
141
- const source = this.scriptSources.get(entry.scriptId);
142
- if (!source) {
143
- Util.logDebug(`Not found js source: ${entry.url}`);
144
- }
145
- entry.source = source || '';
146
- });
136
+ // console.log('coverageList', coverageList);
137
+ if (profileResponse && profileResponse.result) {
138
+ profileResponse.result.forEach((entry) => {
139
+ // anonymous url
140
+ entry.url = entry.url || '';
141
+ // add source
142
+ const source = this.scriptSources.get(entry.scriptId);
143
+ if (!source) {
144
+ Util.logDebug(`Not found js source: ${entry.url}`);
145
+ }
146
+ entry.source = source || '';
147
+ jsCoverage.push(entry);
148
+ });
149
+ }
147
150
 
148
151
  this.scriptSources.clear();
149
152
  this.enabledJS = false;
@@ -239,22 +242,39 @@ class CoverageClient {
239
242
  }
240
243
 
241
244
  async stopCoverage() {
245
+ if (!this.session) {
246
+ return;
247
+ }
242
248
  const [jsCoverage, cssCoverage] = await Promise.all([
243
249
  this.stopJSCoverage(),
244
250
  this.stopCSSCoverage()
245
251
  ]);
246
- return [... jsCoverage, ... cssCoverage];
252
+ // could be undefined
253
+ let coverageList = [];
254
+ if (jsCoverage) {
255
+ coverageList = coverageList.concat(jsCoverage);
256
+ }
257
+ if (cssCoverage) {
258
+ coverageList = coverageList.concat(cssCoverage);
259
+ }
260
+ return coverageList;
247
261
  }
248
262
 
249
263
  // =================================================================================================
250
264
  // write the coverage started by NODE_V8_COVERAGE to disk on demand
251
265
  async writeCoverage() {
252
- // // enable and start v8 coverage
266
+ if (!this.session) {
267
+ return;
268
+ }
269
+
253
270
  await this.session.send('Runtime.enable');
254
271
 
255
- // write the coverage started by NODE_V8_COVERAGE to disk on demand
272
+ // write the coverage started by NODE_V8_COVERAGE to disk on demand
256
273
  const res = await this.session.send('Runtime.evaluate', {
257
- expression: 'new Promise(resolve=>{ require("v8").takeCoverage(); resolve(process.env.NODE_V8_COVERAGE); })',
274
+ expression: `new Promise((resolve) => {
275
+ require("v8").takeCoverage();
276
+ resolve(process.env.NODE_V8_COVERAGE);
277
+ })`,
258
278
  includeCommandLineAPI: true,
259
279
  returnByValue: true,
260
280
  awaitPromise: true
@@ -262,7 +282,31 @@ class CoverageClient {
262
282
 
263
283
  await this.session.send('Runtime.disable');
264
284
 
265
- return res.result.value;
285
+ return res && res.result && res.result.value;
286
+ }
287
+
288
+ // get istanbul coverage data
289
+ async getIstanbulCoverage(coverageKey = '__coverage__') {
290
+ if (!this.session) {
291
+ return;
292
+ }
293
+
294
+ await this.session.send('Runtime.enable');
295
+
296
+ // both browser and Node.js
297
+ const res = await this.session.send('Runtime.evaluate', {
298
+ expression: `new Promise((resolve) => {
299
+ const globalTarget = typeof window !== 'undefined' ? window : global;
300
+ resolve(globalTarget['${coverageKey}']);
301
+ })`,
302
+ includeCommandLineAPI: true,
303
+ returnByValue: true,
304
+ awaitPromise: true
305
+ });
306
+
307
+ await this.session.send('Runtime.disable');
308
+
309
+ return res && res.result && res.result.value;
266
310
  }
267
311
 
268
312
  // =================================================================================================
@@ -270,10 +314,8 @@ class CoverageClient {
270
314
  if (!this.session) {
271
315
  return;
272
316
  }
273
-
274
317
  await this.session.detach();
275
318
  this.session = null;
276
-
277
319
  }
278
320
  }
279
321
 
@@ -19,9 +19,7 @@ const loadSourceMap = async (url = '') => {
19
19
  return JSON.parse(content);
20
20
  }
21
21
 
22
- const [err, res] = await Util.request({
23
- url
24
- });
22
+ const [err, res] = await Util.request(url);
25
23
 
26
24
  if (err) {
27
25
  Util.logDebug(EC.red(`${err.message} ${url}`));