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