@wdio/browser-runner 9.0.0-alpha.78 → 9.0.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.
Files changed (47) hide show
  1. package/build/browser/driver.js +249 -234
  2. package/build/browser/expect.js +107 -148
  3. package/build/browser/frameworks/mocha.d.ts.map +1 -1
  4. package/build/browser/integrations/stencil.js +370 -407
  5. package/build/browser/mock.d.ts +3 -2
  6. package/build/browser/mock.d.ts.map +1 -1
  7. package/build/browser/mock.js +78 -34
  8. package/build/browser/setup.js +313 -37
  9. package/build/browser/spy.d.ts.map +1 -1
  10. package/build/browser/spy.js +29 -40
  11. package/build/browser/utils.d.ts +7 -0
  12. package/build/browser/utils.d.ts.map +1 -1
  13. package/build/index.d.ts.map +1 -1
  14. package/build/index.js +1465 -171
  15. package/build/types.d.ts +19 -2
  16. package/build/types.d.ts.map +1 -1
  17. package/build/utils.d.ts +3 -3
  18. package/build/utils.d.ts.map +1 -1
  19. package/build/vite/constants.d.ts +3 -0
  20. package/build/vite/constants.d.ts.map +1 -1
  21. package/build/vite/plugins/esbuild.d.ts.map +1 -1
  22. package/build/vite/plugins/testrunner.d.ts.map +1 -1
  23. package/build/vite/server.d.ts +0 -1
  24. package/build/vite/server.d.ts.map +1 -1
  25. package/build/vite/utils.d.ts.map +1 -1
  26. package/package.json +57 -26
  27. package/build/browser/commands/debug.js +0 -6
  28. package/build/browser/frameworks/mocha.js +0 -320
  29. package/build/browser/utils.js +0 -61
  30. package/build/communicator.js +0 -82
  31. package/build/constants.js +0 -89
  32. package/build/types.js +0 -1
  33. package/build/utils.js +0 -86
  34. package/build/vite/constants.js +0 -55
  35. package/build/vite/frameworks/index.js +0 -19
  36. package/build/vite/frameworks/nuxt.js +0 -61
  37. package/build/vite/frameworks/stencil.js +0 -165
  38. package/build/vite/frameworks/tailwindcss.js +0 -28
  39. package/build/vite/mock.js +0 -50
  40. package/build/vite/plugins/esbuild.js +0 -25
  41. package/build/vite/plugins/mockHoisting.js +0 -312
  42. package/build/vite/plugins/testrunner.js +0 -152
  43. package/build/vite/plugins/worker.js +0 -12
  44. package/build/vite/server.js +0 -104
  45. package/build/vite/types.js +0 -1
  46. package/build/vite/utils.js +0 -223
  47. /package/{LICENSE-MIT → LICENSE} +0 -0
