@wdio/browser-runner 8.2.4 → 8.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/browser/driver.ts"],"names":[],"mappings":"AAkBA,MAAM,CAAC,OAAO,OAAO,WAAW;;IAG5B,MAAM,CAAC,UAAU,CACb,MAAM,EAAE,GAAG,EACX,QAAQ,EAAE,KAAK,EACf,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACjD,cAAc,EAAE,GAAG;CAkJ1B"}
1
+ {"version":3,"file":"driver.d.ts","sourceRoot":"","sources":["../../src/browser/driver.ts"],"names":[],"mappings":"AAiBA,MAAM,CAAC,OAAO,OAAO,WAAW;;IAG5B,MAAM,CAAC,UAAU,CACb,MAAM,EAAE,GAAG,EACX,QAAQ,EAAE,KAAK,EACf,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACjD,cAAc,EAAE,GAAG;CAmJ1B"}
@@ -1,6 +1,6 @@
1
1
  import stringify from 'fast-safe-stringify';
2
2
  import { commands } from 'virtual:wdio';
3
- import { webdriverMonad } from '@wdio/utils';
3
+ import { webdriverMonad, sessionEnvironmentDetector } from '@wdio/utils';
4
4
  import { getEnvironmentVars } from 'webdriver';
5
5
  import { MESSAGE_TYPES } from '../constants.js';
6
6
  const COMMAND_TIMEOUT = 30 * 1000; // 30s
