@vitest/browser 2.0.0-beta.10 → 2.0.0-beta.11

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/dist/index.js CHANGED
@@ -1,15 +1,19 @@
1
+ import { createDebugger, isFileServingAllowed as isFileServingAllowed$1, getFilePoolName, resolveApiServerConfig, resolveFsAllow, distDir, createServer } from 'vitest/node';
2
+ import fs, { existsSync, readdirSync, promises, readFileSync } from 'node:fs';
3
+ import { WebSocketServer } from 'ws';
4
+ import { isFileServingAllowed } from 'vite';
5
+ import { builtinModules, createRequire } from 'node:module';
6
+ import { readFile as readFile$1, mkdir } from 'node:fs/promises';
1
7
  import { fileURLToPath } from 'node:url';
2
- import { mkdir, readFile as readFile$1 } from 'node:fs/promises';
3
- import { createRequire } from 'node:module';
8
+ import { createDefer, slash, toArray } from '@vitest/utils';
4
9
  import sirv from 'sirv';
5
- import { isFileServingAllowed, getFilePoolName, distDir } from 'vitest/node';
6
- import { coverageConfigDefaults } from 'vitest/config';
7
- import { slash, toArray } from '@vitest/utils';
8
- import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-CXn0ag9T.js';
9
- import fs, { promises } from 'node:fs';
10
+ import { defaultBrowserPort, coverageConfigDefaults } from 'vitest/config';
11
+ import { P as PlaywrightBrowserProvider, W as WebdriverBrowserProvider } from './webdriver-BRud6NtS.js';
10
12
  import { resolve as resolve$1, dirname as dirname$1, normalize as normalize$1 } from 'node:path';
11
13
  import MagicString from 'magic-string';
12
14
  import { esmWalker } from '@vitest/utils/ast';
15
+ import * as nodeos from 'node:os';
16
+ import crypto from 'node:crypto';
13
17
 
14
18
  const _DRIVE_LETTER_START_RE = /^[A-Za-z]:\//;
