Skip to content

Commit 4f40177

Browse files
authored
feat(browser): run test files in isolated iframes (#5036)
1 parent b607f1e commit 4f40177

29 files changed

+808
-490
lines changed

docs/config/index.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1518,7 +1518,21 @@ Run the browser in a `headless` mode. If you are running Vitest in CI, it will b
15181518
- **Default:** `true`
15191519
- **CLI:** `--browser.isolate`, `--browser.isolate=false`
15201520

1521-
Isolate test environment after each test.
1521+
Run every test in a separate iframe.
1522+
1523+
### browser.fileParallelism <Badge type="info">1.3.0+</Badge>
1524+
1525+
- **Type:** `boolean`
1526+
- **Default:** the same as [`fileParallelism`](#fileparallelism-110)
1527+
- **CLI:** `--browser.fileParallelism=false`
1528+
1529+
Create all test iframes at the same time so they are running in parallel.
1530+
1531+
This makes it impossible to use interactive APIs (like clicking or hovering) because there are several iframes on the screen at the same time, but if your tests don't rely on those APIs, it might be much faster to just run all of them at the same time.
1532+
1533+
::: tip
1534+
If you disabled isolation via [`browser.isolate=false`](#browserisolate), your test files will still run one after another because of the nature of the test runner.
1535+
:::
15221536

15231537
#### browser.api
15241538

packages/browser/src/client/client.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { CancelReason } from '@vitest/runner'
2+
import { createClient } from '@vitest/ws-client'
3+
4+
export const PORT = import.meta.hot ? '51204' : location.port
5+
export const HOST = [location.hostname, PORT].filter(Boolean).join(':')
6+
export const ENTRY_URL = `${
7+
location.protocol === 'https:' ? 'wss:' : 'ws:'
8+
}//${HOST}/__vitest_api__`
9+
10+
let setCancel = (_: CancelReason) => {}
11+
export const onCancel = new Promise<CancelReason>((resolve) => {
12+
setCancel = resolve
13+
})
14+
15+
export const client = createClient(ENTRY_URL, {
16+
handlers: {
17+
onCancel: setCancel,
18+
},
19+
})
20+
21+
export const channel = new BroadcastChannel('vitest')

packages/browser/src/client/index.html

Lines changed: 3 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
5+
<link rel="icon" href="{__VITEST_FAVICON__}" type="image/svg+xml">
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Vitest Browser Runner</title>
88
<style>
@@ -21,59 +21,11 @@
2121
border: none;
2222
}
2323
</style>
24+
<script>{__VITEST_INJECTOR__}</script>
2425
</head>
2526
<body>
2627
<iframe id="vitest-ui" src=""></iframe>
27-
<script>
28-
const moduleCache = new Map()
29-
30-
// this method receives a module object or "import" promise that it resolves and keeps track of
31-
// and returns a hijacked module object that can be used to mock module exports
32-
function wrapModule(module) {
33-
if (module instanceof Promise) {
34-
moduleCache.set(module, { promise: module, evaluated: false })
35-
return module
36-
// TODO: add a test
37-
.then(m => '__vi_inject__' in m ? m.__vi_inject__ : m)
38-
.finally(() => moduleCache.delete(module))
39-
}
40-
return '__vi_inject__' in module ? module.__vi_inject__ : module
41-
}
42-
43-
function exportAll(exports, sourceModule) {
44-
// #1120 when a module exports itself it causes
45-
// call stack error
46-
if (exports === sourceModule)
47-
return
48-
49-
if (Object(sourceModule) !== sourceModule || Array.isArray(sourceModule))
50-
return
51-
52-
for (const key in sourceModule) {
53-
if (key !== 'default') {
54-
try {
55-
Object.defineProperty(exports, key, {
56-
enumerable: true,
57-
configurable: true,
58-
get: () => sourceModule[key],
59-
})
60-
}
61-
catch (_err) { }
62-
}
63-
}
64-
}
65-
66-
window.__vi_export_all__ = exportAll
67-
68-
// TODO: allow easier rewriting of import.meta.env
69-
window.__vi_import_meta__ = {
70-
env: {},
71-
url: location.href,
72-
}
73-
74-
window.__vi_module_cache__ = moduleCache
75-
window.__vi_wrap_module__ = wrapModule
76-
</script>
7728
<script type="module" src="/main.ts"></script>
29+
<div id="vitest-tester"></div>
7830
</body>
7931
</html>

packages/browser/src/client/logger.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { importId } from './utils'
33

44
const { Date, console } = globalThis
55

6-
export async function setupConsoleLogSpy(basePath: string) {
7-
const { stringify, format, inspect } = await importId('vitest/utils', basePath) as typeof import('vitest/utils')
6+
export async function setupConsoleLogSpy() {
7+
const { stringify, format, inspect } = await importId('vitest/utils') as typeof import('vitest/utils')
88
const { log, info, error, dir, dirxml, trace, time, timeEnd, timeLog, warn, debug, count, countReset } = console
99
const formatInput = (input: unknown) => {
1010
if (input instanceof Node)

0 commit comments

Comments
 (0)