@wdio/browser-runner 8.3.10 → 8.4.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.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  WebdriverIO Browser Runner
2
2
  ==========================
3
3
 
4
- > A WebdriverIO runner to run unit and component tests within the browser
4
+ > A WebdriverIO runner to run unit and component tests within the browser.
5
5
 
6
- As opposed to the [Local Runner]((https://www.npmjs.com/package/@wdio/local-runner)) the [Browser Runner]((https://www.npmjs.com/package/@wdio/browser-runner)) initiates and executes the framework within the browser. This allows you to run unit tests or component tests in an actual browser rather than in a JSDOM like many other test frameworks.
6
+ As opposed to the [Local Runner](https://www.npmjs.com/package/@wdio/local-runner) the [Browser Runner](https://www.npmjs.com/package/@wdio/browser-runner) initiates and executes the framework within the browser. This allows you to run unit tests or component tests in an actual browser rather than in a JSDOM like many other test frameworks.
7
7
 
8
8
  While [JSDOM](https://www.npmjs.com/package/jsdom) is widely used for testing purposes, it is in the end not an actual browser nor can you emulate mobile environments with it. With this runner WebdriverIO enables you to easily run your tests in the browser and use WebDriver commands to interact with elements rendered on the page.
9
9
 
@@ -14,8 +14,9 @@ Here is an overview of running tests within JSDOM vs. WebdriverIOs Browser Runne
14
14
  |1.| Runs your tests within Node.js using a re-implementation of web standards, notably the WHATWG DOM and HTML Standards | Executes your test in an actual browser and runs the code in an environment that your users use |
15
15
  |2.| Interactions with components can only be imitated via JavaScript | You can use the [WebdriverIO API](api) to interact with elements through the WebDriver protocol |
16
16
  |3.| Canvas support requires [additional dependencies](https://www.npmjs.com/package/canvas) and [has limitations](https://github.com/Automattic/node-canvas/issues) | You have access to the real [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) |
17
- |4.| JSDOM has some [caveats](https://www.npmjs.com/package/jsdom#user-content-caveats) and unsupported Web APIs | All Web APIs are supported as test run in an actual browser |
17
+ |4.| JSDOM has some [caveats](https://github.com/jsdom/jsdom#caveats) and unsupported Web APIs | All Web APIs are supported as test run in an actual browser |
18
18
  |5.| Impossible to detect errors cross browser | Support for all browsers including mobile browser |
19
+ |6.| Can __not__ test for element pseudo states | Support for pseudo states such as `:hover` or `:active` |
19
20
 
20
21
  This runner uses [Vite](https://vitejs.dev/) to compile your test code and load it in the browser. It comes with presets for the following component frameworks:
21
22
 
@@ -23,6 +24,7 @@ This runner uses [Vite](https://vitejs.dev/) to compile your test code and load
23
24
  - Preact
24
25
  - Vue.js
25
26
  - Svelte
27
+ - SolidJS
26
28
 
27
29
  Every test file / test file group runs within a single page which means that between each test the page is being reloaded to guarantee isolation between tests.
28
30
 
@@ -47,19 +49,172 @@ export const {
47
49
  }
48
50
  ```
49
51
 
50
- If you test components using one of the mentioned frameworks above, you can define a preset that ensures everything is configured out of the box, e.g. when you want to import and test Svelte components, define the Browser Runner as follows:
52
+ ### Runner Options
51
53
 
52
- ```js
53
- // wdio.conf.js
54
+ The Browser runner allows following configurations:
55
+
56
+ #### `preset`
57
+
58
+ If you test components using one of the mentioned frameworks above, you can define a preset that ensures everything is configured out of the box. This option can't be used together with `viteConfig`.
59
+
60
+ __Type:__ `vue` | `svelte` | `solid` | `react` | `preact`<br />
61
+ __Example:__
62
+
63
+ ```js title="wdio.conf.js"
54
64
  export const {
55
65
  // ...
56
66
  runner: ['browser', {
57
- preset: 'Svelte'
67
+ preset: 'svelte'
58
68
  }],
59
69
  // ...
60
70
  }
61
71
  ```
62
72
 
73
+ #### `viteConfig`
74
+
75
+ Define your own [Vite configuration](https://vitejs.dev/config/). You can either pass in a custom object or import an existing `vite.conf.ts` file if you use Vite.js for development. Note that WebdriverIO merges custom configurations to set up framework and runner objects. This option can't be used together with `preset`.
76
+
77
+ __Type:__ [`UserConfig`](https://github.com/vitejs/vite/blob/52e64eb43287d241f3fd547c332e16bd9e301e95/packages/vite/src/node/config.ts#L119-L272)<br />
78
+ __Example:__
79
+
80
+ ```js title="wdio.conf.ts"
81
+ import viteConfig from '../vite.config.ts'
82
+
83
+ export const {
84
+ // ...
85
+ runner: ['browser', { viteConfig }],
86
+ // ...
87
+ }
88
+ ```
89
+
90
+ #### `headless`
91
+
92
+ If set to `true` the runner will update capabilities to run tests headless. By default this is enabled within CI environments where a `CI` environment variable is set to `'1'` or `'true'`.
93
+
94
+ __Type:__ `boolean`<br />
95
+ __Default:__ `false`, set to `true` if `CI` environment variable is set
96
+
97
+ #### `rootDir`
98
+
99
+ Project root directory.
100
+
101
+ __Type:__ `string`<br />
102
+ __Default:__ `process.cwd()`
103
+
104
+ #### `coverage`
105
+
106
+ WebdriverIO supports test coverage reporting through [`istanbul`](https://istanbul.js.org/). See [Coverage Options](#coverage-options) for more details.
107
+
108
+ __Type:__ `object`<br />
109
+ __Default:__ `undefined`
110
+
111
+ ### Coverage Options
112
+
113
+ The following options allow to configure coverage reporting.
114
+
115
+ #### `enabled`
116
+
117
+ Enables coverage collection.
118
+
119
+ __Type:__ `boolean`<br />
120
+ __Default:__ `false`
121
+
122
+ #### `include`
123
+
124
+ List of files included in coverage as glob patterns.
125
+
126
+ __Type:__ `string[]`<br />
127
+ __Default:__ `[**]`
128
+
129
+ #### `exclude`
130
+
131
+ List of files excluded in coverage as glob patterns.
132
+
133
+ __Type:__ `string[]`<br />
134
+ __Default:__
135
+
136
+ ```
137
+ [
138
+ 'coverage/**',
139
+ 'dist/**',
140
+ 'packages/*/test{,s}/**',
141
+ '**/*.d.ts',
142
+ 'cypress/**',
143
+ 'test{,s}/**',
144
+ 'test{,-*}.{js,cjs,mjs,ts,tsx,jsx}',
145
+ '**/*{.,-}test.{js,cjs,mjs,ts,tsx,jsx}',
146
+ '**/*{.,-}spec.{js,cjs,mjs,ts,tsx,jsx}',
147
+ '**/__tests__/**',
148
+ '**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
149
+ '**/.{eslint,mocha,prettier}rc.{js,cjs,yml}',
150
+ ]
151
+ ```
152
+
153
+ #### `extension`
154
+
155
+ List of file extensions the report should include.
156
+
157
+ __Type:__ `string | string[]`<br />
158
+ __Default:__ `['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte']`
159
+
160
+ #### `reportsDirectory`
161
+
162
+ Directory to write coverage report to.
163
+
164
+ __Type:__ `string`<br />
165
+ __Default:__ `./coverage`
166
+
167
+ #### `reporter`
168
+
169
+ Coverage reporters to use. See [istanbul documentation](https://istanbul.js.org/docs/advanced/alternative-reporters/) for detailed list of all reporters.
170
+
171
+ __Type:__ `string[]`<br />
172
+ __Default:__ `['text', 'html', 'clover', 'json-summary']`
173
+
174
+ #### `perFile`
175
+
176
+ Check thresholds per file. See `lines`, `functions`, `branches` and `statements` for the actual thresholds.
177
+
178
+ __Type:__ `boolean`<br />
179
+ __Default:__ `false`
180
+
181
+ #### `clean`
182
+
183
+ Clean coverage results before running tests.
184
+
185
+ __Type:__ `boolean`<br />
186
+ __Default:__ `true`
187
+
188
+ #### `lines`
189
+
190
+ Threshold for lines.
191
+
192
+ __Type:__ `number`<br />
193
+ __Default:__ `undefined`
194
+
195
+ #### `functions`
196
+
197
+ Threshold for functions.
198
+
199
+ __Type:__ `number`<br />
200
+ __Default:__ `undefined`
201
+
202
+ #### `branches`
203
+
204
+ Threshold for branches.
205
+
206
+ __Type:__ `number`<br />
207
+ __Default:__ `undefined`
208
+
209
+ #### `statements`
210
+
211
+ Threshold for statements.
212
+
213
+ __Type:__ `number`<br />
214
+ __Default:__ `undefined`
215
+
216
+ ### Examples
217
+
63
218
  Make sure to check out the docs around [component testing](https://webdriver.io/docs/component-testing) and have a look into the [example repository](https://github.com/webdriverio/component-testing-examples) for examples using these and various other frameworks.
64
219
 
65
220
  ---
@@ -1 +1 @@
1
- {"version":3,"file":"mocha.d.ts","sourceRoot":"","sources":["../../../src/browser/frameworks/mocha.ts"],"names":[],"mappings":"AAUA,qBAAa,cAAc;;gBAKV,MAAM,EAAE,SAAS;IA+B9B,GAAG;CA+DN"}
1
+ {"version":3,"file":"mocha.d.ts","sourceRoot":"","sources":["../../../src/browser/frameworks/mocha.ts"],"names":[],"mappings":"AASA,qBAAa,cAAc;;gBAKV,MAAM,EAAE,SAAS;IA+B9B,GAAG;CA+DN"}
@@ -1,4 +1,3 @@
1
- import Mocha from 'mocha';
2
1
  import stringify from 'fast-safe-stringify';
3
2
  import { setupEnv, formatMessage } from '@wdio/mocha-framework/common';
4
3
  import { MESSAGE_TYPES, EVENTS } from '../../constants.js';
@@ -95,7 +94,7 @@ const BaseReporter = window.Mocha.reporters.html;
95
94
  class HTMLReporter extends BaseReporter {
96
95
  addCodeToggle() { }
97
96
  }
98
- Mocha.setup({
97
+ mocha.setup({
99
98
  ...window.__wdioEnv__.args,
100
99
  reporter: HTMLReporter
101
100
  });
@@ -7,6 +7,7 @@ declare global {
7
7
  __wdioEvents__: any[];
8
8
  __wdioSocket__: WebSocket;
9
9
  __wdioConnectPromise__: Promise<WebSocket>;
10
+ __wdioMockFactories__: Record<string, any>;
10
11
  }
11
12
  }
12
13
  export declare const socket: WebSocket;
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/browser/setup.ts"],"names":[],"mappings":"AAQA,KAAK,cAAc,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC,CAAA;AAC9D,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,cAAc,EAAE,cAAc,EAAE,CAAA;QAChC,YAAY,EAAE,MAAM,CAAA;QACpB,gBAAgB,EAAE,MAAM,CAAA;QACxB,cAAc,EAAE,GAAG,EAAE,CAAA;QACrB,cAAc,EAAE,SAAS,CAAA;QACzB,sBAAsB,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;KAC7C;CACJ;AAOD,eAAO,MAAM,MAAM,WAA+C,CAAA;AAClE,eAAO,MAAM,cAAc,oBAGzB,CAAA"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/browser/setup.ts"],"names":[],"mappings":"AAQA,KAAK,cAAc,GAAG,IAAI,CAAC,UAAU,EAAE,UAAU,GAAG,SAAS,CAAC,CAAA;AAC9D,OAAO,CAAC,MAAM,CAAC;IACX,UAAU,MAAM;QACZ,cAAc,EAAE,cAAc,EAAE,CAAA;QAChC,YAAY,EAAE,MAAM,CAAA;QACpB,gBAAgB,EAAE,MAAM,CAAA;QACxB,cAAc,EAAE,GAAG,EAAE,CAAA;QACrB,cAAc,EAAE,SAAS,CAAA;QACzB,sBAAsB,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;QAC1C,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAC7C;CACJ;AAOD,eAAO,MAAM,MAAM,WAA+C,CAAA;AAClE,eAAO,MAAM,cAAc,oBAGzB,CAAA"}
@@ -2,4 +2,7 @@
2
2
  * re-export mock module
3
3
  */
4
4
  export * from '@vitest/spy';
5
+ type MockFactoryWithHelper = (importOriginal: <T = unknown>() => Promise<T>) => any;
6
+ export declare function mock(path: string, factory?: MockFactoryWithHelper): Promise<unknown>;
7
+ export declare function unmock(moduleName: string): void;
5
8
  //# sourceMappingURL=spy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spy.d.ts","sourceRoot":"","sources":["../../src/browser/spy.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,cAAc,aAAa,CAAA"}
1
+ {"version":3,"file":"spy.d.ts","sourceRoot":"","sources":["../../src/browser/spy.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,cAAc,aAAa,CAAA;AAE3B,KAAK,qBAAqB,GAAG,CAAC,cAAc,EAAE,CAAC,CAAC,GAAG,OAAO,OAAO,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC;AAapF,wBAAsB,IAAI,CAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,oBA4BxE;AAGD,wBAAgB,MAAM,CAAC,UAAU,EAAE,MAAM,QAExC"}
@@ -1,4 +1,57 @@
1
+ import { MESSAGE_TYPES } from '../constants.js';
1
2
  /**
2
3
  * re-export mock module
3
4
  */
4
5
  export * from '@vitest/spy';
6
+ const a = document.createElement('a');
7
+ function resolveUrl(path) {
8
+ a.href = path;
9
+ return a.href;
10
+ }
11
+ const ERROR_MESSAGE = '[wdio] There was an error, when mocking a module. If you are using the "mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://webdriver.io/docs/component-testing/mocking';
12
+ const socket = window.__wdioSocket__;
13
+ const mockResolver = new Map();
14
+ const origin = window.__wdioSpec__.split('/').slice(0, -1).join('/');
15
+ window.__wdioMockFactories__ = [];
16
+ export async function mock(path, factory) {
17
+ /**
18
+ * mock calls without factory parameter should get removed from the source code
19
+ * by the mock hoisting plugin
20
+ */
21
+ if (!factory) {
22
+ return;
23
+ }
24
+ const mockLocalFile = path.startsWith('/') || path.startsWith('./');
25
+ const mockPath = mockLocalFile
26
+ ? (new URL(resolveUrl(window.__wdioSpec__.split('/').slice(0, -1).join('/') + '/' + path))).pathname
27
+ : path;
28
+ try {
29
+ const resolvedMock = await factory(() => (import(mockLocalFile ? `/@mock${mockPath}` : `/node_modules/.vite/deps/${mockPath}.js`)));
30
+ socket.send(JSON.stringify({
31
+ type: MESSAGE_TYPES.mockRequest,
32
+ value: { path: mockPath, origin, namedExports: Object.keys(resolvedMock) }
33
+ }));
34
+ window.__wdioMockFactories__[mockPath] = resolvedMock;
35
+ return new Promise((resolve) => mockResolver.set(mockPath, resolve));
36
+ }
37
+ catch (err) {
38
+ throw new Error(ERROR_MESSAGE + '\n' + err.stack);
39
+ }
40
+ }
41
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
42
+ export function unmock(moduleName) {
43
+ // NO-OP: call gets removed by recast
44
+ }
45
+ socket.addEventListener('message', (ev) => {
46
+ try {
47
+ const { type, value } = JSON.parse(ev.data);
48
+ const resolver = mockResolver.get(value.path);
49
+ if (type !== MESSAGE_TYPES.mockResponse || !resolver) {
50
+ return;
51
+ }
52
+ return resolver(null);
53
+ }
54
+ catch {
55
+ // ignore
56
+ }
57
+ });
@@ -20,11 +20,15 @@ export declare enum MESSAGE_TYPES {
20
20
  commandRequestMessage = 1,
21
21
  commandResponseMessage = 2,
22
22
  hookTriggerMessage = 3,
23
- hookResultMessage = 4
23
+ hookResultMessage = 4,
24
+ mockRequest = 5,
25
+ mockResponse = 6
24
26
  }
25
27
  export declare const DEFAULT_INCLUDE: string[];
26
28
  export declare const DEFAULT_FILE_EXTENSIONS: string[];
27
29
  export declare const DEFAULT_REPORTS_DIRECTORY = "coverage";
30
+ export declare const DEFAULT_AUTOMOCK = true;
31
+ export declare const DEFAULT_MOCK_DIRECTORY = "__mocks__";
28
32
  export declare const SUMMARY_REPORTER = "json-summary";
29
33
  export declare const COVERAGE_FACTORS: readonly ["lines", "functions", "branches", "statements"];
30
34
  export declare const DEFAULT_COVERAGE_REPORTS: (keyof ReportOptions)[];
@@ -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;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"}
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;IACjB,WAAW,IAAA;IACX,YAAY,IAAA;CACf;AAED,eAAO,MAAM,eAAe,UAAS,CAAA;AACrC,eAAO,MAAM,uBAAuB,UAAoF,CAAA;AACxH,eAAO,MAAM,yBAAyB,aAAa,CAAA;AACnD,eAAO,MAAM,gBAAgB,OAAO,CAAA;AACpC,eAAO,MAAM,sBAAsB,cAAc,CAAA;AACjD,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"}
@@ -20,10 +20,14 @@ export var MESSAGE_TYPES;
20
20
  MESSAGE_TYPES[MESSAGE_TYPES["commandResponseMessage"] = 2] = "commandResponseMessage";
21
21
  MESSAGE_TYPES[MESSAGE_TYPES["hookTriggerMessage"] = 3] = "hookTriggerMessage";
22
22
  MESSAGE_TYPES[MESSAGE_TYPES["hookResultMessage"] = 4] = "hookResultMessage";
23
+ MESSAGE_TYPES[MESSAGE_TYPES["mockRequest"] = 5] = "mockRequest";
24
+ MESSAGE_TYPES[MESSAGE_TYPES["mockResponse"] = 6] = "mockResponse";
23
25
  })(MESSAGE_TYPES || (MESSAGE_TYPES = {}));
24
26
  export const DEFAULT_INCLUDE = ['**'];
25
27
  export const DEFAULT_FILE_EXTENSIONS = ['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte'];
26
28
  export const DEFAULT_REPORTS_DIRECTORY = 'coverage';
29
+ export const DEFAULT_AUTOMOCK = true;
30
+ export const DEFAULT_MOCK_DIRECTORY = '__mocks__';
27
31
  export const SUMMARY_REPORTER = 'json-summary';
28
32
  export const COVERAGE_FACTORS = ['lines', 'functions', 'branches', 'statements'];
29
33
  export const DEFAULT_COVERAGE_REPORTS = ['text', 'html', 'clover', SUMMARY_REPORTER];
package/build/types.d.ts CHANGED
@@ -92,6 +92,16 @@ export interface BrowserRunnerOptions {
92
92
  * test coverage settings
93
93
  */
94
94
  coverage?: CoverageOptions;
95
+ /**
96
+ * If set to `true` WebdriverIO will automatically mock dependencies within the `automockDir` directory
97
+ * @default true
98
+ */
99
+ automock?: boolean;
100
+ /**
101
+ * Path of auto-mock directory. This tells WebdriverIO where to look for dependencies to mock out.
102
+ * @default ./__mocks__
103
+ */
104
+ automockDir?: string;
95
105
  }
96
106
  export interface RunArgs extends Workers.WorkerRunPayload {
97
107
  command: string;
@@ -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;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"}
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;IAC1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;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"}
@@ -0,0 +1,16 @@
1
+ import type { Options } from '@wdio/types';
2
+ import type { MockRequestEvent } from './types';
3
+ export declare class MockHandler {
4
+ #private;
5
+ manualMocks: string[];
6
+ constructor(options: WebdriverIO.BrowserRunnerOptions, config: Options.Testrunner);
7
+ get mocks(): Map<string, MockRequestEvent>;
8
+ addMock(mock: MockRequestEvent): void;
9
+ unmock(moduleName: string): void;
10
+ resolveId(id: string): Promise<string | undefined>;
11
+ /**
12
+ * reset manual mocks between tests
13
+ */
14
+ resetMocks(): void;
15
+ }
16
+ //# sourceMappingURL=mock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/vite/mock.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAI1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C,qBAAa,WAAW;;IAOpB,WAAW,EAAE,MAAM,EAAE,CAAK;gBAEb,OAAO,EAAE,WAAW,CAAC,oBAAoB,EAAE,MAAM,EAAE,OAAO,CAAC,UAAU;IAMlF,IAAI,KAAK,kCAER;IAED,OAAO,CAAE,IAAI,EAAE,gBAAgB;IAI/B,MAAM,CAAE,UAAU,EAAE,MAAM;IAIpB,SAAS,CAAE,EAAE,EAAE,MAAM;IAwB3B;;OAEG;IACH,UAAU;CAIb"}
@@ -0,0 +1,53 @@
1
+ import path from 'node:path';
2
+ import { getManualMocks } from './utils.js';
3
+ import { DEFAULT_MOCK_DIRECTORY, DEFAULT_AUTOMOCK } from '../constants.js';
4
+ const FIXTURE_PREFIX = '/@fixture/';
5
+ export class MockHandler {
6
+ #automock;
7
+ #automockDir;
8
+ #manualMocksList;
9
+ #mocks = new Map();
10
+ #unmocked = [];
11
+ manualMocks = [];
12
+ constructor(options, config) {
13
+ this.#automock = typeof options.automock === 'boolean' ? options.automock : DEFAULT_AUTOMOCK;
14
+ this.#automockDir = path.resolve(config.rootDir, options.automockDir || DEFAULT_MOCK_DIRECTORY);
15
+ this.#manualMocksList = getManualMocks(this.#automockDir);
16
+ }
17
+ get mocks() {
18
+ return this.#mocks;
19
+ }
20
+ addMock(mock) {
21
+ this.#mocks.set(mock.path, mock);
22
+ }
23
+ unmock(moduleName) {
24
+ this.#unmocked.push(moduleName);
25
+ }
26
+ async resolveId(id) {
27
+ const manualMocksList = await this.#manualMocksList;
28
+ const mockPath = manualMocksList.find((m) => (
29
+ // e.g. someModule
30
+ id === m[1].replace(path.sep, '/') ||
31
+ // e.g. @some/module
32
+ id.slice(1) === m[1].replace(path.sep, '/')));
33
+ /**
34
+ * return manual mock if `automock` is enabled or a manual mock was set via `mock('module')`
35
+ */
36
+ if ((this.manualMocks.includes(id) || this.#automock) && mockPath && !this.#unmocked.includes(id)) {
37
+ return mockPath[0];
38
+ }
39
+ /**
40
+ * return fixture
41
+ */
42
+ if (id.startsWith(FIXTURE_PREFIX)) {
43
+ return path.resolve(this.#automockDir, id.slice(FIXTURE_PREFIX.length));
44
+ }
45
+ }
46
+ /**
47
+ * reset manual mocks between tests
48
+ */
49
+ resetMocks() {
50
+ this.manualMocks = [];
51
+ this.#unmocked = [];
52
+ }
53
+ }
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { MockHandler } from '../mock.js';
3
+ export declare function mockHoisting(mockHandler: MockHandler): Plugin[];
4
+ //# sourceMappingURL=mockHoisting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mockHoisting.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/mockHoisting.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AAElC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAM7C,wBAAgB,YAAY,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,EAAE,CAwM/D"}
@@ -0,0 +1,192 @@
1
+ import os from 'node:os';
2
+ import url from 'node:url';
3
+ import path from 'node:path';
4
+ import fs from 'node:fs/promises';
5
+ import logger from '@wdio/logger';
6
+ import { parse, print, visit, types } from 'recast';
7
+ import typescriptParser from 'recast/parsers/typescript.js';
8
+ const log = logger('@wdio/browser-runner:mockHoisting');
9
+ const b = types.builders;
10
+ const MOCK_PREFIX = '/@mock';
11
+ export function mockHoisting(mockHandler) {
12
+ let spec = null;
13
+ return [{
14
+ name: 'wdio:mockHoisting:pre',
15
+ enforce: 'pre',
16
+ resolveId: mockHandler.resolveId.bind(mockHandler),
17
+ load: async function (id) {
18
+ if (id.startsWith(MOCK_PREFIX)) {
19
+ try {
20
+ const orig = await fs.readFile(id.slice(MOCK_PREFIX.length + (os.platform() === 'win32' ? 1 : 0)));
21
+ return orig.toString();
22
+ }
23
+ catch (err) {
24
+ log.error(`Failed to read file (${id}) for mocking: ${err.message}`);
25
+ return '';
26
+ }
27
+ }
28
+ const mockedMod = (
29
+ // mocked file
30
+ mockHandler.mocks.get(os.platform() === 'win32' ? `/${id}` : id) ||
31
+ // mocked dependency
32
+ mockHandler.mocks.get(path.basename(id, path.extname(id))));
33
+ if (mockedMod) {
34
+ const newCode = mockedMod.namedExports.map((ne) => {
35
+ if (ne === 'default') {
36
+ return /*js*/ `export default window.__wdioMockFactories__['${mockedMod.path}'].default;`;
37
+ }
38
+ return /*js*/ `export const ${ne} = window.__wdioMockFactories__['${mockedMod.path}']['${ne}'];`;
39
+ });
40
+ if (!mockedMod.namedExports.includes('default')) {
41
+ newCode.push(/*js*/ `export default window.__wdioMockFactories__['${mockedMod.path}'];`);
42
+ }
43
+ log.debug(`Resolve mock for module "${mockedMod.path}"`);
44
+ return newCode.join('\n');
45
+ }
46
+ }
47
+ }, {
48
+ name: 'wdio:mockHoisting',
49
+ enforce: 'post',
50
+ transform(code, id) {
51
+ /**
52
+ * only transform when spec file is transformed
53
+ */
54
+ if (id !== spec) {
55
+ return { code };
56
+ }
57
+ const ast = parse(code, {
58
+ parser: typescriptParser,
59
+ sourceFileName: id,
60
+ sourceRoot: path.dirname(id)
61
+ });
62
+ let mockFunctionName;
63
+ let unmockFunctionName;
64
+ const mockCalls = [];
65
+ /**
66
+ * rewrite import statements into variable declarations, e.g. from
67
+ *
68
+ * import React, { RC } from 'react'
69
+ *
70
+ * to
71
+ *
72
+ * var { default: React, RC: RC } = await import("react")
73
+ *
74
+ * so we can hoist the mock call
75
+ */
76
+ visit(ast, {
77
+ visitImportDeclaration: function (path) {
78
+ const dec = path.value;
79
+ const source = dec.source.value;
80
+ /**
81
+ * get name of mock function variable
82
+ */
83
+ if (source === '@wdio/browser-runner') {
84
+ const mockSpecifier = dec.specifiers
85
+ .filter((s) => s.type === types.namedTypes.ImportSpecifier.toString())
86
+ .find((s) => s.imported.name === 'mock');
87
+ if (mockSpecifier && mockSpecifier.local) {
88
+ mockFunctionName = mockSpecifier.local.name;
89
+ }
90
+ const unmockSpecifier = dec.specifiers
91
+ .filter((s) => s.type === types.namedTypes.ImportSpecifier.toString())
92
+ .find((s) => s.imported.name === 'unmock');
93
+ if (unmockSpecifier && unmockSpecifier.local) {
94
+ unmockFunctionName = unmockSpecifier.local.name;
95
+ }
96
+ mockCalls.push(dec);
97
+ path.prune();
98
+ return this.traverse(path);
99
+ }
100
+ const newNode = b.variableDeclaration('const', [
101
+ b.variableDeclarator((dec.specifiers?.length === 1 && dec.specifiers[0].type === types.namedTypes.ImportNamespaceSpecifier.toString())
102
+ /**
103
+ * we deal with a ImportNamespaceSpecifier, e.g.:
104
+ * import * as foo from 'bar'
105
+ */
106
+ ? dec.specifiers[0].local
107
+ /**
108
+ * we deal with default or named import, e.g.
109
+ * import foo from 'bar'
110
+ * or
111
+ * import { foo } from 'bar'
112
+ */
113
+ : b.objectPattern(dec.specifiers.map((s) => {
114
+ if (s.type === types.namedTypes.ImportDefaultSpecifier.toString()) {
115
+ return b.property('init', b.identifier('default'), b.identifier(s.local.name));
116
+ }
117
+ return b.property('init', b.identifier(s.imported.name), b.identifier(s.local.name));
118
+ })), b.awaitExpression(b.importExpression(b.literal(source))))
119
+ ]);
120
+ path.replace(newNode);
121
+ this.traverse(path);
122
+ },
123
+ visitExpressionStatement: function (path) {
124
+ const exp = path.value;
125
+ if (exp.expression.type !== types.namedTypes.CallExpression.toString()) {
126
+ return this.traverse(path);
127
+ }
128
+ const callExp = exp.expression;
129
+ const isUnmockCall = unmockFunctionName && callExp.callee.name === unmockFunctionName;
130
+ const isMockCall = mockFunctionName && callExp.callee.name === mockFunctionName;
131
+ if (!isMockCall && !isUnmockCall) {
132
+ return this.traverse(path);
133
+ }
134
+ /**
135
+ * hoist unmock calls
136
+ */
137
+ if (isUnmockCall && callExp.arguments[0] && typeof callExp.arguments[0].value === 'string') {
138
+ mockHandler.unmock(callExp.arguments[0].value);
139
+ }
140
+ else if (isMockCall) {
141
+ /**
142
+ * if only one mock argument is set, we take the fixture from the automock directory
143
+ */
144
+ const mockCall = exp.expression;
145
+ if (mockCall.arguments.length === 1) {
146
+ /**
147
+ * enable manual mock
148
+ */
149
+ mockHandler.manualMocks.push(mockCall.arguments[0].value);
150
+ }
151
+ else {
152
+ /**
153
+ * hoist mock calls
154
+ */
155
+ mockCalls.push(exp);
156
+ }
157
+ }
158
+ path.prune();
159
+ this.traverse(path);
160
+ }
161
+ });
162
+ ast.program.body.unshift(...mockCalls.map((mc) => {
163
+ const exp = mc;
164
+ if (exp.expression && exp.expression.type === types.namedTypes.CallExpression.toString()) {
165
+ return b.expressionStatement(b.awaitExpression(exp.expression));
166
+ }
167
+ return mc;
168
+ }));
169
+ const newCode = print(ast, {
170
+ sourceMapName: id
171
+ });
172
+ return newCode;
173
+ },
174
+ configureServer(server) {
175
+ return () => {
176
+ server.middlewares.use('/', async (req, res, next) => {
177
+ if (!req.url) {
178
+ return next();
179
+ }
180
+ const urlParsed = url.parse(req.url);
181
+ const urlParamString = new URLSearchParams(urlParsed.query || '');
182
+ const specParam = urlParamString.get('spec');
183
+ if (specParam) {
184
+ mockHandler.resetMocks();
185
+ spec = os.platform() === 'win32' ? specParam.slice(1) : specParam;
186
+ }
187
+ return next();
188
+ });
189
+ };
190
+ }
191
+ }];
192
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"testrunner.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/testrunner.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AA2ClC,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,CAAC,oBAAoB,GAAG,MAAM,EAAE,CAgI9E"}
1
+ {"version":3,"file":"testrunner.d.ts","sourceRoot":"","sources":["../../../src/vite/plugins/testrunner.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAA;AA0ClC,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,CAAC,oBAAoB,GAAG,MAAM,EAAE,CAgI9E"}
@@ -25,8 +25,7 @@ const POLYFILLS = [
25
25
  const FETCH_FROM_ESM = [
26
26
  'serialize-error', 'minimatch', 'css-shorthand-properties', 'lodash.merge', 'lodash.zip',
27
27
  'lodash.clonedeep', 'lodash.pickby', 'lodash.flattendeep', 'aria-query', 'grapheme-splitter',
28
- 'css-value', 'rgb2hex', 'p-iteration', 'fast-safe-stringify', 'deepmerge-ts',
29
- 'mocha'
28
+ 'css-value', 'rgb2hex', 'p-iteration', 'fast-safe-stringify', 'deepmerge-ts'
30
29
  ];
31
30
  export function testrunner(options) {
32
31
  const automationProtocolPath = `/@fs${url.pathToFileURL(path.resolve(__dirname, '..', '..', 'browser', 'driver.js')).pathname}`;
@@ -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;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;IA4B5E,KAAK;IAwCL,KAAK;IA2IX,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;AAQpD,OAAO,KAAK,EACM,gBAAgB,EAEjC,MAAM,YAAY,CAAA;AAYnB,qBAAa,UAAW,SAAQ,YAAY;;IASxC,IAAI,YAAY,gCAEf;IAED,IAAI,MAAM,0BAET;gBAEY,OAAO,EAAE,WAAW,CAAC,oBAAoB,EAAE,MAAM,EAAE,OAAO,CAAC,UAAU;IAiC5E,KAAK;IAwCL,KAAK;IAkJX,OAAO,CAAC,cAAc;IAetB,WAAW,CAAE,MAAM,EAAE,gBAAgB;CAUxC"}
@@ -8,7 +8,9 @@ import { executeHooksWithArgs } from '@wdio/utils';
8
8
  import { createServer } from 'vite';
9
9
  import istanbulPlugin from 'vite-plugin-istanbul';
10
10
  import { testrunner } from './plugins/testrunner.js';
11
+ import { mockHoisting } from './plugins/mockHoisting.js';
11
12
  import { userfriendlyImport } from './utils.js';
13
+ import { MockHandler } from './mock.js';
12
14
  import { PRESET_DEPENDENCIES, DEFAULT_VITE_CONFIG } from './constants.js';
13
15
  import { MESSAGE_TYPES, DEFAULT_INCLUDE, DEFAULT_FILE_EXTENSIONS } from '../constants.js';
14
16
  import { BROWSER_POOL, SESSIONS } from '../constants.js';
@@ -21,6 +23,7 @@ export class ViteServer extends EventEmitter {
21
23
  #viteConfig;
22
24
  #wss;
23
25
  #server;
26
+ #mockHandler;
24
27
  get socketServer() {
25
28
  return this.#wss;
26
29
  }
@@ -30,12 +33,16 @@ export class ViteServer extends EventEmitter {
30
33
  constructor(options, config) {
31
34
  super();
32
35
  this.#options = options;
36
+ this.#mockHandler = new MockHandler(options, config);
33
37
  if (options.preset && options.viteConfig) {
34
38
  throw new Error('Invalid runner configuration: "preset" and "viteConfig" options are defined but only one of each can be used at the same time');
35
39
  }
36
40
  this.#viteConfig = deepmerge(DEFAULT_VITE_CONFIG, {
37
41
  root: options.rootDir || process.cwd(),
38
- plugins: [testrunner(options)]
42
+ plugins: [
43
+ testrunner(options),
44
+ mockHoisting(this.#mockHandler)
45
+ ]
39
46
  });
40
47
  if (options.coverage && options.coverage.enabled) {
41
48
  log.info('Capturing test coverage enabled');
@@ -43,6 +50,7 @@ export class ViteServer extends EventEmitter {
43
50
  cwd: config.rootDir,
44
51
  include: DEFAULT_INCLUDE,
45
52
  extension: DEFAULT_FILE_EXTENSIONS,
53
+ forceBuildInstrument: true,
46
54
  ...options.coverage
47
55
  }));
48
56
  }
@@ -109,6 +117,13 @@ export class ViteServer extends EventEmitter {
109
117
  if (payload.type === MESSAGE_TYPES.commandRequestMessage) {
110
118
  return this.#handleCommand(ws, payload.value);
111
119
  }
120
+ if (payload.type === MESSAGE_TYPES.mockRequest) {
121
+ this.#mockHandler.addMock(payload.value);
122
+ return ws.send(JSON.stringify({
123
+ type: MESSAGE_TYPES.mockResponse,
124
+ value: payload.value
125
+ }));
126
+ }
112
127
  throw new Error(`Unknown socket message ${JSON.stringify(payload)}`);
113
128
  }
114
129
  catch (err) {
@@ -10,6 +10,8 @@ export type SocketMessageValue = {
10
10
  [MESSAGE_TYPES.commandResponseMessage]: CommandResponseEvent;
11
11
  [MESSAGE_TYPES.hookTriggerMessage]: HookTriggerEvent;
12
12
  [MESSAGE_TYPES.hookResultMessage]: HookResultEvent;
13
+ [MESSAGE_TYPES.mockRequest]: MockRequestEvent;
14
+ [MESSAGE_TYPES.mockResponse]: MockResponseEvent;
13
15
  };
14
16
  export type SocketMessagePayload<T extends MESSAGE_TYPES> = T extends any ? SocketMessagePayloadType<T> : never;
15
17
  export type SocketMessage = SocketMessagePayload<MESSAGE_TYPES>;
@@ -40,5 +42,13 @@ export interface CommandResponseEvent {
40
42
  result?: unknown;
41
43
  error?: ErrorObject;
42
44
  }
45
+ export interface MockRequestEvent {
46
+ path: string;
47
+ origin: string;
48
+ namedExports: string[];
49
+ }
50
+ export interface MockResponseEvent {
51
+ path: string;
52
+ }
43
53
  export {};
44
54
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/vite/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,UAAU,wBAAwB,CAAC,CAAC,SAAS,aAAa;IACtD,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAA;CAC/B;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC7B,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,YAAY,CAAA;IAC5C,CAAC,aAAa,CAAC,qBAAqB,CAAC,EAAE,mBAAmB,CAAA;IAC1D,CAAC,aAAa,CAAC,sBAAsB,CAAC,EAAE,oBAAoB,CAAA;IAC5D,CAAC,aAAa,CAAC,kBAAkB,CAAC,EAAE,gBAAgB,CAAA;IACpD,CAAC,aAAa,CAAC,iBAAiB,CAAC,EAAE,eAAe,CAAA;CACrD,CAAA;AAED,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,aAAa,IAAI,CAAC,SAAS,GAAG,GACnE,wBAAwB,CAAC,CAAC,CAAC,GAC3B,KAAK,CAAA;AAEX,MAAM,MAAM,aAAa,GAAG,oBAAoB,CAAC,aAAa,CAAC,CAAA;AAE/D,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,cAAc,CAAA;IACpB,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;IACjD,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;CACd;AAED,UAAU,2BAA2B;IACjC,EAAE,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,gBAAiB,SAAQ,2BAA2B;IACjE,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,2BAA2B;IAChE,KAAK,CAAC,EAAE,WAAW,CAAA;CACtB;AAED,MAAM,WAAW,mBAAoB,SAAQ,2BAA2B;IACpE,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,OAAO,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,oBAAoB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,WAAW,CAAA;CACtB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/vite/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAEpD,UAAU,wBAAwB,CAAC,CAAC,SAAS,aAAa;IACtD,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,kBAAkB,CAAC,CAAC,CAAC,CAAA;CAC/B;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC7B,CAAC,aAAa,CAAC,cAAc,CAAC,EAAE,YAAY,CAAA;IAC5C,CAAC,aAAa,CAAC,qBAAqB,CAAC,EAAE,mBAAmB,CAAA;IAC1D,CAAC,aAAa,CAAC,sBAAsB,CAAC,EAAE,oBAAoB,CAAA;IAC5D,CAAC,aAAa,CAAC,kBAAkB,CAAC,EAAE,gBAAgB,CAAA;IACpD,CAAC,aAAa,CAAC,iBAAiB,CAAC,EAAE,eAAe,CAAA;IAClD,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,gBAAgB,CAAA;IAC7C,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,iBAAiB,CAAA;CAClD,CAAA;AAED,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,aAAa,IAAI,CAAC,SAAS,GAAG,GACnE,wBAAwB,CAAC,CAAC,CAAC,GAC3B,KAAK,CAAA;AAEX,MAAM,MAAM,aAAa,GAAG,oBAAoB,CAAC,aAAa,CAAC,CAAA;AAE/D,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,cAAc,CAAA;IACpB,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAA;IACjD,IAAI,EAAE,OAAO,EAAE,CAAA;IACf,GAAG,EAAE,MAAM,CAAA;CACd;AAED,UAAU,2BAA2B;IACjC,EAAE,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,gBAAiB,SAAQ,2BAA2B;IACjE,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,eAAgB,SAAQ,2BAA2B;IAChE,KAAK,CAAC,EAAE,WAAW,CAAA;CACtB;AAED,MAAM,WAAW,mBAAoB,SAAQ,2BAA2B;IACpE,GAAG,EAAE,MAAM,CAAA;IACX,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,OAAO,EAAE,CAAA;CAClB;AAED,MAAM,WAAW,oBAAoB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,KAAK,CAAC,EAAE,WAAW,CAAA;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,EAAE,CAAA;CACzB;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,EAAE,MAAM,CAAA;CACf"}
@@ -1,6 +1,9 @@
1
+ /// <reference types="node" />
1
2
  import type { Environment, FrameworkPreset } from '../types.js';
2
- export declare function getTemplate(options: WebdriverIO.BrowserRunnerOptions, env: Environment, spec: string): Promise<string>;
3
+ export declare function getTemplate(options: WebdriverIO.BrowserRunnerOptions, env: Environment, spec: string, processEnv?: NodeJS.ProcessEnv): Promise<string>;
3
4
  export declare function userfriendlyImport(preset: FrameworkPreset, pkg?: string): Promise<any>;
4
5
  export declare function normalizeId(id: string, base?: string): string;
6
+ export declare function getFilesFromDirectory(dir: string): Promise<string[]>;
7
+ export declare function getManualMocks(automockDir: string): Promise<[string, string][]>;
5
8
  export declare function getErrorTemplate(filename: string, error: Error): string;
6
9
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/vite/utils.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE/D,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,oBAAoB,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,mBAyE1G;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,EAAE,MAAM,gBAa7E;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAY7D;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,UAU9D"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/vite/utils.ts"],"names":[],"mappings":";AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE/D,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,CAAC,oBAAoB,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,oBAAc,mBAsFpI;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,CAAC,EAAE,MAAM,gBAa7E;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAY7D;AAED,wBAAsB,qBAAqB,CAAE,GAAG,EAAE,MAAM,qBAavD;AAGD,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,+BAgBvD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,UAU9D"}
@@ -2,7 +2,7 @@ import fs from 'node:fs/promises';
2
2
  import url from 'node:url';
3
3
  import path from 'node:path';
4
4
  import { resolve } from 'import-meta-resolve';
5
- export async function getTemplate(options, env, spec) {
5
+ export async function getTemplate(options, env, spec, processEnv = process.env) {
6
6
  const root = options.rootDir || process.cwd();
7
7
  const rootFileUrl = url.pathToFileURL(root).href;
8
8
  let vueDeps = '';
@@ -30,14 +30,19 @@ export async function getTemplate(options, env, spec) {
30
30
  }
31
31
  }
32
32
  const mochaCSSHref = await resolve('mocha', `${rootFileUrl}/node_modules`).then((p) => path.join(url.fileURLToPath(path.dirname(p)), 'mocha.css'), () => 'https://unpkg.com/mocha@10.0.0/mocha.css');
33
+ const mochaJSSrc = await resolve('mocha', `${rootFileUrl}/node_modules`).then((p) => path.join(url.fileURLToPath(path.dirname(p)), 'mocha.js'), () => 'https://unpkg.com/mocha@10.0.0/mocha.js');
34
+ const sourceMapSupportDir = path.dirname(url.fileURLToPath(await resolve('source-map-support', import.meta.url)));
33
35
  return /* html */ `
34
36
  <!doctype html>
35
37
  <html>
36
38
  <head>
37
39
  <title>WebdriverIO Browser Test</title>
38
40
  <link rel="icon" type="image/x-icon" href="https://webdriver.io/img/favicon.png">
39
- <link rel="stylesheet" href="${mochaCSSHref}">
41
+ <link rel="stylesheet" href="${mochaCSSHref}"><script type="module" src="${mochaJSSrc}"></script>
42
+ <script src="/@fs/${sourceMapSupportDir}/browser-source-map-support.js"></script>
40
43
  <script type="module">
44
+ sourceMapSupport.install()
45
+
41
46
  /**
42
47
  * Inject environment variables
43
48
  */
@@ -50,7 +55,8 @@ export async function getTemplate(options, env, spec) {
50
55
  window.__wdioErrors__ = []
51
56
  addEventListener('error', (ev) => window.__wdioErrors__.push({
52
57
  filename: ev.filename,
53
- message: ev.message
58
+ message: ev.message,
59
+ error: ev.error.stack
54
60
  }))
55
61
  /**
56
62
  * mock process
@@ -65,6 +71,9 @@ export async function getTemplate(options, env, spec) {
65
71
  <body>
66
72
  <div id="mocha"></div>
67
73
  <script async type="module" src="@wdio/browser-runner/setup"></script>
74
+ <script type="module">
75
+ window.process.env = ${JSON.stringify(processEnv)}
76
+ </script>
68
77
  </body>
69
78
  </html>`;
70
79
  }
@@ -92,6 +101,37 @@ export function normalizeId(id, base) {
92
101
  .replace(/[?&]v=\w+/, '?') // remove ?v= query
93
102
  .replace(/\?$/, ''); // remove end query mark
94
103
  }
104
+ export async function getFilesFromDirectory(dir) {
105
+ let files = await fs.readdir(dir);
106
+ files = (await Promise.all(files.map(async (file) => {
107
+ const filePath = path.join(dir, file);
108
+ const stats = await fs.stat(filePath);
109
+ if (stats.isDirectory()) {
110
+ return getFilesFromDirectory(filePath);
111
+ }
112
+ else if (stats.isFile()) {
113
+ return filePath;
114
+ }
115
+ }))).filter(Boolean);
116
+ return files.reduce((all, folderContents) => all.concat(folderContents), []);
117
+ }
118
+ let mockedModulesList;
119
+ export async function getManualMocks(automockDir) {
120
+ /**
121
+ * read available mocks only one time
122
+ */
123
+ if (!mockedModulesList) {
124
+ mockedModulesList = (await getFilesFromDirectory(automockDir))
125
+ /**
126
+ * seperate to module name and actual path
127
+ */
128
+ .map((filePath) => [
129
+ filePath,
130
+ filePath.slice(automockDir.length + 1).slice(0, -path.extname(filePath).length)
131
+ ]);
132
+ }
133
+ return mockedModulesList;
134
+ }
95
135
  export function getErrorTemplate(filename, error) {
96
136
  return /*html*/ `
97
137
  <pre>${error.stack}</pre>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wdio/browser-runner",
3
- "version": "8.3.10",
3
+ "version": "8.4.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",
@@ -30,17 +30,18 @@
30
30
  "@babel/plugin-proposal-class-properties": "^7.18.6",
31
31
  "@originjs/vite-plugin-commonjs": "^1.0.3",
32
32
  "@types/istanbul-lib-source-maps": "^4.0.1",
33
- "@types/node": "^18.11.18",
34
- "@vitest/spy": "^0.28.4",
35
- "@wdio/globals": "8.3.10",
36
- "@wdio/local-runner": "8.3.10",
33
+ "@types/node": "^18.14.0",
34
+ "@vitest/spy": "^0.29.1",
35
+ "@wdio/globals": "8.4.0",
36
+ "@wdio/local-runner": "8.4.0",
37
37
  "@wdio/logger": "8.1.0",
38
- "@wdio/mocha-framework": "8.3.0",
39
- "@wdio/protocols": "8.3.5",
40
- "@wdio/types": "8.3.0",
41
- "@wdio/utils": "8.3.0",
42
- "deepmerge-ts": "^4.2.2",
43
- "expect-webdriverio": "^4.1.0",
38
+ "@wdio/mocha-framework": "8.4.0",
39
+ "@wdio/protocols": "8.3.11",
40
+ "@wdio/types": "8.4.0",
41
+ "@wdio/utils": "8.4.0",
42
+ "ast-types": "^0.14.2",
43
+ "deepmerge-ts": "^4.3.0",
44
+ "expect-webdriverio": "^4.1.2",
44
45
  "fast-safe-stringify": "^2.1.1",
45
46
  "get-port": "^6.1.2",
46
47
  "import-meta-resolve": "^2.2.1",
@@ -48,14 +49,16 @@
48
49
  "istanbul-lib-report": "^3.0.0",
49
50
  "istanbul-lib-source-maps": "^4.0.1",
50
51
  "istanbul-reports": "^3.1.5",
51
- "modern-node-polyfills": "^0.0.9",
52
+ "modern-node-polyfills": "^0.1.0",
53
+ "recast": "^0.22.0",
52
54
  "serialize-error": "^11.0.0",
53
- "vite": "^4.0.4",
55
+ "source-map-support": "^0.5.21",
56
+ "vite": "^4.1.3",
54
57
  "vite-plugin-istanbul": "^4.0.0",
55
- "vite-plugin-top-level-await": "^1.2.2",
56
- "webdriver": "8.3.8",
57
- "webdriverio": "8.3.10",
58
- "ws": "^8.12.0"
58
+ "vite-plugin-top-level-await": "^1.2.4",
59
+ "webdriver": "8.4.0",
60
+ "webdriverio": "8.4.0",
61
+ "ws": "^8.12.1"
59
62
  },
60
63
  "scripts": {
61
64
  "prepare": "rimraf node_modules/@wdio/config node_modules/@wdio/repl node_modules/@wdio/utils"
@@ -65,7 +68,7 @@
65
68
  },
66
69
  "devDependencies": {
67
70
  "@types/ws": "^8.5.4",
68
- "@wdio/runner": "8.3.10"
71
+ "@wdio/runner": "8.4.0"
69
72
  },
70
- "gitHead": "5135fb86e53dcbfca0a5935eda4b44aa3ca46dc2"
73
+ "gitHead": "5d003ce35250c9d9dcecc9fd13a97257d6b349fe"
71
74
  }