Skip to content

Commit c61d9d9

Browse files
committed
feat: merge custom config
1 parent 9309826 commit c61d9d9

File tree

7 files changed

+123
-28
lines changed

7 files changed

+123
-28
lines changed

packages/vitest/src/create/browser/creator.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,9 @@ function getPossibleProvider(dependencies: Record<string, string>) {
228228
function getProviderDocsLink(provider: string) {
229229
switch (provider) {
230230
case 'playwright':
231-
return 'https://playwright.dev'
231+
return 'https://vitest.dev/guide/browser/playwright'
232232
case 'webdriverio':
233-
return 'https://webdriver.io'
233+
return 'https://vitest.dev/guide/browser/webdriverio'
234234
}
235235
}
236236

@@ -251,7 +251,7 @@ async function generateWorkspaceFile(options: {
251251
configPath: string
252252
rootConfig: string
253253
provider: string
254-
browser: string
254+
browsers: string[]
255255
}) {
256256
const relativeRoot = relative(dirname(options.configPath), options.rootConfig)
257257
const workspaceContent = [
@@ -265,10 +265,11 @@ async function generateWorkspaceFile(options: {
265265
` test: {`,
266266
` browser: {`,
267267
` enabled: true,`,
268-
` name: '${options.browser}',`,
269268
` provider: '${options.provider}',`,
270269
options.provider !== 'preview' && ` // ${getProviderDocsLink(options.provider)}`,
271-
options.provider !== 'preview' && ` providerOptions: {},`,
270+
` capabilities: [`,
271+
...options.browsers.map(browser => ` { browser: '${browser}' },`),
272+
` ],`,
272273
` },`,
273274
` },`,
274275
` },`,
@@ -283,7 +284,7 @@ async function generateFrameworkConfigFile(options: {
283284
framework: string
284285
frameworkPlugin: string | null
285286
provider: string
286-
browser: string
287+
browsers: string[]
287288
}) {
288289
const frameworkImport = options.framework === 'svelte'
289290
? `import { svelte } from '${options.frameworkPlugin}'`
@@ -297,10 +298,11 @@ async function generateFrameworkConfigFile(options: {
297298
` test: {`,
298299
` browser: {`,
299300
` enabled: true,`,
300-
` name: '${options.browser}',`,
301301
` provider: '${options.provider}',`,
302302
options.provider !== 'preview' && ` // ${getProviderDocsLink(options.provider)}`,
303-
options.provider !== 'preview' && ` providerOptions: {},`,
303+
` capabilities: [`,
304+
...options.browsers.map(browser => ` { browser: '${browser}' },`),
305+
` ],`,
304306
` },`,
305307
` },`,
306308
`})`,
@@ -391,9 +393,10 @@ export async function create() {
391393
return fail()
392394
}
393395

394-
const { browser } = await prompt({
395-
type: 'select',
396-
name: 'browser',
396+
// TODO: allow multiselect
397+
const { browsers } = await prompt({
398+
type: 'multiselect',
399+
name: 'browsers',
397400
message: 'Choose a browser',
398401
choices: getBrowserNames(provider).map(browser => ({
399402
title: browser,
@@ -471,7 +474,7 @@ export async function create() {
471474
configPath: browserWorkspaceFile,
472475
rootConfig,
473476
provider,
474-
browser,
477+
browsers,
475478
})
476479
log(c.green('✔'), 'Created a workspace file for browser tests:', c.bold(relative(process.cwd(), browserWorkspaceFile)))
477480
}
@@ -482,7 +485,7 @@ export async function create() {
482485
framework,
483486
frameworkPlugin,
484487
provider,
485-
browser,
488+
browsers,
486489
})
487490
log(c.green('✔'), 'Created a config file for browser tests', c.bold(relative(process.cwd(), configPath)))
488491
}

packages/vitest/src/node/config/resolveConfig.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,30 @@ export function resolveConfig(
230230
}
231231
}
232232

233+
const browser = resolved.browser
234+
235+
if (browser.enabled) {
236+
if (!browser.name && !browser.capabilities) {
237+
throw new Error(`Vitest Browser Mode requires "browser.name" (deprecated) or "browser.capabilities" options, none were set.`)
238+
}
239+
240+
if (browser.name && browser.capabilities) {
241+
throw new Error(`Cannot use both "browser.name" and "browser.capabilities" options at the same time. Use only "browser.capabilities" instead.`)
242+
}
243+
244+
if (browser.capabilities && !browser.capabilities.length) {
245+
throw new Error(`"browser.capabilities" was set in the config, but the array is empty. Define at least one browser capability.`)
246+
}
247+
248+
// TODO: don't throw if --project=chromium is passed filtering capabilities to a single one
249+
// if (browser.provider === 'preview' && (browser.capabilities?.length || 0) > 1) {
250+
// throw new Error(`Browser provider "preview" does not support multiple capabilities. Use "playwright" or "webdriverio" instead.`)
251+
// }
252+
}
253+
233254
// Browser-mode "Playwright + Chromium" only features:
234-
if (resolved.browser.enabled && !(resolved.browser.provider === 'playwright' && resolved.browser.name === 'chromium')) {
235-
const browserConfig = { browser: { provider: resolved.browser.provider, name: resolved.browser.name } }
255+
if (browser.enabled && !(browser.provider === 'playwright' && browser.name === 'chromium')) {
256+
const browserConfig = { browser: { provider: browser.provider, name: browser.name } }
236257

237258
if (resolved.coverage.enabled && resolved.coverage.provider === 'v8') {
238259
throw new Error(

packages/vitest/src/node/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ export class Vitest {
290290
this._workspaceConfigPath = workspaceConfigPath
291291

292292
if (!workspaceConfigPath) {
293-
return resolveBrowserWorkspace([this._createRootProject()])
293+
return resolveBrowserWorkspace(this, [this._createRootProject()])
294294
}
295295

296296
const workspaceModule = await this.runner.executeFile(workspaceConfigPath) as {

packages/vitest/src/node/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ export class TestProject {
643643
// type is very strict here, so we cast it to any
644644
(clone.provide as (key: string, value: unknown) => void)(
645645
providedKey,
646-
project.config.provide[providedKey],
646+
config.provide[providedKey],
647647
)
648648
}
649649
clone._initBrowserServer = deduped(async () => {

packages/vitest/src/node/types/browser.ts

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Awaitable, ErrorWithDiff, ParsedStack } from '@vitest/utils'
33
import type { StackTraceParserOptions } from '@vitest/utils/source-map'
44
import type { ViteDevServer } from 'vite'
55
import type { TestProject } from '../project'
6-
import type { ApiConfig } from './config'
6+
import type { ApiConfig, ProjectConfig } from './config'
77

88
export interface BrowserProviderInitializationOptions {
99
browser: string
@@ -45,6 +45,32 @@ export interface BrowserProviderOptions {}
4545

4646
export type BrowserBuiltinProvider = 'webdriverio' | 'playwright' | 'preview'
4747

48+
type UnsupportedProperties =
49+
| 'browser'
50+
| 'typecheck'
51+
| 'alias'
52+
| 'sequence'
53+
| 'root'
54+
| 'pool'
55+
| 'poolOptions'
56+
// browser mode doesn't support a custom runner
57+
| 'runner'
58+
// non-browser options
59+
| 'api'
60+
| 'deps'
61+
| 'testTransformMode'
62+
| 'poolMatchGlobs'
63+
| 'environmentMatchGlobs'
64+
| 'environment'
65+
| 'environmentOptions'
66+
| 'server'
67+
| 'benchmark'
68+
69+
// TODO: document all options
70+
export interface BrowserCapabilities extends BrowserProviderOptions, Omit<ProjectConfig, UnsupportedProperties>, Pick<BrowserConfigOptions, 'locators' | 'viewport' | 'testerHtmlPath' | 'screenshotDirectory' | 'screenshotFailures'> {
71+
browser: string
72+
}
73+
4874
export interface BrowserConfigOptions {
4975
/**
5076
* if running tests in the browser should be the default
@@ -57,11 +83,12 @@ export interface BrowserConfigOptions {
5783
* Name of the browser
5884
* @deprecated use `capabilities` instead
5985
*/
60-
name: string
86+
name?: string
6187

62-
capabilities?: ({
63-
browser: string
64-
} & BrowserProviderOptions)[]
88+
/**
89+
* Configurations for different browsers
90+
*/
91+
capabilities?: BrowserCapabilities[]
6592

6693
/**
6794
* Browser provider
@@ -254,6 +281,8 @@ export interface BrowserScript {
254281
}
255282

256283
export interface ResolvedBrowserOptions extends BrowserConfigOptions {
284+
name: string
285+
providerOptions?: BrowserProviderOptions
257286
enabled: boolean
258287
headless: boolean
259288
isolate: boolean

packages/vitest/src/node/types/config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1081,14 +1081,16 @@ type NonProjectOptions =
10811081
| 'maxWorkers'
10821082
| 'minWorkers'
10831083
| 'fileParallelism'
1084+
| 'workspace'
10841085

10851086
export type ProjectConfig = Omit<
1086-
UserConfig,
1087+
InlineConfig,
10871088
NonProjectOptions
10881089
| 'sequencer'
10891090
| 'deps'
10901091
| 'poolOptions'
10911092
> & {
1093+
mode?: string
10921094
sequencer?: Omit<SequenceOptions, 'sequencer' | 'seed'>
10931095
deps?: Omit<DepsOptions, 'moduleDirectories'>
10941096
poolOptions?: {

packages/vitest/src/node/workspace/resolveWorkspace.ts

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Vitest } from '../core'
2-
import type { TestProjectConfiguration, UserConfig, UserWorkspaceConfig } from '../types/config'
2+
import type { ResolvedConfig, TestProjectConfiguration, UserConfig, UserWorkspaceConfig } from '../types/config'
33
import { existsSync, promises as fs } from 'node:fs'
44
import os from 'node:os'
55
import { limitConcurrency } from '@vitest/runner/utils'
@@ -8,6 +8,7 @@ import { dirname, relative, resolve } from 'pathe'
88
import { mergeConfig } from 'vite'
99
import { configFiles as defaultConfigFiles } from '../../constants'
1010
import { initializeProject, TestProject } from '../project'
11+
import { withLabel } from '../reporters/renderers/utils'
1112
import { isDynamicPattern } from './fast-glob-pattern'
1213

1314
export async function resolveWorkspace(
@@ -96,7 +97,7 @@ export async function resolveWorkspace(
9697

9798
// pretty rare case - the glob didn't match anything and there are no inline configs
9899
if (!projectPromises.length) {
99-
return resolveBrowserWorkspace([vitest._createRootProject()])
100+
return resolveBrowserWorkspace(vitest, [vitest._createRootProject()])
100101
}
101102

102103
const resolvedProjects = await Promise.all(projectPromises)
@@ -126,10 +127,11 @@ export async function resolveWorkspace(
126127
names.add(name)
127128
}
128129

129-
return resolveBrowserWorkspace(resolvedProjects)
130+
return resolveBrowserWorkspace(vitest, resolvedProjects)
130131
}
131132

132133
export function resolveBrowserWorkspace(
134+
vitest: Vitest,
133135
resolvedProjects: TestProject[],
134136
) {
135137
resolvedProjects.forEach((project) => {
@@ -142,19 +144,57 @@ export function resolveBrowserWorkspace(
142144
project.config.name ||= project.config.name
143145
? `${project.config.name} (${firstCapability.browser})`
144146
: firstCapability.browser
147+
148+
if (project.config.browser.name) {
149+
vitest.logger.warn(
150+
withLabel('yellow', 'Vitest', `Browser name "${project.config.browser.name}" is ignored because it's overriden by the capabilities. To hide this warning, remove the "name" property from the browser configuration.`),
151+
)
152+
}
153+
154+
if (project.config.browser.providerOptions) {
155+
vitest.logger.warn(
156+
withLabel('yellow', 'Vitest', `"providerOptions"${project.config.name ? ` in "${project.config.name}" project` : ''} is ignored because it's overriden by the capabilities. To hide this warning, remove the "providerOptions" property from the browser configuration.`),
157+
)
158+
}
159+
145160
project.config.browser.name = firstCapability.browser
146161
project.config.browser.providerOptions = firstCapability
147162

148163
restCapabilities.forEach(({ browser, ...capability }) => {
149-
const clone = TestProject._cloneBrowserProject(project, {
164+
// TODO: cover with tests
165+
// browser-only options
166+
const {
167+
locators,
168+
viewport,
169+
testerHtmlPath,
170+
screenshotDirectory,
171+
screenshotFailures,
172+
// @ts-expect-error remove just in case
173+
browser: _browser,
174+
// TODO: need a lot of tests
175+
...overrideConfig
176+
} = capability
177+
const currentConfig = project.config.browser
178+
const clonedConfig = mergeConfig<any, any>({
150179
...project.config,
151180
name: project.config.name ? `${project.config} (${browser})` : browser,
152181
browser: {
153182
...project.config.browser,
183+
locators: locators
184+
? {
185+
testIdAttribute: locators.testIdAttribute ?? currentConfig.locators.testIdAttribute,
186+
}
187+
: project.config.browser.locators,
188+
viewport: viewport ?? currentConfig.viewport,
189+
testerHtmlPath: testerHtmlPath ?? currentConfig.testerHtmlPath,
190+
screenshotDirectory: screenshotDirectory ?? currentConfig.screenshotDirectory,
191+
screenshotFailures: screenshotFailures ?? currentConfig.screenshotFailures,
154192
name: browser,
155193
providerOptions: capability,
156194
},
157-
})
195+
// TODO: should resolve, not merge/override
196+
} satisfies ResolvedConfig, overrideConfig) as ResolvedConfig
197+
const clone = TestProject._cloneBrowserProject(project, clonedConfig)
158198

159199
resolvedProjects.push(clone)
160200
})

0 commit comments

Comments
 (0)