@@ -23,7 +23,8 @@ export default class ProxyDriver {
23
23
  const socket = window.__wdioSocket__;
24
24
  socket.addEventListener('message', this.#handleServerMessage.bind(this));
25
25
  let commandId = 0;
26
- const environmentPrototype = getEnvironmentVars(params);
26
+ const environment = sessionEnvironmentDetector({ capabilities: params.capabilities, requestedCapabilities: {} });
27
+ const environmentPrototype = getEnvironmentVars(environment);
27
28
  // have debug command
28
29
  const commandsProcessedInNodeWorld = [...commands, 'debug'];
29
30
  const protocolCommands = commandsProcessedInNodeWorld.reduce((prev, commandName) => {
@@ -94,8 +95,8 @@ export default class ProxyDriver {
94
95
  return console.error(`Unknown command id "${value.id}"`);
95
96
  }
96
97
  if (value.error) {
97
- console.log(`[WDIO] ${(new Date()).toISOString()} - id: ${value.id} - ERROR: ${JSON.stringify(value.result)}`);
98
- return commandMessage.reject(value.error);
98
+ console.log(`[WDIO] ${(new Date()).toISOString()} - id: ${value.id} - ERROR: ${JSON.stringify(value.error.message)}`);
99
+ return commandMessage.reject(new Error(value.error.message || 'unknown error'));
99
100
  }
100
101
  if (commandMessage.commandTimeout) {
101
102
  clearTimeout(commandMessage.commandTimeout);
@@ -1,4 +1,5 @@
1
1
  import type { Environment } from './types.js';
2
+ import type { ReportOptions } from 'istanbul-reports';
2
3
  export declare const SESSIONS: Map<string, Environment>;
3
4
  export declare const BROWSER_POOL: Map<string, WebdriverIO.Browser>;
4
5
  export declare const EVENTS: {
@@ -21,4 +22,12 @@ export declare enum MESSAGE_TYPES {
21
22
  hookTriggerMessage = 3,
22
23
  hookResultMessage = 4
23
24
  }
25
+ export declare const DEFAULT_INCLUDE: string[];
26
+ export declare const DEFAULT_FILE_EXTENSIONS: string[];
27
+ export declare const DEFAULT_REPORTS_DIRECTORY = "coverage";
28
+ export declare const SUMMARY_REPORTER = "json-summary";
29
+ export declare const COVERAGE_FACTORS: readonly ["lines", "functions", "branches", "statements"];
30
+ export declare const DEFAULT_COVERAGE_REPORTS: (keyof ReportOptions)[];
31
+ export declare const GLOBAL_TRESHOLD_REPORTING = "ERROR: Coverage for %s (%s%) does not meet global threshold (%s%)";
32
+ export declare const FILE_TRESHOLD_REPORTING = "ERROR: Coverage for %s (%s%) does not meet threshold (%s%) for %s";
24
33
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,eAAO,MAAM,QAAQ,0BAAiC,CAAA;AACtD,eAAO,MAAM,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAa,CAAA;AAEvE,eAAO,MAAM,MAAM;;;;;;;;;;;CAWT,CAAA;AAEV,eAAO,MAAM,uBAAuB,wFAAsF,CAAA;AAE1H,oBAAY,aAAa;IACrB,cAAc,IAAI;IAClB,qBAAqB,IAAA;IACrB,sBAAsB,IAAA;IACtB,kBAAkB,IAAA;IAClB,iBAAiB,IAAA;CACpB"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAC7C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAErD,eAAO,MAAM,QAAQ,0BAAiC,CAAA;AACtD,eAAO,MAAM,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAa,CAAA;AAEvE,eAAO,MAAM,MAAM;;;;;;;;;;;CAWT,CAAA;AAEV,eAAO,MAAM,uBAAuB,wFAAsF,CAAA;AAE1H,oBAAY,aAAa;IACrB,cAAc,IAAI;IAClB,qBAAqB,IAAA;IACrB,sBAAsB,IAAA;IACtB,kBAAkB,IAAA;IAClB,iBAAiB,IAAA;CACpB;AAED,eAAO,MAAM,eAAe,UAAS,CAAA;AACrC,eAAO,MAAM,uBAAuB,UAAoF,CAAA;AACxH,eAAO,MAAM,yBAAyB,aAAa,CAAA;AACnD,eAAO,MAAM,gBAAgB,iBAAiB,CAAA;AAC9C,eAAO,MAAM,gBAAgB,2DAA4D,CAAA;AACzF,eAAO,MAAM,wBAAwB,EAAE,CAAC,MAAM,aAAa,CAAC,EAAiD,CAAA;AAC7G,eAAO,MAAM,yBAAyB,sEAAsE,CAAA;AAC5G,eAAO,MAAM,uBAAuB,sEAAsE,CAAA"}
@@ -21,3 +21,11 @@ export var MESSAGE_TYPES;
21
21
  MESSAGE_TYPES[MESSAGE_TYPES["hookTriggerMessage"] = 3] = "hookTriggerMessage";
22
22
  MESSAGE_TYPES[MESSAGE_TYPES["hookResultMessage"] = 4] = "hookResultMessage";
23
23
  })(MESSAGE_TYPES = MESSAGE_TYPES || (MESSAGE_TYPES = {}));
24
+ export const DEFAULT_INCLUDE = ['**'];
25
+ export const DEFAULT_FILE_EXTENSIONS = ['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte'];
26
+ export const DEFAULT_REPORTS_DIRECTORY = 'coverage';
27
+ export const SUMMARY_REPORTER = 'json-summary';
28
+ export const COVERAGE_FACTORS = ['lines', 'functions', 'branches', 'statements'];
29
+ export const DEFAULT_COVERAGE_REPORTS = ['text', 'html', 'clover', SUMMARY_REPORTER];
30
+ export const GLOBAL_TRESHOLD_REPORTING = 'ERROR: Coverage for %s (%s%) does not meet global threshold (%s%)';
31
+ export const FILE_TRESHOLD_REPORTING = 'ERROR: Coverage for %s (%s%) does not meet threshold (%s%) for %s';
package/build/index.d.ts CHANGED
@@ -17,7 +17,8 @@ export default class BrowserRunner extends LocalRunner {
17
17
  *
18
18
  * @return {Promise} resolves when vite server has been shutdown
19
19
  */
20
- shutdown(): Promise<void>;
20
+ shutdown(): Promise<boolean>;
21
+ private _generateCoverageReports;
21
22
  }
22
23
  declare global {
23
24
  namespace WebdriverIO {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACjE,OAAO,WAAW,MAAM,oBAAoB,CAAA;AAI5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAM1C,OAAO,KAAK,EAAE,oBAAoB,IAAI,0BAA0B,EAAE,MAAM,YAAY,CAAA;AAIpF,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;;IAK9C,OAAO,CAAC,OAAO;IACf,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU;gBAD7B,OAAO,EAAE,0BAA0B,EACjC,OAAO,EAAE,OAAO,CAAC,UAAU;IAYzC;;OAEG;IACG,UAAU;IAYhB,GAAG,CAAE,OAAO,EAAE,OAAO,GAAG,cAAc;IAmDtC;;;;OAIG;IACG,QAAQ;CAIjB;AAED,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,WAAW,CAAC;QAClB,UAAU,oBAAqB,SAAQ,0BAA0B;SAAG;KACvE;CACJ"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACjE,OAAO,WAAW,MAAM,oBAAoB,CAAA;AAQ5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAS1C,OAAO,KAAK,EAAE,oBAAoB,IAAI,0BAA0B,EAAmB,MAAM,YAAY,CAAA;AAGrG,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;;IAO9C,OAAO,CAAC,OAAO;IACf,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU;gBAD7B,OAAO,EAAE,0BAA0B,EACjC,OAAO,EAAE,OAAO,CAAC,UAAU;IAczC;;OAEG;IACG,UAAU;IAoBhB,GAAG,CAAE,OAAO,EAAE,OAAO,GAAG,cAAc;IAmDtC;;;;OAIG;IACG,QAAQ;YAMA,wBAAwB;CAmEzC;AAED,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,WAAW,CAAC;QAClB,UAAU,oBAAqB,SAAQ,0BAA0B;SAAG;KACvE;CACJ"}
package/build/index.js CHANGED
@@ -1,15 +1,23 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
1
3
  import logger from '@wdio/logger';
2
4
  import LocalRunner from '@wdio/local-runner';
3
5
  import { attach } from 'webdriverio';
6
+ import libCoverage from 'istanbul-lib-coverage';
7
+ import libReport from 'istanbul-lib-report';
8
+ import libSourceMap from 'istanbul-lib-source-maps';
9
+ import reports from 'istanbul-reports';
4
10
  import { ViteServer } from './vite/server.js';
5
- import { FRAMEWORK_SUPPORT_ERROR, SESSIONS, BROWSER_POOL } from './constants.js';
6
- import { makeHeadless } from './utils.js';
11
+ import { FRAMEWORK_SUPPORT_ERROR, SESSIONS, BROWSER_POOL, DEFAULT_COVERAGE_REPORTS, SUMMARY_REPORTER, DEFAULT_REPORTS_DIRECTORY } from './constants.js';
12
+ import { makeHeadless, getCoverageByFactor } from './utils.js';
7
13
  const log = logger('@wdio/browser-runner');
8
14
  export default class BrowserRunner extends LocalRunner {
9
15
  options;
10
16
  _config;
11
17
  #config;
12
18
  #server;
19
+ #coverageOptions;
20
+ #reportsDirectory;
13
21
  constructor(options, _config) {
14
22
  super(options, _config);
15
23
  this.options = options;
@@ -17,8 +25,10 @@ export default class BrowserRunner extends LocalRunner {
17
25
  if (_config.framework !== 'mocha') {
18
26
  throw new Error(FRAMEWORK_SUPPORT_ERROR);
19
27
  }
20
- this.#server = new ViteServer(options);
28
+ this.#server = new ViteServer(options, _config);
21
29
  this.#config = _config;
30
+ this.#coverageOptions = options.coverage || {};
31
+ this.#reportsDirectory = this.#coverageOptions.reportsDirectory || path.join(this.#config.rootDir, DEFAULT_REPORTS_DIRECTORY);
22
32
  }
23
33
  /**
24
34
  * nothing to initialise when running locally
@@ -32,6 +42,13 @@ export default class BrowserRunner extends LocalRunner {
32
42
  catch (err) {
33
43
  throw new Error(`Vite server failed to start: ${err.stack}`);
34
44
  }
45
+ if (typeof this.#coverageOptions.clean === 'undefined' || this.#coverageOptions.clean) {
46
+ const reportsDirectoryExist = await fs.access(this.#reportsDirectory)
47
+ .then(() => true, () => false);
48
+ if (reportsDirectoryExist) {
49
+ await fs.rm(this.#reportsDirectory, { recursive: true });
50
+ }
51
+ }
35
52
  await super.initialise();
36
53
  }
37
54
  run(runArgs) {
@@ -87,5 +104,62 @@ export default class BrowserRunner extends LocalRunner {
87
104
  async shutdown() {
88
105
  await super.shutdown();
89
106
  await this.#server.close();
107
+ return this._generateCoverageReports();
108
+ }
109
+ async _generateCoverageReports() {
110
+ if (!this.#coverageOptions.enabled) {
111
+ return true;
112
+ }
113
+ /**
114
+ * skip if no coverage directory was created
115
+ */
116
+ const reportsDirectoryExist = await fs.access(this.#reportsDirectory)
117
+ .then(() => true, () => false);
118
+ if (!reportsDirectoryExist) {
119
+ return true;
120
+ }
121
+ const coverageIssues = [];
122
+ try {
123
+ const globalCoverageVar = JSON.parse((await fs.readFile(path.join(this.#reportsDirectory, 'out.json'))).toString());
124
+ const mapStore = libSourceMap.createSourceMapStore();
125
+ const coverageMap = await mapStore.transformCoverage(libCoverage.createCoverageMap(globalCoverageVar));
126
+ const context = libReport.createContext({
127
+ dir: this.#reportsDirectory,
128
+ defaultSummarizer: 'nested',
129
+ coverageMap
130
+ });
131
+ const reporter = this.#coverageOptions.reporter
132
+ ? Array.isArray(this.#coverageOptions.reporter) ? this.#coverageOptions.reporter : [this.#coverageOptions.reporter]
133
+ : DEFAULT_COVERAGE_REPORTS;
134
+ /**
135
+ * ensure summary reporter is set as we need it for treshold comparison
136
+ */
137
+ if (!reporter.includes(SUMMARY_REPORTER)) {
138
+ reporter.push(SUMMARY_REPORTER);
139
+ }
140
+ const reportBases = reporter.map((r) => reports.create(r, {
141
+ projectRoot: this.#config.rootDir,
142
+ subdir: 'html'
143
+ }));
144
+ reportBases.map((reportBase) => reportBase.execute(context));
145
+ log.info(`Successfully created coverage reports for ${reporter.join(', ')}`);
146
+ const summaryFilePath = path.join(this.#reportsDirectory, 'coverage-summary.json');
147
+ const summary = JSON.parse((await fs.readFile(summaryFilePath)).toString());
148
+ coverageIssues.push(...this.#coverageOptions.perFile
149
+ ? Object.entries(summary)
150
+ .filter(([source]) => source !== 'total')
151
+ .map(([source, summary]) => (getCoverageByFactor(this.#coverageOptions, summary, source.replace(this.#config.rootDir, ''))))
152
+ .flat()
153
+ : getCoverageByFactor(this.#coverageOptions, summary.total));
154
+ }
155
+ catch (err) {
156
+ console.error(`Failed to generate code coverage report: ${err.message}`);
157
+ return false;
158
+ }
159
+ if (coverageIssues.length) {
160
+ console.log(coverageIssues.join('\n'));
161
+ return false;
162
+ }
163
+ return true;
90
164
  }
91
165
  }
package/build/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { InlineConfig } from 'vite';
2
2
  import type { Workers, Capabilities, Options } from '@wdio/types';
3
3
  import type { MochaOpts } from '@wdio/mocha-framework';
4
+ import type { IstanbulPluginOptions } from 'vite-plugin-istanbul';
4
5
  declare global {
5
6
  interface Window {
6
7
  __wdioEnv__: Environment;
@@ -8,6 +9,66 @@ declare global {
8
9
  }
9
10
  }
10
11
  export type FrameworkPreset = 'react' | 'preact' | 'vue' | 'svelte' | 'lit' | 'solid';
12
+ type Arrayable<T> = T | Array<T>;
13
+ type CoverageReporter = 'clover' | 'cobertura' | 'html-spa' | 'html' | 'json-summary' | 'json' | 'lcov' | 'lcovonly' | 'none' | 'teamcity' | 'text-lcov' | 'text-summary' | 'text';
14
+ export interface CoverageOptions extends Omit<IstanbulPluginOptions, 'cypress' | 'checkProd' | 'forceBuildInstrument'> {
15
+ /**
16
+ * Enables coverage collection.
17
+ *
18
+ * @default false
19
+ */
20
+ enabled: boolean;
21
+ /**
22
+ * Directory to write coverage report to.
23
+ *
24
+ * @default ./coverage
25
+ */
26
+ reportsDirectory?: string;
27
+ /**
28
+ * Coverage reporters to use.
29
+ * See [istanbul documentation](https://istanbul.js.org/docs/advanced/alternative-reporters/) for detailed list of all reporters.
30
+ *
31
+ * @default ['text', 'html', 'clover', 'json-summary']
32
+ */
33
+ reporter?: Arrayable<CoverageReporter>;
34
+ /**
35
+ * Check thresholds per file.
36
+ * See `lines`, `functions`, `branches` and `statements` for the actual thresholds.
37
+ *
38
+ * @default false
39
+ */
40
+ perFile?: boolean;
41
+ /**
42
+ * Clean coverage results before running tests.
43
+ *
44
+ * @default true
45
+ */
46
+ clean?: boolean;
47
+ /**
48
+ * Threshold for lines
49
+ *
50
+ * @default undefined
51
+ */
52
+ lines?: number;
53
+ /**
54
+ * Threshold for functions
55
+ *
56
+ * @default undefined
57
+ */
58
+ functions?: number;
59
+ /**
60
+ * Threshold for branches
61
+ *
62
+ * @default undefined
63
+ */
64
+ branches?: number;
65
+ /**
66
+ * Threshold for statements
67
+ *
68
+ * @default undefined
69
+ */
70
+ statements?: number;
71
+ }
11
72
  export interface BrowserRunnerOptions {
12
73
  /**
13
74
  * Project root directory
@@ -27,6 +88,10 @@ export interface BrowserRunnerOptions {
27
88
  * @default false // true in CI environment
28
89
  */
29
90
  headless?: boolean;
91
+ /**
92
+ * test coverage settings
93
+ */
94
+ coverage?: CoverageOptions;
30
95
  }
31
96
  export interface RunArgs extends Workers.WorkerRunPayload {
32
97
  command: string;
@@ -40,4 +105,5 @@ export interface Environment {
40
105
  sessionId: string;
41
106
  injectGlobals: boolean;
42
107
  }
108
+ export {};
43
109
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAEtD,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,WAAW,EAAE,WAAW,CAAA;QACxB,iBAAiB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;KAC9C;CACJ;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAA;AAErF,MAAM,WAAW,oBAAoB;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB;;OAEG;IACH,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,OAAQ,SAAQ,OAAO,CAAC,gBAAgB;IACrD,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,GAAG,CAAA;IACT,GAAG,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,SAAS,CAAA;IACf,MAAM,EAAE,OAAO,CAAC,UAAU,CAAA;IAC1B,YAAY,EAAE,YAAY,CAAC,gBAAgB,CAAA;IAC3C,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;CACzB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AACxC,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACtD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAEjE,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,WAAW,EAAE,WAAW,CAAA;QACxB,iBAAiB,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;KAC9C;CACJ;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAA;AACrF,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;AAChC,KAAK,gBAAgB,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,GAAG,MAAM,CAAA;AAClL,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,qBAAqB,EAAE,SAAS,GAAG,WAAW,GAAG,sBAAsB,CAAC;IAClH;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC,gBAAgB,CAAC,CAAA;IACtC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,oBAAoB;IACjC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;OAEG;IACH,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB;;OAEG;IACH,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;OAEG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAA;CAC7B;AAED,MAAM,WAAW,OAAQ,SAAQ,OAAO,CAAC,gBAAgB;IACrD,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,GAAG,CAAA;IACT,GAAG,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,SAAS,CAAA;IACf,MAAM,EAAE,OAAO,CAAC,UAAU,CAAA;IAC1B,YAAY,EAAE,YAAY,CAAC,gBAAgB,CAAA;IAC3C,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,OAAO,CAAA;CACzB"}
package/build/utils.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import type { Capabilities } from '@wdio/types';
2
- import type { BrowserRunnerOptions } from './types.js';
2
+ import type { CoverageSummary } from 'istanbul-lib-coverage';
3
+ import { COVERAGE_FACTORS } from './constants.js';
4
+ import type { BrowserRunnerOptions, CoverageOptions } from './types.js';
3
5
  export declare function makeHeadless(options: BrowserRunnerOptions, caps: Capabilities.RemoteCapability): Capabilities.RemoteCapability;
6
+ export declare function getCoverageByFactor(options: Partial<CoverageOptions>, summary: Pick<CoverageSummary, (typeof COVERAGE_FACTORS)[number]>, fileName?: string): string[];
4
7
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAItD,wBAAgB,YAAY,CAAE,OAAO,EAAE,oBAAoB,EAAE,IAAI,EAAE,YAAY,CAAC,gBAAgB,GAAG,YAAY,CAAC,gBAAgB,CA0C/H"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAE5D,OAAO,EAAE,gBAAgB,EAAsD,MAAM,gBAAgB,CAAA;AACrG,OAAO,KAAK,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAIvE,wBAAgB,YAAY,CAAE,OAAO,EAAE,oBAAoB,EAAE,IAAI,EAAE,YAAY,CAAC,gBAAgB,GAAG,YAAY,CAAC,gBAAgB,CA0C/H;AAED,wBAAgB,mBAAmB,CAC/B,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,EACjC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,EACjE,QAAQ,CAAC,EAAE,MAAM,YAepB"}
package/build/utils.js CHANGED
@@ -1,5 +1,7 @@
1
+ import util from 'node:util';
1
2
  import { deepmerge } from 'deepmerge-ts';
2
3
  import logger from '@wdio/logger';
4
+ import { COVERAGE_FACTORS, GLOBAL_TRESHOLD_REPORTING, FILE_TRESHOLD_REPORTING } from './constants.js';
3
5
  const log = logger('@wdio/browser-runner');
4
6
  export function makeHeadless(options, caps) {
5
7
  const capability = caps.alwaysMatch || caps;
@@ -40,3 +42,17 @@ export function makeHeadless(options, caps) {
40
42
  log.error(`Headless mode not supported for browser "${capability.browserName}"`);
41
43
  return caps;
42
44
  }
45
+ export function getCoverageByFactor(options, summary, fileName) {
46
+ return COVERAGE_FACTORS.map((factor) => {
47
+ const treshold = options[factor];
48
+ if (!treshold) {
49
+ return;
50
+ }
51
+ if (summary[factor].pct >= treshold) {
52
+ return;
53
+ }
54
+ return fileName
55
+ ? util.format(FILE_TRESHOLD_REPORTING, factor, summary[factor].pct, treshold, fileName)
56
+ : util.format(GLOBAL_TRESHOLD_REPORTING, factor, summary[factor].pct, treshold);
57
+ }).filter(Boolean);
58
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/vite/constants.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AAExC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAgB1F,CAAA;AAED,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,YAAY,CAmBrD,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/vite/constants.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AAExC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,CAgB1F,CAAA;AAED,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,YAAY,CAsBrD,CAAA"}
@@ -22,6 +22,9 @@ export const DEFAULT_VITE_CONFIG = {
22
22
  server: { host: 'localhost' },
23
23
  logLevel: 'silent',
24
24
  plugins: [topLevelAwait()],
25
+ build: {
26
+ sourcemap: 'inline'
27
+ },
25
28
  optimizeDeps: {
26
29
  include: ['expect', 'jest-matcher-utils'],
27
30
  esbuildOptions: {
@@ -1 +1 @@
1
- {"version":3,"file":"testrunner.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/testrunner.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AA6ClC,wBAAgB,UAAU,CAAE,OAAO,EAAE,WAAW,CAAC,oBAAoB,GAAG,MAAM,CA6G7E"}
1
+ {"version":3,"file":"testrunner.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/testrunner.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAuClC,wBAAgB,UAAU,CAAE,OAAO,EAAE,WAAW,CAAC,oBAAoB,GAAG,MAAM,CA6G7E"}
@@ -1,23 +1,14 @@
1
1
  import url from 'node:url';
2
2
  import path from 'node:path';
3
3
  import logger from '@wdio/logger';
4
+ import { deepmerge } from 'deepmerge-ts';
4
5
  import { resolve } from 'import-meta-resolve';
5
6
  import { WebDriverProtocol, MJsonWProtocol, JsonWProtocol, AppiumProtocol, ChromiumProtocol, SauceLabsProtocol, SeleniumProtocol, GeckoProtocol, WebDriverBidiProtocol } from '@wdio/protocols';
6
7
  import { SESSIONS } from '../../constants.js';
7
8
  import { getTemplate, getErrorTemplate } from '../utils.js';
8
9
  const log = logger('@wdio/browser-runner:plugin');
9
10
  const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
10
- const commands = {
11
- ...WebDriverProtocol,
12
- ...MJsonWProtocol,
13
- ...JsonWProtocol,
14
- ...AppiumProtocol,
15
- ...ChromiumProtocol,
16
- ...SauceLabsProtocol,
17
- ...SeleniumProtocol,
18
- ...GeckoProtocol,
19
- ...WebDriverBidiProtocol
20
- };
11
+ const commands = deepmerge(WebDriverProtocol, MJsonWProtocol, JsonWProtocol, AppiumProtocol, ChromiumProtocol, SauceLabsProtocol, SeleniumProtocol, GeckoProtocol, WebDriverBidiProtocol);
21
12
  const protocolCommandList = Object.values(commands).map((endpoint) => Object.values(endpoint).map(({ command }) => command)).flat();
22
13
  const WDIO_PACKAGES = ['webdriverio', 'expect-webdriverio'];
23
14
  const virtualModuleId = 'virtual:wdio';
@@ -2,12 +2,13 @@
2
2
  import { EventEmitter } from 'node:events';
3
3
  import { WebSocketServer } from 'ws';
4
4
  import type { InlineConfig } from 'vite';
5
+ import type { Options } from '@wdio/types';
5
6
  import type { HookTriggerEvent } from './types.js';
6
7
  export declare class ViteServer extends EventEmitter {
7
8
  #private;
8
9
  get socketServer(): WebSocketServer | undefined;
9
10
  get config(): Partial<InlineConfig>;
10
- constructor(options: WebdriverIO.BrowserRunnerOptions);
11
+ constructor(options: WebdriverIO.BrowserRunnerOptions, config: Options.Testrunner);
11
12
  start(): Promise<void>;
12
13
  close(): Promise<void>;
13
14
  private runWorkerHooks;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/vite/server.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAM1C,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAGpC,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,MAAM,CAAA;AAQvD,OAAO,KAAK,EAAgB,gBAAgB,EAA6E,MAAM,YAAY,CAAA;AAY3I,qBAAa,UAAW,SAAQ,YAAY;;IAQxC,IAAI,YAAY,gCAEf;IAED,IAAI,MAAM,0BAET;gBAEY,OAAO,EAAE,WAAW,CAAC,oBAAoB;IAkBhD,KAAK;IAwCL,KAAK;IAmIX,OAAO,CAAC,cAAc;IAetB,WAAW,CAAE,MAAM,EAAE,gBAAgB;CAUxC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/vite/server.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAM1C,OAAO,EAAE,eAAe,EAAE,MAAM,IAAI,CAAA;AAGpC,OAAO,KAAK,EAAiB,YAAY,EAAE,MAAM,MAAM,CAAA;AAGvD,OAAO,KAAK,EAAY,OAAO,EAAE,MAAM,aAAa,CAAA;AAMpD,OAAO,KAAK,EAAgB,gBAAgB,EAA6E,MAAM,YAAY,CAAA;AAY3I,qBAAa,UAAW,SAAQ,YAAY;;IAQxC,IAAI,YAAY,gCAEf;IAED,IAAI,MAAM,0BAET;gBAEY,OAAO,EAAE,WAAW,CAAC,oBAAoB,EAAE,MAAM,EAAE,OAAO,CAAC,UAAU;IA6B5E,KAAK;IAwCL,KAAK;IA2IX,OAAO,CAAC,cAAc;IAetB,WAAW,CAAE,MAAM,EAAE,gBAAgB;CAUxC"}
@@ -6,10 +6,11 @@ import { WebSocketServer } from 'ws';
6
6
  import { serializeError } from 'serialize-error';
7
7
  import { executeHooksWithArgs } from '@wdio/utils';
8
8
  import { createServer } from 'vite';
9
+ import istanbulPlugin from 'vite-plugin-istanbul';
9
10
  import { testrunner } from './plugins/testrunner.js';
10
11
  import { userfriendlyImport } from './utils.js';
11
12
  import { PRESET_DEPENDENCIES, DEFAULT_VITE_CONFIG } from './constants.js';
12
- import { MESSAGE_TYPES } from '../constants.js';
13
+ import { MESSAGE_TYPES, DEFAULT_INCLUDE, DEFAULT_FILE_EXTENSIONS } from '../constants.js';
13
14
  import { BROWSER_POOL, SESSIONS } from '../constants.js';
14
15
  const log = logger('@wdio/browser-runner:ViteServer');
15
16
  const HOOK_TIMEOUT = 15 * 1000;
@@ -26,7 +27,7 @@ export class ViteServer extends EventEmitter {
26
27
  get config() {
27
28
  return this.#viteConfig;
28
29
  }
29
- constructor(options) {
30
+ constructor(options, config) {
30
31
  super();
31
32
  this.#options = options;
32
33
  if (options.preset && options.viteConfig) {
@@ -36,6 +37,16 @@ export class ViteServer extends EventEmitter {
36
37
  root: options.rootDir || process.cwd(),
37
38
  plugins: [testrunner(options)]
38
39
  });
40
+ if (options.coverage && options.coverage.enabled) {
41
+ log.info('Capturing test coverage enabled');
42
+ // @ts-expect-error
43
+ this.#viteConfig.plugins?.push(istanbulPlugin({
44
+ cwd: config.rootDir,
45
+ include: DEFAULT_INCLUDE,
46
+ extension: DEFAULT_FILE_EXTENSIONS,
47
+ ...options.coverage
48
+ }));
49
+ }
39
50
  if (options.viteConfig) {
40
51
  this.#viteConfig = deepmerge(this.#viteConfig, options.viteConfig);
41
52
  }
@@ -139,7 +150,7 @@ export class ViteServer extends EventEmitter {
139
150
  return ws.send(JSON.stringify(this.#hookResponse(result)));
140
151
  }
141
152
  async #handleCommand(ws, payload) {
142
- log.info(`Received browser message: ${payload}`);
153
+ log.debug(`Received browser message: ${JSON.stringify(payload)}`);
143
154
  const cid = payload.cid;
144
155
  if (typeof cid !== 'string') {
145
156
  const error = serializeError(new Error(`No "cid" property passed into command message with id "${payload.id}"`));
@@ -157,6 +168,12 @@ export class ViteServer extends EventEmitter {
157
168
  if (payload.commandName === 'debug') {
158
169
  this.emit('debugState', true);
159
170
  }
171
+ /**
172
+ * double check if function is registered
173
+ */
174
+ if (typeof browser[payload.commandName] !== 'function') {
175
+ throw new Error(`browser.${payload.commandName} is not a function`);
176
+ }
160
177
  const result = await browser[payload.commandName](...payload.args);
161
178
  const resultMsg = JSON.stringify(this.#commandResponse({ id: payload.id, result }));
162
179
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdio/browser-runner",
3
- "version": "8.2.4",
3
+ "version": "8.3.0",
4
4
  "description": "A WebdriverIO runner to run unit tests tests in the browser.",
5
5
  "author": "Christian Bromann <mail@bromann.dev>",
6
6
  "homepage": "https://github.com/webdriverio/webdriverio/tree/main/packages/wdio-browser-runner",
@@ -29,24 +29,30 @@
29
29
  "dependencies": {
30
30
  "@babel/plugin-proposal-class-properties": "^7.18.6",
31
31
  "@originjs/vite-plugin-commonjs": "^1.0.3",
32
+ "@types/istanbul-lib-source-maps": "^4.0.1",
32
33
  "@types/node": "^18.11.18",
33
- "@wdio/globals": "8.2.4",
34
- "@wdio/local-runner": "8.2.4",
34
+ "@wdio/globals": "8.3.0",
35
+ "@wdio/local-runner": "8.3.0",
35
36
  "@wdio/logger": "8.1.0",
36
- "@wdio/mocha-framework": "8.2.3",
37
+ "@wdio/mocha-framework": "8.3.0",
37
38
  "@wdio/protocols": "8.2.0",
38
- "@wdio/types": "8.1.2",
39
- "@wdio/utils": "8.2.3",
39
+ "@wdio/types": "8.3.0",
40
+ "@wdio/utils": "8.3.0",
40
41
  "deepmerge-ts": "^4.2.2",
41
42
  "expect-webdriverio": "^4.1.0",
42
43
  "fast-safe-stringify": "^2.1.1",
43
44
  "get-port": "^6.1.2",
44
45
  "import-meta-resolve": "^2.2.1",
46
+ "istanbul-lib-coverage": "^3.2.0",
47
+ "istanbul-lib-report": "^3.0.0",
48
+ "istanbul-lib-source-maps": "^4.0.1",
49
+ "istanbul-reports": "^3.1.5",
45
50
  "serialize-error": "^11.0.0",
46
51
  "vite": "^4.0.4",
52
+ "vite-plugin-istanbul": "^4.0.0",
47
53
  "vite-plugin-top-level-await": "^1.2.2",
48
- "webdriver": "8.2.3",
49
- "webdriverio": "8.2.4",
54
+ "webdriver": "8.3.0",
55
+ "webdriverio": "8.3.0",
50
56
  "ws": "^8.12.0"
51
57
  },
52
58
  "scripts": {
@@ -57,7 +63,7 @@
57
63
  },
58
64
  "devDependencies": {
59
65
  "@types/ws": "^8.5.4",
60
- "@wdio/runner": "8.2.4"
66
+ "@wdio/runner": "8.3.0"
61
67
  },
62
- "gitHead": "eba541a77dbc42173717e1c106a7c4d3ccb198f5"
68
+ "gitHead": "156a246e8117463cf3c762d2da5bf9eef3a476ea"
63
69
  }