diff --git a/packages/playwright-test/src/common/configLoader.ts b/packages/playwright-test/src/common/configLoader.ts index 4e30273f3a..4d72ed73a0 100644 --- a/packages/playwright-test/src/common/configLoader.ts +++ b/packages/playwright-test/src/common/configLoader.ts @@ -27,9 +27,53 @@ import { addToCompilationCache } from '../transform/compilationCache'; import { initializeEsmLoader } from './esmLoaderHost'; const kDefineConfigWasUsed = Symbol('defineConfigWasUsed'); -export const defineConfig = (config: any) => { - config[kDefineConfigWasUsed] = true; - return config; +export const defineConfig = (...configs: any[]) => { + let result = configs[0]; + for (let i = 1; i < configs.length; ++i) { + const config = configs[i]; + result = { + ...result, + ...config, + expect: { + ...result.expect, + ...config.expect, + }, + use: { + ...result.use, + ...config.use, + }, + webServer: [ + ...(Array.isArray(result.webServer) ? result.webServer : (result.webServer ? [result.webServer] : [])), + ...(Array.isArray(config.webServer) ? config.webServer : (config.webServer ? [config.webServer] : [])), + ] + }; + + const projectOverrides = new Map(); + for (const project of config.projects || []) + projectOverrides.set(project.name, project); + + const projects = []; + for (const project of result.projects || []) { + const projectOverride = projectOverrides.get(project.name); + if (projectOverride) { + projects.push({ + ...project, + ...projectOverride, + use: { + ...project.use, + ...projectOverride.use, + } + }); + projectOverrides.delete(project.name); + } else { + projects.push(project); + } + } + projects.push(...projectOverrides.values()); + result.projects = projects; + } + result[kDefineConfigWasUsed] = true; + return result; }; export class ConfigLoader { diff --git a/packages/playwright-test/types/test.d.ts b/packages/playwright-test/types/test.d.ts index 835987141b..8f72539faa 100644 --- a/packages/playwright-test/types/test.d.ts +++ b/packages/playwright-test/types/test.d.ts @@ -5022,6 +5022,9 @@ export const expect: Expect; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {}; diff --git a/tests/library/playwright.config.ts b/tests/library/playwright.config.ts index 0fc1605c71..69c433825f 100644 --- a/tests/library/playwright.config.ts +++ b/tests/library/playwright.config.ts @@ -17,7 +17,7 @@ import { config as loadEnv } from 'dotenv'; loadEnv({ path: path.join(__dirname, '..', '..', '.env'), override: true }); -import type { Config, PlaywrightTestOptions, PlaywrightWorkerOptions, ReporterDescription } from '@playwright/test'; +import { type Config, type PlaywrightTestOptions, type PlaywrightWorkerOptions, type ReporterDescription } from '@playwright/test'; import * as path from 'path'; import type { TestModeWorkerOptions } from '../config/testModeFixtures'; import type { TestModeName } from '../config/testMode'; diff --git a/tests/playwright-test/config.spec.ts b/tests/playwright-test/config.spec.ts index 76c8e416c5..bddbde5f57 100644 --- a/tests/playwright-test/config.spec.ts +++ b/tests/playwright-test/config.spec.ts @@ -497,3 +497,60 @@ test('should not allow tracesDir in launchOptions', async ({ runTSC }) => { }); expect(result.exitCode).not.toBe(0); }); + +test('should merge configs', async ({ runInlineTest }) => { + const result = await runInlineTest({ + 'playwright.config.ts': ` + import { defineConfig, expect } from '@playwright/test'; + const baseConfig = defineConfig({ + timeout: 10, + use: { + foo: 1, + }, + expect: { + timeout: 11, + }, + projects: [ + { + name: 'A', + timeout: 20, + } + ], + }); + const derivedConfig = defineConfig(baseConfig, { + timeout: 30, + use: { + bar: 2, + }, + expect: { + timeout: 12, + }, + projects: [ + { name: 'B', timeout: 40 }, + { name: 'A', timeout: 50 }, + ], + webServer: { + command: 'echo 123', + } + }); + + expect(derivedConfig).toEqual(expect.objectContaining({ + timeout: 30, + use: { foo: 1, bar: 2 }, + expect: { timeout: 12 }, + projects: [ + { name: 'B', timeout: 40, use: {} }, + { name: 'A', timeout: 50, use: {} } + ], + webServer: [{ + command: 'echo 123', + }] + })); + `, + 'a.test.ts': ` + import { test } from '@playwright/test'; + test('pass', async ({}) => {}); + ` + }); + expect(result.exitCode).toBe(0); +}); diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 014453e0ae..0b199d0357 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -400,6 +400,9 @@ export const expect: Expect; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; export function defineConfig(config: PlaywrightTestConfig): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; +export function defineConfig(config: PlaywrightTestConfig, ...configs: PlaywrightTestConfig[]): PlaywrightTestConfig; // This is required to not export everything by default. See https://github.com/Microsoft/TypeScript/issues/19545#issuecomment-340490459 export {};