package/build/index.js CHANGED
@@ -1,189 +1,1483 @@
1
- import fs from 'node:fs/promises';
2
- import url from 'node:url';
3
- import path from 'node:path';
4
- import logger from '@wdio/logger';
5
- import LocalRunner from '@wdio/local-runner';
6
- import libCoverage from 'istanbul-lib-coverage';
7
- import libReport from 'istanbul-lib-report';
8
- import reports from 'istanbul-reports';
9
- import { ViteServer } from './vite/server.js';
10
- import { FRAMEWORK_SUPPORT_ERROR, DEFAULT_COVERAGE_REPORTS, SUMMARY_REPORTER, DEFAULT_REPORTS_DIRECTORY } from './constants.js';
11
- import updateViteConfig from './vite/frameworks/index.js';
12
- import { ServerWorkerCommunicator } from './communicator.js';
13
- import { makeHeadless, getCoverageByFactor, adjustWindowInWatchMode } from './utils.js';
14
- const log = logger('@wdio/browser-runner');
15
- export default class BrowserRunner extends LocalRunner {
16
- options;
17
- _config;
18
- #options;
19
- #config;
20
- #servers = new Set();
21
- #coverageOptions;
22
- #reportsDirectory;
23
- #viteOptimizations = {};
24
- #communicator;
25
- constructor(options, _config) {
26
- super(options, _config);
27
- this.options = options;
28
- this._config = _config;
29
- if (_config.framework !== 'mocha') {
30
- throw new Error(FRAMEWORK_SUPPORT_ERROR);
1
+ // src/index.ts
2
+ import fs4 from "node:fs/promises";
3
+ import url7 from "node:url";
4
+ import path9 from "node:path";
5
+ import logger8 from "@wdio/logger";
6
+ import LocalRunner from "@wdio/local-runner";
7
+ import libCoverage2 from "istanbul-lib-coverage";
8
+ import libReport from "istanbul-lib-report";
9
+ import reports from "istanbul-reports";
10
+
11
+ // src/vite/server.ts
12
+ import path5 from "node:path";
13
+ import { EventEmitter } from "node:events";
14
+ import getPort from "get-port";
15
+ import logger4 from "@wdio/logger";
16
+ import istanbulPlugin from "vite-plugin-istanbul";
17
+ import { deepmerge as deepmerge2 } from "deepmerge-ts";
18
+ import { createServer } from "vite";
19
+
20
+ // src/vite/plugins/testrunner.ts
21
+ import url2 from "node:url";
22
+ import path2 from "node:path";
23
+ import { builtinModules } from "node:module";
24
+ import logger2 from "@wdio/logger";
25
+ import { polyfillPath } from "modern-node-polyfills";
26
+ import { deepmerge } from "deepmerge-ts";
27
+ import { resolve as resolve2 } from "import-meta-resolve";
28
+ import {
29
+ WebDriverProtocol,
30
+ MJsonWProtocol,
31
+ AppiumProtocol,
32
+ ChromiumProtocol,
33
+ SauceLabsProtocol,
34
+ SeleniumProtocol,
35
+ GeckoProtocol
36
+ } from "@wdio/protocols";
37
+
38
+ // src/constants.ts
39
+ var SESSIONS = /* @__PURE__ */ new Map();
40
+ var WDIO_EVENT_NAME = "wdio:workerMessage";
41
+ var FRAMEWORK_SUPPORT_ERROR = 'Currently only "mocha" is supported as framework when using @wdio/browser-runner.';
42
+ var DEFAULT_INCLUDE = ["**"];
43
+ var DEFAULT_FILE_EXTENSIONS = [".js", ".cjs", ".mjs", ".ts", ".mts", ".cts", ".tsx", ".jsx", ".vue", ".svelte"];
44
+ var DEFAULT_REPORTS_DIRECTORY = "coverage";
45
+ var DEFAULT_AUTOMOCK = true;
46
+ var DEFAULT_MOCK_DIRECTORY = "__mocks__";
47
+ var SUMMARY_REPORTER = "json-summary";
48
+ var COVERAGE_FACTORS = ["lines", "functions", "branches", "statements"];
49
+ var DEFAULT_COVERAGE_REPORTS = ["text", "html", "clover", SUMMARY_REPORTER];
50
+ var GLOBAL_TRESHOLD_REPORTING = "ERROR: Coverage for %s (%s%) does not meet global threshold (%s%)";
51
+ var FILE_TRESHOLD_REPORTING = "ERROR: Coverage for %s (%s%) does not meet threshold (%s%) for %s";
52
+ var MOCHA_VARIABELS = (
53
+ /*css*/
54
+ `:root {
55
+ --mocha-color: #000;
56
+ --mocha-bg-color: #fff;
57
+ --mocha-pass-icon-color: #00d6b2;
58
+ --mocha-pass-color: #fff;
59
+ --mocha-pass-shadow-color: rgba(0, 0, 0, .2);
60
+ --mocha-pass-mediump-color: #c09853;
61
+ --mocha-pass-slow-color: #b94a48;
62
+ --mocha-test-pending-color: #0b97c4;
63
+ --mocha-test-pending-icon-color: #0b97c4;
64
+ --mocha-test-fail-color: #c00;
65
+ --mocha-test-fail-icon-color: #c00;
66
+ --mocha-test-fail-pre-color: #000;
67
+ --mocha-test-fail-pre-error-color: #c00;
68
+ --mocha-test-html-error-color: #000;
69
+ --mocha-box-shadow-color: #eee;
70
+ --mocha-box-bottom-color: #ddd;
71
+ --mocha-test-replay-color: #000;
72
+ --mocha-test-replay-bg-color: #eee;
73
+ --mocha-stats-color: #888;
74
+ --mocha-stats-em-color: #000;
75
+ --mocha-stats-hover-color: #eee;
76
+ --mocha-error-color: #c00;
77
+
78
+ --mocha-code-comment: #ddd;
79
+ --mocha-code-init: #2f6fad;
80
+ --mocha-code-string: #5890ad;
81
+ --mocha-code-keyword: #8a6343;
82
+ --mocha-code-number: #2f6fad;
83
+ }
84
+
85
+ @media (prefers-color-scheme: dark) {
86
+ :root {
87
+ --mocha-color: #fff;
88
+ --mocha-bg-color: #222;
89
+ --mocha-pass-icon-color: #00d6b2;
90
+ --mocha-pass-color: #222;
91
+ --mocha-pass-shadow-color: rgba(255, 255, 255, .2);
92
+ --mocha-pass-mediump-color: #f1be67;
93
+ --mocha-pass-slow-color: #f49896;
94
+ --mocha-test-pending-color: #0b97c4;
95
+ --mocha-test-pending-icon-color: #0b97c4;
96
+ --mocha-test-fail-color: #f44;
97
+ --mocha-test-fail-icon-color: #f44;
98
+ --mocha-test-fail-pre-color: #fff;
99
+ --mocha-test-fail-pre-error-color: #f44;
100
+ --mocha-test-html-error-color: #fff;
101
+ --mocha-box-shadow-color: #444;
102
+ --mocha-box-bottom-color: #555;
103
+ --mocha-test-replay-color: #fff;
104
+ --mocha-test-replay-bg-color: #444;
105
+ --mocha-stats-color: #aaa;
106
+ --mocha-stats-em-color: #fff;
107
+ --mocha-stats-hover-color: #444;
108
+ --mocha-error-color: #f44;
109
+
110
+ --mocha-code-comment: #ddd;
111
+ --mocha-code-init: #9cc7f1;
112
+ --mocha-code-string: #80d4ff;
113
+ --mocha-code-keyword: #e3a470;
114
+ --mocha-code-number: #4ca7ff;
115
+ }
116
+ }
117
+ `
118
+ );
119
+
120
+ // src/vite/utils.ts
121
+ import fs from "node:fs/promises";
122
+ import url from "node:url";
123
+ import path from "node:path";
124
+ import logger from "@wdio/logger";
125
+ import { resolve } from "import-meta-resolve";
126
+ var log = logger("@wdio/browser-runner");
127
+ async function getTemplate(options, env, spec, p = process) {
128
+ const root = options.rootDir || process.cwd();
129
+ const rootFileUrl = url.pathToFileURL(root).href;
130
+ const isHeadless = options.headless || Boolean(process.env.CI);
131
+ const alias = options.viteConfig?.resolve?.alias || {};
132
+ const usesTailwindCSS = await hasFileByExtensions(path.join(root, "tailwind.config"));
133
+ if ("runner" in env.config) {
134
+ delete env.config.runner;
135
+ }
136
+ let sourceMapScript = "";
137
+ let sourceMapSetupCommand = "";
138
+ try {
139
+ const sourceMapSupportDir = await resolve("source-map-support", import.meta.url);
140
+ sourceMapScript = /*html*/
141
+ `<script src="/@fs/${url.fileURLToPath(path.dirname(sourceMapSupportDir))}/browser-source-map-support.js"></script>`;
142
+ sourceMapSetupCommand = "sourceMapSupport.install()";
143
+ } catch (err) {
144
+ log.error(`Failed to setup source-map-support: ${err.message}`);
145
+ }
146
+ const mochaPath = await resolve("mocha", `${rootFileUrl}/node_modules`);
147
+ const mochaCSSHref = path.join(url.fileURLToPath(path.dirname(mochaPath)), "mocha.css");
148
+ const mochaJSSrc = path.join(url.fileURLToPath(path.dirname(mochaPath)), "mocha.js");
149
+ return (
150
+ /* html */
151
+ `
152
+ <!doctype html>
153
+ <html>
154
+ <head>
155
+ <title>WebdriverIO Browser Test</title>
156
+ <link rel="icon" type="image/x-icon" href="https://webdriver.io/img/favicon.png">
157
+ ${usesTailwindCSS ? (
158
+ /*html*/
159
+ `<link rel="stylesheet" href="/node_modules/tailwindcss/tailwind.css">`
160
+ ) : ""}
161
+ <script type="module">
162
+ const alias = ${JSON.stringify(alias)}
163
+ window.__wdioMockCache__ = new Map()
164
+ window.WDIO_EVENT_NAME = '${WDIO_EVENT_NAME}'
165
+ window.wdioImport = function (modName, mod) {
166
+ /**
167
+ * attempt to resolve direct import
168
+ */
169
+ if (window.__wdioMockCache__.get(modName)) {
170
+ return window.__wdioMockCache__.get(modName)
171
+ }
172
+
173
+ /**
174
+ * if above fails, check if we have an alias for it
175
+ */
176
+ for (const [aliasName, aliasPath] of Object.entries(alias)) {
177
+ if (modName.slice(0, aliasName.length) === aliasName) {
178
+ modName = modName.replace(aliasName, aliasPath)
179
+ }
180
+ }
181
+ if (window.__wdioMockCache__.get(modName)) {
182
+ return window.__wdioMockCache__.get(modName)
183
+ }
184
+ return mod
185
+ }
186
+ </script>
187
+ <link rel="stylesheet" href="/@fs/${mochaCSSHref}">
188
+ <script type="module" src="/@fs/${mochaJSSrc}"></script>
189
+ ${sourceMapScript}
190
+ <script type="module">
191
+ ${sourceMapSetupCommand}
192
+
193
+ /**
194
+ * Inject environment variables
195
+ */
196
+ window.__wdioEnv__ = ${JSON.stringify(env)}
197
+ window.__wdioSpec__ = '${spec}'
198
+ window.__wdioEvents__ = []
199
+ /**
200
+ * listen to window errors during bootstrap phase
201
+ */
202
+ window.__wdioErrors__ = []
203
+ addEventListener('error', (ev) => window.__wdioErrors__.push({
204
+ filename: ev.filename,
205
+ message: ev.message,
206
+ error: ev.error.stack
207
+ }))
208
+ /**
209
+ * mock process
210
+ */
211
+ window.process = window.process || {
212
+ platform: 'browser',
213
+ env: ${JSON.stringify(p.env)},
214
+ stdout: {},
215
+ stderr: {},
216
+ cwd: () => ${JSON.stringify(p.cwd())},
217
+ }
218
+ </script>
219
+ <script type="module" src="@wdio/browser-runner/setup"></script>
220
+ <style>
221
+ ${MOCHA_VARIABELS}
222
+
223
+ body {
224
+ width: calc(100% - 500px);
225
+ padding: 0;
226
+ margin: 0;
227
+ }
228
+ </style>
229
+ </head>
230
+ <body>
231
+ <mocha-framework spec="${spec}" ${isHeadless ? 'style="display: none"' : ""}></mocha-framework>
232
+ </body>
233
+ </html>`
234
+ );
235
+ }
236
+ async function userfriendlyImport(preset, pkg) {
237
+ if (!pkg) {
238
+ return {};
239
+ }
240
+ try {
241
+ return await import(pkg);
242
+ } catch (err) {
243
+ throw new Error(
244
+ `Couldn't load preset "${preset}" given important dependency ("${pkg}") is not installed.
245
+ Please run:
246
+
247
+ npm install ${pkg}
248
+ or
249
+ yarn add --dev ${pkg}`
250
+ );
251
+ }
252
+ }
253
+ function normalizeId(id, base) {
254
+ if (base && id.startsWith(base)) {
255
+ id = `/${id.slice(base.length)}`;
256
+ }
257
+ return id.replace(/^\/@id\/__x00__/, "\0").replace(/^\/@id\//, "").replace(/^__vite-browser-external:/, "").replace(/^node:/, "").replace(/[?&]v=\w+/, "?").replace(/\?$/, "");
258
+ }
259
+ async function getFilesFromDirectory(dir) {
260
+ const isExisting = await fs.access(dir).then(() => true, () => false);
261
+ if (!isExisting) {
262
+ return [];
263
+ }
264
+ let files = await fs.readdir(dir);
265
+ files = (await Promise.all(files.map(async (file) => {
266
+ const filePath = path.join(dir, file);
267
+ const stats = await fs.stat(filePath);
268
+ if (stats.isDirectory()) {
269
+ return getFilesFromDirectory(filePath);
270
+ } else if (stats.isFile()) {
271
+ return filePath;
272
+ }
273
+ }))).filter(Boolean);
274
+ return files.reduce((all, folderContents) => all.concat(folderContents), []);
275
+ }
276
+ var mockedModulesList;
277
+ async function getManualMocks(automockDir) {
278
+ if (!mockedModulesList) {
279
+ mockedModulesList = (await getFilesFromDirectory(automockDir)).map((filePath) => [
280
+ filePath,
281
+ filePath.slice(automockDir.length + 1).slice(0, -path.extname(filePath).length)
282
+ ]);
283
+ }
284
+ return mockedModulesList;
285
+ }
286
+ var EXTENSION = [".js", ".ts", ".mjs", ".cjs", ".mts"];
287
+ async function hasFileByExtensions(p, extensions = EXTENSION) {
288
+ return (await Promise.all([
289
+ fs.access(p).then(() => p, () => void 0),
290
+ ...extensions.map((ext) => fs.access(p + ext).then(() => p + ext, () => void 0))
291
+ ])).filter(Boolean)[0];
292
+ }
293
+ function hasDir(p) {
294
+ return fs.stat(p).then((s) => s.isDirectory(), () => false);
295
+ }
296
+ function getErrorTemplate(filename, error) {
297
+ return (
298
+ /*html*/
299
+ `
300
+ <pre>${error.stack}</pre>
301
+ <script type="module">
302
+ window.__wdioErrors__ = [{
303
+ filename: "${filename}",
304
+ message: \`${error.message}\`
305
+ }]
306
+ </script>
307
+ `
308
+ );
309
+ }
310
+
311
+ // src/vite/plugins/testrunner.ts
312
+ var log2 = logger2("@wdio/browser-runner:plugin");
313
+ var __dirname = url2.fileURLToPath(new URL(".", import.meta.url));
314
+ var commands = deepmerge(
315
+ WebDriverProtocol,
316
+ MJsonWProtocol,
317
+ AppiumProtocol,
318
+ ChromiumProtocol,
319
+ SauceLabsProtocol,
320
+ SeleniumProtocol,
321
+ GeckoProtocol
322
+ );
323
+ var protocolCommandList = Object.values(commands).map(
324
+ (endpoint) => Object.values(endpoint).map(
325
+ ({ command }) => command
326
+ )
327
+ ).flat();
328
+ var WDIO_PACKAGES = ["webdriverio", "expect-webdriverio"];
329
+ var virtualModuleId = "virtual:wdio";
330
+ var resolvedVirtualModuleId = "\0" + virtualModuleId;
331
+ var MODULES_TO_MOCK = [
332
+ "import-meta-resolve",
333
+ "puppeteer-core",
334
+ "archiver",
335
+ "glob",
336
+ "ws",
337
+ "decamelize",
338
+ "geckodriver",
339
+ "safaridriver",
340
+ "edgedriver",
341
+ "@puppeteer/browsers",
342
+ "locate-app",
343
+ "wait-port",
344
+ "lodash.isequal",
345
+ "@wdio/repl"
346
+ ];
347
+ var POLYFILLS = [
348
+ ...builtinModules,
349
+ ...builtinModules.map((m) => `node:${m}`)
350
+ ];
351
+ function testrunner(options) {
352
+ const browserModules = path2.resolve(__dirname, "browser");
353
+ const automationProtocolPath = `/@fs${url2.pathToFileURL(path2.resolve(browserModules, "driver.js")).pathname}`;
354
+ const mockModulePath = path2.resolve(browserModules, "mock.js");
355
+ const setupModulePath = path2.resolve(browserModules, "setup.js");
356
+ const spyModulePath = path2.resolve(browserModules, "spy.js");
357
+ const wdioExpectModulePath = path2.resolve(browserModules, "expect.js");
358
+ return [{
359
+ name: "wdio:testrunner",
360
+ enforce: "pre",
361
+ resolveId: async (id) => {
362
+ if (id === virtualModuleId) {
363
+ return resolvedVirtualModuleId;
364
+ }
365
+ if (POLYFILLS.includes(id)) {
366
+ return polyfillPath(normalizeId(id.replace("/promises", "")));
367
+ }
368
+ if (id === "@wdio/browser-runner") {
369
+ return spyModulePath;
370
+ }
371
+ if (id.endsWith("@wdio/browser-runner/setup")) {
372
+ return setupModulePath;
373
+ }
374
+ if (id === "expect-webdriverio") {
375
+ return wdioExpectModulePath;
376
+ }
377
+ if (id === "@wdio/logger") {
378
+ const newId = url2.fileURLToPath(await resolve2(id, import.meta.url));
379
+ return path2.resolve(path2.dirname(newId), "browser.js");
380
+ }
381
+ if (id.startsWith("@wdio") || WDIO_PACKAGES.includes(id)) {
382
+ return url2.fileURLToPath(await resolve2(id, import.meta.url));
383
+ }
384
+ if (MODULES_TO_MOCK.includes(id)) {
385
+ return mockModulePath;
386
+ }
387
+ },
388
+ load(id) {
389
+ if (id === resolvedVirtualModuleId) {
390
+ return (
391
+ /*js*/
392
+ `
393
+ import { fn } from '@wdio/browser-runner'
394
+ export const commands = ${JSON.stringify(protocolCommandList)}
395
+ export const automationProtocolPath = ${JSON.stringify(automationProtocolPath)}
396
+ export const wrappedFn = (...args) => fn()(...args)
397
+ `
398
+ );
399
+ }
400
+ },
401
+ transform(code, id) {
402
+ if (id.includes(".vite/deps/expect.js")) {
403
+ return {
404
+ code: code.replace(
405
+ "var fs = _interopRequireWildcard(require_graceful_fs());",
406
+ "var fs = {};"
407
+ ).replace(
408
+ "var expect_default = require_build11();",
409
+ "var expect_default = require_build11();\nwindow.expect = expect_default.default;"
410
+ ).replace(
411
+ "process.stdout.isTTY",
412
+ "false"
413
+ )
414
+ };
415
+ }
416
+ return { code };
417
+ },
418
+ configureServer(server) {
419
+ return () => {
420
+ server.middlewares.use(async (req, res, next) => {
421
+ log2.info(`Received request for: ${req.originalUrl}`);
422
+ if (!req.originalUrl || req.url?.endsWith(".map") || req.url?.endsWith(".wasm")) {
423
+ return next();
424
+ }
425
+ const cookies = (req.headers.cookie && req.headers.cookie.split(";") || []).map((c) => c.trim());
426
+ const urlParsed = url2.parse(req.originalUrl);
427
+ const urlParamString = new URLSearchParams(urlParsed.query || "");
428
+ const cid = urlParamString.get("cid") || cookies.find((c) => c.includes("WDIO_CID"))?.split("=").pop();
429
+ const spec = urlParamString.get("spec") || cookies.find((c) => c.includes("WDIO_SPEC"))?.split("=").pop();
430
+ if (!cid || !SESSIONS.has(cid)) {
431
+ log2.error(`No environment found for ${cid || "non determined environment"}`);
432
+ return next();
433
+ }
434
+ if (!spec) {
435
+ log2.error("No spec file was defined to run for this environment");
436
+ return next();
437
+ }
438
+ const env = SESSIONS.get(cid);
439
+ try {
440
+ const template = await getTemplate(options, env, spec);
441
+ log2.debug(`Render template for ${req.originalUrl}`);
442
+ res.end(await server.transformIndexHtml(`${req.originalUrl}`, template));
443
+ } catch (err) {
444
+ const template = getErrorTemplate(req.originalUrl, err);
445
+ log2.error(`Failed to render template: ${err.message}`);
446
+ res.end(await server.transformIndexHtml(`${req.originalUrl}`, template));
447
+ }
448
+ return next();
449
+ });
450
+ };
451
+ }
452
+ }, {
453
+ name: "modern-node-polyfills",
454
+ async resolveId(id, _, ctx) {
455
+ if (ctx.ssr || !builtinModules.includes(id)) {
456
+ return;
457
+ }
458
+ id = normalizeId(id);
459
+ return { id: await polyfillPath(id), moduleSideEffects: false };
460
+ }
461
+ }];
462
+ }
463
+
464
+ // src/vite/plugins/mockHoisting.ts
465
+ import os from "node:os";
466
+ import url3 from "node:url";
467
+ import path3 from "node:path";
468
+ import fs2 from "node:fs/promises";
469
+ import logger3 from "@wdio/logger";
470
+ import { parse, print, visit, types } from "recast";
471
+ import typescriptParser from "recast/parsers/typescript.js";
472
+ var log3 = logger3("@wdio/browser-runner:mockHoisting");
473
+ var INTERNALS_TO_IGNORE = [
474
+ "@vite/client",
475
+ "vite/dist/client",
476
+ "/webdriverio/build/",
477
+ "/@wdio/",
478
+ "/webdriverio/node_modules/",
479
+ "virtual:wdio",
480
+ "?html-proxy",
481
+ "/__fixtures__/",
482
+ "/__mocks__/",
483
+ "/.vite/deps/@testing-library_vue.js"
484
+ ];
485
+ var b = types.builders;
486
+ var MOCK_PREFIX = "/@mock";
487
+ function mockHoisting(mockHandler) {
488
+ let spec = null;
489
+ let isTestDependency = false;
490
+ const sessionMocks = /* @__PURE__ */ new Set();
491
+ const importMap = /* @__PURE__ */ new Map();
492
+ return [{
493
+ name: "wdio:mockHoisting:pre",
494
+ enforce: "pre",
495
+ resolveId: mockHandler.resolveId.bind(mockHandler),
496
+ load: async function(id) {
497
+ if (id.startsWith(MOCK_PREFIX)) {
498
+ try {
499
+ const orig = await fs2.readFile(id.slice(MOCK_PREFIX.length + (os.platform() === "win32" ? 1 : 0)));
500
+ return orig.toString();
501
+ } catch (err) {
502
+ log3.error(`Failed to read file (${id}) for mocking: ${err.message}`);
503
+ return "";
31
504
  }
32
- this.#options = options;
33
- this.#config = _config;
34
- this.#communicator = new ServerWorkerCommunicator(this.#config);
35
- this.#coverageOptions = options.coverage || {};
36
- this.#reportsDirectory = this.#coverageOptions.reportsDirectory || path.join(this.#config.rootDir, DEFAULT_REPORTS_DIRECTORY);
505
+ }
506
+ }
507
+ }, {
508
+ name: "wdio:mockHoisting",
509
+ enforce: "post",
510
+ transform(code, id) {
511
+ const isSpecFile = id === spec;
512
+ if (isSpecFile) {
513
+ isTestDependency = true;
514
+ }
515
+ if (
516
+ // where loading was inititated through the test file
517
+ !isTestDependency && // however when files are inlined they will be loaded when parsing file under test
518
+ // in this case we want to transform them, but make sure we exclude these paths
519
+ (id.includes("/node_modules/") || id.includes("/?cid=") || id.startsWith("virtual:")) || // are not Vite or WebdriverIO internals
520
+ INTERNALS_TO_IGNORE.find((f) => id.includes(f)) || // when the spec file is actually mocking any dependencies
521
+ !isSpecFile && sessionMocks.size === 0
522
+ ) {
523
+ return { code };
524
+ }
525
+ let ast;
526
+ const start = Date.now();
527
+ try {
528
+ ast = parse(code, {
529
+ parser: typescriptParser,
530
+ sourceFileName: id,
531
+ sourceRoot: path3.dirname(id)
532
+ });
533
+ log3.trace(`Parsed file for mocking: ${id} in ${Date.now() - start}ms`);
534
+ } catch (err) {
535
+ return { code };
536
+ }
537
+ let importIndex = 0;
538
+ let mockFunctionName;
539
+ let unmockFunctionName;
540
+ const mockCalls = [];
541
+ visit(ast, {
37
542
  /**
38
- * transform mochaOpts require params so we can use it in the browser
543
+ * find function name for mock and unmock calls
39
544
  */
40
- if (this.#config.mochaOpts) {
41
- this.#config.mochaOpts.require = (this.#config.mochaOpts.require || [])
42
- .map((r) => path.join(this.#config.rootDir || process.cwd(), r))
43
- .map((r) => url.pathToFileURL(r).pathname);
44
- }
45
- }
46
- /**
47
- * for testing purposes
48
- */
49
- _servers = this.#servers;
50
- /**
51
- * nothing to initialize when running locally
52
- */
53
- async initialize() {
54
- log.info('Initiate browser environment');
55
- if (typeof this.#coverageOptions.clean === 'undefined' || this.#coverageOptions.clean) {
56
- const reportsDirectoryExist = await fs.access(this.#reportsDirectory)
57
- .then(() => true, () => false);
58
- if (reportsDirectoryExist) {
59
- await fs.rm(this.#reportsDirectory, { recursive: true });
545
+ visitImportDeclaration: function(path10) {
546
+ const dec = path10.value;
547
+ const source = dec.source.value;
548
+ if (!dec.specifiers || dec.specifiers.length === 0 || source !== "@wdio/browser-runner") {
549
+ return this.traverse(path10);
550
+ }
551
+ const mockSpecifier = dec.specifiers.filter((s) => s.type === types.namedTypes.ImportSpecifier.toString()).find((s) => s.imported.name === "mock");
552
+ if (mockSpecifier && mockSpecifier.local) {
553
+ mockFunctionName = mockSpecifier.local.name;
554
+ }
555
+ const unmockSpecifier = dec.specifiers.filter((s) => s.type === types.namedTypes.ImportSpecifier.toString()).find((s) => s.imported.name === "unmock");
556
+ if (unmockSpecifier && unmockSpecifier.local) {
557
+ unmockFunctionName = unmockSpecifier.local.name;
558
+ }
559
+ mockCalls.push(dec);
560
+ path10.prune();
561
+ return this.traverse(path10);
562
+ },
563
+ /**
564
+ * detect which modules are supposed to be mocked
565
+ */
566
+ ...isSpecFile ? {
567
+ visitExpressionStatement: function(path10) {
568
+ const exp = path10.value;
569
+ if (exp.expression.type !== types.namedTypes.CallExpression.toString()) {
570
+ return this.traverse(path10);
60
571
  }
61
- }
572
+ const callExp = exp.expression;
573
+ const isUnmockCall = unmockFunctionName && callExp.callee.name === unmockFunctionName;
574
+ const isMockCall = mockFunctionName && callExp.callee.name === mockFunctionName;
575
+ if (!isMockCall && !isUnmockCall) {
576
+ return this.traverse(path10);
577
+ }
578
+ if (isUnmockCall && callExp.arguments[0] && typeof callExp.arguments[0].value === "string") {
579
+ mockHandler.unmock(callExp.arguments[0].value);
580
+ } else if (isMockCall) {
581
+ const mockCall = exp.expression;
582
+ if (mockCall.arguments.length === 1) {
583
+ mockHandler.manualMocks.push(mockCall.arguments[0].value);
584
+ } else {
585
+ if (exp.expression.arguments.length) {
586
+ sessionMocks.add(exp.expression.arguments[0].value);
587
+ }
588
+ mockCalls.push(exp);
589
+ }
590
+ }
591
+ path10.prune();
592
+ this.traverse(path10);
593
+ }
594
+ } : {}
595
+ });
596
+ visit(ast, {
62
597
  /**
63
- * make adjustments based on detected frontend frameworks
598
+ * rewrite import statements
64
599
  */
65
- try {
66
- this.#viteOptimizations = await updateViteConfig(this.#options, this.#config);
600
+ visitImportDeclaration: function(nodePath) {
601
+ const dec = nodePath.value;
602
+ const source = dec.source.value;
603
+ if (!dec.specifiers || dec.specifiers.length === 0) {
604
+ return this.traverse(nodePath);
605
+ }
606
+ const newImportIdentifier = `__wdio_import${importIndex++}`;
607
+ const isMockedModule = Boolean(
608
+ // matches if a dependency is mocked
609
+ sessionMocks.has(source) || // matches if a relative file is mocked
610
+ source.startsWith(".") && [...sessionMocks.values()].find((m) => {
611
+ const fileImportPath = path3.resolve(path3.dirname(id), source);
612
+ const fileImportPathSliced = fileImportPath.slice(0, path3.extname(fileImportPath).length * -1);
613
+ const testMockPath = path3.resolve(path3.dirname(spec || "/"), m);
614
+ const testMockPathSliced = testMockPath.slice(0, path3.extname(testMockPath).length * -1);
615
+ return fileImportPathSliced === testMockPathSliced && fileImportPathSliced.length > 0;
616
+ })
617
+ );
618
+ if (isMockedModule && isSpecFile) {
619
+ importMap.set(source, newImportIdentifier);
620
+ }
621
+ if (!isSpecFile || isMockedModule) {
622
+ const newNode = b.importDeclaration(
623
+ [b.importNamespaceSpecifier(b.identifier(newImportIdentifier))],
624
+ b.literal(source)
625
+ );
626
+ mockCalls.unshift(newNode);
627
+ }
628
+ const mockExtensionLengh = path3.extname(source).length * -1 || Infinity;
629
+ const wdioImportModuleIdentifier = source.startsWith(".") || source.startsWith("/") ? url3.pathToFileURL(path3.resolve(path3.dirname(id), source).slice(0, mockExtensionLengh)).pathname : source;
630
+ const isNamespaceImport = dec.specifiers.length === 1 && dec.specifiers[0].type === types.namedTypes.ImportNamespaceSpecifier.toString();
631
+ const mockImport = isSpecFile && !isMockedModule ? b.variableDeclaration("const", [
632
+ b.variableDeclarator(
633
+ isNamespaceImport ? dec.specifiers[0].local : b.objectPattern(dec.specifiers.map((s) => {
634
+ if (s.type === types.namedTypes.ImportDefaultSpecifier.toString()) {
635
+ return b.property("init", b.identifier("default"), b.identifier(s.local.name));
636
+ }
637
+ return b.property("init", b.identifier(s.imported.name), b.identifier(s.local.name));
638
+ })),
639
+ b.awaitExpression(b.importExpression(b.literal(source)))
640
+ )
641
+ ]) : b.variableDeclaration("const", [
642
+ b.variableDeclarator(
643
+ dec.specifiers.length === 1 && dec.specifiers[0].type === types.namedTypes.ImportNamespaceSpecifier.toString() ? b.identifier(dec.specifiers[0].local.name) : b.objectPattern(dec.specifiers.map((s) => {
644
+ if (s.type === types.namedTypes.ImportDefaultSpecifier.toString()) {
645
+ return b.property("init", b.identifier("default"), b.identifier(s.local.name));
646
+ }
647
+ return b.property("init", b.identifier(s.imported.name), b.identifier(s.local.name));
648
+ })),
649
+ b.callExpression(
650
+ b.identifier("wdioImport"),
651
+ [
652
+ b.literal(wdioImportModuleIdentifier),
653
+ b.identifier(newImportIdentifier)
654
+ ]
655
+ )
656
+ )
657
+ ]);
658
+ nodePath.replace(mockImport);
659
+ this.traverse(nodePath);
67
660
  }
68
- catch (err) {
69
- log.error(`Failed to optimize Vite config: ${err.stack}`);
661
+ });
662
+ ast.program.body.unshift(...mockCalls.map((mc) => {
663
+ const exp = mc;
664
+ if (exp.expression && exp.expression.type === types.namedTypes.CallExpression.toString()) {
665
+ const mockCallExpression = exp.expression;
666
+ const mockedModule = mockCallExpression.arguments[0].value;
667
+ const mockFactory = mockCallExpression.arguments[1];
668
+ if (importMap.has(mockedModule)) {
669
+ mockCallExpression.arguments.push(b.identifier(importMap.get(mockedModule)));
670
+ } else if (mockFactory.params.length > 0) {
671
+ const newImportIdentifier = `__wdio_import${importIndex++}`;
672
+ ast.program.body.unshift(b.importDeclaration(
673
+ [b.importNamespaceSpecifier(b.identifier(newImportIdentifier))],
674
+ b.literal(mockedModule)
675
+ ));
676
+ mockCallExpression.arguments.push(b.identifier(newImportIdentifier));
677
+ }
678
+ return b.expressionStatement(b.awaitExpression(mockCallExpression));
70
679
  }
71
- await super.initialize();
680
+ return mc;
681
+ }));
682
+ try {
683
+ const newCode = print(ast, { sourceMapName: id });
684
+ log3.trace(`Transformed file for mocking: ${id} in ${Date.now() - start}ms`);
685
+ return newCode;
686
+ } catch (err) {
687
+ log3.trace(`Failed to transformed file (${id}) for mocking: ${err.stack}`);
688
+ return { code };
689
+ }
690
+ },
691
+ configureServer(server) {
692
+ return () => {
693
+ server.middlewares.use("/", async (req, res, next) => {
694
+ if (!req.originalUrl) {
695
+ return next();
696
+ }
697
+ const urlParsed = url3.parse(req.originalUrl);
698
+ const urlParamString = new URLSearchParams(urlParsed.query || "");
699
+ const specParam = urlParamString.get("spec");
700
+ if (specParam) {
701
+ mockHandler.resetMocks();
702
+ isTestDependency = false;
703
+ spec = os.platform() === "win32" ? specParam.slice(1) : specParam;
704
+ }
705
+ return next();
706
+ });
707
+ };
72
708
  }
73
- async run(runArgs) {
74
- runArgs.caps = makeHeadless(this.options, runArgs.caps);
75
- runArgs.caps = adjustWindowInWatchMode(this.#config, runArgs.caps);
76
- const server = new ViteServer(this.#options, this.#config, this.#viteOptimizations);
77
- this.#servers.add(server);
78
- try {
79
- await server.start();
80
- runArgs.args.baseUrl = `http://localhost:${server.config.server?.port}`;
81
- }
82
- catch (err) {
83
- throw new Error(`Vite server failed to start: ${err.stack}`);
84
- }
85
- if (!runArgs.args.baseUrl && runArgs.command === 'run') {
86
- runArgs.args.baseUrl = this._config.baseUrl;
709
+ }];
710
+ }
711
+
712
+ // src/vite/plugins/worker.ts
713
+ function workerPlugin(onSocketEvent) {
714
+ return {
715
+ name: "wdio:worker",
716
+ configureServer({ ws }) {
717
+ ws.on(WDIO_EVENT_NAME, onSocketEvent);
718
+ }
719
+ };
720
+ }
721
+
722
+ // src/vite/mock.ts
723
+ import path4 from "node:path";
724
+ var FIXTURE_PREFIX = "/@fixture/";
725
+ var MockHandler = class {
726
+ #automock;
727
+ #automockDir;
728
+ #manualMocksList;
729
+ #mocks = /* @__PURE__ */ new Map();
730
+ #unmocked = [];
731
+ manualMocks = [];
732
+ constructor(options, config) {
733
+ this.#automock = typeof options.automock === "boolean" ? options.automock : DEFAULT_AUTOMOCK;
734
+ this.#automockDir = path4.resolve(config.rootDir, options.automockDir || DEFAULT_MOCK_DIRECTORY);
735
+ this.#manualMocksList = getManualMocks(this.#automockDir);
736
+ }
737
+ get mocks() {
738
+ return this.#mocks;
739
+ }
740
+ unmock(moduleName) {
741
+ this.#unmocked.push(moduleName);
742
+ }
743
+ async resolveId(id) {
744
+ const manualMocksList = await this.#manualMocksList;
745
+ const mockPath = manualMocksList.find((m) => (
746
+ // e.g. someModule
747
+ id === m[1].replace(path4.sep, "/") || // e.g. @some/module
748
+ id.slice(1) === m[1].replace(path4.sep, "/")
749
+ ));
750
+ if ((this.manualMocks.includes(id) || this.#automock) && mockPath && !this.#unmocked.includes(id)) {
751
+ return mockPath[0];
752
+ }
753
+ if (id.startsWith(FIXTURE_PREFIX)) {
754
+ return path4.resolve(this.#automockDir, id.slice(FIXTURE_PREFIX.length));
755
+ }
756
+ }
757
+ /**
758
+ * reset manual mocks between tests
759
+ */
760
+ resetMocks() {
761
+ this.manualMocks = [];
762
+ this.#unmocked = [];
763
+ }
764
+ };
765
+
766
+ // src/vite/constants.ts
767
+ import topLevelAwait from "vite-plugin-top-level-await";
768
+ import { esbuildCommonjs } from "@originjs/vite-plugin-commonjs";
769
+
770
+ // src/vite/plugins/esbuild.ts
771
+ import fs3 from "node:fs/promises";
772
+ function codeFrameFix() {
773
+ return {
774
+ name: "wdio:codeFrameFix",
775
+ setup(build) {
776
+ build.onLoad(
777
+ { filter: /@babel\/code-frame/, namespace: "file" },
778
+ /**
779
+ * mock @babel/code-frame as it fails in Safari due
780
+ * to usage of chalk
781
+ */
782
+ async ({ path: id }) => {
783
+ const code = await fs3.readFile(id).then(
784
+ (buf) => buf.toString(),
785
+ () => void 0
786
+ );
787
+ if (!code) {
788
+ return;
789
+ }
790
+ return {
791
+ contents: code.replace(
792
+ 'require("@babel/highlight");',
793
+ /*js*/
794
+ `{
795
+ shouldHighlight: false,
796
+ reset: () => {}
797
+ }`
798
+ )
799
+ };
87
800
  }
88
- const worker = await super.run(runArgs);
89
- this.#communicator.register(server, worker);
90
- return worker;
801
+ );
91
802
  }
803
+ };
804
+ }
805
+
806
+ // src/vite/constants.ts
807
+ var DEFAULT_PROTOCOL = "http";
808
+ var DEFAULT_HOSTNAME = "localhost";
809
+ var DEFAULT_HOST = `${DEFAULT_PROTOCOL}://${DEFAULT_HOSTNAME}`;
810
+ var PRESET_DEPENDENCIES = {
811
+ react: ["@vitejs/plugin-react", "default", {
812
+ babel: {
813
+ assumptions: {
814
+ setPublicClassFields: true
815
+ },
816
+ parserOpts: {
817
+ plugins: ["decorators-legacy", "classProperties"]
818
+ }
819
+ }
820
+ }],
821
+ preact: ["@preact/preset-vite", "default", void 0],
822
+ vue: ["@vitejs/plugin-vue", "default", void 0],
823
+ svelte: ["@sveltejs/vite-plugin-svelte", "svelte", void 0],
824
+ solid: ["vite-plugin-solid", "default", void 0],
825
+ stencil: void 0,
826
+ lit: void 0
827
+ };
828
+ var DEFAULT_VITE_CONFIG = {
829
+ configFile: false,
830
+ server: { host: DEFAULT_HOSTNAME },
831
+ logLevel: "silent",
832
+ plugins: [topLevelAwait()],
833
+ build: {
834
+ sourcemap: "inline",
835
+ commonjsOptions: {
836
+ include: [/node_modules/]
837
+ }
838
+ },
839
+ optimizeDeps: {
92
840
  /**
93
- * shutdown vite server
94
- *
95
- * @return {Promise} resolves when vite server has been shutdown
841
+ * the following deps are CJS packages and need to be optimized (compiled to ESM) by Vite
96
842
  */
97
- async shutdown() {
98
- await super.shutdown();
99
- for (const server of this.#servers) {
100
- await server.close();
843
+ include: [
844
+ "expect",
845
+ "minimatch",
846
+ "css-shorthand-properties",
847
+ "lodash.merge",
848
+ "lodash.zip",
849
+ "ws",
850
+ "lodash.clonedeep",
851
+ "lodash.pickby",
852
+ "lodash.flattendeep",
853
+ "aria-query",
854
+ "grapheme-splitter",
855
+ "css-value",
856
+ "rgb2hex",
857
+ "p-iteration",
858
+ "deepmerge-ts",
859
+ "jest-util",
860
+ "jest-matcher-utils",
861
+ "split2"
862
+ ],
863
+ esbuildOptions: {
864
+ logLevel: "silent",
865
+ // Node.js global to browser globalThis
866
+ define: {
867
+ global: "globalThis"
868
+ },
869
+ // Enable esbuild polyfill plugins
870
+ plugins: [
871
+ esbuildCommonjs(["@testing-library/vue"]),
872
+ codeFrameFix()
873
+ ]
874
+ }
875
+ }
876
+ };
877
+
878
+ // src/vite/server.ts
879
+ var log4 = logger4("@wdio/browser-runner:ViteServer");
880
+ var DEFAULT_CONFIG_ENV = {
881
+ command: "serve",
882
+ mode: process.env.NODE_ENV === "production" ? "production" : "development"
883
+ };
884
+ var ViteServer = class extends EventEmitter {
885
+ #options;
886
+ #config;
887
+ #viteConfig;
888
+ #server;
889
+ #mockHandler;
890
+ #socketEventHandler = [];
891
+ get config() {
892
+ return this.#viteConfig;
893
+ }
894
+ constructor(options, config, optimizations) {
895
+ super();
896
+ this.#options = options;
897
+ this.#config = config;
898
+ this.#mockHandler = new MockHandler(options, config);
899
+ const root = options.rootDir || config.rootDir || process.cwd();
900
+ this.#viteConfig = deepmerge2(DEFAULT_VITE_CONFIG, optimizations, {
901
+ root,
902
+ plugins: [
903
+ testrunner(options),
904
+ mockHoisting(this.#mockHandler),
905
+ workerPlugin((payload, client) => this.#socketEventHandler.forEach(
906
+ (handler) => handler(payload, client)
907
+ ))
908
+ ]
909
+ });
910
+ if (options.coverage && options.coverage.enabled) {
911
+ log4.info("Capturing test coverage enabled");
912
+ const plugin = istanbulPlugin;
913
+ this.#viteConfig.plugins?.push(plugin({
914
+ cwd: config.rootDir,
915
+ include: DEFAULT_INCLUDE,
916
+ extension: DEFAULT_FILE_EXTENSIONS,
917
+ forceBuildInstrument: true,
918
+ ...options.coverage
919
+ }));
920
+ }
921
+ }
922
+ onBrowserEvent(handler) {
923
+ this.#socketEventHandler.push(handler);
924
+ }
925
+ async start() {
926
+ const vitePort = await getPort();
927
+ this.#viteConfig = deepmerge2(this.#viteConfig, {
928
+ server: {
929
+ ...this.#viteConfig.server,
930
+ port: vitePort
931
+ }
932
+ });
933
+ if (this.#options.preset) {
934
+ const [pkg, importProp, opts] = PRESET_DEPENDENCIES[this.#options.preset] || [];
935
+ const plugin = (await userfriendlyImport(this.#options.preset, pkg))[importProp || "default"];
936
+ if (plugin) {
937
+ this.#viteConfig.plugins.push(plugin(opts));
938
+ }
939
+ }
940
+ if (this.#options.viteConfig) {
941
+ const { plugins, ...configToMerge } = typeof this.#options.viteConfig === "string" ? (await import(path5.resolve(this.#config.rootDir || process.cwd(), this.#options.viteConfig))).default : typeof this.#options.viteConfig === "function" ? await this.#options.viteConfig(DEFAULT_CONFIG_ENV) : this.#options.viteConfig;
942
+ this.#viteConfig = deepmerge2(this.#viteConfig, configToMerge);
943
+ this.#viteConfig.plugins = [...plugins || [], ...this.#viteConfig.plugins];
944
+ }
945
+ this.#server = await createServer(this.#viteConfig);
946
+ await this.#server.listen();
947
+ log4.info(`Vite server started successfully on port ${vitePort}, root directory: ${this.#viteConfig.root}`);
948
+ }
949
+ async close() {
950
+ await this.#server?.close();
951
+ }
952
+ };
953
+
954
+ // src/vite/frameworks/nuxt.ts
955
+ import url4 from "node:url";
956
+ import path6 from "node:path";
957
+ import logger5 from "@wdio/logger";
958
+ import { resolve as resolve3 } from "import-meta-resolve";
959
+ var log5 = logger5("@wdio/browser-runner:NuxtOptimization");
960
+ async function isNuxtFramework(rootDir) {
961
+ return (await Promise.all([
962
+ hasFileByExtensions(path6.join(rootDir, "nuxt.config")),
963
+ hasDir(path6.join(rootDir, ".nuxt"))
964
+ ])).filter(Boolean).length > 0;
965
+ }
966
+ async function optimizeForNuxt(options, config) {
967
+ const Unimport = (await import("unimport/unplugin")).default;
968
+ const { scanDirExports, scanExports } = await import("unimport");
969
+ const { loadNuxtConfig } = await import("@nuxt/kit");
970
+ const rootDir = config.rootDir || process.cwd();
971
+ const nuxtOptions = await loadNuxtConfig({ rootDir });
972
+ if (nuxtOptions.imports?.autoImport === false) {
973
+ return {};
974
+ }
975
+ const nuxtDepPath = await resolve3("nuxt", import.meta.url);
976
+ const composablesDirs = [];
977
+ for (const layer of nuxtOptions._layers) {
978
+ composablesDirs.push(path6.resolve(layer.config.srcDir, "composables"));
979
+ composablesDirs.push(path6.resolve(layer.config.srcDir, "utils"));
980
+ for (const dir of layer.config.imports?.dirs ?? []) {
981
+ if (!dir) {
982
+ continue;
983
+ }
984
+ composablesDirs.push(path6.resolve(layer.config.srcDir, dir));
985
+ }
986
+ }
987
+ const composableImports = await Promise.all([
988
+ scanDirExports([
989
+ ...composablesDirs,
990
+ path6.resolve(path6.dirname(url4.fileURLToPath(nuxtDepPath)), "app", "components")
991
+ ]),
992
+ scanExports(path6.resolve(path6.dirname(url4.fileURLToPath(nuxtDepPath)), "app", "composables", "index.js"))
993
+ ]).then((scannedExports) => scannedExports.flat().map((ci) => {
994
+ if (ci.from.includes("/nuxt/dist/app/composables")) {
995
+ ci.from = "virtual:wdio";
996
+ ci.name = "wrappedFn";
997
+ }
998
+ return ci;
999
+ }));
1000
+ const viteConfig = {
1001
+ resolve: {
1002
+ alias: nuxtOptions.alias || {}
1003
+ },
1004
+ plugins: [Unimport.vite({
1005
+ presets: ["vue"],
1006
+ imports: composableImports
1007
+ })]
1008
+ };
1009
+ log5.info(`Optimized Vite config for Nuxt project at ${rootDir}`);
1010
+ return viteConfig;
1011
+ }
1012
+
1013
+ // src/vite/frameworks/tailwindcss.ts
1014
+ import url5 from "node:url";
1015
+ import path7 from "node:path";
1016
+ import { resolve as resolve4 } from "import-meta-resolve";
1017
+ function isUsingTailwindCSS(rootDir) {
1018
+ return Promise.all([
1019
+ hasFileByExtensions(path7.join(rootDir, "tailwind.config")),
1020
+ hasFileByExtensions(path7.join(rootDir, "postcss.config"))
1021
+ ]).then(([hasTailwindConfig, hasPostCSSConfig]) => {
1022
+ return hasTailwindConfig && !hasPostCSSConfig;
1023
+ });
1024
+ }
1025
+ async function optimizeForTailwindCSS(rootDir) {
1026
+ const viteConfig = {};
1027
+ const tailwindcssPath = await resolve4("tailwindcss", url5.pathToFileURL(path7.resolve(rootDir, "index.js")).href);
1028
+ const tailwindcss = (await import(tailwindcssPath)).default;
1029
+ viteConfig.css = {
1030
+ postcss: { plugins: [tailwindcss] }
1031
+ };
1032
+ return viteConfig;
1033
+ }
1034
+
1035
+ // src/vite/frameworks/stencil.ts
1036
+ import path8 from "node:path";
1037
+ import url6 from "node:url";
1038
+ import { findStaticImports, parseStaticImport } from "mlly";
1039
+ var __dirname2 = url6.fileURLToPath(new URL(".", import.meta.url));
1040
+ var STENCIL_IMPORT = "@stencil/core";
1041
+ async function isUsingStencilJS(rootDir, options) {
1042
+ return Boolean(options.preset === "stencil" || await hasFileByExtensions(path8.join(rootDir, "stencil.config")));
1043
+ }
1044
+ async function optimizeForStencil(rootDir) {
1045
+ const stencilConfig = await importStencilConfig(rootDir);
1046
+ const stencilPlugins = stencilConfig.config.plugins;
1047
+ const stencilOptimizations = {
1048
+ plugins: [await stencilVitePlugin(rootDir)],
1049
+ optimizeDeps: { include: [] }
1050
+ };
1051
+ if (stencilPlugins) {
1052
+ const esbuildPlugin = stencilPlugins.find((plugin) => plugin.name === "esbuild-plugin");
1053
+ if (esbuildPlugin) {
1054
+ stencilOptimizations.optimizeDeps?.include?.push(...esbuildPlugin.options.include);
1055
+ }
1056
+ }
1057
+ stencilOptimizations.optimizeDeps?.include?.push(
1058
+ "@wdio/browser-runner/stencil > @stencil/core/internal/testing/index.js"
1059
+ );
1060
+ return stencilOptimizations;
1061
+ }
1062
+ async function stencilVitePlugin(rootDir) {
1063
+ const { transpileSync, ts } = await import("@stencil/core/compiler/stencil.js");
1064
+ const stencilHelperPath = path8.resolve(__dirname2, "browser", "integrations", "stencil.js");
1065
+ return {
1066
+ name: "wdio-stencil",
1067
+ enforce: "pre",
1068
+ resolveId(source) {
1069
+ if (source === "@wdio/browser-runner/stencil") {
1070
+ return stencilHelperPath;
1071
+ }
1072
+ },
1073
+ transform: function(code, id) {
1074
+ const staticImports = findStaticImports(code);
1075
+ const stencilImports = staticImports.filter((imp) => imp.specifier === STENCIL_IMPORT).map((imp) => parseStaticImport(imp));
1076
+ const isStencilComponent = stencilImports.some((imp) => "Component" in (imp.namedImports || {}));
1077
+ if (!isStencilComponent) {
1078
+ const stencilHelperImport = staticImports.find((imp) => imp.specifier === "@wdio/browser-runner/stencil");
1079
+ if (stencilHelperImport) {
1080
+ const imports = parseStaticImport(stencilHelperImport);
1081
+ if ("render" in (imports.namedImports || {})) {
1082
+ code = injectStencilImports(code, stencilImports);
1083
+ }
101
1084
  }
102
- return this._generateCoverageReports();
1085
+ return { code };
1086
+ }
1087
+ const tsCompilerOptions = getCompilerOptions(ts, rootDir);
1088
+ const opts = {
1089
+ componentExport: "module",
1090
+ componentMetadata: "compilerstatic",
1091
+ coreImportPath: "@stencil/core/internal/client",
1092
+ currentDirectory: rootDir,
1093
+ file: path8.basename(id),
1094
+ module: "esm",
1095
+ sourceMap: "inline",
1096
+ style: "static",
1097
+ proxy: "defineproperty",
1098
+ styleImportData: "queryparams",
1099
+ transformAliasedImportPaths: process.env.__STENCIL_TRANSPILE_PATHS__ === "true",
1100
+ target: tsCompilerOptions?.target || "es2018",
1101
+ paths: tsCompilerOptions?.paths,
1102
+ baseUrl: tsCompilerOptions?.baseUrl
1103
+ };
1104
+ const transpiledCode = transpileSync(code, opts);
1105
+ let transformedCode = transpiledCode.code.replace(
1106
+ "static get style()",
1107
+ "static set style(_) {}\n static get style()"
1108
+ );
1109
+ transformedCode = injectStencilImports(transformedCode, stencilImports);
1110
+ findStaticImports(transformedCode).filter((imp) => imp.specifier.includes("&encapsulation=shadow")).forEach((imp) => {
1111
+ const cssPath = path8.resolve(path8.dirname(id), imp.specifier);
1112
+ transformedCode = transformedCode.replace(
1113
+ imp.code,
1114
+ `import ${imp.imports.trim()} from '/@fs/${cssPath}&inline';
1115
+ `
1116
+ );
1117
+ });
1118
+ return {
1119
+ ...transpiledCode,
1120
+ code: transformedCode,
1121
+ inputFilePath: id
1122
+ };
103
1123
  }
104
- async _generateCoverageReports() {
105
- const coverageMaps = this.#communicator.coverageMaps;
106
- /**
107
- * if no coverage was collected we don't need to do anything
108
- * and return `true` which will mark the test as successful
109
- */
110
- if (!this.#coverageOptions.enabled || coverageMaps.length === 0) {
111
- return true;
1124
+ };
1125
+ }
1126
+ function injectStencilImports(code, imports) {
1127
+ const hasRenderFunctionImport = imports.some((imp) => "h" in (imp.namedImports || {}));
1128
+ if (!hasRenderFunctionImport) {
1129
+ code = `import { h } from '@stencil/core/internal/client';
1130
+ ${code}`;
1131
+ }
1132
+ const hasFragmentImport = imports.some((imp) => "Fragment" in (imp.namedImports || {}));
1133
+ if (!hasFragmentImport) {
1134
+ code = `import { Fragment } from '@stencil/core/internal/client';
1135
+ ${code}`;
1136
+ }
1137
+ return code;
1138
+ }
1139
+ var _tsCompilerOptions = null;
1140
+ function getCompilerOptions(ts, rootDir) {
1141
+ if (_tsCompilerOptions) {
1142
+ return _tsCompilerOptions;
1143
+ }
1144
+ if (typeof rootDir !== "string") {
1145
+ return null;
1146
+ }
1147
+ const tsconfigFilePath = ts.findConfigFile(rootDir, ts.sys.fileExists);
1148
+ if (!tsconfigFilePath) {
1149
+ return null;
1150
+ }
1151
+ const tsconfigResults = ts.readConfigFile(tsconfigFilePath, ts.sys.readFile);
1152
+ if (tsconfigResults.error) {
1153
+ throw new Error(tsconfigResults.error);
1154
+ }
1155
+ const parseResult = ts.parseJsonConfigFileContent(
1156
+ tsconfigResults.config,
1157
+ ts.sys,
1158
+ rootDir,
1159
+ void 0,
1160
+ tsconfigFilePath
1161
+ );
1162
+ _tsCompilerOptions = parseResult.options;
1163
+ return _tsCompilerOptions;
1164
+ }
1165
+ async function importStencilConfig(rootDir) {
1166
+ const configPath = path8.join(rootDir, "stencil.config.ts");
1167
+ const config = await import(configPath).catch(() => ({ config: {} }));
1168
+ if ("default" in config) {
1169
+ return config.default;
1170
+ }
1171
+ return config;
1172
+ }
1173
+
1174
+ // src/vite/frameworks/index.ts
1175
+ async function updateViteConfig(options, config) {
1176
+ const optimizations = {};
1177
+ const rootDir = options.rootDir || config.rootDir || process.cwd();
1178
+ const isNuxt = await isNuxtFramework(rootDir);
1179
+ if (isNuxt) {
1180
+ Object.assign(optimizations, await optimizeForNuxt(options, config));
1181
+ }
1182
+ const isTailwind = await isUsingTailwindCSS(rootDir);
1183
+ if (isTailwind) {
1184
+ Object.assign(optimizations, await optimizeForTailwindCSS(rootDir));
1185
+ }
1186
+ if (await isUsingStencilJS(rootDir, options)) {
1187
+ Object.assign(optimizations, await optimizeForStencil(rootDir));
1188
+ }
1189
+ return optimizations;
1190
+ }
1191
+
1192
+ // src/communicator.ts
1193
+ import libSourceMap from "istanbul-lib-source-maps";
1194
+ import libCoverage from "istanbul-lib-coverage";
1195
+ import logger6 from "@wdio/logger";
1196
+ import { MESSAGE_TYPES } from "@wdio/types";
1197
+ var log6 = logger6("@wdio/browser-runner");
1198
+ var ServerWorkerCommunicator = class {
1199
+ #mapStore = libSourceMap.createSourceMapStore();
1200
+ #config;
1201
+ #msgId = 0;
1202
+ /**
1203
+ * keep track of custom commands per session
1204
+ */
1205
+ #customCommands = /* @__PURE__ */ new Map();
1206
+ /**
1207
+ * keep track of request/response messages on browser/worker level
1208
+ */
1209
+ #pendingMessages = /* @__PURE__ */ new Map();
1210
+ coverageMaps = [];
1211
+ constructor(config) {
1212
+ this.#config = config;
1213
+ }
1214
+ register(server, worker) {
1215
+ server.onBrowserEvent((data, client) => this.#onBrowserEvent(data, client, worker));
1216
+ worker.on("message", this.#onWorkerMessage.bind(this));
1217
+ }
1218
+ async #onWorkerMessage(payload) {
1219
+ if (payload.name === "sessionStarted" && !SESSIONS.has(payload.cid)) {
1220
+ SESSIONS.set(payload.cid, {
1221
+ args: this.#config.mochaOpts || {},
1222
+ config: this.#config,
1223
+ capabilities: payload.content.capabilities,
1224
+ sessionId: payload.content.sessionId,
1225
+ injectGlobals: payload.content.injectGlobals
1226
+ });
1227
+ }
1228
+ if (payload.name === "sessionEnded") {
1229
+ SESSIONS.delete(payload.cid);
1230
+ }
1231
+ if (payload.name === "workerEvent" && payload.args.type === MESSAGE_TYPES.coverageMap) {
1232
+ const coverageMapData = payload.args.value;
1233
+ this.coverageMaps.push(
1234
+ await this.#mapStore.transformCoverage(libCoverage.createCoverageMap(coverageMapData))
1235
+ );
1236
+ }
1237
+ if (payload.name === "workerEvent" && payload.args.type === MESSAGE_TYPES.customCommand) {
1238
+ const { commandName, cid } = payload.args.value;
1239
+ if (!this.#customCommands.has(cid)) {
1240
+ this.#customCommands.set(cid, /* @__PURE__ */ new Set());
1241
+ }
1242
+ const customCommands = this.#customCommands.get(cid) || /* @__PURE__ */ new Set();
1243
+ customCommands.add(commandName);
1244
+ return;
1245
+ }
1246
+ if (payload.name === "workerResponse") {
1247
+ const msg = this.#pendingMessages.get(payload.args.id);
1248
+ if (!msg) {
1249
+ return log6.error(`Couldn't find message with id ${payload.args.id} from type ${payload.args.message.type}`);
1250
+ }
1251
+ this.#pendingMessages.delete(payload.args.id);
1252
+ return msg.client.send(WDIO_EVENT_NAME, payload.args.message);
1253
+ }
1254
+ }
1255
+ #onBrowserEvent(message, client, worker) {
1256
+ if (message.type === MESSAGE_TYPES.initiateBrowserStateRequest) {
1257
+ const result = {
1258
+ type: MESSAGE_TYPES.initiateBrowserStateResponse,
1259
+ value: {
1260
+ customCommands: [...this.#customCommands.get(message.value.cid) || []]
112
1261
  }
113
- const firstCoverageMapEntry = coverageMaps.shift();
114
- const coverageMap = libCoverage.createCoverageMap(firstCoverageMapEntry);
115
- coverageMaps.forEach((cm) => coverageMap.merge(cm));
116
- const coverageIssues = [];
117
- try {
118
- const context = libReport.createContext({
119
- dir: this.#reportsDirectory,
120
- defaultSummarizer: 'nested',
121
- coverageMap
122
- });
123
- const reporter = this.#coverageOptions.reporter
124
- ? Array.isArray(this.#coverageOptions.reporter) ? this.#coverageOptions.reporter : [this.#coverageOptions.reporter]
125
- : DEFAULT_COVERAGE_REPORTS;
126
- /**
127
- * ensure summary reporter is set as we need it for treshold comparison
128
- */
129
- if (!reporter.includes(SUMMARY_REPORTER)) {
130
- reporter.push(SUMMARY_REPORTER);
1262
+ };
1263
+ return client.send(WDIO_EVENT_NAME, result);
1264
+ }
1265
+ const id = this.#msgId++;
1266
+ const msg = { id, client };
1267
+ this.#pendingMessages.set(id, msg);
1268
+ const args = { id, message };
1269
+ return worker.postMessage("workerRequest", args, true);
1270
+ }
1271
+ };
1272
+
1273
+ // src/utils.ts
1274
+ import util from "node:util";
1275
+ import { deepmerge as deepmerge3 } from "deepmerge-ts";
1276
+ import logger7 from "@wdio/logger";
1277
+ var log7 = logger7("@wdio/browser-runner");
1278
+ function makeHeadless(options, caps) {
1279
+ const capability = caps.alwaysMatch || caps;
1280
+ if (!capability.browserName) {
1281
+ throw new Error(
1282
+ 'No "browserName" defined in capability object. It seems you are trying to run tests in a non web environment, however WebdriverIOs browser runner only supports web environments'
1283
+ );
1284
+ }
1285
+ if (
1286
+ // either user sets headless option implicitly
1287
+ typeof options.headless === "boolean" && !options.headless || // or CI environment is set
1288
+ typeof process.env.CI !== "undefined" && !process.env.CI || // or non are set
1289
+ typeof options.headless !== "boolean" && typeof process.env.CI === "undefined"
1290
+ ) {
1291
+ return caps;
1292
+ }
1293
+ if (capability.browserName === "chrome") {
1294
+ return deepmerge3(capability, {
1295
+ "goog:chromeOptions": {
1296
+ args: ["headless", "disable-gpu"]
1297
+ }
1298
+ });
1299
+ } else if (capability.browserName === "firefox") {
1300
+ return deepmerge3(capability, {
1301
+ "moz:firefoxOptions": {
1302
+ args: ["-headless"]
1303
+ }
1304
+ });
1305
+ } else if (capability.browserName === "msedge" || capability.browserName === "edge") {
1306
+ return deepmerge3(capability, {
1307
+ "ms:edgeOptions": {
1308
+ args: ["--headless"]
1309
+ }
1310
+ });
1311
+ }
1312
+ log7.error(`Headless mode not supported for browser "${capability.browserName}"`);
1313
+ return caps;
1314
+ }
1315
+ function adjustWindowInWatchMode(config, caps) {
1316
+ if (!config.watch) {
1317
+ return caps;
1318
+ }
1319
+ const capability = caps.alwaysMatch || caps;
1320
+ if (config.watch && capability.browserName === "chrome") {
1321
+ return deepmerge3(capability, {
1322
+ "goog:chromeOptions": {
1323
+ args: ["auto-open-devtools-for-tabs", "window-size=1600,1200"],
1324
+ prefs: {
1325
+ devtools: {
1326
+ preferences: {
1327
+ "panel-selectedTab": '"console"'
131
1328
  }
132
- const reportBases = reporter.map((r) => reports.create(r, {
133
- projectRoot: this.#config.rootDir,
134
- subdir: 'html'
135
- }));
136
- reportBases.map((reportBase) => reportBase.execute(context));
137
- log.info(`Successfully created coverage reports for ${reporter.join(', ')}`);
138
- const summaryFilePath = path.join(this.#reportsDirectory, 'coverage-summary.json');
139
- const summary = JSON.parse((await fs.readFile(summaryFilePath)).toString());
140
- coverageIssues.push(...this.#coverageOptions.perFile
141
- ? Object.entries(summary)
142
- .filter(([source]) => source !== 'total')
143
- .map(([source, summary]) => (getCoverageByFactor(this.#coverageOptions, summary, source.replace(this.#config.rootDir, ''))))
144
- .flat()
145
- : getCoverageByFactor(this.#coverageOptions, summary.total));
1329
+ }
146
1330
  }
147
- catch (err) {
148
- console.error(`Failed to generate code coverage report: ${err.message}`);
149
- return false;
150
- }
151
- if (coverageIssues.length) {
152
- console.log(coverageIssues.join('\n'));
153
- return false;
154
- }
155
- return true;
156
- }
157
- }
158
- /**
159
- * re-export mock types
160
- */
161
- export * from '@vitest/spy';
162
- /**
163
- * The following exports are meaningless and only there to allow proper type support.
164
- * The actual implementation can be found in /src/browser.spy.ts
165
- */
166
- /**
167
- * Makes all imports to passed module to be mocked.
168
- *
169
- * If there is a factory, will return it's result. The call to `mock` is hoisted to the top of the file,
170
- * so you don't have access to variables declared in the global file scope, if you didn't put them before imports!
171
- *
172
- * If __mocks__ folder with file of the same name exist, all imports will return it.
173
- *
174
- * If there is no __mocks__ folder or a file with the same name inside, will call original module and mock it.
175
- *
176
- * @param {string} path Path to the module.
177
- * @param {MockFactoryWithHelper} factory (optional) Factory for the mocked module. Has the highest priority.
178
- */
179
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
180
- export function mock(path, factory) { }
181
- /**
182
- * Removes module from mocked registry. All subsequent calls to import will return original module even if it was mocked.
183
- *
184
- * @param path Path to the module.
185
- */
186
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
187
- export function unmock(moduleName) { }
188
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
189
- export function mocked(item, options) { }
1331
+ }
1332
+ });
1333
+ }
1334
+ return caps;
1335
+ }
1336
+ function getCoverageByFactor(options, summary, fileName) {
1337
+ return COVERAGE_FACTORS.map((factor) => {
1338
+ const treshold = options[factor];
1339
+ if (!treshold) {
1340
+ return;
1341
+ }
1342
+ if (summary[factor].pct >= treshold) {
1343
+ return;
1344
+ }
1345
+ return fileName ? util.format(FILE_TRESHOLD_REPORTING, factor, summary[factor].pct, treshold, fileName) : util.format(GLOBAL_TRESHOLD_REPORTING, factor, summary[factor].pct, treshold);
1346
+ }).filter(Boolean);
1347
+ }
1348
+
1349
+ // src/index.ts
1350
+ export * from "@vitest/spy";
1351
+ var log8 = logger8("@wdio/browser-runner");
1352
+ var BrowserRunner = class extends LocalRunner {
1353
+ constructor(options, _config) {
1354
+ super(options, _config);
1355
+ this.options = options;
1356
+ this._config = _config;
1357
+ if (_config.framework !== "mocha") {
1358
+ throw new Error(FRAMEWORK_SUPPORT_ERROR);
1359
+ }
1360
+ this.#options = options;
1361
+ this.#config = _config;
1362
+ this.#communicator = new ServerWorkerCommunicator(this.#config);
1363
+ this.#coverageOptions = options.coverage || {};
1364
+ this.#reportsDirectory = this.#coverageOptions.reportsDirectory || path9.join(this.#config.rootDir, DEFAULT_REPORTS_DIRECTORY);
1365
+ if (this.#config.mochaOpts) {
1366
+ this.#config.mochaOpts.require = (this.#config.mochaOpts.require || []).map((r) => path9.join(this.#config.rootDir || process.cwd(), r)).map((r) => url7.pathToFileURL(r).pathname);
1367
+ }
1368
+ }
1369
+ #options;
1370
+ #config;
1371
+ #servers = /* @__PURE__ */ new Set();
1372
+ #coverageOptions;
1373
+ #reportsDirectory;
1374
+ #viteOptimizations = {};
1375
+ #communicator;
1376
+ /**
1377
+ * for testing purposes
1378
+ */
1379
+ _servers = this.#servers;
1380
+ /**
1381
+ * nothing to initialize when running locally
1382
+ */
1383
+ async initialize() {
1384
+ log8.info("Initiate browser environment");
1385
+ if (typeof this.#coverageOptions.clean === "undefined" || this.#coverageOptions.clean) {
1386
+ const reportsDirectoryExist = await fs4.access(this.#reportsDirectory).then(() => true, () => false);
1387
+ if (reportsDirectoryExist) {
1388
+ await fs4.rm(this.#reportsDirectory, { recursive: true });
1389
+ }
1390
+ }
1391
+ try {
1392
+ this.#viteOptimizations = await updateViteConfig(this.#options, this.#config);
1393
+ } catch (err) {
1394
+ log8.error(`Failed to optimize Vite config: ${err.stack}`);
1395
+ }
1396
+ await super.initialize();
1397
+ }
1398
+ async run(runArgs) {
1399
+ runArgs.caps = makeHeadless(this.options, runArgs.caps);
1400
+ runArgs.caps = adjustWindowInWatchMode(this.#config, runArgs.caps);
1401
+ const server = new ViteServer(this.#options, this.#config, this.#viteOptimizations);
1402
+ this.#servers.add(server);
1403
+ try {
1404
+ await server.start();
1405
+ const host = this.#options.host || DEFAULT_HOST;
1406
+ runArgs.args.baseUrl = `${host}:${server.config.server?.port}`;
1407
+ } catch (err) {
1408
+ throw new Error(`Vite server failed to start: ${err.stack}`);
1409
+ }
1410
+ if (!runArgs.args.baseUrl && runArgs.command === "run") {
1411
+ runArgs.args.baseUrl = this._config.baseUrl;
1412
+ }
1413
+ const worker = await super.run(runArgs);
1414
+ this.#communicator.register(server, worker);
1415
+ return worker;
1416
+ }
1417
+ /**
1418
+ * shutdown vite server
1419
+ *
1420
+ * @return {Promise} resolves when vite server has been shutdown
1421
+ */
1422
+ async shutdown() {
1423
+ await super.shutdown();
1424
+ for (const server of this.#servers) {
1425
+ await server.close();
1426
+ }
1427
+ return this._generateCoverageReports();
1428
+ }
1429
+ async _generateCoverageReports() {
1430
+ const coverageMaps = this.#communicator.coverageMaps;
1431
+ if (!this.#coverageOptions.enabled || coverageMaps.length === 0) {
1432
+ return true;
1433
+ }
1434
+ const firstCoverageMapEntry = coverageMaps.shift();
1435
+ const coverageMap = libCoverage2.createCoverageMap(firstCoverageMapEntry);
1436
+ coverageMaps.forEach((cm) => coverageMap.merge(cm));
1437
+ const coverageIssues = [];
1438
+ try {
1439
+ const context = libReport.createContext({
1440
+ dir: this.#reportsDirectory,
1441
+ defaultSummarizer: "nested",
1442
+ coverageMap
1443
+ });
1444
+ const reporter = this.#coverageOptions.reporter ? Array.isArray(this.#coverageOptions.reporter) ? this.#coverageOptions.reporter : [this.#coverageOptions.reporter] : DEFAULT_COVERAGE_REPORTS;
1445
+ if (!reporter.includes(SUMMARY_REPORTER)) {
1446
+ reporter.push(SUMMARY_REPORTER);
1447
+ }
1448
+ const reportBases = reporter.map((r) => reports.create(r, {
1449
+ projectRoot: this.#config.rootDir,
1450
+ subdir: "html"
1451
+ }));
1452
+ reportBases.map((reportBase) => reportBase.execute(context));
1453
+ log8.info(`Successfully created coverage reports for ${reporter.join(", ")}`);
1454
+ const summaryFilePath = path9.join(this.#reportsDirectory, "coverage-summary.json");
1455
+ const summary = JSON.parse((await fs4.readFile(summaryFilePath)).toString());
1456
+ coverageIssues.push(
1457
+ ...this.#coverageOptions.perFile ? Object.entries(summary).filter(([source]) => source !== "total").map(
1458
+ ([source, summary2]) => getCoverageByFactor(this.#coverageOptions, summary2, source.replace(this.#config.rootDir, ""))
1459
+ ).flat() : getCoverageByFactor(this.#coverageOptions, summary.total)
1460
+ );
1461
+ } catch (err) {
1462
+ console.error(`Failed to generate code coverage report: ${err.message}`);
1463
+ return false;
1464
+ }
1465
+ if (coverageIssues.length) {
1466
+ console.log(coverageIssues.join("\n"));
1467
+ return false;
1468
+ }
1469
+ return true;
1470
+ }
1471
+ };
1472
+ function mock(path10, factory) {
1473
+ }
1474
+ function unmock(moduleName) {
1475
+ }
1476
+ function mocked(item, options) {
1477
+ }
1478
+ export {
1479
+ BrowserRunner as default,
1480
+ mock,
1481
+ mocked,
1482
+ unmock
1483
+ };