Skip to content

Commit

Permalink
feat: add browser.fileParallelism command
Browse files Browse the repository at this point in the history
  • Loading branch information
sheremet-va committed Feb 6, 2024
1 parent eac0817 commit dedaea8
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 65 deletions.
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
35 changes: 29 additions & 6 deletions packages/browser/src/client/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,19 +96,42 @@ client.ws.addEventListener('open', async () => {
}
})

const fileParallelism = config.browser.fileParallelism ?? config.fileParallelism

if (config.isolate === false) {
createIframe(
container,
ID_ALL,
)
}
else {
// TODO: if cofnig.fileParallelism, then at the same time, otherwise one after another
for (const file of testFiles) {
createIframe(
container,
file,
)
// if fileParallelism is enabled, we can create all iframes at once
if (fileParallelism) {
for (const file of testFiles) {
createIframe(
container,
file,
)
}
}
else {
// otherwise, we need to wait for each iframe to finish before creating the next one
// this is the most stable way to run tests in the browser
for (const file of testFiles) {
createIframe(
container,
file,
)
await new Promise<void>((resolve) => {
channel.addEventListener('message', function handler(e: MessageEvent<IframeChannelEvent>) {
// done and error can only be triggered by the previous iframe
if (e.data.type === 'done' || e.data.type === 'error') {
channel.removeEventListener('message', handler)
resolve()
}
})
})
}
}
}
})
7 changes: 7 additions & 0 deletions packages/vitest/src/types/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ export interface BrowserConfigOptions {
* @default true
*/
isolate?: boolean

/**
* Run test files in parallel. Fallbacks to `test.fileParallelism`.
*
* @default test.fileParallelism
*/
fileParallelism?: boolean
}

