|
1 | 1 | import type { Vitest } from '../core'
|
2 |
| -import type { ResolvedConfig, TestProjectConfiguration, UserConfig, UserWorkspaceConfig } from '../types/config' |
| 2 | +import type { BrowserConfig, ResolvedConfig, TestProjectConfiguration, UserConfig, UserWorkspaceConfig } from '../types/config' |
3 | 3 | import { existsSync, promises as fs } from 'node:fs'
|
4 | 4 | import os from 'node:os'
|
5 | 5 | import { limitConcurrency } from '@vitest/runner/utils'
|
@@ -97,7 +97,7 @@ export async function resolveWorkspace(
|
97 | 97 |
|
98 | 98 | // pretty rare case - the glob didn't match anything and there are no inline configs
|
99 | 99 | if (!projectPromises.length) {
|
100 |
| - return resolveBrowserWorkspace(vitest, [vitest._createRootProject()]) |
| 100 | + return resolveBrowserWorkspace(vitest, new Set(), [vitest._createRootProject()]) |
101 | 101 | }
|
102 | 102 |
|
103 | 103 | const resolvedProjects = await Promise.all(projectPromises)
|
@@ -127,76 +127,101 @@ export async function resolveWorkspace(
|
127 | 127 | names.add(name)
|
128 | 128 | }
|
129 | 129 |
|
130 |
| - return resolveBrowserWorkspace(vitest, resolvedProjects) |
| 130 | + return resolveBrowserWorkspace(vitest, names, resolvedProjects) |
131 | 131 | }
|
132 | 132 |
|
133 | 133 | export function resolveBrowserWorkspace(
|
134 | 134 | vitest: Vitest,
|
| 135 | + names: Set<string>, |
135 | 136 | resolvedProjects: TestProject[],
|
136 | 137 | ) {
|
| 138 | + const newConfigs: [project: TestProject, config: ResolvedConfig][] = [] |
| 139 | + |
137 | 140 | resolvedProjects.forEach((project) => {
|
138 | 141 | const configs = project.config.browser.configs
|
139 | 142 | if (!project.config.browser.enabled || !configs || configs.length === 0) {
|
140 | 143 | return
|
141 | 144 | }
|
142 | 145 | const [firstConfig, ...restConfigs] = configs
|
| 146 | + const originalName = project.config.name |
143 | 147 |
|
144 |
| - project.config.name ||= project.config.name |
145 |
| - ? `${project.config.name} (${firstConfig.browser})` |
| 148 | + const newName = originalName |
| 149 | + ? `${originalName} (${firstConfig.browser})` |
146 | 150 | : firstConfig.browser
|
| 151 | + if (names.has(newName)) { |
| 152 | + throw new Error(`Cannot redefine the project name for a nameless project. The project name "${firstConfig.browser}" was already defined. All projects in a workspace should have unique names. Make sure your configuration is correct.`) |
| 153 | + } |
| 154 | + names.add(newName) |
147 | 155 |
|
148 | 156 | if (project.config.browser.providerOptions) {
|
149 | 157 | vitest.logger.warn(
|
150 |
| - withLabel('yellow', 'Vitest', `"providerOptions"${project.config.name ? ` in "${project.config.name}" project` : ''} is ignored because it's overriden by the configs. To hide this warning, remove the "providerOptions" property from the browser configuration.`), |
| 158 | + withLabel('yellow', 'Vitest', `"providerOptions"${originalName ? ` in "${originalName}" project` : ''} is ignored because it's overriden by the configs. To hide this warning, remove the "providerOptions" property from the browser configuration.`), |
151 | 159 | )
|
152 | 160 | }
|
153 | 161 |
|
154 |
| - project.config.browser.name = firstConfig.browser |
155 |
| - project.config.browser.providerOptions = firstConfig |
156 |
| - |
157 |
| - restConfigs.forEach(({ browser, ...capability }) => { |
158 |
| - // TODO: cover with tests |
159 |
| - // browser-only options |
160 |
| - const { |
161 |
| - locators, |
162 |
| - viewport, |
163 |
| - testerHtmlPath, |
164 |
| - screenshotDirectory, |
165 |
| - screenshotFailures, |
166 |
| - // @ts-expect-error remove just in case |
167 |
| - browser: _browser, |
168 |
| - // TODO: need a lot of tests |
169 |
| - ...overrideConfig |
170 |
| - } = capability |
171 |
| - const currentConfig = project.config.browser |
172 |
| - const clonedConfig = mergeConfig<any, any>({ |
173 |
| - ...project.config, |
174 |
| - name: project.config.name ? `${project.config} (${browser})` : browser, |
175 |
| - browser: { |
176 |
| - ...project.config.browser, |
177 |
| - locators: locators |
178 |
| - ? { |
179 |
| - testIdAttribute: locators.testIdAttribute ?? currentConfig.locators.testIdAttribute, |
180 |
| - } |
181 |
| - : project.config.browser.locators, |
182 |
| - viewport: viewport ?? currentConfig.viewport, |
183 |
| - testerHtmlPath: testerHtmlPath ?? currentConfig.testerHtmlPath, |
184 |
| - screenshotDirectory: screenshotDirectory ?? currentConfig.screenshotDirectory, |
185 |
| - screenshotFailures: screenshotFailures ?? currentConfig.screenshotFailures, |
186 |
| - name: browser, |
187 |
| - providerOptions: capability, |
188 |
| - configs: undefined, // projects cannot spawn more configs |
189 |
| - }, |
190 |
| - // TODO: should resolve, not merge/override |
191 |
| - } satisfies ResolvedConfig, overrideConfig) as ResolvedConfig |
192 |
| - const clone = TestProject._cloneBrowserProject(project, clonedConfig) |
193 |
| - |
194 |
| - resolvedProjects.push(clone) |
| 162 | + restConfigs.forEach((config) => { |
| 163 | + const browser = config.browser |
| 164 | + const name = config.name |
| 165 | + const newName = name || (originalName ? `${originalName} (${browser})` : browser) |
| 166 | + if (names.has(newName)) { |
| 167 | + throw new Error( |
| 168 | + [ |
| 169 | + `Cannot define a nested project for a ${browser} browser. The project name "${newName}" was already defined. `, |
| 170 | + 'If you have multiple configs for the same browser, make sure to define a custom "name". ', |
| 171 | + 'All projects in a workspace should have unique names. Make sure your configuration is correct.', |
| 172 | + ].join(''), |
| 173 | + ) |
| 174 | + } |
| 175 | + names.add(newName) |
| 176 | + const clonedConfig = cloneConfig(project, config) |
| 177 | + clonedConfig.name = newName |
| 178 | + newConfigs.push([project, clonedConfig]) |
195 | 179 | })
|
| 180 | + |
| 181 | + Object.assign(project.config, cloneConfig(project, firstConfig)) |
| 182 | + project.config.name = newName |
| 183 | + }) |
| 184 | + newConfigs.forEach(([project, clonedConfig]) => { |
| 185 | + const clone = TestProject._cloneBrowserProject(project, clonedConfig) |
| 186 | + resolvedProjects.push(clone) |
196 | 187 | })
|
197 | 188 | return resolvedProjects
|
198 | 189 | }
|
199 | 190 |
|
| 191 | +function cloneConfig(project: TestProject, { browser, ...config }: BrowserConfig) { |
| 192 | + const { |
| 193 | + locators, |
| 194 | + viewport, |
| 195 | + testerHtmlPath, |
| 196 | + screenshotDirectory, |
| 197 | + screenshotFailures, |
| 198 | + // @ts-expect-error remove just in case |
| 199 | + browser: _browser, |
| 200 | + name, |
| 201 | + ...overrideConfig |
| 202 | + } = config |
| 203 | + const currentConfig = project.config.browser |
| 204 | + return mergeConfig<any, any>({ |
| 205 | + ...project.config, |
| 206 | + browser: { |
| 207 | + ...project.config.browser, |
| 208 | + locators: locators |
| 209 | + ? { |
| 210 | + testIdAttribute: locators.testIdAttribute ?? currentConfig.locators.testIdAttribute, |
| 211 | + } |
| 212 | + : project.config.browser.locators, |
| 213 | + viewport: viewport ?? currentConfig.viewport, |
| 214 | + testerHtmlPath: testerHtmlPath ?? currentConfig.testerHtmlPath, |
| 215 | + screenshotDirectory: screenshotDirectory ?? currentConfig.screenshotDirectory, |
| 216 | + screenshotFailures: screenshotFailures ?? currentConfig.screenshotFailures, |
| 217 | + name: browser, |
| 218 | + providerOptions: config, |
| 219 | + configs: undefined, // projects cannot spawn more configs |
| 220 | + }, |
| 221 | + // TODO: should resolve, not merge/override |
| 222 | + } satisfies ResolvedConfig, overrideConfig) as ResolvedConfig |
| 223 | +} |
| 224 | + |
200 | 225 | async function resolveTestProjectConfigs(
|
201 | 226 | vitest: Vitest,
|
202 | 227 | workspaceConfigPath: string | undefined,
|
|
0 commit comments