chore(test runner): move config loading to runner (#11186)

In preparation for the TestRunner api.
This commit is contained in:
Dmitry Gozman 2022-01-05 13:44:29 -08:00 committed by GitHub
parent 576a9c1ae3
commit 3839917eb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 67 deletions

View File

@ -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>', `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 <file>', `Configuration file, or a test directory with optional "${tsConfig}"/"${jsConfig}"`);
command.option('-c, --config <file>', `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 <grep>', `Only run tests matching this regular expression (default: ".*")`);
command.option('-gv, --grep-invert <grep>', `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<Loader> {
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<Loader> {
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);

View File

@ -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) {

View File

@ -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<void>>;
@ -51,8 +52,42 @@ export class Runner {
private _didBegin = false;
private _internalGlobalSetups: Array<InternalGlobalSetupFunction> = [];
constructor(loader: Loader) {
this._loader = loader;
constructor(configOverrides: Config, defaultConfig: Config = {}) {
this._loader = new Loader(defaultConfig, configOverrides);
}
async loadConfigFromFile(configFileOrDirectory: string): Promise<Config> {
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) {