Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(browser): run test files in isolated iframes #5036

Merged
merged 6 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 15 additions & 1 deletion docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -1518,7 +1518,21 @@ Run the browser in a `headless` mode. If you are running Vitest in CI, it will b
- **Default:** `true`
- **CLI:** `--browser.isolate`, `--browser.isolate=false`

Isolate test environment after each test.
Run every test in a separate iframe.

### browser.fileParallelism <Badge type="info">1.3.0+</Badge>

- **Type:** `boolean`
- **Default:** the same as [`fileParallelism`](#fileparallelism-110)
- **CLI:** `--browser.fileParallelism=false`

Create all test iframes at the same time so they are running in parallel.

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.

::: tip
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.
:::

#### browser.api

Expand Down
21 changes: 21 additions & 0 deletions packages/browser/src/client/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { CancelReason } from '@vitest/runner'
import { createClient } from '@vitest/ws-client'

export const PORT = import.meta.hot ? '51204' : location.port
export const HOST = [location.hostname, PORT].filter(Boolean).join(':')
export const ENTRY_URL = `${
location.protocol === 'https:' ? 'wss:' : 'ws:'
}//${HOST}/__vitest_api__`

let setCancel = (_: CancelReason) => {}
export const onCancel = new Promise<CancelReason>((resolve) => {
setCancel = resolve
})

export const client = createClient(ENTRY_URL, {
handlers: {
onCancel: setCancel,
},
})

export const channel = new BroadcastChannel('vitest')
54 changes: 3 additions & 51 deletions packages/browser/src/client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="icon" href="{__VITEST_FAVICON__}" type="image/svg+xml">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vitest Browser Runner</title>
<style>
Expand All @@ -21,59 +21,11 @@
border: none;
}
</style>
<script>{__VITEST_INJECTOR__}</script>
</head>
<body>
<iframe id="vitest-ui" src=""></iframe>
<script>
const moduleCache = new Map()

// this method receives a module object or "import" promise that it resolves and keeps track of
// and returns a hijacked module object that can be used to mock module exports
function wrapModule(module) {
if (module instanceof Promise) {
moduleCache.set(module, { promise: module, evaluated: false })
return module
// TODO: add a test
.then(m => '__vi_inject__' in m ? m.__vi_inject__ : m)
.finally(() => moduleCache.delete(module))
}
return '__vi_inject__' in module ? module.__vi_inject__ : module
}

function exportAll(exports, sourceModule) {
// #1120 when a module exports itself it causes
// call stack error
if (exports === sourceModule)
return

if (Object(sourceModule) !== sourceModule || Array.isArray(sourceModule))
return

for (const key in sourceModule) {
if (key !== 'default') {
try {
Object.defineProperty(exports, key, {
enumerable: true,
configurable: true,
get: () => sourceModule[key],
})
}
catch (_err) { }
}
}
}

window.__vi_export_all__ = exportAll

// TODO: allow easier rewriting of import.meta.env
window.__vi_import_meta__ = {
env: {},
url: location.href,
}

window.__vi_module_cache__ = moduleCache
window.__vi_wrap_module__ = wrapModule
</script>
<script type="module" src="/main.ts"></script>
<div id="vitest-tester"></div>
</body>
</html>
4 changes: 2 additions & 2 deletions packages/browser/src/client/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { importId } from './utils'

const { Date, console } = globalThis

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