diff --git a/packages/playwright-test/src/cli.ts b/packages/playwright-test/src/cli.ts index def8b74e27..baa3750732 100644 --- a/packages/playwright-test/src/cli.ts +++ b/packages/playwright-test/src/cli.ts @@ -20,10 +20,9 @@ import { Command } from 'commander'; import fs from 'fs'; import path from 'path'; import type { Config } from './types'; -import { Runner, builtInReporters, BuiltInReporter } from './runner'; +import { Runner, builtInReporters, BuiltInReporter, kDefaultConfigFiles } from './runner'; import { stopProfiling, startProfiling } from './profiler'; import { FilePatternFilter } from './util'; -import { Loader } from './loader'; import { showHTMLReport } from './reporters/html'; import { GridServer } from 'playwright-core/lib/grid/gridServer'; import dockerFactory from 'playwright-core/lib/grid/dockerGridFactory'; @@ -31,17 +30,6 @@ import { createGuid } from 'playwright-core/lib/utils/utils'; const defaultTimeout = 30000; const defaultReporter: BuiltInReporter = process.env.CI ? 'dot' : 'list'; -const tsConfig = 'playwright.config.ts'; -const jsConfig = 'playwright.config.js'; -const mjsConfig = 'playwright.config.mjs'; -const defaultConfig: Config = { - preserveOutput: 'always', - reporter: [ [defaultReporter] ], - reportSlowTests: { max: 5, threshold: 15000 }, - timeout: defaultTimeout, - updateSnapshots: 'missing', - workers: Math.ceil(require('os').cpus().length / 2), -}; export function addTestCommand(program: Command) { const command = program.command('test [test-filter...]'); @@ -49,7 +37,7 @@ export function addTestCommand(program: Command) { command.option('--browser ', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`); command.option('--headed', `Run tests in headed browsers (default: headless)`); command.option('--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --maxFailures=1 --headed --workers=1" options`); - command.option('-c, --config ', `Configuration file, or a test directory with optional "${tsConfig}"/"${jsConfig}"`); + command.option('-c, --config ', `Configuration file, or a test directory with optional ${kDefaultConfigFiles.map(file => `"${file}"`).join('/')}`); command.option('--forbid-only', `Fail if test.only is called (default: false)`); command.option('-g, --grep ', `Only run tests matching this regular expression (default: ".*")`); command.option('-gv, --grep-invert ', `Only run tests that do not match this regular expression`); @@ -98,7 +86,18 @@ Examples: $ npx playwright show-report playwright-report`); } -async function createLoader(opts: { [key: string]: any }): Promise { +async function runTests(args: string[], opts: { [key: string]: any }) { + await startProfiling(); + + const defaultConfig: Config = { + preserveOutput: 'always', + reporter: [ [defaultReporter] ], + reportSlowTests: { max: 5, threshold: 15000 }, + timeout: defaultTimeout, + updateSnapshots: 'missing', + workers: Math.ceil(require('os').cpus().length / 2), + }; + if (opts.browser) { const browserOpt = opts.browser.toLowerCase(); if (!['all', 'chromium', 'firefox', 'webkit'].includes(browserOpt)) @@ -122,55 +121,14 @@ async function createLoader(opts: { [key: string]: any }): Promise { process.env.PWDEBUG = '1'; } - const loader = new Loader(defaultConfig, overrides); + const runner = new Runner(overrides, defaultConfig); - async function loadConfig(configFile: string) { - if (fs.existsSync(configFile)) { - if (process.stdout.isTTY) - console.log(`Using config at ` + configFile); - const loadedConfig = await loader.loadConfigFile(configFile); - if (('projects' in loadedConfig) && opts.browser) - throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`); - return true; - } - return false; - } + // When no --config option is passed, let's look for the config file in the current directory. + const configFile = opts.config ? path.resolve(process.cwd(), opts.config) : process.cwd(); + const config = await runner.loadConfigFromFile(configFile); + if (('projects' in config) && opts.browser) + throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`); - async function loadConfigFromDirectory(directory: string) { - const configNames = [tsConfig, jsConfig, mjsConfig]; - for (const configName of configNames) { - if (await loadConfig(path.resolve(directory, configName))) - return true; - } - return false; - } - - if (opts.config) { - const configFile = path.resolve(process.cwd(), opts.config); - if (!fs.existsSync(configFile)) - throw new Error(`${opts.config} does not exist`); - if (fs.statSync(configFile).isDirectory()) { - // When passed a directory, look for a config file inside. - if (!await loadConfigFromDirectory(configFile)) { - // If there is no config, assume this as a root testing directory. - loader.loadEmptyConfig(configFile); - } - } else { - // When passed a file, it must be a config file. - await loadConfig(configFile); - } - } else if (!await loadConfigFromDirectory(process.cwd())) { - // No --config option, let's look for the config file in the current directory. - // If not, scan the world. - loader.loadEmptyConfig(process.cwd()); - } - return loader; -} - -async function runTests(args: string[], opts: { [key: string]: any }) { - await startProfiling(); - - const loader = await createLoader(opts); const filePatternFilters: FilePatternFilter[] = args.map(arg => { const match = /^(.*):(\d+)$/.exec(arg); return { @@ -179,7 +137,6 @@ async function runTests(args: string[], opts: { [key: string]: any }) { }; }); - const runner = new Runner(loader); if (process.env.PLAYWRIGHT_DOCKER) runner.addInternalGlobalSetup(launchDockerContainer); const result = await runner.run(!!opts.list, filePatternFilters, opts.project || undefined); diff --git a/packages/playwright-test/src/loader.ts b/packages/playwright-test/src/loader.ts index f344196e56..21abc096ac 100644 --- a/packages/playwright-test/src/loader.ts +++ b/packages/playwright-test/src/loader.ts @@ -68,9 +68,10 @@ export class Loader { return rawConfig; } - loadEmptyConfig(rootDir: string) { + loadEmptyConfig(rootDir: string): Config { this._config = {}; this._processConfigObject(rootDir); + return {}; } private _processConfigObject(rootDir: string) { diff --git a/packages/playwright-test/src/runner.ts b/packages/playwright-test/src/runner.ts index 6d3ba0e793..e2149a33e0 100644 --- a/packages/playwright-test/src/runner.ts +++ b/packages/playwright-test/src/runner.ts @@ -35,13 +35,14 @@ import EmptyReporter from './reporters/empty'; import HtmlReporter from './reporters/html'; import { ProjectImpl } from './project'; import { Minimatch } from 'minimatch'; -import { FullConfig } from './types'; +import { Config, FullConfig } from './types'; import { WebServer } from './webServer'; import { raceAgainstDeadline } from 'playwright-core/lib/utils/async'; const removeFolderAsync = promisify(rimraf); const readDirAsync = promisify(fs.readdir); const readFileAsync = promisify(fs.readFile); +export const kDefaultConfigFiles = ['playwright.config.ts', 'playwright.config.js', 'playwright.config.mjs']; type InternalGlobalSetupFunction = () => Promise<() => Promise>; @@ -51,8 +52,42 @@ export class Runner { private _didBegin = false; private _internalGlobalSetups: Array = []; - constructor(loader: Loader) { - this._loader = loader; + constructor(configOverrides: Config, defaultConfig: Config = {}) { + this._loader = new Loader(defaultConfig, configOverrides); + } + + async loadConfigFromFile(configFileOrDirectory: string): Promise { + const loadConfig = async (configFile: string) => { + if (fs.existsSync(configFile)) { + if (process.stdout.isTTY) + console.log(`Using config at ` + configFile); + const config = await this._loader.loadConfigFile(configFile); + return config; + } + }; + + const loadConfigFromDirectory = async (directory: string) => { + for (const configName of kDefaultConfigFiles) { + const config = await loadConfig(path.resolve(directory, configName)); + if (config) + return config; + } + }; + + if (!fs.existsSync(configFileOrDirectory)) + throw new Error(`${configFileOrDirectory} does not exist`); + if (fs.statSync(configFileOrDirectory).isDirectory()) { + // When passed a directory, look for a config file inside. + const config = await loadConfigFromDirectory(configFileOrDirectory); + if (config) + return config; + // If there is no config, assume this as a root testing directory. + return this._loader.loadEmptyConfig(configFileOrDirectory); + } else { + // When passed a file, it must be a config file. + const config = await loadConfig(configFileOrDirectory); + return config!; + } } private async _createReporter(list: boolean) {