mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	chore(test runner): move config loading to runner (#11186)
In preparation for the TestRunner api.
This commit is contained in:
		
							parent
							
								
									576a9c1ae3
								
							
						
					
					
						commit
						3839917eb6
					
				@ -20,10 +20,9 @@ import { Command } from 'commander';
 | 
				
			|||||||
import fs from 'fs';
 | 
					import fs from 'fs';
 | 
				
			||||||
import path from 'path';
 | 
					import path from 'path';
 | 
				
			||||||
import type { Config } from './types';
 | 
					import type { Config } from './types';
 | 
				
			||||||
import { Runner, builtInReporters, BuiltInReporter } from './runner';
 | 
					import { Runner, builtInReporters, BuiltInReporter, kDefaultConfigFiles } from './runner';
 | 
				
			||||||
import { stopProfiling, startProfiling } from './profiler';
 | 
					import { stopProfiling, startProfiling } from './profiler';
 | 
				
			||||||
import { FilePatternFilter } from './util';
 | 
					import { FilePatternFilter } from './util';
 | 
				
			||||||
import { Loader } from './loader';
 | 
					 | 
				
			||||||
import { showHTMLReport } from './reporters/html';
 | 
					import { showHTMLReport } from './reporters/html';
 | 
				
			||||||
import { GridServer } from 'playwright-core/lib/grid/gridServer';
 | 
					import { GridServer } from 'playwright-core/lib/grid/gridServer';
 | 
				
			||||||
import dockerFactory from 'playwright-core/lib/grid/dockerGridFactory';
 | 
					import dockerFactory from 'playwright-core/lib/grid/dockerGridFactory';
 | 
				
			||||||
@ -31,17 +30,6 @@ import { createGuid } from 'playwright-core/lib/utils/utils';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const defaultTimeout = 30000;
 | 
					const defaultTimeout = 30000;
 | 
				
			||||||
const defaultReporter: BuiltInReporter = process.env.CI ? 'dot' : 'list';
 | 
					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) {
 | 
					export function addTestCommand(program: Command) {
 | 
				
			||||||
  const command = program.command('test [test-filter...]');
 | 
					  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('--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('--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('--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('--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('-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`);
 | 
					  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`);
 | 
					  $ 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) {
 | 
					  if (opts.browser) {
 | 
				
			||||||
    const browserOpt = opts.browser.toLowerCase();
 | 
					    const browserOpt = opts.browser.toLowerCase();
 | 
				
			||||||
    if (!['all', 'chromium', 'firefox', 'webkit'].includes(browserOpt))
 | 
					    if (!['all', 'chromium', 'firefox', 'webkit'].includes(browserOpt))
 | 
				
			||||||
@ -122,55 +121,14 @@ async function createLoader(opts: { [key: string]: any }): Promise<Loader> {
 | 
				
			|||||||
    process.env.PWDEBUG = '1';
 | 
					    process.env.PWDEBUG = '1';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const loader = new Loader(defaultConfig, overrides);
 | 
					  const runner = new Runner(overrides, defaultConfig);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async function loadConfig(configFile: string) {
 | 
					  // When no --config option is passed, let's look for the config file in the current directory.
 | 
				
			||||||
    if (fs.existsSync(configFile)) {
 | 
					  const configFile = opts.config ? path.resolve(process.cwd(), opts.config) : process.cwd();
 | 
				
			||||||
      if (process.stdout.isTTY)
 | 
					  const config = await runner.loadConfigFromFile(configFile);
 | 
				
			||||||
        console.log(`Using config at ` + configFile);
 | 
					  if (('projects' in config) && opts.browser)
 | 
				
			||||||
      const loadedConfig = await loader.loadConfigFile(configFile);
 | 
					    throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`);
 | 
				
			||||||
      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;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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 filePatternFilters: FilePatternFilter[] = args.map(arg => {
 | 
				
			||||||
    const match = /^(.*):(\d+)$/.exec(arg);
 | 
					    const match = /^(.*):(\d+)$/.exec(arg);
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
@ -179,7 +137,6 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const runner = new Runner(loader);
 | 
					 | 
				
			||||||
  if (process.env.PLAYWRIGHT_DOCKER)
 | 
					  if (process.env.PLAYWRIGHT_DOCKER)
 | 
				
			||||||
    runner.addInternalGlobalSetup(launchDockerContainer);
 | 
					    runner.addInternalGlobalSetup(launchDockerContainer);
 | 
				
			||||||
  const result = await runner.run(!!opts.list, filePatternFilters, opts.project || undefined);
 | 
					  const result = await runner.run(!!opts.list, filePatternFilters, opts.project || undefined);
 | 
				
			||||||
 | 
				
			|||||||
@ -68,9 +68,10 @@ export class Loader {
 | 
				
			|||||||
    return rawConfig;
 | 
					    return rawConfig;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  loadEmptyConfig(rootDir: string) {
 | 
					  loadEmptyConfig(rootDir: string): Config {
 | 
				
			||||||
    this._config = {};
 | 
					    this._config = {};
 | 
				
			||||||
    this._processConfigObject(rootDir);
 | 
					    this._processConfigObject(rootDir);
 | 
				
			||||||
 | 
					    return {};
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private _processConfigObject(rootDir: string) {
 | 
					  private _processConfigObject(rootDir: string) {
 | 
				
			||||||
 | 
				
			|||||||
@ -35,13 +35,14 @@ import EmptyReporter from './reporters/empty';
 | 
				
			|||||||
import HtmlReporter from './reporters/html';
 | 
					import HtmlReporter from './reporters/html';
 | 
				
			||||||
import { ProjectImpl } from './project';
 | 
					import { ProjectImpl } from './project';
 | 
				
			||||||
import { Minimatch } from 'minimatch';
 | 
					import { Minimatch } from 'minimatch';
 | 
				
			||||||
import { FullConfig } from './types';
 | 
					import { Config, FullConfig } from './types';
 | 
				
			||||||
import { WebServer } from './webServer';
 | 
					import { WebServer } from './webServer';
 | 
				
			||||||
import { raceAgainstDeadline } from 'playwright-core/lib/utils/async';
 | 
					import { raceAgainstDeadline } from 'playwright-core/lib/utils/async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const removeFolderAsync = promisify(rimraf);
 | 
					const removeFolderAsync = promisify(rimraf);
 | 
				
			||||||
const readDirAsync = promisify(fs.readdir);
 | 
					const readDirAsync = promisify(fs.readdir);
 | 
				
			||||||
const readFileAsync = promisify(fs.readFile);
 | 
					const readFileAsync = promisify(fs.readFile);
 | 
				
			||||||
 | 
					export const kDefaultConfigFiles = ['playwright.config.ts', 'playwright.config.js', 'playwright.config.mjs'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type InternalGlobalSetupFunction = () => Promise<() => Promise<void>>;
 | 
					type InternalGlobalSetupFunction = () => Promise<() => Promise<void>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -51,8 +52,42 @@ export class Runner {
 | 
				
			|||||||
  private _didBegin = false;
 | 
					  private _didBegin = false;
 | 
				
			||||||
  private _internalGlobalSetups: Array<InternalGlobalSetupFunction> = [];
 | 
					  private _internalGlobalSetups: Array<InternalGlobalSetupFunction> = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(loader: Loader) {
 | 
					  constructor(configOverrides: Config, defaultConfig: Config = {}) {
 | 
				
			||||||
    this._loader = loader;
 | 
					    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) {
 | 
					  private async _createReporter(list: boolean) {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user