export interface ResolvedBrowserOptions extends BrowserConfigOptions {
Expand Down
123 changes: 65 additions & 58 deletions test/browser/specs/runner.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,76 @@ import assert from 'node:assert'
import test from 'node:test'
import runVitest from './run-vitest.mjs'

const {
stderr,
stdout,
browserResultJson,
passedTests,
failedTests,
} = await runVitest()
const cliArguments = [
['not parallel', ['--no-browser.fileParallelism']],
['parallel', []],
]

await test('tests are actually running', async () => {
assert.equal(browserResultJson.testResults.length, 14, 'Not all the tests have been run')
assert.equal(passedTests.length, 12, 'Some tests failed')
assert.equal(failedTests.length, 2, 'Some tests have passed but should fail')
for (const [description, args] of cliArguments) {
const {
stderr,
stdout,
browserResultJson,
passedTests,
failedTests,
} = await runVitest(args)

assert.doesNotMatch(stderr, /has been externalized for browser compatibility/, 'doesn\'t have any externalized modules')
assert.doesNotMatch(stderr, /Unhandled Error/, 'doesn\'t have any unhandled errors')
})
await test(`[${description}] tests are actually running`, async () => {
assert.equal(browserResultJson.testResults.length, 14, 'Not all the tests have been run')
assert.equal(passedTests.length, 12, 'Some tests failed')
assert.equal(failedTests.length, 2, 'Some tests have passed but should fail')

await test('correctly prints error', () => {
assert.match(stderr, /expected 1 to be 2/, 'prints failing error')
assert.match(stderr, /- 2\s+\+ 1/, 'prints failing diff')
assert.match(stderr, /Expected to be/, 'prints \`Expected to be\`')
assert.match(stderr, /But got/, 'prints \`But got\`')
})
assert.doesNotMatch(stderr, /has been externalized for browser compatibility/, 'doesn\'t have any externalized modules')
assert.doesNotMatch(stderr, /Unhandled Error/, 'doesn\'t have any unhandled errors')
})

await test('logs are redirected to stdout', async () => {
assert.match(stdout, /stdout | test\/logs.test.ts > logging to stdout/)
assert.match(stdout, /hello from console.log/, 'prints console.log')
assert.match(stdout, /hello from console.info/, 'prints console.info')
assert.match(stdout, /hello from console.debug/, 'prints console.debug')
assert.match(stdout, /{ hello: 'from dir' }/, 'prints console.dir')
assert.match(stdout, /{ hello: 'from dirxml' }/, 'prints console.dixml')
// safari logs the stack files with @https://...
assert.match(stdout, /hello from console.trace\s+(\w+|@)/, 'prints console.trace')
assert.match(stdout, /dom <div \/>/, 'prints dom')
assert.match(stdout, /default: 1/, 'prints first default count')
assert.match(stdout, /default: 2/, 'prints second default count')
assert.match(stdout, /default: 3/, 'prints third default count')
assert.match(stdout, /count: 1/, 'prints first custom count')
assert.match(stdout, /count: 2/, 'prints second custom count')
assert.match(stdout, /count: 3/, 'prints third custom count')
assert.match(stdout, /default: [\d.]+ ms/, 'prints default time')
assert.match(stdout, /time: [\d.]+ ms/, 'prints custom time')
})
await test(`[${description}] correctly prints error`, () => {
assert.match(stderr, /expected 1 to be 2/, 'prints failing error')
assert.match(stderr, /- 2\s+\+ 1/, 'prints failing diff')
assert.match(stderr, /Expected to be/, 'prints \`Expected to be\`')
assert.match(stderr, /But got/, 'prints \`But got\`')
})

await test('logs are redirected to stderr', async () => {
assert.match(stderr, /stderr | test\/logs.test.ts > logging to stderr/)
assert.match(stderr, /hello from console.error/, 'prints console.log')
assert.match(stderr, /hello from console.warn/, 'prints console.info')
assert.match(stderr, /Timer "invalid timeLog" does not exist/, 'prints errored timeLog')
assert.match(stderr, /Timer "invalid timeEnd" does not exist/, 'prints errored timeEnd')
})
await test(`[${description}] logs are redirected to stdout`, async () => {
assert.match(stdout, /stdout | test\/logs.test.ts > logging to stdout/)
assert.match(stdout, /hello from console.log/, 'prints console.log')
assert.match(stdout, /hello from console.info/, 'prints console.info')
assert.match(stdout, /hello from console.debug/, 'prints console.debug')
assert.match(stdout, /{ hello: 'from dir' }/, 'prints console.dir')
assert.match(stdout, /{ hello: 'from dirxml' }/, 'prints console.dixml')
// safari logs the stack files with @https://...
assert.match(stdout, /hello from console.trace\s+(\w+|@)/, 'prints console.trace')
assert.match(stdout, /dom <div \/>/, 'prints dom')
assert.match(stdout, /default: 1/, 'prints first default count')
assert.match(stdout, /default: 2/, 'prints second default count')
assert.match(stdout, /default: 3/, 'prints third default count')
assert.match(stdout, /count: 1/, 'prints first custom count')
assert.match(stdout, /count: 2/, 'prints second custom count')
assert.match(stdout, /count: 3/, 'prints third custom count')
assert.match(stdout, /default: [\d.]+ ms/, 'prints default time')
assert.match(stdout, /time: [\d.]+ ms/, 'prints custom time')
})

await test('stack trace points to correct file in every browser', () => {
// dependeing on the browser it references either `.toBe()` or `expect()`
assert.match(stderr, /test\/failing.test.ts:4:(12|17)/, 'prints stack trace')
})
await test(`[${description}] logs are redirected to stderr`, async () => {
assert.match(stderr, /stderr | test\/logs.test.ts > logging to stderr/)
assert.match(stderr, /hello from console.error/, 'prints console.log')
assert.match(stderr, /hello from console.warn/, 'prints console.info')
assert.match(stderr, /Timer "invalid timeLog" does not exist/, 'prints errored timeLog')
assert.match(stderr, /Timer "invalid timeEnd" does not exist/, 'prints errored timeEnd')
})

await test('popup apis should log a warning', () => {
assert.ok(stderr.includes('Vitest encountered a \`alert\("test"\)\`'), 'prints warning for alert')
assert.ok(stderr.includes('Vitest encountered a \`confirm\("test"\)\`'), 'prints warning for confirm')
assert.ok(stderr.includes('Vitest encountered a \`prompt\("test"\)\`'), 'prints warning for prompt')
})
await test(`[${description}] stack trace points to correct file in every browser`, () => {
// dependeing on the browser it references either `.toBe()` or `expect()`
assert.match(stderr, /test\/failing.test.ts:4:(12|17)/, 'prints stack trace')
})

await test('snapshot inaccessible file debuggability', () => {
assert.ok(stdout.includes('Access denied to "/inaccesible/path".'), 'file security enforcement explained')
})
await test(`[${description}] popup apis should log a warning`, () => {
assert.ok(stderr.includes('Vitest encountered a \`alert\("test"\)\`'), 'prints warning for alert')
assert.ok(stderr.includes('Vitest encountered a \`confirm\("test"\)\`'), 'prints warning for confirm')
assert.ok(stderr.includes('Vitest encountered a \`prompt\("test"\)\`'), 'prints warning for prompt')
})

await test(`[${description}] snapshot inaccessible file debuggability`, () => {
assert.ok(stdout.includes('Access denied to "/inaccesible/path".'), 'file security enforcement explained')
})
}

0 comments on commit dedaea8

Please sign in to comment.