15
19
  function normalizeWindowsPath(input = "") {
@@ -157,6 +161,11 @@ function normalizeString(path, allowAboveRoot) {
157
161
  const isAbsolute = function(p) {
158
162
  return _IS_ABSOLUTE_RE.test(p);
159
163
  };
164
+ const _EXTNAME_RE = /.(\.[^./]+)$/;
165
+ const extname = function(p) {
166
+ const match = _EXTNAME_RE.exec(normalizeWindowsPath(p));
167
+ return match && match[1] || "";
168
+ };
160
169
  const relative = function(from, to) {
161
170
  const _from = resolve(from).replace(_ROOT_FOLDER_RE, "$1").split("/");
162
171
  const _to = resolve(to).replace(_ROOT_FOLDER_RE, "$1").split("/");
@@ -185,40 +194,1341 @@ const basename = function(p, extension) {
185
194
  return extension && lastSegment.endsWith(extension) ? lastSegment.slice(0, -extension.length) : lastSegment;
186
195
  };
187
196
 
197
+ const DEFAULT_TIMEOUT = 6e4;
198
+ function defaultSerialize(i) {
199
+ return i;
200
+ }
201
+ const defaultDeserialize = defaultSerialize;
202
+ const { clearTimeout, setTimeout } = globalThis;
203
+ const random = Math.random.bind(Math);
204
+ function createBirpc(functions, options) {
205
+ const {
206
+ post,
207
+ on,
208
+ eventNames = [],
209
+ serialize = defaultSerialize,
210
+ deserialize = defaultDeserialize,
211
+ resolver,
212
+ timeout = DEFAULT_TIMEOUT
213
+ } = options;
214
+ const rpcPromiseMap = /* @__PURE__ */ new Map();
215
+ let _promise;
216
+ const rpc = new Proxy({}, {
217
+ get(_, method) {
218
+ if (method === "$functions")
219
+ return functions;
220
+ if (method === "then" && !eventNames.includes("then") && !("then" in functions))
221
+ return void 0;
222
+ const sendEvent = (...args) => {
223
+ post(serialize({ m: method, a: args, t: "q" }));
224
+ };
225
+ if (eventNames.includes(method)) {
226
+ sendEvent.asEvent = sendEvent;
227
+ return sendEvent;
228
+ }
229
+ const sendCall = async (...args) => {
230
+ await _promise;
231
+ return new Promise((resolve, reject) => {
232
+ const id = nanoid();
233
+ let timeoutId;
234
+ if (timeout >= 0) {
235
+ timeoutId = setTimeout(() => {
236
+ try {
237
+ options.onTimeoutError?.(method, args);
238
+ throw new Error(`[birpc] timeout on calling "${method}"`);
239
+ } catch (e) {
240
+ reject(e);
241
+ }
242
+ rpcPromiseMap.delete(id);
243
+ }, timeout);
244
+ if (typeof timeoutId === "object")
245
+ timeoutId = timeoutId.unref?.();
246
+ }
247
+ rpcPromiseMap.set(id, { resolve, reject, timeoutId });
248
+ post(serialize({ m: method, a: args, i: id, t: "q" }));
249
+ });
250
+ };
251
+ sendCall.asEvent = sendEvent;
252
+ return sendCall;
253
+ }
254
+ });
255
+ _promise = on(async (data, ...extra) => {
256
+ const msg = deserialize(data);
257
+ if (msg.t === "q") {
258
+ const { m: method, a: args } = msg;
259
+ let result, error;
260
+ const fn = resolver ? resolver(method, functions[method]) : functions[method];
261
+ if (!fn) {
262
+ error = new Error(`[birpc] function "${method}" not found`);
263
+ } else {
264
+ try {
265
+ result = await fn.apply(rpc, args);
266
+ } catch (e) {
267
+ error = e;
268
+ }
269
+ }
270
+ if (msg.i) {
271
+ if (error && options.onError)
272
+ options.onError(error, method, args);
273
+ post(serialize({ t: "s", i: msg.i, r: result, e: error }), ...extra);
274
+ }
275
+ } else {
276
+ const { i: ack, r: result, e: error } = msg;
277
+ const promise = rpcPromiseMap.get(ack);
278
+ if (promise) {
279
+ clearTimeout(promise.timeoutId);
280
+ if (error)
281
+ promise.reject(error);
282
+ else
283
+ promise.resolve(result);
284
+ }
285
+ rpcPromiseMap.delete(ack);
286
+ }
287
+ });
288
+ return rpc;
289
+ }
290
+ const urlAlphabet = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
291
+ function nanoid(size = 21) {
292
+ let id = "";
293
+ let i = size;
294
+ while (i--)
295
+ id += urlAlphabet[random() * 64 | 0];
296
+ return id;
297
+ }
298
+
299
+ /// <reference types="../types/index.d.ts" />
300
+
301
+ // (c) 2020-present Andrea Giammarchi
302
+
303
+ const {parse: $parse, stringify: $stringify} = JSON;
304
+ const {keys} = Object;
305
+
306
+ const Primitive = String; // it could be Number
307
+ const primitive = 'string'; // it could be 'number'
308
+
309
+ const ignore = {};
310
+ const object = 'object';
311
+
312
+ const noop = (_, value) => value;
313
+
314
+ const primitives = value => (
315
+ value instanceof Primitive ? Primitive(value) : value
316
+ );
317
+
318
+ const Primitives = (_, value) => (
319
+ typeof value === primitive ? new Primitive(value) : value
320
+ );
321
+
322
+ const revive = (input, parsed, output, $) => {
323
+ const lazy = [];
324
+ for (let ke = keys(output), {length} = ke, y = 0; y < length; y++) {
325
+ const k = ke[y];
326
+ const value = output[k];
327
+ if (value instanceof Primitive) {
328
+ const tmp = input[value];
329
+ if (typeof tmp === object && !parsed.has(tmp)) {
330
+ parsed.add(tmp);
331
+ output[k] = ignore;
332
+ lazy.push({k, a: [input, parsed, tmp, $]});
333
+ }
334
+ else
335
+ output[k] = $.call(output, k, tmp);
336
+ }
337
+ else if (output[k] !== ignore)
338
+ output[k] = $.call(output, k, value);
339
+ }
340
+ for (let {length} = lazy, i = 0; i < length; i++) {
341
+ const {k, a} = lazy[i];
342
+ output[k] = $.call(output, k, revive.apply(null, a));
343
+ }
344
+ return output;
345
+ };
346
+
347
+ const set = (known, input, value) => {
348
+ const index = Primitive(input.push(value) - 1);
349
+ known.set(value, index);
350
+ return index;
351
+ };
352
+
353
+ /**
354
+ * Converts a specialized flatted string into a JS value.
355
+ * @param {string} text
356
+ * @param {(this: any, key: string, value: any) => any} [reviver]
357
+ * @returns {any}
358
+ */
359
+ const parse = (text, reviver) => {
360
+ const input = $parse(text, Primitives).map(primitives);
361
+ const value = input[0];
362
+ const $ = reviver || noop;
363
+ const tmp = typeof value === object && value ?
364
+ revive(input, new Set, value, $) :
365
+ value;
366
+ return $.call({'': tmp}, '', tmp);
367
+ };
368
+
369
+ /**
370
+ * Converts a JS value into a specialized flatted string.
371
+ * @param {any} value
372
+ * @param {((this: any, key: string, value: any) => any) | (string | number)[] | null | undefined} [replacer]
373
+ * @param {string | number | undefined} [space]
374
+ * @returns {string}
375
+ */
376
+ const stringify = (value, replacer, space) => {
377
+ const $ = replacer && typeof replacer === object ?
378
+ (k, v) => (k === '' || -1 < replacer.indexOf(k) ? v : void 0) :
379
+ (replacer || noop);
380
+ const known = new Map;
381
+ const input = [];
382
+ const output = [];
383
+ let i = +set(known, input, $.call({'': value}, '', value));
384
+ let firstRun = !i;
385
+ while (i < input.length) {
386
+ firstRun = true;
387
+ output[i] = $stringify(input[i++], replace, space);
388
+ }
389
+ return '[' + output.join(',') + ']';
390
+ function replace(key, value) {
391
+ if (firstRun) {
392
+ firstRun = !firstRun;
393
+ return value;
394
+ }
395
+ const after = $.call(this, key, value);
396
+ switch (typeof after) {
397
+ case object:
398
+ if (after === null) return after;
399
+ case primitive:
400
+ return known.get(after) || set(known, input, after);
401
+ }
402
+ return after;
403
+ }
404
+ };
405
+
406
+ async function resolveMock(project, rawId, importer, hasFactory) {
407
+ const { id, fsPath, external } = await resolveId(project, rawId, importer);
408
+ if (hasFactory) {
409
+ return { type: "factory", resolvedId: id };
410
+ }
411
+ const mockPath = resolveMockPath(project.config.root, fsPath, external);
412
+ return {
413
+ type: mockPath === null ? "automock" : "redirect",
414
+ mockPath,
415
+ resolvedId: id
416
+ };
417
+ }
418
+ async function resolveId(project, rawId, importer) {
419
+ const resolved = await project.browser.vite.pluginContainer.resolveId(
420
+ rawId,
421
+ importer,
422
+ {
423
+ ssr: false
424
+ }
425
+ );
426
+ return resolveModule(project, rawId, resolved);
427
+ }
428
+ async function resolveModule(project, rawId, resolved) {
429
+ const id = resolved?.id || rawId;
430
+ const external = !isAbsolute(id) || isModuleDirectory(project.config, id) ? rawId : null;
431
+ return {
432
+ id,
433
+ fsPath: cleanUrl(id),
434
+ external
435
+ };
436
+ }
437
+ function isModuleDirectory(config, path) {
438
+ const moduleDirectories = config.server.deps?.moduleDirectories || [
439
+ "/node_modules/"
440
+ ];
441
+ return moduleDirectories.some((dir) => path.includes(dir));
442
+ }
443
+ function resolveMockPath(root, mockPath, external) {
444
+ const path = external || mockPath;
445
+ if (external || isNodeBuiltin(mockPath) || !existsSync(mockPath)) {
446
+ const mockDirname = dirname(path);
447
+ const mockFolder = join(
448
+ root,
449
+ "__mocks__",
450
+ mockDirname
451
+ );
452
+ if (!existsSync(mockFolder)) {
453
+ return null;
454
+ }
455
+ const files = readdirSync(mockFolder);
456
+ const baseOriginal = basename(path);
457
+ for (const file of files) {
458
+ const baseFile = basename(file, extname(file));
459
+ if (baseFile === baseOriginal) {
460
+ return resolve(mockFolder, file);
461
+ }
462
+ }
463
+ return null;
464
+ }
465
+ const dir = dirname(path);
466
+ const baseId = basename(path);
467
+ const fullPath = resolve(dir, "__mocks__", baseId);
468
+ return existsSync(fullPath) ? fullPath : null;
469
+ }
470
+ const prefixedBuiltins = /* @__PURE__ */ new Set(["node:test"]);
471
+ const builtins = /* @__PURE__ */ new Set([
472
+ ...builtinModules,
473
+ "assert/strict",
474
+ "diagnostics_channel",
475
+ "dns/promises",
476
+ "fs/promises",
477
+ "path/posix",
478
+ "path/win32",
479
+ "readline/promises",
480
+ "stream/consumers",
481
+ "stream/promises",
482
+ "stream/web",
483
+ "timers/promises",
484
+ "util/types",
485
+ "wasi"
486
+ ]);
487
+ const NODE_BUILTIN_NAMESPACE = "node:";
488
+ function isNodeBuiltin(id) {
489
+ if (prefixedBuiltins.has(id)) {
490
+ return true;
491
+ }
492
+ return builtins.has(
493
+ id.startsWith(NODE_BUILTIN_NAMESPACE) ? id.slice(NODE_BUILTIN_NAMESPACE.length) : id
494
+ );
495
+ }
496
+ const postfixRE = /[?#].*$/;
497
+ function cleanUrl(url) {
498
+ return url.replace(postfixRE, "");
499
+ }
500
+
501
+ const debug$1 = createDebugger("vitest:browser:api");
502
+ const BROWSER_API_PATH = "/__vitest_browser_api__";
503
+ function setupBrowserRpc(server) {
504
+ const project = server.project;
505
+ const vite = server.vite;
506
+ const ctx = project.ctx;
507
+ const wss = new WebSocketServer({ noServer: true });
508
+ vite.httpServer?.on("upgrade", (request, socket, head) => {
509
+ if (!request.url) {
510
+ return;
511
+ }
512
+ const { pathname, searchParams } = new URL(request.url, "http://localhost");
513
+ if (pathname !== BROWSER_API_PATH) {
514
+ return;
515
+ }
516
+ const type = searchParams.get("type") ?? "tester";
517
+ const sessionId = searchParams.get("sessionId") ?? "0";
518
+ wss.handleUpgrade(request, socket, head, (ws) => {
519
+ wss.emit("connection", ws, request);
520
+ const rpc = setupClient(ws);
521
+ const state = server.state;
522
+ const clients = type === "tester" ? state.testers : state.orchestrators;
523
+ clients.set(sessionId, rpc);
524
+ debug$1?.("[%s] Browser API connected to %s", sessionId, type);
525
+ ws.on("close", () => {
526
+ debug$1?.("[%s] Browser API disconnected from %s", sessionId, type);
527
+ clients.delete(sessionId);
528
+ });
529
+ });
530
+ });
531
+ function checkFileAccess(path) {
532
+ if (!isFileServingAllowed(path, vite)) {
533
+ throw new Error(
534
+ `Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`
535
+ );
536
+ }
537
+ }
538
+ function setupClient(ws) {
539
+ const rpc = createBirpc(
540
+ {
541
+ async onUnhandledError(error, type) {
542
+ ctx.state.catchError(error, type);
543
+ },
544
+ async onCollected(files) {
545
+ ctx.state.collectFiles(files);
546
+ await ctx.report("onCollected", files);
547
+ },
548
+ async onTaskUpdate(packs) {
549
+ ctx.state.updateTasks(packs);
550
+ await ctx.report("onTaskUpdate", packs);
551
+ },
552
+ onAfterSuiteRun(meta) {
553
+ ctx.coverageProvider?.onAfterSuiteRun(meta);
554
+ },
555
+ sendLog(log) {
556
+ return ctx.report("onUserConsoleLog", log);
557
+ },
558
+ resolveSnapshotPath(testPath) {
559
+ return ctx.snapshot.resolvePath(testPath);
560
+ },
561
+ resolveSnapshotRawPath(testPath, rawPath) {
562
+ return ctx.snapshot.resolveRawPath(testPath, rawPath);
563
+ },
564
+ snapshotSaved(snapshot) {
565
+ ctx.snapshot.add(snapshot);
566
+ },
567
+ async readSnapshotFile(snapshotPath) {
568
+ checkFileAccess(snapshotPath);
569
+ if (!existsSync(snapshotPath)) {
570
+ return null;
571
+ }
572
+ return promises.readFile(snapshotPath, "utf-8");
573
+ },
574
+ async saveSnapshotFile(id, content) {
575
+ checkFileAccess(id);
576
+ await promises.mkdir(dirname(id), { recursive: true });
577
+ return promises.writeFile(id, content, "utf-8");
578
+ },
579
+ async removeSnapshotFile(id) {
580
+ checkFileAccess(id);
581
+ if (!existsSync(id)) {
582
+ throw new Error(`Snapshot file "${id}" does not exist.`);
583
+ }
584
+ return promises.unlink(id);
585
+ },
586
+ getBrowserFileSourceMap(id) {
587
+ const mod = server.vite.moduleGraph.getModuleById(id);
588
+ return mod?.transformResult?.map;
589
+ },
590
+ onCancel(reason) {
591
+ ctx.cancelCurrentRun(reason);
592
+ },
593
+ async resolveId(id, importer) {
594
+ const result = await project.server.pluginContainer.resolveId(
595
+ id,
596
+ importer,
597
+ {
598
+ ssr: false
599
+ }
600
+ );
601
+ return result;
602
+ },
603
+ debug(...args) {
604
+ ctx.logger.console.debug(...args);
605
+ },
606
+ getCountOfFailedTests() {
607
+ return ctx.state.getCountOfFailedTests();
608
+ },
609
+ async triggerCommand(contextId, command, testPath, payload) {
610
+ debug$1?.('[%s] Triggering command "%s"', contextId, command);
611
+ const provider = server.provider;
612
+ if (!provider) {
613
+ throw new Error("Commands are only available for browser tests.");
614
+ }
615
+ const commands = project.config.browser?.commands;
616
+ if (!commands || !commands[command]) {
617
+ throw new Error(`Unknown command "${command}".`);
618
+ }
619
+ if (provider.beforeCommand) {
620
+ await provider.beforeCommand(command, payload);
621
+ }
622
+ const context = Object.assign(
623
+ {
624
+ testPath,
625
+ project,
626
+ provider,
627
+ contextId
628
+ },
629
+ provider.getCommandsContext(contextId)
630
+ );
631
+ let result;
632
+ try {
633
+ result = await commands[command](context, ...payload);
634
+ } finally {
635
+ if (provider.afterCommand) {
636
+ await provider.afterCommand(command, payload);
637
+ }
638
+ }
639
+ return result;
640
+ },
641
+ finishBrowserTests(contextId) {
642
+ debug$1?.("[%s] Finishing browser tests for context", contextId);
643
+ return server.state.getContext(contextId)?.resolve();
644
+ },
645
+ resolveMock(rawId, importer, hasFactory) {
646
+ return resolveMock(project, rawId, importer, hasFactory);
647
+ },
648
+ invalidate(ids) {
649
+ ids.forEach((id) => {
650
+ const moduleGraph = server.vite.moduleGraph;
651
+ const module = moduleGraph.getModuleById(id);
652
+ if (module) {
653
+ moduleGraph.invalidateModule(module, /* @__PURE__ */ new Set(), Date.now(), true);
654
+ }
655
+ });
656
+ }
657
+ },
658
+ {
659
+ post: (msg) => ws.send(msg),
660
+ on: (fn) => ws.on("message", fn),
661
+ eventNames: ["onCancel"],
662
+ serialize: (data) => stringify(data, stringifyReplace),
663
+ deserialize: parse,
664
+ onTimeoutError(functionName) {
665
+ throw new Error(`[vitest-api]: Timeout calling "${functionName}"`);
666
+ }
667
+ }
668
+ );
669
+ ctx.onCancel((reason) => rpc.onCancel(reason));
670
+ return rpc;
671
+ }
672
+ }
673
+ function cloneByOwnProperties(value) {
674
+ return Object.getOwnPropertyNames(value).reduce(
675
+ (clone, prop) => ({
676
+ ...clone,
677
+ [prop]: value[prop]
678
+ }),
679
+ {}
680
+ );
681
+ }
682
+ function stringifyReplace(key, value) {
683
+ if (value instanceof Error) {
684
+ const cloned = cloneByOwnProperties(value);
685
+ return {
686
+ name: value.name,
687
+ message: value.message,
688
+ stack: value.stack,
689
+ ...cloned
690
+ };
691
+ } else {
692
+ return value;
693
+ }
694
+ }
695
+
696
+ class BrowserServerState {
697
+ orchestrators = /* @__PURE__ */ new Map();
698
+ testers = /* @__PURE__ */ new Map();
699
+ contexts = /* @__PURE__ */ new Map();
700
+ getContext(contextId) {
701
+ return this.contexts.get(contextId);
702
+ }
703
+ createAsyncContext(contextId, files) {
704
+ const defer = createDefer();
705
+ this.contexts.set(contextId, {
706
+ files,
707
+ resolve: () => {
708
+ defer.resolve();
709
+ this.contexts.delete(contextId);
710
+ },
711
+ reject: defer.reject
712
+ });
713
+ return defer;
714
+ }
715
+ }
716
+
717
+ function replacer(code, values) {
718
+ return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? "");
719
+ }
720
+ const builtinProviders = ["webdriverio", "playwright", "preview"];
721
+ async function getBrowserProvider(options, project) {
722
+ if (options.provider == null || builtinProviders.includes(options.provider)) {
723
+ const providers = await import('./providers.js');
724
+ const provider = options.provider || "preview";
725
+ return providers[provider];
726
+ }
727
+ let customProviderModule;
728
+ try {
729
+ customProviderModule = await project.runner.executeId(
730
+ options.provider
731
+ );
732
+ } catch (error) {
733
+ throw new Error(
734
+ `Failed to load custom BrowserProvider from ${options.provider}`,
735
+ { cause: error }
736
+ );
737
+ }
738
+ if (customProviderModule.default == null) {
739
+ throw new Error(
740
+ `Custom BrowserProvider loaded from ${options.provider} was not the default export`
741
+ );
742
+ }
743
+ return customProviderModule.default;
744
+ }
745
+
746
+ class BrowserServer {
747
+ constructor(project, base) {
748
+ this.project = project;
749
+ this.base = base;
750
+ this.state = new BrowserServerState();
751
+ const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
752
+ const distRoot = resolve(pkgRoot, "dist");
753
+ this.prefixTesterUrl = `${base}__vitest_test__/__test__/`;
754
+ this.faviconUrl = `${base}__vitest__/favicon.svg`;
755
+ this.manifest = (async () => {
756
+ return JSON.parse(
757
+ await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8")
758
+ );
759
+ })().then((manifest) => this.manifest = manifest);
760
+ this.testerHtml = readFile$1(
761
+ resolve(distRoot, "client/tester/tester.html"),
762
+ "utf8"
763
+ ).then((html) => this.testerHtml = html);
764
+ this.orchestratorHtml = (project.config.browser.ui ? readFile$1(resolve(distRoot, "client/__vitest__/index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8")).then((html) => this.orchestratorHtml = html);
765
+ this.injectorJs = readFile$1(
766
+ resolve(distRoot, "client/esm-client-injector.js"),
767
+ "utf8"
768
+ ).then((js) => this.injectorJs = js);
769
+ this.stateJs = readFile$1(
770
+ resolve(distRoot, "state.js"),
771
+ "utf-8"
772
+ ).then((js) => this.stateJs = js);
773
+ }
774
+ faviconUrl;
775
+ prefixTesterUrl;
776
+ orchestratorScripts;
777
+ testerScripts;
778
+ manifest;
779
+ testerHtml;
780
+ orchestratorHtml;
781
+ injectorJs;
782
+ stateJs;
783
+ state;
784
+ provider;
785
+ vite;
786
+ setServer(server) {
787
+ this.vite = server;
788
+ }
789
+ getSerializableConfig() {
790
+ const config = wrapConfig(this.project.getSerializableConfig());
791
+ config.env ??= {};
792
+ config.env.VITEST_BROWSER_DEBUG = process.env.VITEST_BROWSER_DEBUG || "";
793
+ return config;
794
+ }
795
+ resolveTesterUrl(pathname) {
796
+ const [contextId, testFile] = pathname.slice(this.prefixTesterUrl.length).split("/");
797
+ const decodedTestFile = decodeURIComponent(testFile);
798
+ return { contextId, testFile: decodedTestFile };
799
+ }
800
+ async formatScripts(scripts) {
801
+ if (!scripts?.length) {
802
+ return "";
803
+ }
804
+ const server = this.vite;
805
+ const promises = scripts.map(
806
+ async ({ content, src, async, id, type = "module" }, index) => {
807
+ const srcLink = (src ? (await server.pluginContainer.resolveId(src))?.id : void 0) || src;
808
+ const transformId = srcLink || join(server.config.root, `virtual__${id || `injected-${index}.js`}`);
809
+ await server.moduleGraph.ensureEntryFromUrl(transformId);
810
+ const contentProcessed = content && type === "module" ? (await server.pluginContainer.transform(content, transformId)).code : content;
811
+ return `<script type="${type}"${async ? " async" : ""}${srcLink ? ` src="${slash(`/@fs/${srcLink}`)}"` : ""}>${contentProcessed || ""}<\/script>`;
812
+ }
813
+ );
814
+ return (await Promise.all(promises)).join("\n");
815
+ }
816
+ async initBrowserProvider() {
817
+ if (this.provider) {
818
+ return;
819
+ }
820
+ const Provider = await getBrowserProvider(this.project.config.browser, this.project);
821
+ this.provider = new Provider();
822
+ const browser = this.project.config.browser.name;
823
+ if (!browser) {
824
+ throw new Error(
825
+ `[${this.project.getName()}] Browser name is required. Please, set \`test.browser.name\` option manually.`
826
+ );
827
+ }
828
+ const supportedBrowsers = this.provider.getSupportedBrowsers();
829
+ if (supportedBrowsers.length && !supportedBrowsers.includes(browser)) {
830
+ throw new Error(
831
+ `[${this.project.getName()}] Browser "${browser}" is not supported by the browser provider "${this.provider.name}". Supported browsers: ${supportedBrowsers.join(", ")}.`
832
+ );
833
+ }
834
+ const providerOptions = this.project.config.browser.providerOptions;
835
+ await this.provider.initialize(this.project, {
836
+ browser,
837
+ options: providerOptions
838
+ });
839
+ }
840
+ async close() {
841
+ await this.vite.close();
842
+ }
843
+ }
844
+ function wrapConfig(config) {
845
+ return {
846
+ ...config,
847
+ // workaround RegExp serialization
848
+ testNamePattern: config.testNamePattern ? config.testNamePattern.toString() : void 0
849
+ };
850
+ }
851
+
188
852
  const click = async (context, xpath, options = {}) => {
189
853
  const provider = context.provider;
190
854
  if (provider instanceof PlaywrightBrowserProvider) {
191
- const tester = context.tester;
192
- await tester.locator(`xpath=${xpath}`).click(options);
855
+ const tester = context.frame;
856
+ await tester.locator(`xpath=${xpath}`).click({
857
+ timeout: 1e3,
858
+ ...options
859
+ });
860
+ } else if (provider instanceof WebdriverBrowserProvider) {
861
+ const browser = context.browser;
862
+ const markedXpath = `//${xpath}`;
863
+ await browser.$(markedXpath).click(options);
864
+ } else {
865
+ throw new TypeError(`Provider "${provider.name}" doesn't support click command`);
866
+ }
867
+ };
868
+ const dblClick = async (context, xpath, options = {}) => {
869
+ const provider = context.provider;
870
+ if (provider instanceof PlaywrightBrowserProvider) {
871
+ const tester = context.frame;
872
+ await tester.locator(`xpath=${xpath}`).dblclick(options);
873
+ } else if (provider instanceof WebdriverBrowserProvider) {
874
+ const browser = context.browser;
875
+ const markedXpath = `//${xpath}`;
876
+ await browser.$(markedXpath).doubleClick();
877
+ } else {
878
+ throw new TypeError(`Provider "${provider.name}" doesn't support dblClick command`);
879
+ }
880
+ };
881
+
882
+ var clickableInputTypes;
883
+ (function(clickableInputTypes) {
884
+ clickableInputTypes["button"] = "button";
885
+ clickableInputTypes["color"] = "color";
886
+ clickableInputTypes["file"] = "file";
887
+ clickableInputTypes["image"] = "image";
888
+ clickableInputTypes["reset"] = "reset";
889
+ clickableInputTypes["submit"] = "submit";
890
+ clickableInputTypes["checkbox"] = "checkbox";
891
+ clickableInputTypes["radio"] = "radio";
892
+ })(clickableInputTypes || (clickableInputTypes = {}));
893
+
894
+ const ClipboardStubControl = Symbol('Manage ClipboardSub');
895
+ function isClipboardStub(clipboard) {
896
+ return !!(clipboard === null || clipboard === void 0 ? void 0 : clipboard[ClipboardStubControl]);
897
+ }
898
+ function resetClipboardStubOnView(window) {
899
+ if (isClipboardStub(window.navigator.clipboard)) {
900
+ window.navigator.clipboard[ClipboardStubControl].resetClipboardStub();
901
+ }
902
+ }
903
+ function detachClipboardStubFromView(window) {
904
+ if (isClipboardStub(window.navigator.clipboard)) {
905
+ window.navigator.clipboard[ClipboardStubControl].detachClipboardStub();
906
+ }
907
+ }
908
+ const g = globalThis;
909
+ /* istanbul ignore else */ if (typeof g.afterEach === 'function') {
910
+ g.afterEach(()=>resetClipboardStubOnView(globalThis.window));
911
+ }
912
+ /* istanbul ignore else */ if (typeof g.afterAll === 'function') {
913
+ g.afterAll(()=>detachClipboardStubFromView(globalThis.window));
914
+ }
915
+
916
+ var editableInputTypes;
917
+ (function(editableInputTypes) {
918
+ editableInputTypes["text"] = "text";
919
+ editableInputTypes["date"] = "date";
920
+ editableInputTypes["datetime-local"] = "datetime-local";
921
+ editableInputTypes["email"] = "email";
922
+ editableInputTypes["month"] = "month";
923
+ editableInputTypes["number"] = "number";
924
+ editableInputTypes["password"] = "password";
925
+ editableInputTypes["search"] = "search";
926
+ editableInputTypes["tel"] = "tel";
927
+ editableInputTypes["time"] = "time";
928
+ editableInputTypes["url"] = "url";
929
+ editableInputTypes["week"] = "week";
930
+ })(editableInputTypes || (editableInputTypes = {}));
931
+
932
+ var maxLengthSupportedTypes;
933
+ (function(maxLengthSupportedTypes) {
934
+ maxLengthSupportedTypes["email"] = "email";
935
+ maxLengthSupportedTypes["password"] = "password";
936
+ maxLengthSupportedTypes["search"] = "search";
937
+ maxLengthSupportedTypes["telephone"] = "telephone";
938
+ maxLengthSupportedTypes["text"] = "text";
939
+ maxLengthSupportedTypes["url"] = "url";
940
+ })(maxLengthSupportedTypes || (maxLengthSupportedTypes = {}));
941
+
942
+ var bracketDict;
943
+ (function(bracketDict) {
944
+ bracketDict["{"] = "}";
945
+ bracketDict["["] = "]";
946
+ })(bracketDict || (bracketDict = {}));
947
+ /**
948
+ * Read the next key definition from user input
949
+ *
950
+ * Describe key per `{descriptor}` or `[descriptor]`.
951
+ * Everything else will be interpreted as a single character as descriptor - e.g. `a`.
952
+ * Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
953
+ * A previously pressed key can be released per `{/descriptor}`.
954
+ * Keeping the key pressed can be written as `{descriptor>}`.
955
+ * When keeping the key pressed you can choose how long the key is pressed `{descriptor>3}`.
956
+ * You can then release the key per `{descriptor>3/}` or keep it pressed and continue with the next key.
957
+ */ function readNextDescriptor(text, context) {
958
+ let pos = 0;
959
+ const startBracket = text[pos] in bracketDict ? text[pos] : '';
960
+ pos += startBracket.length;
961
+ const isEscapedChar = new RegExp(`^\\${startBracket}{2}`).test(text);
962
+ const type = isEscapedChar ? '' : startBracket;
963
+ return {
964
+ type,
965
+ ...type === '' ? readPrintableChar(text, pos) : readTag(text, pos, type)
966
+ };
967
+ }
968
+ function readPrintableChar(text, pos, context) {
969
+ const descriptor = text[pos];
970
+ assertDescriptor(descriptor, text, pos);
971
+ pos += descriptor.length;
972
+ return {
973
+ consumedLength: pos,
974
+ descriptor,
975
+ releasePrevious: false,
976
+ releaseSelf: true,
977
+ repeat: 1
978
+ };
979
+ }
980
+ function readTag(text, pos, startBracket, context) {
981
+ var _text_slice_match, _text_slice_match1;
982
+ const releasePreviousModifier = text[pos] === '/' ? '/' : '';
983
+ pos += releasePreviousModifier.length;
984
+ const escapedDescriptor = startBracket === '{' && text[pos] === '\\';
985
+ pos += Number(escapedDescriptor);
986
+ const descriptor = escapedDescriptor ? text[pos] : (_text_slice_match = text.slice(pos).match(startBracket === '{' ? /^\w+|^[^}>/]/ : /^\w+/)) === null || _text_slice_match === void 0 ? void 0 : _text_slice_match[0];
987
+ assertDescriptor(descriptor, text, pos);
988
+ pos += descriptor.length;
989
+ var _text_slice_match_;
990
+ const repeatModifier = (_text_slice_match_ = (_text_slice_match1 = text.slice(pos).match(/^>\d+/)) === null || _text_slice_match1 === void 0 ? void 0 : _text_slice_match1[0]) !== null && _text_slice_match_ !== void 0 ? _text_slice_match_ : '';
991
+ pos += repeatModifier.length;
992
+ const releaseSelfModifier = text[pos] === '/' || !repeatModifier && text[pos] === '>' ? text[pos] : '';
993
+ pos += releaseSelfModifier.length;
994
+ const expectedEndBracket = bracketDict[startBracket];
995
+ const endBracket = text[pos] === expectedEndBracket ? expectedEndBracket : '';
996
+ if (!endBracket) {
997
+ throw new Error(getErrorMessage([
998
+ !repeatModifier && 'repeat modifier',
999
+ !releaseSelfModifier && 'release modifier',
1000
+ `"${expectedEndBracket}"`
1001
+ ].filter(Boolean).join(' or '), text[pos], text));
1002
+ }
1003
+ pos += endBracket.length;
1004
+ return {
1005
+ consumedLength: pos,
1006
+ descriptor,
1007
+ releasePrevious: !!releasePreviousModifier,
1008
+ repeat: repeatModifier ? Math.max(Number(repeatModifier.substr(1)), 1) : 1,
1009
+ releaseSelf: hasReleaseSelf(releaseSelfModifier, repeatModifier)
1010
+ };
1011
+ }
1012
+ function assertDescriptor(descriptor, text, pos, context) {
1013
+ if (!descriptor) {
1014
+ throw new Error(getErrorMessage('key descriptor', text[pos], text));
1015
+ }
1016
+ }
1017
+ function hasReleaseSelf(releaseSelfModifier, repeatModifier) {
1018
+ if (releaseSelfModifier) {
1019
+ return releaseSelfModifier === '/';
1020
+ }
1021
+ if (repeatModifier) {
1022
+ return false;
1023
+ }
1024
+ }
1025
+ function getErrorMessage(expected, found, text, context) {
1026
+ return `Expected ${expected} but found "${found !== null && found !== void 0 ? found : ''}" in "${text}"
1027
+ See ${`https://testing-library.com/docs/user-event/keyboard`}
1028
+ for more information about how userEvent parses your input.`;
1029
+ }
1030
+
1031
+ var ApiLevel;
1032
+ (function(ApiLevel) {
1033
+ ApiLevel[ApiLevel["Trigger"] = 2] = "Trigger";
1034
+ ApiLevel[ApiLevel["Call"] = 1] = "Call";
1035
+ })(ApiLevel || (ApiLevel = {}));
1036
+
1037
+ var PointerEventsCheckLevel;
1038
+ (function(PointerEventsCheckLevel) {
1039
+ /**
1040
+ * Check pointer events on every user interaction that triggers a bunch of events.
1041
+ * E.g. once for releasing a mouse button even though this triggers `pointerup`, `mouseup`, `click`, etc...
1042
+ */ PointerEventsCheckLevel[PointerEventsCheckLevel["EachTrigger"] = 4] = "EachTrigger";
1043
+ /** Check each target once per call to pointer (related) API */ PointerEventsCheckLevel[PointerEventsCheckLevel["EachApiCall"] = 2] = "EachApiCall";
1044
+ /** Check each event target once */ PointerEventsCheckLevel[PointerEventsCheckLevel["EachTarget"] = 1] = "EachTarget";
1045
+ /** No pointer events check */ PointerEventsCheckLevel[PointerEventsCheckLevel["Never"] = 0] = "Never";
1046
+ })(PointerEventsCheckLevel || (PointerEventsCheckLevel = {}));
1047
+
1048
+ /**
1049
+ * Parse key defintions per `keyboardMap`
1050
+ *
1051
+ * Keys can be referenced by `{key}` or `{special}` as well as physical locations per `[code]`.
1052
+ * Everything else will be interpreted as a typed character - e.g. `a`.
1053
+ * Brackets `{` and `[` can be escaped by doubling - e.g. `foo[[bar` translates to `foo[bar`.
1054
+ * Keeping the key pressed can be written as `{key>}`.
1055
+ * When keeping the key pressed you can choose how long (how many keydown and keypress) the key is pressed `{key>3}`.
1056
+ * You can then release the key per `{key>3/}` or keep it pressed and continue with the next key.
1057
+ */ function parseKeyDef(keyboardMap, text) {
1058
+ const defs = [];
1059
+ do {
1060
+ const { type, descriptor, consumedLength, releasePrevious, releaseSelf = true, repeat } = readNextDescriptor(text);
1061
+ var _keyboardMap_find;
1062
+ const keyDef = (_keyboardMap_find = keyboardMap.find((def)=>{
1063
+ if (type === '[') {
1064
+ var _def_code;
1065
+ return ((_def_code = def.code) === null || _def_code === void 0 ? void 0 : _def_code.toLowerCase()) === descriptor.toLowerCase();
1066
+ } else if (type === '{') {
1067
+ var _def_key;
1068
+ return ((_def_key = def.key) === null || _def_key === void 0 ? void 0 : _def_key.toLowerCase()) === descriptor.toLowerCase();
1069
+ }
1070
+ return def.key === descriptor;
1071
+ })) !== null && _keyboardMap_find !== void 0 ? _keyboardMap_find : {
1072
+ key: 'Unknown',
1073
+ code: 'Unknown',
1074
+ [type === '[' ? 'code' : 'key']: descriptor
1075
+ };
1076
+ defs.push({
1077
+ keyDef,
1078
+ releasePrevious,
1079
+ releaseSelf,
1080
+ repeat
1081
+ });
1082
+ text = text.slice(consumedLength);
1083
+ }while (text)
1084
+ return defs;
1085
+ }
1086
+
1087
+ var DOM_KEY_LOCATION;
1088
+ (function(DOM_KEY_LOCATION) {
1089
+ DOM_KEY_LOCATION[DOM_KEY_LOCATION["STANDARD"] = 0] = "STANDARD";
1090
+ DOM_KEY_LOCATION[DOM_KEY_LOCATION["LEFT"] = 1] = "LEFT";
1091
+ DOM_KEY_LOCATION[DOM_KEY_LOCATION["RIGHT"] = 2] = "RIGHT";
1092
+ DOM_KEY_LOCATION[DOM_KEY_LOCATION["NUMPAD"] = 3] = "NUMPAD";
1093
+ })(DOM_KEY_LOCATION || (DOM_KEY_LOCATION = {}));
1094
+
1095
+ /**
1096
+ * Mapping for a default US-104-QWERTY keyboard
1097
+ */ const defaultKeyMap = [
1098
+ // alphanumeric keys
1099
+ ...'0123456789'.split('').map((c)=>({
1100
+ code: `Digit${c}`,
1101
+ key: c
1102
+ })),
1103
+ ...')!@#$%^&*('.split('').map((c, i)=>({
1104
+ code: `Digit${i}`,
1105
+ key: c,
1106
+ shiftKey: true
1107
+ })),
1108
+ ...'abcdefghijklmnopqrstuvwxyz'.split('').map((c)=>({
1109
+ code: `Key${c.toUpperCase()}`,
1110
+ key: c
1111
+ })),
1112
+ ...'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').map((c)=>({
1113
+ code: `Key${c}`,
1114
+ key: c,
1115
+ shiftKey: true
1116
+ })),
1117
+ // alphanumeric block - functional
1118
+ {
1119
+ code: 'Space',
1120
+ key: ' '
1121
+ },
1122
+ {
1123
+ code: 'AltLeft',
1124
+ key: 'Alt',
1125
+ location: DOM_KEY_LOCATION.LEFT
1126
+ },
1127
+ {
1128
+ code: 'AltRight',
1129
+ key: 'Alt',
1130
+ location: DOM_KEY_LOCATION.RIGHT
1131
+ },
1132
+ {
1133
+ code: 'ShiftLeft',
1134
+ key: 'Shift',
1135
+ location: DOM_KEY_LOCATION.LEFT
1136
+ },
1137
+ {
1138
+ code: 'ShiftRight',
1139
+ key: 'Shift',
1140
+ location: DOM_KEY_LOCATION.RIGHT
1141
+ },
1142
+ {
1143
+ code: 'ControlLeft',
1144
+ key: 'Control',
1145
+ location: DOM_KEY_LOCATION.LEFT
1146
+ },
1147
+ {
1148
+ code: 'ControlRight',
1149
+ key: 'Control',
1150
+ location: DOM_KEY_LOCATION.RIGHT
1151
+ },
1152
+ {
1153
+ code: 'MetaLeft',
1154
+ key: 'Meta',
1155
+ location: DOM_KEY_LOCATION.LEFT
1156
+ },
1157
+ {
1158
+ code: 'MetaRight',
1159
+ key: 'Meta',
1160
+ location: DOM_KEY_LOCATION.RIGHT
1161
+ },
1162
+ {
1163
+ code: 'OSLeft',
1164
+ key: 'OS',
1165
+ location: DOM_KEY_LOCATION.LEFT
1166
+ },
1167
+ {
1168
+ code: 'OSRight',
1169
+ key: 'OS',
1170
+ location: DOM_KEY_LOCATION.RIGHT
1171
+ },
1172
+ {
1173
+ code: 'Tab',
1174
+ key: 'Tab'
1175
+ },
1176
+ {
1177
+ code: 'CapsLock',
1178
+ key: 'CapsLock'
1179
+ },
1180
+ {
1181
+ code: 'Backspace',
1182
+ key: 'Backspace'
1183
+ },
1184
+ {
1185
+ code: 'Enter',
1186
+ key: 'Enter'
1187
+ },
1188
+ // function
1189
+ {
1190
+ code: 'Escape',
1191
+ key: 'Escape'
1192
+ },
1193
+ // arrows
1194
+ {
1195
+ code: 'ArrowUp',
1196
+ key: 'ArrowUp'
1197
+ },
1198
+ {
1199
+ code: 'ArrowDown',
1200
+ key: 'ArrowDown'
1201
+ },
1202
+ {
1203
+ code: 'ArrowLeft',
1204
+ key: 'ArrowLeft'
1205
+ },
1206
+ {
1207
+ code: 'ArrowRight',
1208
+ key: 'ArrowRight'
1209
+ },
1210
+ // control pad
1211
+ {
1212
+ code: 'Home',
1213
+ key: 'Home'
1214
+ },
1215
+ {
1216
+ code: 'End',
1217
+ key: 'End'
1218
+ },
1219
+ {
1220
+ code: 'Delete',
1221
+ key: 'Delete'
1222
+ },
1223
+ {
1224
+ code: 'PageUp',
1225
+ key: 'PageUp'
1226
+ },
1227
+ {
1228
+ code: 'PageDown',
1229
+ key: 'PageDown'
1230
+ },
1231
+ // Special keys that are not part of a default US-layout but included for specific behavior
1232
+ {
1233
+ code: 'Fn',
1234
+ key: 'Fn'
1235
+ },
1236
+ {
1237
+ code: 'Symbol',
1238
+ key: 'Symbol'
1239
+ },
1240
+ {
1241
+ code: 'AltRight',
1242
+ key: 'AltGraph'
1243
+ }
1244
+ ];
1245
+
1246
+ const keyboard = async (context, text) => {
1247
+ function focusIframe() {
1248
+ if (!document.activeElement || document.activeElement.ownerDocument !== document || document.activeElement === document.body) {
1249
+ window.focus();
1250
+ }
1251
+ }
1252
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1253
+ await context.frame.evaluate(focusIframe);
1254
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1255
+ await context.browser.execute(focusIframe);
1256
+ }
1257
+ await keyboardImplementation(
1258
+ context.provider,
1259
+ context.contextId,
1260
+ text,
1261
+ async () => {
1262
+ function selectAll() {
1263
+ const element = document.activeElement;
1264
+ if (element && element.select) {
1265
+ element.select();
1266
+ }
1267
+ }
1268
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1269
+ await context.frame.evaluate(selectAll);
1270
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1271
+ await context.browser.execute(selectAll);
1272
+ } else {
1273
+ throw new TypeError(`Provider "${context.provider.name}" does not support selecting all text`);
1274
+ }
1275
+ },
1276
+ false
1277
+ );
1278
+ };
1279
+ async function keyboardImplementation(provider, contextId, text, selectAll, skipRelease) {
1280
+ const pressed = /* @__PURE__ */ new Set();
1281
+ if (provider instanceof PlaywrightBrowserProvider) {
1282
+ const page = provider.getPage(contextId);
1283
+ const actions = parseKeyDef(defaultKeyMap, text);
1284
+ for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
1285
+ const key = keyDef.key;
1286
+ if (pressed.has(key)) {
1287
+ await page.keyboard.up(key);
1288
+ pressed.delete(key);
1289
+ }
1290
+ if (!releasePrevious) {
1291
+ if (key === "selectall") {
1292
+ await selectAll();
1293
+ continue;
1294
+ }
1295
+ for (let i = 1; i <= repeat; i++) {
1296
+ await page.keyboard.down(key);
1297
+ }
1298
+ if (releaseSelf) {
1299
+ await page.keyboard.up(key);
1300
+ } else {
1301
+ pressed.add(key);
1302
+ }
1303
+ }
1304
+ }
1305
+ if (!skipRelease && pressed.size) {
1306
+ for (const key of pressed) {
1307
+ await page.keyboard.up(key);
1308
+ }
1309
+ }
1310
+ } else if (provider instanceof WebdriverBrowserProvider) {
1311
+ const { Key } = await import('webdriverio');
1312
+ const browser = provider.browser;
1313
+ const actions = parseKeyDef(defaultKeyMap, text);
1314
+ let keyboard2 = browser.action("key");
1315
+ for (const { releasePrevious, releaseSelf, repeat, keyDef } of actions) {
1316
+ let key = keyDef.key;
1317
+ const code = "location" in keyDef ? keyDef.key : keyDef.code;
1318
+ const special = Key[code];
1319
+ if (special) {
1320
+ key = special;
1321
+ }
1322
+ if (pressed.has(key)) {
1323
+ keyboard2.up(key);
1324
+ pressed.delete(key);
1325
+ }
1326
+ if (!releasePrevious) {
1327
+ if (key === "selectall") {
1328
+ await keyboard2.perform();
1329
+ keyboard2 = browser.action("key");
1330
+ await selectAll();
1331
+ continue;
1332
+ }
1333
+ for (let i = 1; i <= repeat; i++) {
1334
+ keyboard2.down(key);
1335
+ }
1336
+ if (releaseSelf) {
1337
+ keyboard2.up(key);
1338
+ } else {
1339
+ pressed.add(key);
1340
+ }
1341
+ }
1342
+ }
1343
+ await keyboard2.perform(skipRelease);
1344
+ }
1345
+ return {
1346
+ pressed
1347
+ };
1348
+ }
1349
+
1350
+ const type = async (context, xpath, text, options = {}) => {
1351
+ const { skipClick = false, skipAutoClose = false } = options;
1352
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1353
+ const { frame } = context;
1354
+ const element = frame.locator(`xpath=${xpath}`);
1355
+ if (!skipClick) {
1356
+ await element.focus();
1357
+ }
1358
+ await keyboardImplementation(
1359
+ context.provider,
1360
+ context.contextId,
1361
+ text,
1362
+ () => element.selectText(),
1363
+ skipAutoClose
1364
+ );
1365
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1366
+ const browser = context.browser;
1367
+ const markedXpath = `//${xpath}`;
1368
+ const element = browser.$(markedXpath);
1369
+ if (!skipClick && !await element.isFocused()) {
1370
+ await element.click();
1371
+ }
1372
+ await keyboardImplementation(
1373
+ context.provider,
1374
+ context.contextId,
1375
+ text,
1376
+ () => browser.execute(() => {
1377
+ const element2 = document.activeElement;
1378
+ if (element2) {
1379
+ element2.select();
1380
+ }
1381
+ }),
1382
+ skipAutoClose
1383
+ );
1384
+ } else {
1385
+ throw new TypeError(`Provider "${context.provider.name}" does not support typing`);
1386
+ }
1387
+ };
1388
+
1389
+ const clear = async (context, xpath) => {
1390
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1391
+ const { frame } = context;
1392
+ const element = frame.locator(`xpath=${xpath}`);
1393
+ await element.clear({
1394
+ timeout: 1e3
1395
+ });
1396
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1397
+ const browser = context.browser;
1398
+ const markedXpath = `//${xpath}`;
1399
+ const element = await browser.$(markedXpath);
1400
+ await element.clearValue();
1401
+ } else {
1402
+ throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
1403
+ }
1404
+ };
1405
+
1406
+ const fill = async (context, xpath, text, options = {}) => {
1407
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1408
+ const { frame } = context;
1409
+ const element = frame.locator(`xpath=${xpath}`);
1410
+ await element.fill(text, { timeout: 1e3, ...options });
1411
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1412
+ const browser = context.browser;
1413
+ const markedXpath = `//${xpath}`;
1414
+ await browser.$(markedXpath).setValue(text);
1415
+ } else {
1416
+ throw new TypeError(`Provider "${context.provider.name}" does not support clearing elements`);
1417
+ }
1418
+ };
1419
+
1420
+ const selectOptions = async (context, xpath, userValues, options = {}) => {
1421
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1422
+ const value = userValues;
1423
+ const { frame } = context;
1424
+ const selectElement = frame.locator(`xpath=${xpath}`);
1425
+ const values = await Promise.all(value.map(async (v) => {
1426
+ if (typeof v === "string") {
1427
+ return v;
1428
+ }
1429
+ const elementHandler = await frame.locator(`xpath=${v.element}`).elementHandle();
1430
+ if (!elementHandler) {
1431
+ throw new Error(`Element not found: ${v.element}`);
1432
+ }
1433
+ return elementHandler;
1434
+ }));
1435
+ await selectElement.selectOption(values, {
1436
+ timeout: 1e3,
1437
+ ...options
1438
+ });
1439
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1440
+ const values = userValues;
1441
+ if (!values.length) {
1442
+ return;
1443
+ }
1444
+ const markedXpath = `//${xpath}`;
1445
+ const browser = context.browser;
1446
+ if (values.length === 1 && "index" in values[0]) {
1447
+ const selectElement = browser.$(markedXpath);
1448
+ await selectElement.selectByIndex(values[0].index);
1449
+ } else {
1450
+ throw new Error(`Provider "webdriverio" doesn't support selecting multiple values at once`);
1451
+ }
1452
+ } else {
1453
+ throw new TypeError(`Provider "${context.provider.name}" doesn't support selectOptions command`);
1454
+ }
1455
+ };
1456
+
1457
+ const tab = async (context, options = {}) => {
1458
+ const provider = context.provider;
1459
+ if (provider instanceof PlaywrightBrowserProvider) {
1460
+ const page = context.page;
1461
+ await page.keyboard.press(options.shift === true ? "Shift+Tab" : "Tab");
193
1462
  return;
194
1463
  }
195
1464
  if (provider instanceof WebdriverBrowserProvider) {
196
- const page = provider.browser;
197
- const markedXpath = `//${xpath}`;
198
- const element = await page.$(markedXpath);
199
- await element.click(options);
1465
+ const { Key } = await import('webdriverio');
1466
+ const browser = context.browser;
1467
+ await browser.keys(options.shift === true ? [Key.Shift, Key.Tab] : [Key.Tab]);
200
1468
  return;
201
1469
  }
202
- throw new Error(`Provider "${provider.name}" doesn't support click command`);
1470
+ throw new Error(`Provider "${provider.name}" doesn't support tab command`);
1471
+ };
1472
+
1473
+ const dragAndDrop = async (context, source, target, options) => {
1474
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1475
+ await context.frame.dragAndDrop(
1476
+ `xpath=${source}`,
1477
+ `xpath=${target}`,
1478
+ {
1479
+ timeout: 1e3,
1480
+ ...options
1481
+ }
1482
+ );
1483
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1484
+ const sourceXpath = `//${source}`;
1485
+ const targetXpath = `//${target}`;
1486
+ const $source = context.browser.$(sourceXpath);
1487
+ const $target = context.browser.$(targetXpath);
1488
+ const duration = options?.duration ?? 10;
1489
+ await context.browser.action("pointer").move({ duration: 0, origin: $source, x: 0, y: 0 }).down({ button: 0 }).move({ duration: 0, origin: "pointer", x: 0, y: 0 }).pause(duration).move({ duration: 0, origin: $target, x: 0, y: 0 }).move({ duration: 0, origin: "pointer", x: 1, y: 0 }).move({ duration: 0, origin: "pointer", x: -1, y: 0 }).up({ button: 0 }).perform();
1490
+ } else {
1491
+ throw new TypeError(`Provider "${context.provider.name}" does not support dragging elements`);
1492
+ }
1493
+ };
1494
+
1495
+ const hover = async (context, xpath, options = {}) => {
1496
+ if (context.provider instanceof PlaywrightBrowserProvider) {
1497
+ await context.frame.locator(`xpath=${xpath}`).hover({
1498
+ timeout: 1e3,
1499
+ ...options
1500
+ });
1501
+ } else if (context.provider instanceof WebdriverBrowserProvider) {
1502
+ const browser = context.browser;
1503
+ const markedXpath = `//${xpath}`;
1504
+ await browser.$(markedXpath).moveTo(options);
1505
+ } else {
1506
+ throw new TypeError(`Provider "${context.provider.name}" does not support hover`);
1507
+ }
203
1508
  };
204
1509
 
205
1510
  function assertFileAccess(path, project) {
206
- if (!isFileServingAllowed(path, project.server) && !isFileServingAllowed(path, project.ctx.server))
207
- throw new Error(`Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`);
1511
+ if (!isFileServingAllowed$1(path, project.server) && !isFileServingAllowed$1(path, project.ctx.server)) {
1512
+ throw new Error(
1513
+ `Access denied to "${path}". See Vite config documentation for "server.fs": https://vitejs.dev/config/server-options.html#server-fs-strict.`
1514
+ );
1515
+ }
208
1516
  }
209
1517
  const readFile = async ({ project, testPath = process.cwd() }, path, options = {}) => {
210
1518
  const filepath = resolve$1(dirname$1(testPath), path);
211
1519
  assertFileAccess(filepath, project);
212
- if (typeof options === "object" && !options.encoding)
1520
+ if (typeof options === "object" && !options.encoding) {
213
1521
  options.encoding = "utf-8";
1522
+ }
214
1523
  return promises.readFile(filepath, options);
215
1524
  };
216
1525
  const writeFile = async ({ project, testPath = process.cwd() }, path, data, options) => {
217
1526
  const filepath = resolve$1(dirname$1(testPath), path);
218
1527
  assertFileAccess(filepath, project);
219
1528
  const dir = dirname$1(filepath);
220
- if (!fs.existsSync(dir))
1529
+ if (!fs.existsSync(dir)) {
221
1530
  await promises.mkdir(dir, { recursive: true });
1531
+ }
222
1532
  await promises.writeFile(filepath, data, options);
223
1533
  };
224
1534
  const removeFile = async ({ project, testPath = process.cwd() }, path) => {
@@ -227,87 +1537,25 @@ const removeFile = async ({ project, testPath = process.cwd() }, path) => {
227
1537
  await promises.rm(filepath);
228
1538
  };
229
1539
 
230
- function isObject(payload) {
231
- return payload != null && typeof payload === "object";
232
- }
233
- function isSendKeysPayload(payload) {
234
- const validOptions = ["type", "press", "down", "up"];
235
- if (!isObject(payload))
236
- throw new Error("You must provide a `SendKeysPayload` object");
237
- const numberOfValidOptions = Object.keys(payload).filter(
238
- (key) => validOptions.includes(key)
239
- ).length;
240
- const unknownOptions = Object.keys(payload).filter((key) => !validOptions.includes(key));
241
- if (numberOfValidOptions > 1) {
242
- throw new Error(
243
- `You must provide ONLY one of the following properties to pass to the browser runner: ${validOptions.join(
244
- ", "
245
- )}.`
246
- );
247
- }
248
- if (numberOfValidOptions === 0) {
249
- throw new Error(
250
- `You must provide one of the following properties to pass to the browser runner: ${validOptions.join(
251
- ", "
252
- )}.`
253
- );
254
- }
255
- if (unknownOptions.length > 0)
256
- throw new Error(`Unknown options \`${unknownOptions.join(", ")}\` present.`);
257
- return true;
258
- }
259
- function isTypePayload(payload) {
260
- return "type" in payload;
261
- }
262
- function isPressPayload(payload) {
263
- return "press" in payload;
264
- }
265
- function isDownPayload(payload) {
266
- return "down" in payload;
267
- }
268
- function isUpPayload(payload) {
269
- return "up" in payload;
270
- }
271
- const sendKeys = async ({ provider, contextId }, payload) => {
272
- if (!isSendKeysPayload(payload) || !payload)
273
- throw new Error("You must provide a `SendKeysPayload` object");
274
- if (provider instanceof PlaywrightBrowserProvider) {
275
- const page = provider.getPage(contextId);
276
- if (isTypePayload(payload))
277
- await page.keyboard.type(payload.type);
278
- else if (isPressPayload(payload))
279
- await page.keyboard.press(payload.press);
280
- else if (isDownPayload(payload))
281
- await page.keyboard.down(payload.down);
282
- else if (isUpPayload(payload))
283
- await page.keyboard.up(payload.up);
284
- } else if (provider instanceof WebdriverBrowserProvider) {
285
- const browser = provider.browser;
286
- if (isTypePayload(payload))
287
- await browser.keys(payload.type.split(""));
288
- else if (isPressPayload(payload))
289
- await browser.keys([payload.press]);
290
- else
291
- throw new Error('Only "press" and "type" are supported by webdriverio.');
292
- } else {
293
- throw new TypeError(`"sendKeys" is not supported for ${provider.name} browser provider.`);
294
- }
295
- };
296
-
297
1540
  const screenshot = async (context, name, options = {}) => {
298
- if (!context.testPath)
1541
+ if (!context.testPath) {
299
1542
  throw new Error(`Cannot take a screenshot without a test path`);
300
- const path = resolveScreenshotPath(context.testPath, name, context.project.config);
1543
+ }
1544
+ const path = resolveScreenshotPath(
1545
+ context.testPath,
1546
+ name,
1547
+ context.project.config
1548
+ );
301
1549
  const savePath = normalize$1(path);
302
1550
  await mkdir(dirname(path), { recursive: true });
303
1551
  if (context.provider instanceof PlaywrightBrowserProvider) {
304
1552
  if (options.element) {
305
1553
  const { element: elementXpath, ...config } = options;
306
- const iframe = context.tester;
1554
+ const iframe = context.frame;
307
1555
  const element = iframe.locator(`xpath=${elementXpath}`);
308
1556
  await element.screenshot({ ...config, path: savePath });
309
1557
  } else {
310
- await context.body.screenshot({ ...options, path: savePath });
1558
+ await context.frame.locator("body").screenshot({ ...options, path: savePath });
311
1559
  }
312
1560
  return path;
313
1561
  }
@@ -323,7 +1571,9 @@ const screenshot = async (context, name, options = {}) => {
323
1571
  await element.saveScreenshot(savePath);
324
1572
  return path;
325
1573
  }
326
- throw new Error(`Provider "${context.provider.name}" does not support screenshots`);
1574
+ throw new Error(
1575
+ `Provider "${context.provider.name}" does not support screenshots`
1576
+ );
327
1577
  };
328
1578
  function resolveScreenshotPath(testPath, name, config) {
329
1579
  const dir = dirname(testPath);
@@ -343,43 +1593,61 @@ var builtinCommands = {
343
1593
  readFile,
344
1594
  removeFile,
345
1595
  writeFile,
346
- sendKeys,
347
1596
  __vitest_click: click,
348
- __vitest_screenshot: screenshot
1597
+ __vitest_dblClick: dblClick,
1598
+ __vitest_screenshot: screenshot,
1599
+ __vitest_type: type,
1600
+ __vitest_clear: clear,
1601
+ __vitest_fill: fill,
1602
+ __vitest_tab: tab,
1603
+ __vitest_keyboard: keyboard,
1604
+ __vitest_selectOptions: selectOptions,
1605
+ __vitest_dragAndDrop: dragAndDrop,
1606
+ __vitest_hover: hover
349
1607
  };
350
1608
 
351
1609
  const VIRTUAL_ID_CONTEXT = "\0@vitest/browser/context";
352
1610
  const ID_CONTEXT = "@vitest/browser/context";
353
1611
  const __dirname = dirname(fileURLToPath(import.meta.url));
354
- function BrowserContext(project) {
1612
+ function BrowserContext(server) {
1613
+ const project = server.project;
355
1614
  project.config.browser.commands ??= {};
356
- for (const [name, command] of Object.entries(builtinCommands))
1615
+ for (const [name, command] of Object.entries(builtinCommands)) {
357
1616
  project.config.browser.commands[name] ??= command;
1617
+ }
358
1618
  for (const command in project.config.browser.commands) {
359
- if (!/^[a-z_$][\w$]*$/i.test(command))
360
- throw new Error(`Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`);
1619
+ if (!/^[a-z_$][\w$]*$/i.test(command)) {
1620
+ throw new Error(
1621
+ `Invalid command name "${command}". Only alphanumeric characters, $ and _ are allowed.`
1622
+ );
1623
+ }
361
1624
  }
362
1625
  return {
363
1626
  name: "vitest:browser:virtual-module:context",
364
1627
  enforce: "pre",
365
1628
  resolveId(id) {
366
- if (id === ID_CONTEXT)
1629
+ if (id === ID_CONTEXT) {
367
1630
  return VIRTUAL_ID_CONTEXT;
1631
+ }
368
1632
  },
369
1633
  load(id) {
370
- if (id === VIRTUAL_ID_CONTEXT)
371
- return generateContextFile.call(this, project);
1634
+ if (id === VIRTUAL_ID_CONTEXT) {
1635
+ return generateContextFile.call(this, server);
1636
+ }
372
1637
  }
373
1638
  };
374
1639
  }
375
- async function generateContextFile(project) {
376
- const commands = Object.keys(project.config.browser.commands ?? {});
1640
+ async function generateContextFile(server) {
1641
+ const commands = Object.keys(server.project.config.browser.commands ?? {});
377
1642
  const filepathCode = "__vitest_worker__.filepath || __vitest_worker__.current?.file?.filepath || undefined";
378
- const provider = project.browserProvider;
1643
+ const provider = server.provider;
379
1644
  const commandsCode = commands.filter((command) => !command.startsWith("__vitest")).map((command) => {
380
1645
  return ` ["${command}"]: (...args) => rpc().triggerCommand(contextId, "${command}", filepath(), args),`;
381
1646
  }).join("\n");
382
- const userEventNonProviderImport = await getUserEventImport(provider, this.resolve.bind(this));
1647
+ const userEventNonProviderImport = await getUserEventImport(
1648
+ provider,
1649
+ this.resolve.bind(this)
1650
+ );
383
1651
  const distContextPath = slash(`/@fs/${resolve(__dirname, "context.js")}`);
384
1652
  return `
385
1653
  import { page, userEvent as __userEvent_CDP__ } from '${distContextPath}'
@@ -392,23 +1660,42 @@ export const server = {
392
1660
  platform: ${JSON.stringify(process.platform)},
393
1661
  version: ${JSON.stringify(process.version)},
394
1662
  provider: ${JSON.stringify(provider.name)},
395
- browser: ${JSON.stringify(project.config.browser.name)},
1663
+ browser: ${JSON.stringify(server.project.config.browser.name)},
396
1664
  commands: {
397
1665
  ${commandsCode}
398
1666
  }
399
1667
  }
400
1668
  export const commands = server.commands
401
- export const userEvent = ${provider.name === "preview" ? "__vitest_user_event__" : "__userEvent_CDP__"}
1669
+ export const userEvent = ${getUserEvent(provider)}
402
1670
  export { page }
403
1671
  `;
404
1672
  }
1673
+ function getUserEvent(provider) {
1674
+ if (provider.name !== "preview") {
1675
+ return "__userEvent_CDP__";
1676
+ }
1677
+ return `{
1678
+ ...__vitest_user_event__,
1679
+ fill: async (element, text) => {
1680
+ await __vitest_user_event__.clear(element)
1681
+ await __vitest_user_event__.type(element, text)
1682
+ },
1683
+ dragAndDrop: async () => {
1684
+ throw new Error('Provider "preview" does not support dragging elements')
1685
+ }
1686
+ }`;
1687
+ }
405
1688
  async function getUserEventImport(provider, resolve2) {
406
- if (provider.name !== "preview")
1689
+ if (provider.name !== "preview") {
407
1690
  return "";
1691
+ }
408
1692
  const resolved = await resolve2("@testing-library/user-event", __dirname);
409
- if (!resolved)
1693
+ if (!resolved) {
410
1694
  throw new Error(`Failed to resolve user-event package from ${__dirname}`);
411
- return `import { userEvent as __vitest_user_event__ } from '${slash(`/@fs/${resolved.id}`)}'`;
1695
+ }
1696
+ return `import { userEvent as __vitest_user_event__ } from '${slash(
1697
+ `/@fs/${resolved.id}`
1698
+ )}'`;
412
1699
  }
413
1700
 
414
1701
  function injectDynamicImport(code, id, parse) {
@@ -427,7 +1714,11 @@ ${err.message}`);
427
1714
  },
428
1715
  onDynamicImport(node) {
429
1716
  const replace = "__vitest_browser_runner__.wrapModule(() => import(";
430
- s.overwrite(node.start, node.source.start, replace);
1717
+ s.overwrite(
1718
+ node.start,
1719
+ node.source.start,
1720
+ replace
1721
+ );
431
1722
  s.overwrite(node.end - 1, node.end, "))");
432
1723
  }
433
1724
  });
@@ -444,159 +1735,186 @@ var DynamicImport = () => {
444
1735
  name: "vitest:browser:esm-injector",
445
1736
  enforce: "post",
446
1737
  transform(source, id) {
447
- if (!regexDynamicImport.test(source))
1738
+ if (!regexDynamicImport.test(source)) {
448
1739
  return;
1740
+ }
449
1741
  return injectDynamicImport(source, id, this.parse);
450
1742
  }
451
1743
  };
452
1744
  };
453
1745
 
454
- function defineBrowserCommand(fn) {
455
- return fn;
1746
+ async function resolveOrchestrator(server, url, res) {
1747
+ const project = server.project;
1748
+ let contextId = url.searchParams.get("contextId");
1749
+ if (!contextId) {
1750
+ const contexts = [...server.state.orchestrators.keys()];
1751
+ contextId = contexts[contexts.length - 1] ?? "none";
1752
+ }
1753
+ const files = server.state.getContext(contextId)?.files ?? [];
1754
+ const config = server.getSerializableConfig();
1755
+ const injectorJs = typeof server.injectorJs === "string" ? server.injectorJs : await server.injectorJs;
1756
+ const injector = replacer(injectorJs, {
1757
+ __VITEST_PROVIDER__: JSON.stringify(server.provider.name),
1758
+ __VITEST_CONFIG__: JSON.stringify(config),
1759
+ __VITEST_VITE_CONFIG__: JSON.stringify({
1760
+ root: server.vite.config.root
1761
+ }),
1762
+ __VITEST_FILES__: JSON.stringify(files),
1763
+ __VITEST_TYPE__: '"orchestrator"',
1764
+ __VITEST_CONTEXT_ID__: JSON.stringify(contextId),
1765
+ __VITEST_PROVIDED_CONTEXT__: "{}"
1766
+ });
1767
+ res.removeHeader("Content-Security-Policy");
1768
+ if (!server.orchestratorScripts) {
1769
+ server.orchestratorScripts = await server.formatScripts(
1770
+ project.config.browser.orchestratorScripts
1771
+ );
1772
+ }
1773
+ let baseHtml = typeof server.orchestratorHtml === "string" ? server.orchestratorHtml : await server.orchestratorHtml;
1774
+ if (project.config.browser.ui) {
1775
+ const manifestContent = server.manifest instanceof Promise ? await server.manifest : server.manifest;
1776
+ const jsEntry = manifestContent["orchestrator.html"].file;
1777
+ const base = server.vite.config.base || "/";
1778
+ baseHtml = baseHtml.replaceAll("./assets/", `${base}__vitest__/assets/`).replace(
1779
+ "<!-- !LOAD_METADATA! -->",
1780
+ [
1781
+ "<script>{__VITEST_INJECTOR__}<\/script>",
1782
+ "{__VITEST_SCRIPTS__}",
1783
+ `<script type="module" crossorigin src="${base}${jsEntry}"><\/script>`
1784
+ ].join("\n")
1785
+ );
1786
+ }
1787
+ return replacer(baseHtml, {
1788
+ __VITEST_FAVICON__: server.faviconUrl,
1789
+ __VITEST_TITLE__: "Vitest Browser Runner",
1790
+ __VITEST_SCRIPTS__: server.orchestratorScripts,
1791
+ __VITEST_INJECTOR__: injector,
1792
+ __VITEST_CONTEXT_ID__: JSON.stringify(contextId)
1793
+ });
1794
+ }
1795
+
1796
+ async function resolveTester(server, url, res) {
1797
+ const csp = res.getHeader("Content-Security-Policy");
1798
+ if (typeof csp === "string") {
1799
+ res.setHeader(
1800
+ "Content-Security-Policy",
1801
+ csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *")
1802
+ );
1803
+ }
1804
+ const { contextId, testFile } = server.resolveTesterUrl(url.pathname);
1805
+ const project = server.project;
1806
+ const state = server.state;
1807
+ const testFiles = await project.globTestFiles();
1808
+ const tests = testFile === "__vitest_all__" || !testFiles.includes(testFile) ? "__vitest_browser_runner__.files" : JSON.stringify([testFile]);
1809
+ const iframeId = JSON.stringify(testFile);
1810
+ const files = state.getContext(contextId)?.files ?? [];
1811
+ const injectorJs = typeof server.injectorJs === "string" ? server.injectorJs : await server.injectorJs;
1812
+ const config = server.getSerializableConfig();
1813
+ const injector = replacer(injectorJs, {
1814
+ __VITEST_PROVIDER__: JSON.stringify(server.provider.name),
1815
+ __VITEST_CONFIG__: JSON.stringify(config),
1816
+ __VITEST_FILES__: JSON.stringify(files),
1817
+ __VITEST_VITE_CONFIG__: JSON.stringify({
1818
+ root: server.vite.config.root
1819
+ }),
1820
+ __VITEST_TYPE__: '"tester"',
1821
+ __VITEST_CONTEXT_ID__: JSON.stringify(contextId),
1822
+ __VITEST_PROVIDED_CONTEXT__: JSON.stringify(stringify(project.getProvidedContext()))
1823
+ });
1824
+ if (!server.testerScripts) {
1825
+ const testerScripts = await server.formatScripts(
1826
+ project.config.browser.testerScripts
1827
+ );
1828
+ const clientScript = `<script type="module" src="${config.base || "/"}@vite/client"><\/script>`;
1829
+ const stateJs = typeof server.stateJs === "string" ? server.stateJs : await server.stateJs;
1830
+ const stateScript = `<script type="module">${stateJs}<\/script>`;
1831
+ server.testerScripts = `${stateScript}${clientScript}${testerScripts}`;
1832
+ }
1833
+ const testerHtml = typeof server.testerHtml === "string" ? server.testerHtml : await server.testerHtml;
1834
+ return replacer(testerHtml, {
1835
+ __VITEST_FAVICON__: server.faviconUrl,
1836
+ __VITEST_TITLE__: "Vitest Browser Tester",
1837
+ __VITEST_SCRIPTS__: server.testerScripts,
1838
+ __VITEST_INJECTOR__: injector,
1839
+ __VITEST_APPEND__: `<script type="module">
1840
+ __vitest_browser_runner__.runningFiles = ${tests}
1841
+ __vitest_browser_runner__.iframeId = ${iframeId}
1842
+ __vitest_browser_runner__.runTests(__vitest_browser_runner__.runningFiles)
1843
+ <\/script>`
1844
+ });
456
1845
  }
457
1846
 
458
- var index = (project, base = "/") => {
1847
+ var BrowserPlugin = (browserServer, base = "/") => {
459
1848
  const pkgRoot = resolve(fileURLToPath(import.meta.url), "../..");
460
1849
  const distRoot = resolve(pkgRoot, "dist");
1850
+ const project = browserServer.project;
461
1851
  return [
462
1852
  {
463
1853
  enforce: "pre",
464
1854
  name: "vitest:browser",
465
- async config(viteConfig) {
466
- // Enables using ignore hint for coverage providers with @preserve keyword
467
- if (viteConfig.esbuild !== false) {
468
- viteConfig.esbuild ||= {};
469
- viteConfig.esbuild.legalComments = "inline";
470
- }
471
- },
472
1855
  async configureServer(server) {
473
- const testerHtml = readFile$1(resolve(distRoot, "client/tester.html"), "utf8");
474
- const orchestratorHtml = project.config.browser.ui ? readFile$1(resolve(distRoot, "client/__vitest__/index.html"), "utf8") : readFile$1(resolve(distRoot, "client/orchestrator.html"), "utf8");
475
- const injectorJs = readFile$1(resolve(distRoot, "client/esm-client-injector.js"), "utf8");
476
- const manifest = (async () => {
477
- return JSON.parse(await readFile$1(`${distRoot}/client/.vite/manifest.json`, "utf8"));
478
- })();
479
- const favicon = `${base}favicon.svg`;
480
- const testerPrefix = `${base}__vitest_test__/__test__/`;
481
- server.middlewares.use((_req, res, next) => {
1856
+ browserServer.setServer(server);
1857
+ server.middlewares.use(function vitestHeaders(_req, res, next) {
482
1858
  const headers = server.config.server.headers;
483
1859
  if (headers) {
484
- for (const name in headers)
1860
+ for (const name in headers) {
485
1861
  res.setHeader(name, headers[name]);
1862
+ }
486
1863
  }
487
1864
  next();
488
1865
  });
489
- let orchestratorScripts;
490
- let testerScripts;
491
- server.middlewares.use(async (req, res, next) => {
492
- if (!req.url)
1866
+ server.middlewares.use(async function vitestBrowserMode(req, res, next) {
1867
+ if (!req.url) {
493
1868
  return next();
1869
+ }
494
1870
  const url = new URL(req.url, "http://localhost");
495
- if (!url.pathname.startsWith(testerPrefix) && url.pathname !== base)
1871
+ if (!url.pathname.startsWith(browserServer.prefixTesterUrl) && url.pathname !== base) {
496
1872
  return next();
497
- res.setHeader("Cache-Control", "no-cache, max-age=0, must-revalidate");
1873
+ }
1874
+ res.setHeader(
1875
+ "Cache-Control",
1876
+ "no-cache, max-age=0, must-revalidate"
1877
+ );
498
1878
  res.setHeader("Content-Type", "text/html; charset=utf-8");
499
- const config = wrapConfig(project.getSerializableConfig());
500
- config.env ??= {};
501
- config.env.VITEST_BROWSER_DEBUG = process.env.VITEST_BROWSER_DEBUG || "";
502
1879
  res.removeHeader("X-Frame-Options");
503
1880
  if (url.pathname === base) {
504
- let contextId2 = url.searchParams.get("contextId");
505
- if (!contextId2)
506
- contextId2 = project.browserState.keys().next().value ?? "none";
507
- const files2 = project.browserState.get(contextId2)?.files ?? [];
508
- const injector2 = replacer(await injectorJs, {
509
- __VITEST_CONFIG__: JSON.stringify(config),
510
- __VITEST_VITE_CONFIG__: JSON.stringify({
511
- root: project.browser.config.root
512
- }),
513
- __VITEST_FILES__: JSON.stringify(files2),
514
- __VITEST_TYPE__: url.pathname === base ? '"orchestrator"' : '"tester"',
515
- __VITEST_CONTEXT_ID__: JSON.stringify(contextId2)
516
- });
517
- res.removeHeader("Content-Security-Policy");
518
- if (!orchestratorScripts)
519
- orchestratorScripts = await formatScripts(project.config.browser.orchestratorScripts, server);
520
- let baseHtml = await orchestratorHtml;
521
- if (project.config.browser.ui) {
522
- const manifestContent = await manifest;
523
- const jsEntry = manifestContent["orchestrator.html"].file;
524
- baseHtml = baseHtml.replaceAll("./assets/", `${base}__vitest__/assets/`).replace(
525
- "<!-- !LOAD_METADATA! -->",
526
- [
527
- "<script>{__VITEST_INJECTOR__}<\/script>",
528
- "{__VITEST_SCRIPTS__}",
529
- `<script type="module" crossorigin src="${jsEntry}"><\/script>`
530
- ].join("\n")
531
- );
532
- }
533
- const html2 = replacer(baseHtml, {
534
- __VITEST_FAVICON__: favicon,
535
- __VITEST_TITLE__: "Vitest Browser Runner",
536
- __VITEST_SCRIPTS__: orchestratorScripts,
537
- __VITEST_INJECTOR__: injector2,
538
- __VITEST_CONTEXT_ID__: JSON.stringify(contextId2)
539
- });
1881
+ const html2 = await resolveOrchestrator(browserServer, url, res);
540
1882
  res.write(html2, "utf-8");
541
1883
  res.end();
542
1884
  return;
543
1885
  }
544
- const csp = res.getHeader("Content-Security-Policy");
545
- if (typeof csp === "string") {
546
- res.setHeader("Content-Security-Policy", csp.replace(/frame-ancestors [^;]+/, "frame-ancestors *"));
547
- }
548
- const [contextId, testFile] = url.pathname.slice(testerPrefix.length).split("/");
549
- const decodedTestFile = decodeURIComponent(testFile);
550
- const testFiles = await project.globTestFiles();
551
- const tests = decodedTestFile === "__vitest_all__" || !testFiles.includes(decodedTestFile) ? "__vitest_browser_runner__.files" : JSON.stringify([decodedTestFile]);
552
- const iframeId = JSON.stringify(decodedTestFile);
553
- const files = project.browserState.get(contextId)?.files ?? [];
554
- const injector = replacer(await injectorJs, {
555
- __VITEST_CONFIG__: JSON.stringify(config),
556
- __VITEST_FILES__: JSON.stringify(files),
557
- __VITEST_VITE_CONFIG__: JSON.stringify({
558
- root: project.browser.config.root
559
- }),
560
- __VITEST_TYPE__: url.pathname === base ? '"orchestrator"' : '"tester"',
561
- __VITEST_CONTEXT_ID__: JSON.stringify(contextId)
562
- });
563
- if (!testerScripts)
564
- testerScripts = await formatScripts(project.config.browser.testerScripts, server);
565
- const html = replacer(await testerHtml, {
566
- __VITEST_FAVICON__: favicon,
567
- __VITEST_TITLE__: "Vitest Browser Tester",
568
- __VITEST_SCRIPTS__: testerScripts,
569
- __VITEST_INJECTOR__: injector,
570
- __VITEST_APPEND__: (
571
- // TODO: have only a single global variable to not pollute the global scope
572
- `<script type="module">
573
- __vitest_browser_runner__.runningFiles = ${tests}
574
- __vitest_browser_runner__.iframeId = ${iframeId}
575
- __vitest_browser_runner__.runTests(__vitest_browser_runner__.runningFiles)
576
- <\/script>`
577
- )
578
- });
1886
+ const html = await resolveTester(browserServer, url, res);
579
1887
  res.write(html, "utf-8");
580
1888
  res.end();
581
1889
  });
582
1890
  server.middlewares.use(
583
- base,
584
- sirv(resolve(distRoot, "client"), {
585
- single: false,
586
- dev: true
587
- })
1891
+ `${base}favicon.svg`,
1892
+ (_, res) => {
1893
+ const content = readFileSync(resolve(distRoot, "client/favicon.svg"));
1894
+ res.write(content, "utf-8");
1895
+ res.end();
1896
+ }
588
1897
  );
589
1898
  const coverageFolder = resolveCoverageFolder(project);
590
1899
  const coveragePath = coverageFolder ? coverageFolder[1] : void 0;
591
- if (coveragePath && base === coveragePath)
592
- throw new Error(`The ui base path and the coverage path cannot be the same: ${base}, change coverage.reportsDirectory`);
593
- coverageFolder && server.middlewares.use(coveragePath, sirv(coverageFolder[0], {
594
- single: true,
595
- dev: true,
596
- setHeaders: (res) => {
597
- res.setHeader("Cache-Control", "public,max-age=0,must-revalidate");
598
- }
599
- }));
1900
+ if (coveragePath && base === coveragePath) {
1901
+ throw new Error(
1902
+ `The ui base path and the coverage path cannot be the same: ${base}, change coverage.reportsDirectory`
1903
+ );
1904
+ }
1905
+ coverageFolder && server.middlewares.use(
1906
+ coveragePath,
1907
+ sirv(coverageFolder[0], {
1908
+ single: true,
1909
+ dev: true,
1910
+ setHeaders: (res) => {
1911
+ res.setHeader(
1912
+ "Cache-Control",
1913
+ "public,max-age=0,must-revalidate"
1914
+ );
1915
+ }
1916
+ })
1917
+ );
600
1918
  }
601
1919
  },
602
1920
  {
@@ -604,20 +1922,19 @@ var index = (project, base = "/") => {
604
1922
  enforce: "pre",
605
1923
  async config() {
606
1924
  const allTestFiles = await project.globTestFiles();
607
- const browserTestFiles = allTestFiles.filter((file) => getFilePoolName(project, file) === "browser");
1925
+ const browserTestFiles = allTestFiles.filter(
1926
+ (file) => getFilePoolName(project, file) === "browser"
1927
+ );
608
1928
  const setupFiles = toArray(project.config.setupFiles);
609
- const vitestPaths = [
610
- resolve(distDir, "index.js"),
611
- resolve(distDir, "browser.js"),
612
- resolve(distDir, "runners.js"),
613
- resolve(distDir, "utils.js")
614
- ];
615
1929
  return {
616
1930
  optimizeDeps: {
617
1931
  entries: [
618
1932
  ...browserTestFiles,
619
1933
  ...setupFiles,
620
- ...vitestPaths
1934
+ resolve(distDir, "index.js"),
1935
+ resolve(distDir, "browser.js"),
1936
+ resolve(distDir, "runners.js"),
1937
+ resolve(distDir, "utils.js")
621
1938
  ],
622
1939
  exclude: [
623
1940
  "vitest",
@@ -625,6 +1942,7 @@ var index = (project, base = "/") => {
625
1942
  "vitest/browser",
626
1943
  "vitest/runners",
627
1944
  "@vitest/utils",
1945
+ "@vitest/utils/source-map",
628
1946
  "@vitest/runner",
629
1947
  "@vitest/spy",
630
1948
  "@vitest/utils/error",
@@ -655,36 +1973,86 @@ var index = (project, base = "/") => {
655
1973
  };
656
1974
  },
657
1975
  async resolveId(id) {
658
- if (!/\?browserv=\w+$/.test(id))
1976
+ if (!/\?browserv=\w+$/.test(id)) {
659
1977
  return;
1978
+ }
660
1979
  let useId = id.slice(0, id.lastIndexOf("?"));
661
- if (useId.startsWith("/@fs/"))
1980
+ if (useId.startsWith("/@fs/")) {
662
1981
  useId = useId.slice(5);
663
- if (/^\w:/.test(useId))
1982
+ }
1983
+ if (/^\w:/.test(useId)) {
664
1984
  useId = useId.replace(/\\/g, "/");
1985
+ }
665
1986
  return useId;
666
1987
  }
667
1988
  },
668
1989
  {
669
1990
  name: "vitest:browser:resolve-virtual",
670
1991
  async resolveId(rawId) {
671
- if (rawId.startsWith("/__virtual_vitest__:")) {
672
- let id = rawId.slice("/__virtual_vitest__:".length);
673
- if (id === "mocker-worker.js")
674
- id = "msw/mockServiceWorker.js";
675
- const resolved = await this.resolve(
676
- id,
677
- distRoot,
678
- {
679
- skipSelf: true
680
- }
681
- );
1992
+ if (rawId.startsWith("/__virtual_vitest__")) {
1993
+ const url = new URL(rawId, "http://localhost");
1994
+ if (!url.searchParams.has("id")) {
1995
+ throw new TypeError(`Invalid virtual module id: ${rawId}, requires "id" query.`);
1996
+ }
1997
+ const id = decodeURIComponent(url.searchParams.get("id"));
1998
+ const resolved = await this.resolve(id, distRoot, {
1999
+ skipSelf: true
2000
+ });
682
2001
  return resolved;
683
2002
  }
2003
+ if (rawId === "/__vitest_msw__") {
2004
+ return this.resolve("msw/mockServiceWorker.js", distRoot, {
2005
+ skipSelf: true
2006
+ });
2007
+ }
684
2008
  }
685
2009
  },
686
- BrowserContext(project),
2010
+ {
2011
+ name: "vitest:browser:assets",
2012
+ resolveId(id) {
2013
+ if (id.startsWith("/__vitest_browser__/") || id.startsWith("/__vitest__/")) {
2014
+ return resolve(distRoot, "client", id.slice(1));
2015
+ }
2016
+ }
2017
+ },
2018
+ BrowserContext(browserServer),
687
2019
  DynamicImport(),
2020
+ {
2021
+ name: "vitest:browser:config",
2022
+ enforce: "post",
2023
+ async config(viteConfig) {
2024
+ // Enables using ignore hint for coverage providers with @preserve keyword
2025
+ if (viteConfig.esbuild !== false) {
2026
+ viteConfig.esbuild ||= {};
2027
+ viteConfig.esbuild.legalComments = "inline";
2028
+ }
2029
+ const server = resolveApiServerConfig(
2030
+ viteConfig.test?.browser || {},
2031
+ defaultBrowserPort
2032
+ ) || {
2033
+ port: defaultBrowserPort
2034
+ };
2035
+ server.middlewareMode = false;
2036
+ viteConfig.server = {
2037
+ ...viteConfig.server,
2038
+ ...server,
2039
+ open: false
2040
+ };
2041
+ viteConfig.server.fs ??= {};
2042
+ viteConfig.server.fs.allow = viteConfig.server.fs.allow || [];
2043
+ viteConfig.server.fs.allow.push(
2044
+ ...resolveFsAllow(
2045
+ project.ctx.config.root,
2046
+ project.ctx.server.config.configFile
2047
+ )
2048
+ );
2049
+ return {
2050
+ resolve: {
2051
+ alias: viteConfig.test?.alias
2052
+ }
2053
+ };
2054
+ }
2055
+ },
688
2056
  // TODO: remove this when @testing-library/vue supports ESM
689
2057
  {
690
2058
  name: "vitest:browser:support-vue-testing-library",
@@ -698,7 +2066,9 @@ var index = (project, base = "/") => {
698
2066
  setup(build) {
699
2067
  const _require = createRequire(import.meta.url);
700
2068
  build.onResolve({ filter: /@vue\/test-utils/ }, (args) => {
701
- const resolved = _require.resolve(args.path, { paths: [args.importer] });
2069
+ const resolved = _require.resolve(args.path, {
2070
+ paths: [args.importer]
2071
+ });
702
2072
  return { path: resolved };
703
2073
  });
704
2074
  }
@@ -713,43 +2083,148 @@ var index = (project, base = "/") => {
713
2083
  };
714
2084
  function resolveCoverageFolder(project) {
715
2085
  const options = project.ctx.config;
716
- const htmlReporter = options.coverage?.enabled ? options.coverage.reporter.find((reporter) => {
717
- if (typeof reporter === "string")
2086
+ const htmlReporter = options.coverage?.enabled ? toArray(options.coverage.reporter).find((reporter) => {
2087
+ if (typeof reporter === "string") {
718
2088
  return reporter === "html";
2089
+ }
719
2090
  return reporter[0] === "html";
720
2091
  }) : void 0;
721
- if (!htmlReporter)
2092
+ if (!htmlReporter) {
722
2093
  return void 0;
2094
+ }
723
2095
  const root = resolve(
724
2096
  options.root || process.cwd(),
725
2097
  options.coverage.reportsDirectory || coverageConfigDefaults.reportsDirectory
726
2098
  );
727
2099
  const subdir = Array.isArray(htmlReporter) && htmlReporter.length > 1 && "subdir" in htmlReporter[1] ? htmlReporter[1].subdir : void 0;
728
- if (!subdir || typeof subdir !== "string")
2100
+ if (!subdir || typeof subdir !== "string") {
729
2101
  return [root, `/${basename(root)}/`];
2102
+ }
730
2103
  return [resolve(root, subdir), `/${basename(root)}/${subdir}/`];
731
2104
  }
732
- function wrapConfig(config) {
2105
+
2106
+ const debug = createDebugger("vitest:browser:pool");
2107
+ function createBrowserPool(ctx) {
2108
+ const providers = /* @__PURE__ */ new Set();
2109
+ const waitForTests = async (contextId, project, files) => {
2110
+ const context = project.browser.state.createAsyncContext(contextId, files);
2111
+ return await context;
2112
+ };
2113
+ const runTests = async (project, files) => {
2114
+ ctx.state.clearFiles(project, files);
2115
+ const browser = project.browser;
2116
+ const threadsCount = getThreadsCount(project);
2117
+ const provider = browser.provider;
2118
+ providers.add(provider);
2119
+ const resolvedUrls = browser.vite.resolvedUrls;
2120
+ const origin = resolvedUrls?.local[0] ?? resolvedUrls?.network[0];
2121
+ if (!origin) {
2122
+ throw new Error(
2123
+ `Can't find browser origin URL for project "${project.getName()}" when running tests for files "${files.join('", "')}"`
2124
+ );
2125
+ }
2126
+ const filesPerThread = Math.ceil(files.length / threadsCount);
2127
+ const chunks = [];
2128
+ for (let i = 0; i < files.length; i += filesPerThread) {
2129
+ const chunk = files.slice(i, i + filesPerThread);
2130
+ chunks.push(chunk);
2131
+ }
2132
+ debug?.(
2133
+ `[%s] Running %s tests in %s chunks (%s threads)`,
2134
+ project.getName() || "core",
2135
+ files.length,
2136
+ chunks.length,
2137
+ threadsCount
2138
+ );
2139
+ const orchestrators = [...browser.state.orchestrators.entries()];
2140
+ const promises = [];
2141
+ chunks.forEach((files2, index) => {
2142
+ if (orchestrators[index]) {
2143
+ const [contextId, orchestrator] = orchestrators[index];
2144
+ debug?.(
2145
+ "Reusing orchestrator (context %s) for files: %s",
2146
+ contextId,
2147
+ [...files2.map((f) => relative(project.config.root, f))].join(", ")
2148
+ );
2149
+ const promise = waitForTests(contextId, project, files2);
2150
+ promises.push(promise);
2151
+ orchestrator.createTesters(files2);
2152
+ } else {
2153
+ const contextId = crypto.randomUUID();
2154
+ const waitPromise = waitForTests(contextId, project, files2);
2155
+ debug?.(
2156
+ "Opening a new context %s for files: %s",
2157
+ contextId,
2158
+ [...files2.map((f) => relative(project.config.root, f))].join(", ")
2159
+ );
2160
+ const url = new URL("/", origin);
2161
+ url.searchParams.set("contextId", contextId);
2162
+ const page = provider.openPage(contextId, url.toString()).then(() => waitPromise);
2163
+ promises.push(page);
2164
+ }
2165
+ });
2166
+ await Promise.all(promises);
2167
+ };
2168
+ const runWorkspaceTests = async (specs) => {
2169
+ const groupedFiles = /* @__PURE__ */ new Map();
2170
+ for (const [project, file] of specs) {
2171
+ const files = groupedFiles.get(project) || [];
2172
+ files.push(file);
2173
+ groupedFiles.set(project, files);
2174
+ }
2175
+ for (const [project, files] of groupedFiles.entries()) {
2176
+ await runTests(project, files);
2177
+ }
2178
+ };
2179
+ const numCpus = typeof nodeos.availableParallelism === "function" ? nodeos.availableParallelism() : nodeos.cpus().length;
2180
+ function getThreadsCount(project) {
2181
+ const config = project.config.browser;
2182
+ if (!config.headless || !project.browser.provider.supportsParallelism) {
2183
+ return 1;
2184
+ }
2185
+ if (!config.fileParallelism) {
2186
+ return 1;
2187
+ }
2188
+ return ctx.config.watch ? Math.max(Math.floor(numCpus / 2), 1) : Math.max(numCpus - 1, 1);
2189
+ }
733
2190
  return {
734
- ...config,
735
- // workaround RegExp serialization
736
- testNamePattern: config.testNamePattern ? config.testNamePattern.toString() : void 0
2191
+ name: "browser",
2192
+ async close() {
2193
+ await Promise.all([...providers].map((provider) => provider.close()));
2194
+ providers.clear();
2195
+ },
2196
+ runTests: runWorkspaceTests
737
2197
  };
738
2198
  }
739
- function replacer(code, values) {
740
- return code.replace(/\{\s*(\w+)\s*\}/g, (_, key) => values[key] ?? "");
741
- }
742
- async function formatScripts(scripts, server) {
743
- if (!scripts?.length)
744
- return "";
745
- const promises = scripts.map(async ({ content, src, async, id, type = "module" }, index) => {
746
- const srcLink = (src ? (await server.pluginContainer.resolveId(src))?.id : void 0) || src;
747
- const transformId = srcLink || join(server.config.root, `virtual__${id || `injected-${index}.js`}`);
748
- await server.moduleGraph.ensureEntryFromUrl(transformId);
749
- const contentProcessed = content && type === "module" ? (await server.pluginContainer.transform(content, transformId)).code : content;
750
- return `<script type="${type}"${async ? " async" : ""}${srcLink ? ` src="${slash(`/@fs/${srcLink}`)}"` : ""}>${contentProcessed || ""}<\/script>`;
2199
+
2200
+ async function createBrowserServer(project, configFile, prePlugins = [], postPlugins = []) {
2201
+ const server = new BrowserServer(project, "/");
2202
+ const root = project.config.root;
2203
+ await project.ctx.packageInstaller.ensureInstalled("@vitest/browser", root);
2204
+ const configPath = typeof configFile === "string" ? configFile : false;
2205
+ const vite = await createServer({
2206
+ ...project.options,
2207
+ // spread project config inlined in root workspace config
2208
+ base: "/",
2209
+ logLevel: "error",
2210
+ mode: project.config.mode,
2211
+ configFile: configPath,
2212
+ // watch is handled by Vitest
2213
+ server: {
2214
+ hmr: false,
2215
+ watch: null,
2216
+ preTransformRequests: false
2217
+ },
2218
+ plugins: [
2219
+ ...prePlugins,
2220
+ ...project.options?.plugins || [],
2221
+ BrowserPlugin(server),
2222
+ ...postPlugins
2223
+ ]
751
2224
  });
752
- return (await Promise.all(promises)).join("\n");
2225
+ await vite.listen();
2226
+ setupBrowserRpc(server);
2227
+ return server;
753
2228
  }
754
2229
 
755
- export { index as default, defineBrowserCommand };
2230
+ export { createBrowserPool, createBrowserServer };