mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	chore: simplify full config and full project building (#22278)
This commit is contained in:
		
							parent
							
								
									8d2502ee62
								
							
						
					
					
						commit
						3b94bb6740
					
				| @ -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 <project-name...>', `Only run tests from the specified list of projects (default: run all projects)`], | ||||
|   ['--quiet', `Suppress stdio`], | ||||
|   ['--repeat-each <N>', `Run each test N times (default: 1)`], | ||||
|   ['--reporter <reporter>', `Reporter to use, comma-separated, can be ${builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${baseFullConfig.reporter[0]}")`], | ||||
|   ['--reporter <reporter>', `Reporter to use, comma-separated, can be ${builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${defaultReporter}")`], | ||||
|   ['--retries <retries>', `Maximum retry count for flaky tests, zero for no retries (default: no retries)`], | ||||
|   ['--shard <shard>', `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"`], | ||||
|   ['--timeout <timeout>', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${defaultTimeout})`], | ||||
|  | ||||
| @ -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<FullConfig['webServer'], null>[] = []; | ||||
|   plugins: TestRunnerPluginRegistration[] = []; | ||||
|   listOnly = false; | ||||
|   readonly globalOutputDir: string; | ||||
|   readonly configDir: string; | ||||
|   readonly configCLIOverrides: ConfigCLIOverrides; | ||||
|   readonly storeDir: string; | ||||
|   readonly ignoreSnapshots: boolean; | ||||
|   readonly webServers: Exclude<FullConfig['webServer'], null>[]; | ||||
|   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<T>(...args: (T | undefined)[]): T { | ||||
|   for (const arg of args) { | ||||
|     if (arg !== undefined) | ||||
| @ -216,6 +194,12 @@ export function takeFirst<T>(...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'); | ||||
|  | ||||
| @ -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<FullConfigInternal> { | ||||
|     // 1. Validate data provided in the config file.
 | ||||
|     validateConfig(configFile || '<default config>', 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 { | ||||
|  | ||||
| @ -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, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
| @ -49,7 +49,7 @@ export class Runner { | ||||
| 
 | ||||
|   async runAllTests(): Promise<FullResult['status']> { | ||||
|     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.
 | ||||
|  | ||||
| @ -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`); | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @ -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<string>(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)); | ||||
|  | ||||
| @ -108,7 +108,7 @@ class FSWatcher { | ||||
| 
 | ||||
| export async function runWatchModeLoop(config: FullConfigInternal): Promise<FullResult['status']> { | ||||
|   // 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; | ||||
| 
 | ||||
|  | ||||
| @ -151,10 +151,10 @@ export function createTitleMatcher(patterns: RegExp | RegExp[]): Matcher { | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function mergeObjects<A extends object, B extends object>(a: A | undefined | void, b: B | undefined | void): A & B { | ||||
| export function mergeObjects<A extends object, B extends object, C extends object>(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; | ||||
|     } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Pavel Feldman
						Pavel Feldman