diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index 514f79455a..c024baa93c 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -28,7 +28,7 @@ import { ConfigLoader, kDefaultConfigFiles, resolveConfigFile } from './common/c import type { ConfigCLIOverrides } from './common/ipc'; import type { FullResult } from '../reporter'; import type { TraceMode } from '../types/test'; -import { baseFullConfig, builtInReporters, defaultTimeout } from './common/config'; +import { builtInReporters, defaultReporter, defaultTimeout } from './common/config'; import type { FullConfigInternal } from './common/config'; export function addTestCommands(program: Command) { @@ -117,41 +117,13 @@ Examples: async function runTests(args: string[], opts: { [key: string]: any }) { await startProfiling(); - const overrides = overridesFromOptions(opts); - if (opts.browser) { - const browserOpt = opts.browser.toLowerCase(); - if (!['all', 'chromium', 'firefox', 'webkit'].includes(browserOpt)) - throw new Error(`Unsupported browser "${opts.browser}", must be one of "all", "chromium", "firefox" or "webkit"`); - const browserNames = browserOpt === 'all' ? ['chromium', 'firefox', 'webkit'] : [browserOpt]; - overrides.projects = browserNames.map(browserName => { - return { - name: browserName, - use: { browserName }, - }; - }); - } - - if (opts.headed || opts.debug) - overrides.use = { headless: false }; - if (!opts.ui && opts.debug) { - overrides.maxFailures = 1; - overrides.timeout = 0; - overrides.workers = 1; - process.env.PWDEBUG = '1'; - } - if (!opts.ui && opts.trace) { - if (!kTraceModes.includes(opts.trace)) - throw new Error(`Unsupported trace mode "${opts.trace}", must be one of ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`); - overrides.use = overrides.use || {}; - overrides.use.trace = opts.trace; - } - // When no --config option is passed, let's look for the config file in the current directory. const configFileOrDirectory = opts.config ? path.resolve(process.cwd(), opts.config) : process.cwd(); const resolvedConfigFile = resolveConfigFile(configFileOrDirectory); if (restartWithExperimentalTsEsm(resolvedConfigFile)) return; + const overrides = overridesFromOptions(opts); const configLoader = new ConfigLoader(overrides); let config: FullConfigInternal; if (resolvedConfigFile) @@ -162,9 +134,9 @@ async function runTests(args: string[], opts: { [key: string]: any }) { config.cliArgs = args; config.cliGrep = opts.grep as string | undefined; config.cliGrepInvert = opts.grepInvert as string | undefined; - config.listOnly = !!opts.list; + config.cliListOnly = !!opts.list; config.cliProjectFilter = opts.project || undefined; - config.passWithNoTests = !!opts.passWithNoTests; + config.cliPassWithNoTests = !!opts.passWithNoTests; const runner = new Runner(config); let status: FullResult['status']; @@ -218,7 +190,7 @@ async function mergeReports(reportDir: string | undefined, opts: { [key: string] function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrides { const shardPair = options.shard ? options.shard.split('/').map((t: string) => parseInt(t, 10)) : undefined; - return { + const overrides: ConfigCLIOverrides = { forbidOnly: options.forbidOnly ? true : undefined, fullyParallel: options.fullyParallel ? true : undefined, globalTimeout: options.globalTimeout ? parseInt(options.globalTimeout, 10) : undefined, @@ -234,6 +206,35 @@ function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrid updateSnapshots: options.updateSnapshots ? 'all' as const : undefined, workers: options.workers, }; + + if (options.browser) { + const browserOpt = options.browser.toLowerCase(); + if (!['all', 'chromium', 'firefox', 'webkit'].includes(browserOpt)) + throw new Error(`Unsupported browser "${options.browser}", must be one of "all", "chromium", "firefox" or "webkit"`); + const browserNames = browserOpt === 'all' ? ['chromium', 'firefox', 'webkit'] : [browserOpt]; + overrides.projects = browserNames.map(browserName => { + return { + name: browserName, + use: { browserName }, + }; + }); + } + + if (options.headed || options.debug) + overrides.use = { headless: false }; + if (!options.ui && options.debug) { + overrides.maxFailures = 1; + overrides.timeout = 0; + overrides.workers = 1; + process.env.PWDEBUG = '1'; + } + if (!options.ui && options.trace) { + if (!kTraceModes.includes(options.trace)) + throw new Error(`Unsupported trace mode "${options.trace}", must be one of ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`); + overrides.use = overrides.use || {}; + overrides.use.trace = options.trace; + } + return overrides; } function resolveReporter(id: string) { @@ -295,7 +296,7 @@ const testOptions: [string, string][] = [ ['--project ', `Only run tests from the specified list of projects (default: run all projects)`], ['--quiet', `Suppress stdio`], ['--repeat-each ', `Run each test N times (default: 1)`], - ['--reporter ', `Reporter to use, comma-separated, can be ${builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${baseFullConfig.reporter[0]}")`], + ['--reporter ', `Reporter to use, comma-separated, can be ${builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${defaultReporter}")`], ['--retries ', `Maximum retry count for flaky tests, zero for no retries (default: no retries)`], ['--shard ', `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"`], ['--timeout ', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${defaultTimeout})`], diff --git a/packages/playwright-test/src/common/config.ts b/packages/playwright-test/src/common/config.ts index 8610982298..390548470d 100644 --- a/packages/playwright-test/src/common/config.ts +++ b/packages/playwright-test/src/common/config.ts @@ -14,13 +14,14 @@ * limitations under the License. */ +import fs from 'fs'; import path from 'path'; import os from 'os'; import type { Config, Fixtures, Project, ReporterDescription } from '../../types/test'; import type { Location } from '../../types/testReporter'; import type { TestRunnerPluginRegistration } from '../plugins'; +import { getPackageJsonPath, mergeObjects } from '../util'; import type { Matcher } from '../util'; -import { mergeObjects } from '../util'; import type { ConfigCLIOverrides } from './ipc'; import type { FullConfig, FullProject } from '../../types/test'; @@ -34,54 +35,68 @@ export const defaultTimeout = 30000; export class FullConfigInternal { readonly config: FullConfig; - globalOutputDir = path.resolve(process.cwd()); - configDir = ''; - configCLIOverrides: ConfigCLIOverrides = {}; - storeDir = ''; - ignoreSnapshots = false; - webServers: Exclude[] = []; - plugins: TestRunnerPluginRegistration[] = []; - listOnly = false; + readonly globalOutputDir: string; + readonly configDir: string; + readonly configCLIOverrides: ConfigCLIOverrides; + readonly storeDir: string; + readonly ignoreSnapshots: boolean; + readonly webServers: Exclude[]; + readonly plugins: TestRunnerPluginRegistration[]; + readonly projects: FullProjectInternal[] = []; cliArgs: string[] = []; cliGrep: string | undefined; cliGrepInvert: string | undefined; cliProjectFilter?: string[]; + cliListOnly = false; + cliPassWithNoTests?: boolean; testIdMatcher?: Matcher; - passWithNoTests?: boolean; defineConfigWasUsed = false; - projects: FullProjectInternal[] = []; static from(config: FullConfig): FullConfigInternal { return (config as any)[configInternalSymbol]; } - constructor(configDir: string, configFile: string | undefined, config: Config, throwawayArtifactsPath: string) { + constructor(configDir: string, configFile: string | undefined, config: Config, configCLIOverrides: ConfigCLIOverrides) { + if (configCLIOverrides.projects && config.projects) + throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`); + + const packageJsonPath = getPackageJsonPath(configDir); + const packageJsonDir = packageJsonPath ? path.dirname(packageJsonPath) : undefined; + const throwawayArtifactsPath = packageJsonDir || process.cwd(); + this.configDir = configDir; - this.config = { ...baseFullConfig }; - (this.config as any)[configInternalSymbol] = this; + this.configCLIOverrides = configCLIOverrides; this.storeDir = path.resolve(configDir, (config as any)._storeDir || 'playwright'); - this.globalOutputDir = takeFirst(config.outputDir, throwawayArtifactsPath, path.resolve(process.cwd())); - this.ignoreSnapshots = takeFirst(config.ignoreSnapshots, false); + this.globalOutputDir = takeFirst(configCLIOverrides.outputDir, pathResolve(configDir, config.outputDir), throwawayArtifactsPath, path.resolve(process.cwd())); + this.ignoreSnapshots = takeFirst(configCLIOverrides.ignoreSnapshots, config.ignoreSnapshots, false); this.plugins = ((config as any)._plugins || []).map((p: any) => ({ factory: p })); - this.config.configFile = configFile; - this.config.rootDir = config.testDir || configDir; - this.config.forbidOnly = takeFirst(config.forbidOnly, baseFullConfig.forbidOnly); - this.config.fullyParallel = takeFirst(config.fullyParallel, baseFullConfig.fullyParallel); - this.config.globalSetup = takeFirst(config.globalSetup, baseFullConfig.globalSetup); - this.config.globalTeardown = takeFirst(config.globalTeardown, baseFullConfig.globalTeardown); - this.config.globalTimeout = takeFirst(config.globalTimeout, baseFullConfig.globalTimeout); - this.config.grep = takeFirst(config.grep, baseFullConfig.grep); - this.config.grepInvert = takeFirst(config.grepInvert, baseFullConfig.grepInvert); - this.config.maxFailures = takeFirst(config.maxFailures, baseFullConfig.maxFailures); - this.config.preserveOutput = takeFirst(config.preserveOutput, baseFullConfig.preserveOutput); - this.config.reporter = takeFirst(resolveReporters(config.reporter, configDir), baseFullConfig.reporter); - this.config.reportSlowTests = takeFirst(config.reportSlowTests, baseFullConfig.reportSlowTests); - this.config.quiet = takeFirst(config.quiet, baseFullConfig.quiet); - this.config.shard = takeFirst(config.shard, baseFullConfig.shard); - this.config.updateSnapshots = takeFirst(config.updateSnapshots, baseFullConfig.updateSnapshots); + this.config = { + configFile, + rootDir: pathResolve(configDir, config.testDir) || configDir, + forbidOnly: takeFirst(configCLIOverrides.forbidOnly, config.forbidOnly, false), + fullyParallel: takeFirst(configCLIOverrides.fullyParallel, config.fullyParallel, false), + globalSetup: takeFirst(resolveScript(config.globalSetup, configDir), null), + globalTeardown: takeFirst(resolveScript(config.globalTeardown, configDir), null), + globalTimeout: takeFirst(configCLIOverrides.globalTimeout, config.globalTimeout, 0), + grep: takeFirst(config.grep, defaultGrep), + grepInvert: takeFirst(config.grepInvert, null), + maxFailures: takeFirst(configCLIOverrides.maxFailures, config.maxFailures, 0), + metadata: takeFirst(config.metadata, {}), + preserveOutput: takeFirst(config.preserveOutput, 'always'), + reporter: takeFirst(configCLIOverrides.reporter ? toReporters(configCLIOverrides.reporter as any) : undefined, resolveReporters(config.reporter, configDir), [[defaultReporter]]), + reportSlowTests: takeFirst(config.reportSlowTests, { max: 5, threshold: 15000 }), + quiet: takeFirst(configCLIOverrides.quiet, config.quiet, false), + projects: [], + shard: takeFirst(configCLIOverrides.shard, config.shard, null), + updateSnapshots: takeFirst(configCLIOverrides.updateSnapshots, config.updateSnapshots, 'missing'), + version: require('../../package.json').version, + workers: 0, + webServer: null, + }; + (this.config as any)[configInternalSymbol] = this; - const workers = takeFirst(config.workers, '50%'); + const workers = takeFirst(configCLIOverrides.workers, config.workers, '50%'); if (typeof workers === 'string') { if (workers.endsWith('%')) { const cpus = os.cpus().length; @@ -93,7 +108,7 @@ export class FullConfigInternal { this.config.workers = workers; } - const webServers = takeFirst(config.webServer, baseFullConfig.webServer); + const webServers = takeFirst(config.webServer, null); if (Array.isArray(webServers)) { // multiple web server mode // Due to previous choices, this value shows up to the user in globalSetup as part of FullConfig. Arrays are not supported by the old type. this.config.webServer = null; @@ -101,9 +116,12 @@ export class FullConfigInternal { } else if (webServers) { // legacy singleton mode this.config.webServer = webServers; this.webServers = [webServers]; + } else { + this.webServers = []; } - this.config.metadata = takeFirst(config.metadata, baseFullConfig.metadata); - this.projects = (config.projects || [config]).map(p => this._resolveProject(config, p, throwawayArtifactsPath)); + + const projectConfigs = configCLIOverrides.projects || config.projects || [config]; + this.projects = projectConfigs.map(p => new FullProjectInternal(configDir, config, this, p, this.configCLIOverrides, throwawayArtifactsPath)); resolveProjectDependencies(this.projects); this._assignUniqueProjectIds(this.projects); this.config.projects = this.projects.map(p => p.project); @@ -123,91 +141,51 @@ export class FullConfigInternal { } } } - - private _resolveProject(config: Config, projectConfig: Project, throwawayArtifactsPath: string): FullProjectInternal { - // Resolve all config dirs relative to configDir. - if (projectConfig.testDir !== undefined) - projectConfig.testDir = path.resolve(this.configDir, projectConfig.testDir); - if (projectConfig.outputDir !== undefined) - projectConfig.outputDir = path.resolve(this.configDir, projectConfig.outputDir); - if (projectConfig.snapshotDir !== undefined) - projectConfig.snapshotDir = path.resolve(this.configDir, projectConfig.snapshotDir); - return new FullProjectInternal(config, this, projectConfig, throwawayArtifactsPath); - } } export class FullProjectInternal { readonly project: FullProject; + readonly fullConfig: FullConfigInternal; + readonly fullyParallel: boolean; + readonly expect: Project['expect']; + readonly respectGitIgnore: boolean; + readonly snapshotPathTemplate: string; id = ''; - fullConfig: FullConfigInternal; - fullyParallel: boolean; - expect: Project['expect']; - respectGitIgnore: boolean; deps: FullProjectInternal[] = []; - snapshotPathTemplate: string; static from(project: FullProject): FullProjectInternal { return (project as any)[projectInternalSymbol]; } - constructor(config: Config, fullConfig: FullConfigInternal, projectConfig: Project, throwawayArtifactsPath: string) { + constructor(configDir: string, config: Config, fullConfig: FullConfigInternal, projectConfig: Project, configCLIOverrides: ConfigCLIOverrides, throwawayArtifactsPath: string) { this.fullConfig = fullConfig; - - const testDir = takeFirst(projectConfig.testDir, config.testDir, fullConfig.configDir); - - const outputDir = takeFirst(projectConfig.outputDir, config.outputDir, path.join(throwawayArtifactsPath, 'test-results')); - const snapshotDir = takeFirst(projectConfig.snapshotDir, config.snapshotDir, testDir); - const name = takeFirst(projectConfig.name, config.name, ''); - + const testDir = takeFirst(pathResolve(configDir, projectConfig.testDir), pathResolve(configDir, config.testDir), fullConfig.configDir); const defaultSnapshotPathTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}'; this.snapshotPathTemplate = takeFirst(projectConfig.snapshotPathTemplate, config.snapshotPathTemplate, defaultSnapshotPathTemplate); this.project = { - grep: takeFirst(projectConfig.grep, config.grep, baseFullConfig.grep), - grepInvert: takeFirst(projectConfig.grepInvert, config.grepInvert, baseFullConfig.grepInvert), - outputDir, - repeatEach: takeFirst(projectConfig.repeatEach, config.repeatEach, 1), - retries: takeFirst(projectConfig.retries, config.retries, 0), + grep: takeFirst(projectConfig.grep, config.grep, defaultGrep), + grepInvert: takeFirst(projectConfig.grepInvert, config.grepInvert, null), + outputDir: takeFirst(configCLIOverrides.outputDir, pathResolve(configDir, projectConfig.outputDir), pathResolve(configDir, config.outputDir), path.join(throwawayArtifactsPath, 'test-results')), + repeatEach: takeFirst(configCLIOverrides.repeatEach, projectConfig.repeatEach, config.repeatEach, 1), + retries: takeFirst(configCLIOverrides.retries, projectConfig.retries, config.retries, 0), metadata: takeFirst(projectConfig.metadata, config.metadata, undefined), - name, + name: takeFirst(projectConfig.name, config.name, ''), testDir, - snapshotDir, + snapshotDir: takeFirst(pathResolve(configDir, projectConfig.snapshotDir), pathResolve(configDir, config.snapshotDir), testDir), testIgnore: takeFirst(projectConfig.testIgnore, config.testIgnore, []), testMatch: takeFirst(projectConfig.testMatch, config.testMatch, '**/?(*.)@(spec|test).?(m)[jt]s?(x)'), - timeout: takeFirst(projectConfig.timeout, config.timeout, defaultTimeout), - use: mergeObjects(config.use, projectConfig.use), + timeout: takeFirst(configCLIOverrides.timeout, projectConfig.timeout, config.timeout, defaultTimeout), + use: mergeObjects(config.use, projectConfig.use, configCLIOverrides.use), dependencies: projectConfig.dependencies || [], }; (this.project as any)[projectInternalSymbol] = this; - this.fullyParallel = takeFirst(projectConfig.fullyParallel, config.fullyParallel, undefined); + this.fullyParallel = takeFirst(configCLIOverrides.fullyParallel, projectConfig.fullyParallel, config.fullyParallel, undefined); this.expect = takeFirst(projectConfig.expect, config.expect, {}); this.respectGitIgnore = !projectConfig.testDir && !config.testDir; } } -export const baseFullConfig: FullConfig = { - forbidOnly: false, - fullyParallel: false, - globalSetup: null, - globalTeardown: null, - globalTimeout: 0, - grep: /.*/, - grepInvert: null, - maxFailures: 0, - metadata: {}, - preserveOutput: 'always', - projects: [], - reporter: [[process.env.CI ? 'dot' : 'list']], - reportSlowTests: { max: 5, threshold: 15000 }, - rootDir: path.resolve(process.cwd()), - quiet: false, - shard: null, - updateSnapshots: 'missing', - version: require('../../package.json').version, - workers: 0, - webServer: null, -}; - export function takeFirst(...args: (T | undefined)[]): T { for (const arg of args) { if (arg !== undefined) @@ -216,6 +194,12 @@ export function takeFirst(...args: (T | undefined)[]): T { return undefined as any as T; } +function pathResolve(baseDir: string, relative: string | undefined): string | undefined { + if (!relative) + return undefined; + return path.resolve(baseDir, relative); +} + function resolveReporters(reporters: Config['reporter'], rootDir: string): ReporterDescription[] | undefined { return toReporters(reporters as any)?.map(([id, arg]) => { if (builtInReporters.includes(id as any)) @@ -250,5 +234,17 @@ export type BuiltInReporter = typeof builtInReporters[number]; export type ContextReuseMode = 'none' | 'force' | 'when-possible'; +function resolveScript(id: string | undefined, rootDir: string): string | undefined { + if (!id) + return undefined; + const localPath = path.resolve(rootDir, id); + if (fs.existsSync(localPath)) + return localPath; + return require.resolve(id, { paths: [rootDir] }); +} + +export const defaultGrep = /.*/; +export const defaultReporter = process.env.CI ? 'dot' : 'list'; + const configInternalSymbol = Symbol('configInternalSymbol'); const projectInternalSymbol = Symbol('projectInternalSymbol'); diff --git a/packages/playwright-test/src/common/configLoader.ts b/packages/playwright-test/src/common/configLoader.ts index db488838ff..1e29e67dfb 100644 --- a/packages/playwright-test/src/common/configLoader.ts +++ b/packages/playwright-test/src/common/configLoader.ts @@ -20,9 +20,9 @@ import { isRegExp } from 'playwright-core/lib/utils'; import type { ConfigCLIOverrides, SerializedConfig } from './ipc'; import { requireOrImport } from './transform'; import type { Config, Project } from '../../types/test'; -import { errorWithFile, getPackageJsonPath, mergeObjects } from '../util'; +import { errorWithFile } from '../util'; import { setCurrentConfig } from './globals'; -import { FullConfigInternal, takeFirst, toReporters } from './config'; +import { FullConfigInternal } from './config'; const kDefineConfigWasUsed = Symbol('defineConfigWasUsed'); export const defineConfig = (config: any) => { @@ -68,62 +68,10 @@ export class ConfigLoader { private async _loadConfig(config: Config, configDir: string, configFile?: string): Promise { // 1. Validate data provided in the config file. validateConfig(configFile || '', config); - - // 2. Override settings from CLI. - config.forbidOnly = takeFirst(this._configCLIOverrides.forbidOnly, config.forbidOnly); - config.fullyParallel = takeFirst(this._configCLIOverrides.fullyParallel, config.fullyParallel); - config.globalTimeout = takeFirst(this._configCLIOverrides.globalTimeout, config.globalTimeout); - config.maxFailures = takeFirst(this._configCLIOverrides.maxFailures, config.maxFailures); - config.outputDir = takeFirst(this._configCLIOverrides.outputDir, config.outputDir); - config.quiet = takeFirst(this._configCLIOverrides.quiet, config.quiet); - config.repeatEach = takeFirst(this._configCLIOverrides.repeatEach, config.repeatEach); - config.retries = takeFirst(this._configCLIOverrides.retries, config.retries); - if (this._configCLIOverrides.reporter) - config.reporter = toReporters(this._configCLIOverrides.reporter as any); - config.shard = takeFirst(this._configCLIOverrides.shard, config.shard); - config.timeout = takeFirst(this._configCLIOverrides.timeout, config.timeout); - config.updateSnapshots = takeFirst(this._configCLIOverrides.updateSnapshots, config.updateSnapshots); - config.ignoreSnapshots = takeFirst(this._configCLIOverrides.ignoreSnapshots, config.ignoreSnapshots); - if (this._configCLIOverrides.projects && config.projects) - throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`); - config.projects = takeFirst(this._configCLIOverrides.projects, config.projects as any); - config.workers = takeFirst(this._configCLIOverrides.workers, config.workers); - config.use = mergeObjects(config.use, this._configCLIOverrides.use); - for (const project of config.projects || []) - this._applyCLIOverridesToProject(project); - - // 3. Resolve config. - const packageJsonPath = getPackageJsonPath(configDir); - const packageJsonDir = packageJsonPath ? path.dirname(packageJsonPath) : undefined; - const throwawayArtifactsPath = packageJsonDir || process.cwd(); - - // Resolve script hooks relative to the root dir. - if (config.globalSetup) - config.globalSetup = resolveScript(config.globalSetup, configDir); - if (config.globalTeardown) - config.globalTeardown = resolveScript(config.globalTeardown, configDir); - // Resolve all config dirs relative to configDir. - if (config.testDir !== undefined) - config.testDir = path.resolve(configDir, config.testDir); - if (config.outputDir !== undefined) - config.outputDir = path.resolve(configDir, config.outputDir); - if (config.snapshotDir !== undefined) - config.snapshotDir = path.resolve(configDir, config.snapshotDir); - - const fullConfig = new FullConfigInternal(configDir, configFile, config, throwawayArtifactsPath); + const fullConfig = new FullConfigInternal(configDir, configFile, config, this._configCLIOverrides); fullConfig.defineConfigWasUsed = !!(config as any)[kDefineConfigWasUsed]; - fullConfig.configCLIOverrides = this._configCLIOverrides; return fullConfig; } - - private _applyCLIOverridesToProject(projectConfig: Project) { - projectConfig.fullyParallel = takeFirst(this._configCLIOverrides.fullyParallel, projectConfig.fullyParallel); - projectConfig.outputDir = takeFirst(this._configCLIOverrides.outputDir, projectConfig.outputDir); - projectConfig.repeatEach = takeFirst(this._configCLIOverrides.repeatEach, projectConfig.repeatEach); - projectConfig.retries = takeFirst(this._configCLIOverrides.retries, projectConfig.retries); - projectConfig.timeout = takeFirst(this._configCLIOverrides.timeout, projectConfig.timeout); - projectConfig.use = mergeObjects(projectConfig.use, this._configCLIOverrides.use); - } } async function requireOrImportDefaultObject(file: string) { @@ -305,13 +253,6 @@ function validateProject(file: string, project: Project, title: string) { } } -function resolveScript(id: string, rootDir: string) { - const localPath = path.resolve(rootDir, id); - if (fs.existsSync(localPath)) - return localPath; - return require.resolve(id, { paths: [rootDir] }); -} - export const kDefaultConfigFiles = ['playwright.config.ts', 'playwright.config.js', 'playwright.config.mjs']; export function resolveConfigFile(configFileOrDirectory: string): string | null { diff --git a/packages/playwright-test/src/reporters/teleEmitter.ts b/packages/playwright-test/src/reporters/teleEmitter.ts index 513b24a6ed..b232850d41 100644 --- a/packages/playwright-test/src/reporters/teleEmitter.ts +++ b/packages/playwright-test/src/reporters/teleEmitter.ts @@ -123,7 +123,7 @@ export class TeleReporterEmitter implements Reporter { return { rootDir: config.rootDir, configFile: this._relativePath(config.configFile), - listOnly: FullConfigInternal.from(config).listOnly, + listOnly: FullConfigInternal.from(config).cliListOnly, workers: config.workers, }; } diff --git a/packages/playwright-test/src/runner/runner.ts b/packages/playwright-test/src/runner/runner.ts index c9225de18b..8c59ad4482 100644 --- a/packages/playwright-test/src/runner/runner.ts +++ b/packages/playwright-test/src/runner/runner.ts @@ -49,7 +49,7 @@ export class Runner { async runAllTests(): Promise { const config = this._config; - const listOnly = config.listOnly; + const listOnly = config.cliListOnly; const deadline = config.config.globalTimeout ? monotonicTime() + config.config.globalTimeout : 0; // Legacy webServer support. diff --git a/packages/playwright-test/src/runner/tasks.ts b/packages/playwright-test/src/runner/tasks.ts index dd4d573421..9b76c8da82 100644 --- a/packages/playwright-test/src/runner/tasks.ts +++ b/packages/playwright-test/src/runner/tasks.ts @@ -170,7 +170,7 @@ function createLoadTask(mode: 'out-of-process' | 'in-process', shouldFilterOnly: await loadFileSuites(testRun, mode, errors); testRun.rootSuite = await createRootSuite(testRun, errors, shouldFilterOnly); // Fail when no tests. - if (!testRun.rootSuite.allTests().length && !testRun.config.passWithNoTests && !testRun.config.config.shard) + if (!testRun.rootSuite.allTests().length && !testRun.config.cliPassWithNoTests && !testRun.config.config.shard) throw new Error(`No tests found`); }; } diff --git a/packages/playwright-test/src/runner/uiMode.ts b/packages/playwright-test/src/runner/uiMode.ts index 9c182df3fb..3146b08cb1 100644 --- a/packages/playwright-test/src/runner/uiMode.ts +++ b/packages/playwright-test/src/runner/uiMode.ts @@ -42,13 +42,8 @@ class UIMode { constructor(config: FullConfigInternal) { this._config = config; process.env.PW_LIVE_TRACE_STACKS = '1'; - config.configCLIOverrides.forbidOnly = false; - config.configCLIOverrides.globalTimeout = 0; - config.configCLIOverrides.repeatEach = 0; - config.configCLIOverrides.shard = undefined; - config.configCLIOverrides.updateSnapshots = undefined; - config.listOnly = false; - config.passWithNoTests = true; + config.cliListOnly = false; + config.cliPassWithNoTests = true; for (const project of config.projects) project.deps = []; @@ -148,7 +143,7 @@ class UIMode { private async _listTests() { const listReporter = new TeleReporterEmitter(e => this._dispatchEvent(e)); const reporter = new Multiplexer([listReporter]); - this._config.listOnly = true; + this._config.cliListOnly = true; this._config.testIdMatcher = undefined; const taskRunner = createTaskRunnerForList(this._config, reporter, 'out-of-process'); const testRun = new TestRun(this._config, reporter); @@ -167,7 +162,7 @@ class UIMode { await this._stopTests(); const testIdSet = testIds ? new Set(testIds) : null; - this._config.listOnly = false; + this._config.cliListOnly = false; this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id); const runReporter = new TeleReporterEmitter(e => this._dispatchEvent(e)); diff --git a/packages/playwright-test/src/runner/watchMode.ts b/packages/playwright-test/src/runner/watchMode.ts index ae931b55ce..830f47a35b 100644 --- a/packages/playwright-test/src/runner/watchMode.ts +++ b/packages/playwright-test/src/runner/watchMode.ts @@ -108,7 +108,7 @@ class FSWatcher { export async function runWatchModeLoop(config: FullConfigInternal): Promise { // Reset the settings that don't apply to watch. - config.passWithNoTests = true; + config.cliPassWithNoTests = true; for (const p of config.projects) p.project.retries = 0; diff --git a/packages/playwright-test/src/util.ts b/packages/playwright-test/src/util.ts index 656d7f7393..e81eb60d00 100644 --- a/packages/playwright-test/src/util.ts +++ b/packages/playwright-test/src/util.ts @@ -151,10 +151,10 @@ export function createTitleMatcher(patterns: RegExp | RegExp[]): Matcher { }; } -export function mergeObjects(a: A | undefined | void, b: B | undefined | void): A & B { +export function mergeObjects(a: A | undefined | void, b: B | undefined | void, c: B | undefined | void): A & B & C { const result = { ...a } as any; - if (!Object.is(b, undefined)) { - for (const [name, value] of Object.entries(b as B)) { + for (const x of [b, c].filter(Boolean)) { + for (const [name, value] of Object.entries(x as any)) { if (!Object.is(value, undefined)) result[name] = value; }