chore: inside out the config & project internal (#22260)
This commit is contained in:
parent
02ca63b381
commit
a42567d549
|
|
@ -23,10 +23,12 @@ import { Runner } from './runner/runner';
|
|||
import { stopProfiling, startProfiling } from 'playwright-core/lib/utils';
|
||||
import { experimentalLoaderOption, fileIsModule } from './util';
|
||||
import { showHTMLReport } from './reporters/html';
|
||||
import { baseFullConfig, builtInReporters, ConfigLoader, defaultTimeout, kDefaultConfigFiles, resolveConfigFile } from './common/configLoader';
|
||||
import type { TraceMode } from './common/types';
|
||||
import { ConfigLoader, kDefaultConfigFiles, resolveConfigFile } from './common/configLoader';
|
||||
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 type { FullConfigInternal } from './common/config';
|
||||
|
||||
export function addTestCommands(program: Command) {
|
||||
addTestCommand(program);
|
||||
|
|
@ -127,20 +129,18 @@ async function runTests(args: string[], opts: { [key: string]: any }) {
|
|||
return;
|
||||
|
||||
const configLoader = new ConfigLoader(overrides);
|
||||
let config: FullConfigInternal;
|
||||
if (resolvedConfigFile)
|
||||
await configLoader.loadConfigFile(resolvedConfigFile);
|
||||
config = await configLoader.loadConfigFile(resolvedConfigFile, opts.deps === false);
|
||||
else
|
||||
await configLoader.loadEmptyConfig(configFileOrDirectory);
|
||||
if (opts.deps === false)
|
||||
configLoader.ignoreProjectDependencies();
|
||||
config = await configLoader.loadEmptyConfig(configFileOrDirectory);
|
||||
|
||||
const config = configLoader.fullConfig();
|
||||
config._internal.cliArgs = args;
|
||||
config._internal.cliGrep = opts.grep as string | undefined;
|
||||
config._internal.cliGrepInvert = opts.grepInvert as string | undefined;
|
||||
config._internal.listOnly = !!opts.list;
|
||||
config._internal.cliProjectFilter = opts.project || undefined;
|
||||
config._internal.passWithNoTests = !!opts.passWithNoTests;
|
||||
config.cliArgs = args;
|
||||
config.cliGrep = opts.grep as string | undefined;
|
||||
config.cliGrepInvert = opts.grepInvert as string | undefined;
|
||||
config.listOnly = !!opts.list;
|
||||
config.cliProjectFilter = opts.project || undefined;
|
||||
config.passWithNoTests = !!opts.passWithNoTests;
|
||||
|
||||
const runner = new Runner(config);
|
||||
let status: FullResult['status'];
|
||||
|
|
@ -166,8 +166,8 @@ async function listTestFiles(opts: { [key: string]: any }) {
|
|||
return;
|
||||
|
||||
const configLoader = new ConfigLoader();
|
||||
const runner = new Runner(configLoader.fullConfig());
|
||||
await configLoader.loadConfigFile(resolvedConfigFile);
|
||||
const config = await configLoader.loadConfigFile(resolvedConfigFile);
|
||||
const runner = new Runner(config);
|
||||
const report = await runner.listTestFiles(opts.project);
|
||||
write(JSON.stringify(report), () => {
|
||||
process.exit(0);
|
||||
|
|
|
|||
255
packages/playwright-test/src/common/config.ts
Normal file
255
packages/playwright-test/src/common/config.ts
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
/**
|
||||
* Copyright Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
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 type { Matcher } from '../util';
|
||||
import { mergeObjects } from '../util';
|
||||
import type { ConfigCLIOverrides } from './ipc';
|
||||
import type { FullConfig, FullProject } from '../../types/test';
|
||||
|
||||
export type FixturesWithLocation = {
|
||||
fixtures: Fixtures;
|
||||
location: Location;
|
||||
};
|
||||
export type Annotation = { type: string, description?: string };
|
||||
|
||||
export const defaultTimeout = 30000;
|
||||
|
||||
export class FullConfigInternal {
|
||||
readonly config: FullConfig;
|
||||
globalOutputDir = path.resolve(process.cwd());
|
||||
configDir = '';
|
||||
configCLIOverrides: ConfigCLIOverrides = {};
|
||||
storeDir = '';
|
||||
maxConcurrentTestGroups = 0;
|
||||
ignoreSnapshots = false;
|
||||
webServers: Exclude<FullConfig['webServer'], null>[] = [];
|
||||
plugins: TestRunnerPluginRegistration[] = [];
|
||||
listOnly = false;
|
||||
cliArgs: string[] = [];
|
||||
cliGrep: string | undefined;
|
||||
cliGrepInvert: string | undefined;
|
||||
cliProjectFilter?: string[];
|
||||
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) {
|
||||
this.configDir = configDir;
|
||||
this.config = { ...baseFullConfig };
|
||||
(this.config as any)[configInternalSymbol] = this;
|
||||
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.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);
|
||||
|
||||
const workers = takeFirst(config.workers, '50%');
|
||||
if (typeof workers === 'string') {
|
||||
if (workers.endsWith('%')) {
|
||||
const cpus = os.cpus().length;
|
||||
this.config.workers = Math.max(1, Math.floor(cpus * (parseInt(workers, 10) / 100)));
|
||||
} else {
|
||||
this.config.workers = parseInt(workers, 10);
|
||||
}
|
||||
} else {
|
||||
this.config.workers = workers;
|
||||
}
|
||||
|
||||
const webServers = takeFirst(config.webServer, baseFullConfig.webServer);
|
||||
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;
|
||||
this.webServers = webServers;
|
||||
} else if (webServers) { // legacy singleton mode
|
||||
this.config.webServer = webServers;
|
||||
this.webServers = [webServers];
|
||||
}
|
||||
this.config.metadata = takeFirst(config.metadata, baseFullConfig.metadata);
|
||||
this.projects = (config.projects || [config]).map(p => this._resolveProject(config, p, throwawayArtifactsPath));
|
||||
resolveProjectDependencies(this.projects);
|
||||
this._assignUniqueProjectIds(this.projects);
|
||||
this.config.projects = this.projects.map(p => p.project);
|
||||
}
|
||||
|
||||
private _assignUniqueProjectIds(projects: FullProjectInternal[]) {
|
||||
const usedNames = new Set();
|
||||
for (const p of projects) {
|
||||
const name = p.project.name || '';
|
||||
for (let i = 0; i < projects.length; ++i) {
|
||||
const candidate = name + (i ? i : '');
|
||||
if (usedNames.has(candidate))
|
||||
continue;
|
||||
p.id = candidate;
|
||||
usedNames.add(candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
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) {
|
||||
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 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),
|
||||
metadata: takeFirst(projectConfig.metadata, config.metadata, undefined),
|
||||
name,
|
||||
testDir,
|
||||
snapshotDir,
|
||||
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),
|
||||
dependencies: projectConfig.dependencies || [],
|
||||
};
|
||||
(this.project as any)[projectInternalSymbol] = this;
|
||||
this.fullyParallel = takeFirst(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)
|
||||
return arg;
|
||||
}
|
||||
return undefined as any as T;
|
||||
}
|
||||
|
||||
function resolveReporters(reporters: Config['reporter'], rootDir: string): ReporterDescription[] | undefined {
|
||||
return toReporters(reporters as any)?.map(([id, arg]) => {
|
||||
if (builtInReporters.includes(id as any))
|
||||
return [id, arg];
|
||||
return [require.resolve(id, { paths: [rootDir] }), arg];
|
||||
});
|
||||
}
|
||||
|
||||
function resolveProjectDependencies(projects: FullProjectInternal[]) {
|
||||
for (const project of projects) {
|
||||
for (const dependencyName of project.project.dependencies) {
|
||||
const dependencies = projects.filter(p => p.project.name === dependencyName);
|
||||
if (!dependencies.length)
|
||||
throw new Error(`Project '${project.project.name}' depends on unknown project '${dependencyName}'`);
|
||||
if (dependencies.length > 1)
|
||||
throw new Error(`Project dependencies should have unique names, reading ${dependencyName}`);
|
||||
project.deps.push(...dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function toReporters(reporters: BuiltInReporter | ReporterDescription[] | undefined): ReporterDescription[] | undefined {
|
||||
if (!reporters)
|
||||
return;
|
||||
if (typeof reporters === 'string')
|
||||
return [[reporters]];
|
||||
return reporters;
|
||||
}
|
||||
|
||||
export const builtInReporters = ['list', 'line', 'dot', 'json', 'junit', 'null', 'github', 'html'] as const;
|
||||
export type BuiltInReporter = typeof builtInReporters[number];
|
||||
|
||||
export type ContextReuseMode = 'none' | 'force' | 'when-possible';
|
||||
|
||||
const configInternalSymbol = Symbol('configInternalSymbol');
|
||||
const projectInternalSymbol = Symbol('projectInternalSymbol');
|
||||
|
|
@ -15,16 +15,14 @@
|
|||
*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { isRegExp } from 'playwright-core/lib/utils';
|
||||
import type { ConfigCLIOverrides, SerializedConfig } from './ipc';
|
||||
import { requireOrImport } from './transform';
|
||||
import type { Config, FullConfigInternal, FullProjectInternal, Project, ReporterDescription } from './types';
|
||||
import type { Config, Project } from '../../types/test';
|
||||
import { errorWithFile, getPackageJsonPath, mergeObjects } from '../util';
|
||||
import { setCurrentConfig } from './globals';
|
||||
|
||||
export const defaultTimeout = 30000;
|
||||
import { FullConfigInternal, takeFirst, toReporters } from './config';
|
||||
|
||||
const kDefineConfigWasUsed = Symbol('defineConfigWasUsed');
|
||||
export const defineConfig = (config: any) => {
|
||||
|
|
@ -33,62 +31,64 @@ export const defineConfig = (config: any) => {
|
|||
};
|
||||
|
||||
export class ConfigLoader {
|
||||
private _fullConfig: FullConfigInternal;
|
||||
private _configCLIOverrides: ConfigCLIOverrides;
|
||||
private _fullConfig: FullConfigInternal | undefined;
|
||||
|
||||
constructor(configCLIOverrides?: ConfigCLIOverrides) {
|
||||
this._fullConfig = { ...baseFullConfig };
|
||||
this._fullConfig._internal.configCLIOverrides = configCLIOverrides || {};
|
||||
this._configCLIOverrides = configCLIOverrides || {};
|
||||
}
|
||||
|
||||
static async deserialize(data: SerializedConfig): Promise<ConfigLoader> {
|
||||
static async deserialize(data: SerializedConfig): Promise<FullConfigInternal> {
|
||||
const loader = new ConfigLoader(data.configCLIOverrides);
|
||||
if (data.configFile)
|
||||
await loader.loadConfigFile(data.configFile);
|
||||
else
|
||||
await loader.loadEmptyConfig(data.configDir);
|
||||
return loader;
|
||||
return await loader.loadConfigFile(data.configFile);
|
||||
return await loader.loadEmptyConfig(data.configDir);
|
||||
}
|
||||
|
||||
async loadConfigFile(file: string): Promise<FullConfigInternal> {
|
||||
if (this._fullConfig.configFile)
|
||||
async loadConfigFile(file: string, ignoreProjectDependencies = false): Promise<FullConfigInternal> {
|
||||
if (this._fullConfig)
|
||||
throw new Error('Cannot load two config files');
|
||||
const config = await requireOrImportDefaultObject(file) as Config;
|
||||
await this._processConfigObject(config, path.dirname(file), file);
|
||||
setCurrentConfig(this._fullConfig);
|
||||
return this._fullConfig;
|
||||
const fullConfig = await this._loadConfig(config, path.dirname(file), file);
|
||||
setCurrentConfig(fullConfig);
|
||||
if (ignoreProjectDependencies) {
|
||||
for (const project of fullConfig.projects)
|
||||
project.deps = [];
|
||||
}
|
||||
this._fullConfig = fullConfig;
|
||||
return fullConfig;
|
||||
}
|
||||
|
||||
async loadEmptyConfig(configDir: string): Promise<Config> {
|
||||
await this._processConfigObject({}, configDir);
|
||||
setCurrentConfig(this._fullConfig);
|
||||
return {};
|
||||
async loadEmptyConfig(configDir: string): Promise<FullConfigInternal> {
|
||||
const fullConfig = await this._loadConfig({}, configDir);
|
||||
setCurrentConfig(fullConfig);
|
||||
return fullConfig;
|
||||
}
|
||||
|
||||
private async _processConfigObject(config: Config, configDir: string, configFile?: string) {
|
||||
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.
|
||||
const configCLIOverrides = this._fullConfig._internal.configCLIOverrides;
|
||||
config.forbidOnly = takeFirst(configCLIOverrides.forbidOnly, config.forbidOnly);
|
||||
config.fullyParallel = takeFirst(configCLIOverrides.fullyParallel, config.fullyParallel);
|
||||
config.globalTimeout = takeFirst(configCLIOverrides.globalTimeout, config.globalTimeout);
|
||||
config.maxFailures = takeFirst(configCLIOverrides.maxFailures, config.maxFailures);
|
||||
config.outputDir = takeFirst(configCLIOverrides.outputDir, config.outputDir);
|
||||
config.quiet = takeFirst(configCLIOverrides.quiet, config.quiet);
|
||||
config.repeatEach = takeFirst(configCLIOverrides.repeatEach, config.repeatEach);
|
||||
config.retries = takeFirst(configCLIOverrides.retries, config.retries);
|
||||
if (configCLIOverrides.reporter)
|
||||
config.reporter = toReporters(configCLIOverrides.reporter as any);
|
||||
config.shard = takeFirst(configCLIOverrides.shard, config.shard);
|
||||
config.timeout = takeFirst(configCLIOverrides.timeout, config.timeout);
|
||||
config.updateSnapshots = takeFirst(configCLIOverrides.updateSnapshots, config.updateSnapshots);
|
||||
config.ignoreSnapshots = takeFirst(configCLIOverrides.ignoreSnapshots, config.ignoreSnapshots);
|
||||
if (configCLIOverrides.projects && config.projects)
|
||||
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(configCLIOverrides.projects, config.projects as any);
|
||||
config.workers = takeFirst(configCLIOverrides.workers, config.workers);
|
||||
config.use = mergeObjects(config.use, configCLIOverrides.use);
|
||||
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);
|
||||
|
||||
|
|
@ -110,134 +110,19 @@ export class ConfigLoader {
|
|||
if (config.snapshotDir !== undefined)
|
||||
config.snapshotDir = path.resolve(configDir, config.snapshotDir);
|
||||
|
||||
this._fullConfig._internal.configDir = configDir;
|
||||
this._fullConfig._internal.storeDir = path.resolve(configDir, (config as any)._storeDir || 'playwright');
|
||||
this._fullConfig.configFile = configFile;
|
||||
this._fullConfig.rootDir = config.testDir || configDir;
|
||||
this._fullConfig._internal.globalOutputDir = takeFirst(config.outputDir, throwawayArtifactsPath, baseFullConfig._internal.globalOutputDir);
|
||||
this._fullConfig.forbidOnly = takeFirst(config.forbidOnly, baseFullConfig.forbidOnly);
|
||||
this._fullConfig.fullyParallel = takeFirst(config.fullyParallel, baseFullConfig.fullyParallel);
|
||||
this._fullConfig.globalSetup = takeFirst(config.globalSetup, baseFullConfig.globalSetup);
|
||||
this._fullConfig.globalTeardown = takeFirst(config.globalTeardown, baseFullConfig.globalTeardown);
|
||||
this._fullConfig.globalTimeout = takeFirst(config.globalTimeout, baseFullConfig.globalTimeout);
|
||||
this._fullConfig.grep = takeFirst(config.grep, baseFullConfig.grep);
|
||||
this._fullConfig.grepInvert = takeFirst(config.grepInvert, baseFullConfig.grepInvert);
|
||||
this._fullConfig.maxFailures = takeFirst(config.maxFailures, baseFullConfig.maxFailures);
|
||||
this._fullConfig.preserveOutput = takeFirst(config.preserveOutput, baseFullConfig.preserveOutput);
|
||||
this._fullConfig.reporter = takeFirst(resolveReporters(config.reporter, configDir), baseFullConfig.reporter);
|
||||
this._fullConfig.reportSlowTests = takeFirst(config.reportSlowTests, baseFullConfig.reportSlowTests);
|
||||
this._fullConfig.quiet = takeFirst(config.quiet, baseFullConfig.quiet);
|
||||
this._fullConfig.shard = takeFirst(config.shard, baseFullConfig.shard);
|
||||
this._fullConfig._internal.ignoreSnapshots = takeFirst(config.ignoreSnapshots, baseFullConfig._internal.ignoreSnapshots);
|
||||
this._fullConfig.updateSnapshots = takeFirst(config.updateSnapshots, baseFullConfig.updateSnapshots);
|
||||
this._fullConfig._internal.plugins = ((config as any)._plugins || []).map((p: any) => ({ factory: p }));
|
||||
this._fullConfig._internal.defineConfigWasUsed = !!(config as any)[kDefineConfigWasUsed];
|
||||
|
||||
const workers = takeFirst(config.workers, '50%');
|
||||
if (typeof workers === 'string') {
|
||||
if (workers.endsWith('%')) {
|
||||
const cpus = os.cpus().length;
|
||||
this._fullConfig.workers = Math.max(1, Math.floor(cpus * (parseInt(workers, 10) / 100)));
|
||||
} else {
|
||||
this._fullConfig.workers = parseInt(workers, 10);
|
||||
}
|
||||
} else {
|
||||
this._fullConfig.workers = workers;
|
||||
}
|
||||
|
||||
const webServers = takeFirst(config.webServer, baseFullConfig.webServer);
|
||||
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._fullConfig.webServer = null;
|
||||
this._fullConfig._internal.webServers = webServers;
|
||||
} else if (webServers) { // legacy singleton mode
|
||||
this._fullConfig.webServer = webServers;
|
||||
this._fullConfig._internal.webServers = [webServers];
|
||||
}
|
||||
this._fullConfig.metadata = takeFirst(config.metadata, baseFullConfig.metadata);
|
||||
this._fullConfig.projects = (config.projects || [config]).map(p => this._resolveProject(config, this._fullConfig, p, throwawayArtifactsPath));
|
||||
|
||||
resolveProjectDependencies(this._fullConfig.projects);
|
||||
this._assignUniqueProjectIds(this._fullConfig.projects);
|
||||
}
|
||||
|
||||
ignoreProjectDependencies() {
|
||||
for (const project of this._fullConfig.projects)
|
||||
project._internal.deps = [];
|
||||
}
|
||||
|
||||
private _assignUniqueProjectIds(projects: FullProjectInternal[]) {
|
||||
const usedNames = new Set();
|
||||
for (const p of projects) {
|
||||
const name = p.name || '';
|
||||
for (let i = 0; i < projects.length; ++i) {
|
||||
const candidate = name + (i ? i : '');
|
||||
if (usedNames.has(candidate))
|
||||
continue;
|
||||
p._internal.id = candidate;
|
||||
usedNames.add(candidate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fullConfig(): FullConfigInternal {
|
||||
return this._fullConfig;
|
||||
const fullConfig = new FullConfigInternal(configDir, configFile, config, throwawayArtifactsPath);
|
||||
fullConfig.defineConfigWasUsed = !!(config as any)[kDefineConfigWasUsed];
|
||||
fullConfig.configCLIOverrides = this._configCLIOverrides;
|
||||
return fullConfig;
|
||||
}
|
||||
|
||||
private _applyCLIOverridesToProject(projectConfig: Project) {
|
||||
const configCLIOverrides = this._fullConfig._internal.configCLIOverrides;
|
||||
projectConfig.fullyParallel = takeFirst(configCLIOverrides.fullyParallel, projectConfig.fullyParallel);
|
||||
projectConfig.outputDir = takeFirst(configCLIOverrides.outputDir, projectConfig.outputDir);
|
||||
projectConfig.repeatEach = takeFirst(configCLIOverrides.repeatEach, projectConfig.repeatEach);
|
||||
projectConfig.retries = takeFirst(configCLIOverrides.retries, projectConfig.retries);
|
||||
projectConfig.timeout = takeFirst(configCLIOverrides.timeout, projectConfig.timeout);
|
||||
projectConfig.use = mergeObjects(projectConfig.use, configCLIOverrides.use);
|
||||
}
|
||||
|
||||
private _resolveProject(config: Config, fullConfig: FullConfigInternal, projectConfig: Project, throwawayArtifactsPath: string): FullProjectInternal {
|
||||
// Resolve all config dirs relative to configDir.
|
||||
if (projectConfig.testDir !== undefined)
|
||||
projectConfig.testDir = path.resolve(fullConfig._internal.configDir, projectConfig.testDir);
|
||||
if (projectConfig.outputDir !== undefined)
|
||||
projectConfig.outputDir = path.resolve(fullConfig._internal.configDir, projectConfig.outputDir);
|
||||
if (projectConfig.snapshotDir !== undefined)
|
||||
projectConfig.snapshotDir = path.resolve(fullConfig._internal.configDir, projectConfig.snapshotDir);
|
||||
|
||||
const testDir = takeFirst(projectConfig.testDir, config.testDir, fullConfig._internal.configDir);
|
||||
const respectGitIgnore = !projectConfig.testDir && !config.testDir;
|
||||
|
||||
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 defaultSnapshotPathTemplate = '{snapshotDir}/{testFileDir}/{testFileName}-snapshots/{arg}{-projectName}{-snapshotSuffix}{ext}';
|
||||
const snapshotPathTemplate = takeFirst(projectConfig.snapshotPathTemplate, config.snapshotPathTemplate, defaultSnapshotPathTemplate);
|
||||
return {
|
||||
_internal: {
|
||||
id: '',
|
||||
fullConfig: fullConfig,
|
||||
fullyParallel: takeFirst(projectConfig.fullyParallel, config.fullyParallel, undefined),
|
||||
expect: takeFirst(projectConfig.expect, config.expect, {}),
|
||||
deps: [],
|
||||
respectGitIgnore: respectGitIgnore,
|
||||
},
|
||||
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),
|
||||
metadata: takeFirst(projectConfig.metadata, config.metadata, undefined),
|
||||
name,
|
||||
testDir,
|
||||
snapshotDir,
|
||||
snapshotPathTemplate,
|
||||
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),
|
||||
dependencies: projectConfig.dependencies || [],
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,14 +133,6 @@ async function requireOrImportDefaultObject(file: string) {
|
|||
return object;
|
||||
}
|
||||
|
||||
function takeFirst<T>(...args: (T | undefined)[]): T {
|
||||
for (const arg of args) {
|
||||
if (arg !== undefined)
|
||||
return arg;
|
||||
}
|
||||
return undefined as any as T;
|
||||
}
|
||||
|
||||
function validateConfig(file: string, config: Config) {
|
||||
if (typeof config !== 'object' || !config)
|
||||
throw errorWithFile(file, `Configuration file must export a single object`);
|
||||
|
|
@ -428,53 +305,6 @@ function validateProject(file: string, project: Project, title: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export const baseFullConfig: FullConfigInternal = {
|
||||
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 },
|
||||
configFile: '',
|
||||
rootDir: path.resolve(process.cwd()),
|
||||
quiet: false,
|
||||
shard: null,
|
||||
updateSnapshots: 'missing',
|
||||
version: require('../../package.json').version,
|
||||
workers: 0,
|
||||
webServer: null,
|
||||
_internal: {
|
||||
webServers: [],
|
||||
globalOutputDir: path.resolve(process.cwd()),
|
||||
configDir: '',
|
||||
configCLIOverrides: {},
|
||||
storeDir: '',
|
||||
maxConcurrentTestGroups: 0,
|
||||
ignoreSnapshots: false,
|
||||
plugins: [],
|
||||
cliArgs: [],
|
||||
cliGrep: undefined,
|
||||
cliGrepInvert: undefined,
|
||||
listOnly: false,
|
||||
defineConfigWasUsed: false,
|
||||
}
|
||||
};
|
||||
|
||||
function resolveReporters(reporters: Config['reporter'], rootDir: string): ReporterDescription[]|undefined {
|
||||
return toReporters(reporters as any)?.map(([id, arg]) => {
|
||||
if (builtInReporters.includes(id as any))
|
||||
return [id, arg];
|
||||
return [require.resolve(id, { paths: [rootDir] }), arg];
|
||||
});
|
||||
}
|
||||
|
||||
function resolveScript(id: string, rootDir: string) {
|
||||
const localPath = path.resolve(rootDir, id);
|
||||
if (fs.existsSync(localPath))
|
||||
|
|
@ -482,19 +312,6 @@ function resolveScript(id: string, rootDir: string) {
|
|||
return require.resolve(id, { paths: [rootDir] });
|
||||
}
|
||||
|
||||
function resolveProjectDependencies(projects: FullProjectInternal[]) {
|
||||
for (const project of projects) {
|
||||
for (const dependencyName of project.dependencies) {
|
||||
const dependencies = projects.filter(p => p.name === dependencyName);
|
||||
if (!dependencies.length)
|
||||
throw new Error(`Project '${project.name}' depends on unknown project '${dependencyName}'`);
|
||||
if (dependencies.length > 1)
|
||||
throw new Error(`Project dependencies should have unique names, reading ${dependencyName}`);
|
||||
project._internal.deps.push(...dependencies);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const kDefaultConfigFiles = ['playwright.config.ts', 'playwright.config.js', 'playwright.config.mjs'];
|
||||
|
||||
export function resolveConfigFile(configFileOrDirectory: string): string | null {
|
||||
|
|
@ -526,14 +343,3 @@ export function resolveConfigFile(configFileOrDirectory: string): string | null
|
|||
return configFile!;
|
||||
}
|
||||
}
|
||||
|
||||
export const builtInReporters = ['list', 'line', 'dot', 'json', 'junit', 'null', 'github', 'html'] as const;
|
||||
export type BuiltInReporter = typeof builtInReporters[number];
|
||||
|
||||
export function toReporters(reporters: BuiltInReporter | ReporterDescription[] | undefined): ReporterDescription[] | undefined {
|
||||
if (!reporters)
|
||||
return;
|
||||
if (typeof reporters === 'string')
|
||||
return [[reporters]];
|
||||
return reporters;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@
|
|||
|
||||
import { formatLocation } from '../util';
|
||||
import * as crypto from 'crypto';
|
||||
import type { Fixtures, FixturesWithLocation, Location } from './types';
|
||||
import type { Fixtures } from '../../types/test';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
import type { FixturesWithLocation } from './config';
|
||||
|
||||
export type FixtureScope = 'test' | 'worker';
|
||||
type FixtureAuto = boolean | 'all-hooks-included';
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
|
||||
import type { TestInfoImpl } from '../worker/testInfo';
|
||||
import type { Suite } from './test';
|
||||
import type { FullConfigInternal } from './types';
|
||||
import { FullProjectInternal } from './config';
|
||||
import type { FullConfigInternal } from './config';
|
||||
|
||||
let currentTestInfoValue: TestInfoImpl | null = null;
|
||||
export function setCurrentTestInfo(testInfo: TestInfoImpl | null) {
|
||||
|
|
@ -38,7 +39,7 @@ export function currentExpectTimeout(options: { timeout?: number }) {
|
|||
const testInfo = currentTestInfo();
|
||||
if (options.timeout !== undefined)
|
||||
return options.timeout;
|
||||
let defaultExpectTimeout = testInfo?.project._internal.expect?.timeout;
|
||||
let defaultExpectTimeout = testInfo?.project ? FullProjectInternal.from(testInfo.project).expect?.timeout : undefined;
|
||||
if (typeof defaultExpectTimeout === 'undefined')
|
||||
defaultExpectTimeout = 5000;
|
||||
return defaultExpectTimeout;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
*/
|
||||
|
||||
import { serializeCompilationCache } from './compilationCache';
|
||||
import type { FullConfigInternal, TestInfoError, TestStatus } from './types';
|
||||
import type { FullConfigInternal } from './config';
|
||||
import type { TestInfoError, TestStatus } from '../../types/test';
|
||||
|
||||
export type ConfigCLIOverrides = {
|
||||
forbidOnly?: boolean;
|
||||
|
|
@ -126,9 +127,9 @@ export type EnvProducedPayload = [string, string | null][];
|
|||
|
||||
export function serializeConfig(config: FullConfigInternal): SerializedConfig {
|
||||
const result: SerializedConfig = {
|
||||
configFile: config.configFile,
|
||||
configDir: config._internal.configDir,
|
||||
configCLIOverrides: config._internal.configCLIOverrides,
|
||||
configFile: config.config.configFile,
|
||||
configDir: config.configDir,
|
||||
configCLIOverrides: config.configCLIOverrides,
|
||||
compilationCache: serializeCompilationCache(),
|
||||
};
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { FixturePool } from './fixtures';
|
|||
import type { LoadError } from './fixtures';
|
||||
import type { Suite, TestCase } from './test';
|
||||
import type { TestTypeImpl } from './testType';
|
||||
import type { FullProjectInternal } from './types';
|
||||
import type { FullProjectInternal } from './config';
|
||||
import { formatLocation } from '../util';
|
||||
import type { TestError } from '../../reporter';
|
||||
|
||||
|
|
@ -74,8 +74,8 @@ export class PoolBuilder {
|
|||
private _buildTestTypePool(testType: TestTypeImpl, testErrors?: TestError[]): FixturePool {
|
||||
if (!this._testTypePools.has(testType)) {
|
||||
const optionOverrides = {
|
||||
overrides: this._project?.use ?? {},
|
||||
location: { file: `project#${this._project?._internal.id}`, line: 1, column: 1 }
|
||||
overrides: this._project?.project?.use ?? {},
|
||||
location: { file: `project#${this._project?.id}`, line: 1, column: 1 }
|
||||
};
|
||||
const pool = new FixturePool(testType.fixtures, e => this._handleLoadError(e, testErrors), undefined, undefined, optionOverrides);
|
||||
this._testTypePools.set(testType, pool);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import type { WriteStream } from 'tty';
|
||||
import type { EnvProducedPayload, ProcessInitParams, TtyParams } from './ipc';
|
||||
import { startProfiling, stopProfiling } from 'playwright-core/lib/utils';
|
||||
import type { TestInfoError } from './types';
|
||||
import type { TestInfoError } from '../../types/test';
|
||||
import { serializeError } from '../util';
|
||||
|
||||
export type ProtocolRequest = {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import path from 'path';
|
||||
import { calculateSha1 } from 'playwright-core/lib/utils';
|
||||
import type { Suite, TestCase } from './test';
|
||||
import type { FullProjectInternal } from './types';
|
||||
import type { FullProjectInternal } from './config';
|
||||
import type { Matcher, TestFileFilter } from '../util';
|
||||
import { createFileMatcher } from '../util';
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ export function filterTestsRemoveEmptySuites(suite: Suite, filter: (test: TestCa
|
|||
}
|
||||
|
||||
export function buildFileSuiteForProject(project: FullProjectInternal, suite: Suite, repeatEachIndex: number): Suite {
|
||||
const relativeFile = path.relative(project.testDir, suite.location!.file).split(path.sep).join('/');
|
||||
const relativeFile = path.relative(project.project.testDir, suite.location!.file).split(path.sep).join('/');
|
||||
const fileId = calculateSha1(relativeFile).slice(0, 20);
|
||||
|
||||
// Clone suite.
|
||||
|
|
@ -54,11 +54,11 @@ export function buildFileSuiteForProject(project: FullProjectInternal, suite: Su
|
|||
const repeatEachIndexSuffix = repeatEachIndex ? ` (repeat:${repeatEachIndex})` : '';
|
||||
|
||||
// At the point of the query, suite is not yet attached to the project, so we only get file, describe and test titles.
|
||||
const testIdExpression = `[project=${project._internal.id}]${test.titlePath().join('\x1e')}${repeatEachIndexSuffix}`;
|
||||
const testIdExpression = `[project=${project.id}]${test.titlePath().join('\x1e')}${repeatEachIndexSuffix}`;
|
||||
const testId = fileId + '-' + calculateSha1(testIdExpression).slice(0, 20);
|
||||
test.id = testId;
|
||||
test.repeatEachIndex = repeatEachIndex;
|
||||
test._projectId = project._internal.id;
|
||||
test._projectId = project.id;
|
||||
|
||||
// Inherit properties from parent suites.
|
||||
let inheritedRetries: number | undefined;
|
||||
|
|
@ -70,8 +70,8 @@ export function buildFileSuiteForProject(project: FullProjectInternal, suite: Su
|
|||
if (inheritedTimeout === undefined && parentSuite._timeout !== undefined)
|
||||
inheritedTimeout = parentSuite._timeout;
|
||||
}
|
||||
test.retries = inheritedRetries ?? project.retries;
|
||||
test.timeout = inheritedTimeout ?? project.timeout;
|
||||
test.retries = inheritedRetries ?? project.project.retries;
|
||||
test.timeout = inheritedTimeout ?? project.project.timeout;
|
||||
|
||||
// Skip annotations imply skipped expectedStatus.
|
||||
if (test._staticAnnotations.some(a => a.type === 'skip' || a.type === 'fixme'))
|
||||
|
|
@ -79,7 +79,7 @@ export function buildFileSuiteForProject(project: FullProjectInternal, suite: Su
|
|||
|
||||
// We only compute / set digest in the runner.
|
||||
if (test._poolDigest)
|
||||
test._workerHash = `${project._internal.id}-${test._poolDigest}-${repeatEachIndex}`;
|
||||
test._workerHash = `${project.id}-${test._poolDigest}-${repeatEachIndex}`;
|
||||
});
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,9 @@ import type * as reporterTypes from '../../types/testReporter';
|
|||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||
import type { TestTypeImpl } from './testType';
|
||||
import { rootTestType } from './testType';
|
||||
import type { Annotation, FixturesWithLocation, FullProject, FullProjectInternal, Location } from './types';
|
||||
import type { Annotation, FixturesWithLocation, FullProjectInternal } from './config';
|
||||
import type { FullProject } from '../../types/test';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
|
||||
class Base {
|
||||
title: string;
|
||||
|
|
@ -197,7 +199,7 @@ export class Suite extends Base implements SuitePrivate {
|
|||
}
|
||||
|
||||
project(): FullProject | undefined {
|
||||
return this._projectConfig || this.parent?.project();
|
||||
return this._projectConfig?.project || this.parent?.project();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ import { expect } from '../matchers/expect';
|
|||
import { currentlyLoadingFileSuite, currentTestInfo, setCurrentlyLoadingFileSuite } from './globals';
|
||||
import { TestCase, Suite } from './test';
|
||||
import { wrapFunctionWithLocation } from './transform';
|
||||
import type { Fixtures, FixturesWithLocation, Location, TestType } from './types';
|
||||
import type { FixturesWithLocation } from './config';
|
||||
import type { Fixtures, TestType } from '../../types/test';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
|
||||
const testTypeSymbol = Symbol('testType');
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import path from 'path';
|
|||
import fs from 'fs';
|
||||
import { sourceMapSupport, pirates } from '../utilsBundle';
|
||||
import url from 'url';
|
||||
import type { Location } from './types';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
import type { TsConfigLoaderResult } from '../third_party/tsconfig-loader';
|
||||
import { tsConfigLoader } from '../third_party/tsconfig-loader';
|
||||
import Module from 'module';
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
/**
|
||||
* Copyright Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Fixtures, Project } from '../../types/test';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
import type { TestRunnerPluginRegistration } from '../plugins';
|
||||
import type { Matcher } from '../util';
|
||||
import type { ConfigCLIOverrides } from './ipc';
|
||||
import type { FullConfig as FullConfigPublic, FullProject as FullProjectPublic } from './types';
|
||||
export * from '../../types/test';
|
||||
export type { Location } from '../../types/testReporter';
|
||||
|
||||
export type FixturesWithLocation = {
|
||||
fixtures: Fixtures;
|
||||
location: Location;
|
||||
};
|
||||
export type Annotation = { type: string, description?: string };
|
||||
|
||||
type ConfigInternal = {
|
||||
globalOutputDir: string;
|
||||
configDir: string;
|
||||
configCLIOverrides: ConfigCLIOverrides;
|
||||
storeDir: string;
|
||||
maxConcurrentTestGroups: number;
|
||||
ignoreSnapshots: boolean;
|
||||
webServers: Exclude<FullConfigPublic['webServer'], null>[];
|
||||
plugins: TestRunnerPluginRegistration[];
|
||||
listOnly: boolean;
|
||||
cliArgs: string[];
|
||||
cliGrep: string | undefined;
|
||||
cliGrepInvert: string | undefined;
|
||||
cliProjectFilter?: string[];
|
||||
testIdMatcher?: Matcher;
|
||||
passWithNoTests?: boolean;
|
||||
defineConfigWasUsed: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* FullConfigInternal allows the plumbing of configuration details throughout the Test Runner without
|
||||
* increasing the surface area of the public API type called FullConfig.
|
||||
*/
|
||||
export interface FullConfigInternal extends FullConfigPublic {
|
||||
_internal: ConfigInternal;
|
||||
|
||||
// Overrides the public field.
|
||||
projects: FullProjectInternal[];
|
||||
}
|
||||
|
||||
type ProjectInternal = {
|
||||
id: string;
|
||||
fullConfig: FullConfigInternal;
|
||||
fullyParallel: boolean;
|
||||
expect: Project['expect'];
|
||||
respectGitIgnore: boolean;
|
||||
deps: FullProjectInternal[];
|
||||
};
|
||||
|
||||
/**
|
||||
* FullProjectInternal allows the plumbing of configuration details throughout the Test Runner without
|
||||
* increasing the surface area of the public API type called FullProject.
|
||||
*/
|
||||
export interface FullProjectInternal extends FullProjectPublic {
|
||||
_internal: ProjectInternal;
|
||||
snapshotPathTemplate: string;
|
||||
}
|
||||
|
||||
export type ContextReuseMode = 'none' | 'force' | 'when-possible';
|
||||
|
|
@ -22,7 +22,7 @@ import { createGuid, debugMode, addInternalStackPrefix, mergeTraceFiles, saveTra
|
|||
import type { Fixtures, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, ScreenshotMode, TestInfo, TestType, TraceMode, VideoMode } from '../types/test';
|
||||
import type { TestInfoImpl } from './worker/testInfo';
|
||||
import { rootTestType } from './common/testType';
|
||||
import { type ContextReuseMode } from './common/types';
|
||||
import { type ContextReuseMode } from './common/config';
|
||||
import { artifactsFolderName } from './isomorphic/folders';
|
||||
export { expect } from './matchers/expect';
|
||||
export { store as _store } from './store';
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
*/
|
||||
|
||||
import type { FullConfig, FullResult, Location, Reporter, TestError, TestResult, TestStatus, TestStep } from '../../types/testReporter';
|
||||
import type { Annotation, FullProject, Metadata } from '../common/types';
|
||||
import type { Annotation } from '../common/config';
|
||||
import type { FullProject, Metadata } from '../../types/test';
|
||||
import type * as reporterTypes from '../../types/testReporter';
|
||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import type { SerializedConfig } from '../common/ipc';
|
||||
import { ConfigLoader } from '../common/configLoader';
|
||||
import { ProcessRunner } from '../common/process';
|
||||
import type { FullConfigInternal } from '../common/types';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import { loadTestFile } from '../common/testLoader';
|
||||
import type { TestError } from '../../reporter';
|
||||
import { addToCompilationCache, serializeCompilationCache } from '../common/compilationCache';
|
||||
|
|
@ -36,14 +36,14 @@ export class LoaderMain extends ProcessRunner {
|
|||
|
||||
private _config(): Promise<FullConfigInternal> {
|
||||
if (!this._configPromise)
|
||||
this._configPromise = ConfigLoader.deserialize(this._serializedConfig).then(configLoader => configLoader.fullConfig());
|
||||
this._configPromise = ConfigLoader.deserialize(this._serializedConfig);
|
||||
return this._configPromise;
|
||||
}
|
||||
|
||||
async loadTestFile(params: { file: string }) {
|
||||
const testErrors: TestError[] = [];
|
||||
const config = await this._config();
|
||||
const fileSuite = await loadTestFile(params.file, config.rootDir, testErrors);
|
||||
const fileSuite = await loadTestFile(params.file, config.config.rootDir, testErrors);
|
||||
this._poolBuilder.buildPools(fileSuite);
|
||||
return { fileSuite: fileSuite._deepSerialize(), testErrors };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ import {
|
|||
toPass
|
||||
} from './matchers';
|
||||
import { toMatchSnapshot, toHaveScreenshot } from './toMatchSnapshot';
|
||||
import type { Expect } from '../common/types';
|
||||
import type { Expect } from '../../types/test';
|
||||
import { currentTestInfo, currentExpectTimeout } from '../common/globals';
|
||||
import { filteredStackTrace, serializeError, stringifyStackFrames, trimLongString } from '../util';
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import type { Expect } from '../common/types';
|
||||
import type { Expect } from '../../types/test';
|
||||
|
||||
export function matcherHint(state: ReturnType<Expect['getState']>, matcherName: string, a: any, b: any, matcherOptions: any, timeout?: number) {
|
||||
const message = state.utils.matcherHint(matcherName, a, b, matcherOptions);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import type { Locator, Page, APIResponse } from 'playwright-core';
|
||||
import type { FrameExpectOptions } from 'playwright-core/lib/client/types';
|
||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import type { Expect } from '../common/types';
|
||||
import type { Expect } from '../../types/test';
|
||||
import { expectTypes, callLogText } from '../util';
|
||||
import { currentTestInfo } from '../common/globals';
|
||||
import type { TestInfoErrorState } from '../worker/testInfo';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Expect } from '../common/types';
|
||||
import type { Expect } from '../../types/test';
|
||||
import { expectTypes, callLogText } from '../util';
|
||||
import { matcherHint } from './matcherHint';
|
||||
import { currentExpectTimeout } from '../common/globals';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Expect } from '../common/types';
|
||||
import type { Expect } from '../../types/test';
|
||||
import { expectTypes } from '../util';
|
||||
import { callLogText } from '../util';
|
||||
import { matcherHint } from './matcherHint';
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
import type { Locator, Page } from 'playwright-core';
|
||||
import type { Page as PageEx } from 'playwright-core/lib/client/page';
|
||||
import type { Locator as LocatorEx } from 'playwright-core/lib/client/locator';
|
||||
import type { Expect } from '../common/types';
|
||||
import type { Expect } from '../../types/test';
|
||||
import { currentTestInfo, currentExpectTimeout } from '../common/globals';
|
||||
import type { ImageComparatorOptions, Comparator } from 'playwright-core/lib/utils';
|
||||
import { getComparator } from 'playwright-core/lib/utils';
|
||||
|
|
@ -253,12 +253,12 @@ export function toMatchSnapshot(
|
|||
if (received instanceof Promise)
|
||||
throw new Error('An unresolved Promise was passed to toMatchSnapshot(), make sure to resolve it by adding await to it.');
|
||||
|
||||
if (testInfo.config._internal.ignoreSnapshots)
|
||||
if (testInfo._configInternal.ignoreSnapshots)
|
||||
return { pass: !this.isNot, message: () => '' };
|
||||
|
||||
const helper = new SnapshotHelper(
|
||||
testInfo, testInfo.snapshotPath.bind(testInfo), determineFileExtension(received),
|
||||
testInfo.project._internal.expect?.toMatchSnapshot || {},
|
||||
testInfo._projectInternal.expect?.toMatchSnapshot || {},
|
||||
nameOrOptions, optOptions);
|
||||
|
||||
if (this.isNot) {
|
||||
|
|
@ -298,10 +298,10 @@ export async function toHaveScreenshot(
|
|||
if (!testInfo)
|
||||
throw new Error(`toHaveScreenshot() must be called during the test`);
|
||||
|
||||
if (testInfo.config._internal.ignoreSnapshots)
|
||||
if (testInfo._configInternal.ignoreSnapshots)
|
||||
return { pass: !this.isNot, message: () => '' };
|
||||
|
||||
const config = (testInfo.project._internal.expect as any)?.toHaveScreenshot;
|
||||
const config = (testInfo._projectInternal.expect as any)?.toHaveScreenshot;
|
||||
const snapshotPathResolver = testInfo.snapshotPath.bind(testInfo);
|
||||
const helper = new SnapshotHelper(
|
||||
testInfo, snapshotPathResolver, 'png',
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
import type { ExpectedTextValue } from '@protocol/channels';
|
||||
import { isRegExp, isString } from 'playwright-core/lib/utils';
|
||||
import type { Expect } from '../common/types';
|
||||
import type { Expect } from '../../types/test';
|
||||
import { expectTypes, callLogText } from '../util';
|
||||
import {
|
||||
printReceivedStringContainExpectedResult,
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Fixtures, Locator, Page, BrowserContextOptions, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, BrowserContext, ContextReuseMode, FullConfigInternal } from './common/types';
|
||||
import type { Fixtures, Locator, Page, BrowserContextOptions, PlaywrightTestArgs, PlaywrightTestOptions, PlaywrightWorkerArgs, PlaywrightWorkerOptions, BrowserContext } from '../types/test';
|
||||
import type { Component, JsxComponent, MountOptions } from '../types/experimentalComponent';
|
||||
import type { ContextReuseMode, FullConfigInternal } from './common/config';
|
||||
|
||||
let boundCallbacksForMount: Function[] = [];
|
||||
|
||||
|
|
@ -38,7 +39,7 @@ export const fixtures: Fixtures<
|
|||
_ctWorker: [{ context: undefined, hash: '' }, { scope: 'worker' }],
|
||||
|
||||
page: async ({ page }, use, info) => {
|
||||
if (!(info.config as FullConfigInternal)._internal.defineConfigWasUsed)
|
||||
if (!((info as any)._configInternal as FullConfigInternal).defineConfigWasUsed)
|
||||
throw new Error('Component testing requires the use of the defineConfig() in your playwright-ct.config.{ts,js}: https://aka.ms/playwright/ct-define-config');
|
||||
await (page as any)._wrapApiCall(async () => {
|
||||
await page.exposeFunction('__ct_dispatch', (ordinal: number, args: any[]) => {
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Suite } from '../../types/testReporter';
|
||||
import type { FullConfig } from '../common/types';
|
||||
import type { FullConfig, Suite } from '../../types/testReporter';
|
||||
import type { Multiplexer } from '../reporters/multiplexer';
|
||||
|
||||
export interface TestRunnerPlugin {
|
||||
|
|
|
|||
|
|
@ -23,11 +23,10 @@ import { parse, traverse, types as t } from '../common/babelBundle';
|
|||
import { stoppable } from '../utilsBundle';
|
||||
import type { ComponentInfo } from '../common/tsxTransform';
|
||||
import { collectComponentUsages, componentInfo } from '../common/tsxTransform';
|
||||
import type { FullConfig } from '../common/types';
|
||||
import { assert, calculateSha1 } from 'playwright-core/lib/utils';
|
||||
import type { AddressInfo } from 'net';
|
||||
import { getPlaywrightVersion } from 'playwright-core/lib/utils';
|
||||
import type { PlaywrightTestConfig as BasePlaywrightTestConfig } from '@playwright/test';
|
||||
import type { PlaywrightTestConfig as BasePlaywrightTestConfig, FullConfig } from '@playwright/test';
|
||||
import type { PluginContext } from 'rollup';
|
||||
import { setExternalDependencies } from '../common/compilationCache';
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { raceAgainstTimeout, launchProcess, httpRequest } from 'playwright-core/
|
|||
|
||||
import type { FullConfig } from '../../types/testReporter';
|
||||
import type { TestRunnerPlugin } from '.';
|
||||
import type { FullConfigInternal } from '../common/types';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import { envWithoutExperimentalLoaderOptions } from '../util';
|
||||
import type { Multiplexer } from '../reporters/multiplexer';
|
||||
|
||||
|
|
@ -204,9 +204,9 @@ export const webServer = (options: WebServerPluginOptions): TestRunnerPlugin =>
|
|||
};
|
||||
|
||||
export const webServerPluginsForConfig = (config: FullConfigInternal): TestRunnerPlugin[] => {
|
||||
const shouldSetBaseUrl = !!config.webServer;
|
||||
const shouldSetBaseUrl = !!config.config.webServer;
|
||||
const webServerPlugins = [];
|
||||
for (const webServerConfig of config._internal.webServers) {
|
||||
for (const webServerConfig of config.webServers) {
|
||||
if ((!webServerConfig.port && !webServerConfig.url) || (webServerConfig.port && webServerConfig.url))
|
||||
throw new Error(`Exactly one of 'port' or 'url' is required in config.webServer.`);
|
||||
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location, Reporter } from '../../types/testReporter';
|
||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||
import type { FullConfigInternal } from '../common/types';
|
||||
import { codeFrameColumns } from '../common/babelBundle';
|
||||
import { monotonicTime } from 'playwright-core/lib/utils';
|
||||
import { FullConfigInternal } from '../common/config';
|
||||
|
||||
export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' };
|
||||
export const kOutputSymbol = Symbol('output');
|
||||
|
|
@ -49,7 +49,7 @@ type TestSummary = {
|
|||
|
||||
export class BaseReporter implements Reporter {
|
||||
duration = 0;
|
||||
config!: FullConfigInternal;
|
||||
config!: FullConfig;
|
||||
suite!: Suite;
|
||||
totalTestCount = 0;
|
||||
result!: FullResult;
|
||||
|
|
@ -66,7 +66,7 @@ export class BaseReporter implements Reporter {
|
|||
|
||||
onBegin(config: FullConfig, suite: Suite) {
|
||||
this.monotonicStartTime = monotonicTime();
|
||||
this.config = config as FullConfigInternal;
|
||||
this.config = config;
|
||||
this.suite = suite;
|
||||
this.totalTestCount = suite.allTests().length;
|
||||
}
|
||||
|
|
@ -122,7 +122,7 @@ export class BaseReporter implements Reporter {
|
|||
}
|
||||
|
||||
protected generateStartingMessage() {
|
||||
const jobs = Math.min(this.config.workers, this.config._internal.maxConcurrentTestGroups);
|
||||
const jobs = Math.min(this.config.workers, FullConfigInternal.from(this.config).maxConcurrentTestGroups);
|
||||
const shardDetails = this.config.shard ? `, shard ${this.config.shard.current} of ${this.config.shard.total}` : '';
|
||||
if (!this.totalTestCount)
|
||||
return '';
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import type { JsonAttachment, JsonReport, JsonSuite, JsonTestCase, JsonTestResul
|
|||
import RawReporter from './raw';
|
||||
import { stripAnsiEscapes } from './base';
|
||||
import { getPackageJsonPath, sanitizeForFilePath } from '../util';
|
||||
import type { Metadata } from '../common/types';
|
||||
import type { Metadata } from '../../types/test';
|
||||
import type { ZipFile } from 'playwright-core/lib/zipBundle';
|
||||
import { yazl } from 'playwright-core/lib/zipBundle';
|
||||
import { mime } from 'playwright-core/lib/utilsBundle';
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import type { FullConfig, TestCase, Suite, TestResult, TestError, TestStep, Full
|
|||
import { formatError, prepareErrorStack } from './base';
|
||||
import { MultiMap } from 'playwright-core/lib/utils';
|
||||
import { assert } from 'playwright-core/lib/utils';
|
||||
import type { FullProjectInternal } from '../common/types';
|
||||
import { FullProjectInternal } from '../common/config';
|
||||
|
||||
export function toPosixPath(aPath: string): string {
|
||||
return aPath.split(path.sep).join(path.posix.sep);
|
||||
|
|
@ -64,7 +64,7 @@ class JSONReporter implements Reporter {
|
|||
repeatEach: project.repeatEach,
|
||||
retries: project.retries,
|
||||
metadata: project.metadata,
|
||||
id: (project as FullProjectInternal)._internal.id,
|
||||
id: FullProjectInternal.from(project).id,
|
||||
name: project.name,
|
||||
testDir: toPosixPath(project.testDir),
|
||||
testIgnore: serializePatterns(project.testIgnore),
|
||||
|
|
@ -81,7 +81,7 @@ class JSONReporter implements Reporter {
|
|||
private _mergeSuites(suites: Suite[]): JSONReportSuite[] {
|
||||
const fileSuites = new MultiMap<string, JSONReportSuite>();
|
||||
for (const projectSuite of suites) {
|
||||
const projectId = (projectSuite.project() as FullProjectInternal)._internal.id;
|
||||
const projectId = FullProjectInternal.from(projectSuite.project()!).id;
|
||||
const projectName = projectSuite.project()!.name;
|
||||
for (const fileSuite of projectSuite.suites) {
|
||||
const file = fileSuite.location!.file;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
import type { FullConfig, TestCase, TestError, TestResult, FullResult, TestStep, Reporter } from '../../types/testReporter';
|
||||
import { Suite } from '../common/test';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import { addSnippetToError } from './base';
|
||||
|
||||
type StdIOChunk = {
|
||||
|
|
@ -27,7 +28,7 @@ type StdIOChunk = {
|
|||
export class Multiplexer {
|
||||
private _reporters: Reporter[];
|
||||
private _deferred: { error?: TestError, stdout?: StdIOChunk, stderr?: StdIOChunk }[] | null = [];
|
||||
private _config!: FullConfig;
|
||||
private _config!: FullConfigInternal;
|
||||
|
||||
constructor(reporters: Reporter[]) {
|
||||
this._reporters = reporters;
|
||||
|
|
@ -37,7 +38,7 @@ export class Multiplexer {
|
|||
return this._reporters.some(r => r.printsToStdio ? r.printsToStdio() : true);
|
||||
}
|
||||
|
||||
onConfigure(config: FullConfig) {
|
||||
onConfigure(config: FullConfigInternal) {
|
||||
this._config = config;
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +93,7 @@ export class Multiplexer {
|
|||
async onExit(result: FullResult) {
|
||||
if (this._deferred) {
|
||||
// onBegin was not reported, emit it.
|
||||
this.onBegin(this._config, new Suite('', 'root'));
|
||||
this.onBegin(this._config.config, new Suite('', 'root'));
|
||||
}
|
||||
|
||||
for (const reporter of this._reporters)
|
||||
|
|
@ -107,7 +108,7 @@ export class Multiplexer {
|
|||
this._deferred.push({ error });
|
||||
return;
|
||||
}
|
||||
addSnippetToError(this._config, error);
|
||||
addSnippetToError(this._config.config, error);
|
||||
for (const reporter of this._reporters)
|
||||
wrap(() => reporter.onError?.(error));
|
||||
}
|
||||
|
|
@ -125,12 +126,12 @@ export class Multiplexer {
|
|||
|
||||
private _addSnippetToTestErrors(test: TestCase, result: TestResult) {
|
||||
for (const error of result.errors)
|
||||
addSnippetToError(this._config, error, test.location.file);
|
||||
addSnippetToError(this._config.config, error, test.location.file);
|
||||
}
|
||||
|
||||
private _addSnippetToStepError(test: TestCase, step: TestStep) {
|
||||
if (step.error)
|
||||
addSnippetToError(this._config, step.error, test.location.file);
|
||||
addSnippetToError(this._config.config, step.error, test.location.file);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import { formatResultFailure } from './base';
|
|||
import { toPosixPath, serializePatterns } from './json';
|
||||
import { MultiMap } from 'playwright-core/lib/utils';
|
||||
import { codeFrameColumns } from '../common/babelBundle';
|
||||
import type { Metadata } from '../common/types';
|
||||
import type { Metadata } from '../../types/test';
|
||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||
|
||||
export type JsonLocation = Location;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import type { FullConfig, FullResult, Reporter, TestError, TestResult, TestStep,
|
|||
import type { Suite, TestCase } from '../common/test';
|
||||
import type { JsonConfig, JsonProject, JsonSuite, JsonTestCase, JsonTestEnd, JsonTestResultEnd, JsonTestResultStart, JsonTestStepEnd, JsonTestStepStart } from '../isomorphic/teleReceiver';
|
||||
import type { SuitePrivate } from '../../types/reporterPrivate';
|
||||
import type { FullConfigInternal, FullProjectInternal } from '../common/types';
|
||||
import { FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
import { createGuid } from 'playwright-core/lib/utils';
|
||||
import { serializeRegexPatterns } from '../isomorphic/teleReceiver';
|
||||
import path from 'path';
|
||||
|
|
@ -123,14 +123,14 @@ export class TeleReporterEmitter implements Reporter {
|
|||
return {
|
||||
rootDir: config.rootDir,
|
||||
configFile: this._relativePath(config.configFile),
|
||||
listOnly: (config as FullConfigInternal)._internal.listOnly,
|
||||
listOnly: FullConfigInternal.from(config).listOnly,
|
||||
};
|
||||
}
|
||||
|
||||
private _serializeProject(suite: Suite): JsonProject {
|
||||
const project = suite.project()!;
|
||||
const report: JsonProject = {
|
||||
id: (project as FullProjectInternal)._internal.id,
|
||||
id: FullProjectInternal.from(project).id,
|
||||
metadata: project.metadata,
|
||||
name: project.name,
|
||||
outputDir: this._relativePath(project.outputDir),
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import type { TestCase } from '../common/test';
|
|||
import { ManualPromise } from 'playwright-core/lib/utils';
|
||||
import { WorkerHost } from './workerHost';
|
||||
import type { TestGroup } from './testGroups';
|
||||
import type { FullConfigInternal } from '../common/types';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import type { Multiplexer } from '../reporters/multiplexer';
|
||||
|
||||
type TestResultData = {
|
||||
|
|
@ -180,7 +180,7 @@ export class Dispatcher {
|
|||
this._isStopped = false;
|
||||
this._workerSlots = [];
|
||||
// 1. Allocate workers.
|
||||
for (let i = 0; i < this._config.workers; i++)
|
||||
for (let i = 0; i < this._config.config.workers; i++)
|
||||
this._workerSlots.push({ busy: false });
|
||||
// 2. Schedule enough jobs.
|
||||
for (let i = 0; i < this._workerSlots.length; i++)
|
||||
|
|
@ -504,7 +504,7 @@ export class Dispatcher {
|
|||
}
|
||||
|
||||
private _hasReachedMaxFailures() {
|
||||
const maxFailures = this._config.maxFailures;
|
||||
const maxFailures = this._config.config.maxFailures;
|
||||
return maxFailures > 0 && this._failureCount >= maxFailures;
|
||||
}
|
||||
|
||||
|
|
@ -512,7 +512,7 @@ export class Dispatcher {
|
|||
if (result.status !== 'skipped' && result.status !== test.expectedStatus)
|
||||
++this._failureCount;
|
||||
this._reporter.onTestEnd?.(test, result);
|
||||
const maxFailures = this._config.maxFailures;
|
||||
const maxFailures = this._config.config.maxFailures;
|
||||
if (maxFailures && this._failureCount === maxFailures)
|
||||
this.stop().catch(e => {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import type { Reporter, TestError } from '../../types/testReporter';
|
||||
import type { FullConfig, Reporter, TestError } from '../../types/testReporter';
|
||||
import { InProcessLoaderHost, OutOfProcessLoaderHost } from './loaderHost';
|
||||
import { Suite } from '../common/test';
|
||||
import type { TestCase } from '../common/test';
|
||||
import type { FullConfigInternal, FullProjectInternal } from '../common/types';
|
||||
import type { FullProjectInternal } from '../common/config';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import { createFileMatcherFromArguments, createFileFiltersFromArguments, createTitleMatcher, errorWithFile, forceRegExp } from '../util';
|
||||
import type { Matcher, TestFileFilter } from '../util';
|
||||
import { buildProjectsClosure, collectFilesForProject, filterProjects } from './projectUtils';
|
||||
|
|
@ -35,11 +36,11 @@ export async function collectProjectsAndTestFiles(testRun: TestRun, additionalFi
|
|||
const config = testRun.config;
|
||||
const fsCache = new Map();
|
||||
const sourceMapCache = new Map();
|
||||
const cliFileMatcher = config._internal.cliArgs.length ? createFileMatcherFromArguments(config._internal.cliArgs) : null;
|
||||
const cliFileMatcher = config.cliArgs.length ? createFileMatcherFromArguments(config.cliArgs) : null;
|
||||
|
||||
// First collect all files for the projects in the command line, don't apply any file filters.
|
||||
const allFilesForProject = new Map<FullProjectInternal, string[]>();
|
||||
for (const project of filterProjects(config.projects, config._internal.cliProjectFilter)) {
|
||||
for (const project of filterProjects(config.projects, config.cliProjectFilter)) {
|
||||
const files = await collectFilesForProject(project, fsCache);
|
||||
allFilesForProject.set(project, files);
|
||||
}
|
||||
|
|
@ -100,8 +101,8 @@ export async function loadFileSuites(testRun: TestRun, mode: 'out-of-process' |
|
|||
for (const file of allTestFiles) {
|
||||
for (const dependency of dependenciesForTestFile(file)) {
|
||||
if (allTestFiles.has(dependency)) {
|
||||
const importer = path.relative(config.rootDir, file);
|
||||
const importee = path.relative(config.rootDir, dependency);
|
||||
const importer = path.relative(config.config.rootDir, file);
|
||||
const importee = path.relative(config.config.rootDir, dependency);
|
||||
errors.push({
|
||||
message: `Error: test file "${importer}" should not import test file "${importee}"`,
|
||||
location: { file, line: 1, column: 1 },
|
||||
|
|
@ -125,20 +126,20 @@ export async function createRootSuite(testRun: TestRun, errors: TestError[], sho
|
|||
// First add top-level projects, so that we can filterOnly and shard just top-level.
|
||||
{
|
||||
// Interpret cli parameters.
|
||||
const cliFileFilters = createFileFiltersFromArguments(config._internal.cliArgs);
|
||||
const grepMatcher = config._internal.cliGrep ? createTitleMatcher(forceRegExp(config._internal.cliGrep)) : () => true;
|
||||
const grepInvertMatcher = config._internal.cliGrepInvert ? createTitleMatcher(forceRegExp(config._internal.cliGrepInvert)) : () => false;
|
||||
const cliFileFilters = createFileFiltersFromArguments(config.cliArgs);
|
||||
const grepMatcher = config.cliGrep ? createTitleMatcher(forceRegExp(config.cliGrep)) : () => true;
|
||||
const grepInvertMatcher = config.cliGrepInvert ? createTitleMatcher(forceRegExp(config.cliGrepInvert)) : () => false;
|
||||
const cliTitleMatcher = (title: string) => !grepInvertMatcher(title) && grepMatcher(title);
|
||||
|
||||
// Clone file suites for top-level projects.
|
||||
for (const [project, fileSuites] of testRun.projectSuites) {
|
||||
if (testRun.projectType.get(project) === 'top-level')
|
||||
rootSuite._addSuite(await createProjectSuite(fileSuites, project, { cliFileFilters, cliTitleMatcher, testIdMatcher: config._internal.testIdMatcher }));
|
||||
rootSuite._addSuite(await createProjectSuite(fileSuites, project, { cliFileFilters, cliTitleMatcher, testIdMatcher: config.testIdMatcher }));
|
||||
}
|
||||
}
|
||||
|
||||
// Complain about only.
|
||||
if (config.forbidOnly) {
|
||||
if (config.config.forbidOnly) {
|
||||
const onlyTestsAndSuites = rootSuite._getOnlyItems();
|
||||
if (onlyTestsAndSuites.length > 0)
|
||||
errors.push(...createForbidOnlyErrors(onlyTestsAndSuites));
|
||||
|
|
@ -149,14 +150,14 @@ export async function createRootSuite(testRun: TestRun, errors: TestError[], sho
|
|||
filterOnly(rootSuite);
|
||||
|
||||
// Shard only the top-level projects.
|
||||
if (config.shard) {
|
||||
if (config.config.shard) {
|
||||
// Create test groups for top-level projects.
|
||||
const testGroups: TestGroup[] = [];
|
||||
for (const projectSuite of rootSuite.suites)
|
||||
testGroups.push(...createTestGroups(projectSuite, config.workers));
|
||||
testGroups.push(...createTestGroups(projectSuite, config.config.workers));
|
||||
|
||||
// Shard test groups.
|
||||
const testGroupsInThisShard = filterForShard(config.shard, testGroups);
|
||||
const testGroupsInThisShard = filterForShard(config.config.shard, testGroups);
|
||||
const testsInThisShard = new Set<TestCase>();
|
||||
for (const group of testGroupsInThisShard) {
|
||||
for (const test of group.tests)
|
||||
|
|
@ -171,7 +172,7 @@ export async function createRootSuite(testRun: TestRun, errors: TestError[], sho
|
|||
{
|
||||
// Filtering only and sharding might have reduced the number of top-level projects.
|
||||
// Build the project closure to only include dependencies that are still needed.
|
||||
const projectClosure = new Map(buildProjectsClosure(rootSuite.suites.map(suite => suite.project() as FullProjectInternal)));
|
||||
const projectClosure = new Map(buildProjectsClosure(rootSuite.suites.map(suite => suite._projectConfig!)));
|
||||
|
||||
// Clone file suites for dependency projects.
|
||||
for (const [project, fileSuites] of testRun.projectSuites) {
|
||||
|
|
@ -184,12 +185,12 @@ export async function createRootSuite(testRun: TestRun, errors: TestError[], sho
|
|||
}
|
||||
|
||||
async function createProjectSuite(fileSuites: Suite[], project: FullProjectInternal, options: { cliFileFilters: TestFileFilter[], cliTitleMatcher?: Matcher, testIdMatcher?: Matcher }): Promise<Suite> {
|
||||
const projectSuite = new Suite(project.name, 'project');
|
||||
const projectSuite = new Suite(project.project.name, 'project');
|
||||
projectSuite._projectConfig = project;
|
||||
if (project._internal.fullyParallel)
|
||||
if (project.fullyParallel)
|
||||
projectSuite._parallelMode = 'parallel';
|
||||
for (const fileSuite of fileSuites) {
|
||||
for (let repeatEachIndex = 0; repeatEachIndex < project.repeatEach; repeatEachIndex++) {
|
||||
for (let repeatEachIndex = 0; repeatEachIndex < project.project.repeatEach; repeatEachIndex++) {
|
||||
const builtSuite = buildFileSuiteForProject(project, fileSuite, repeatEachIndex);
|
||||
projectSuite._addSuite(builtSuite);
|
||||
}
|
||||
|
|
@ -198,8 +199,8 @@ async function createProjectSuite(fileSuites: Suite[], project: FullProjectInter
|
|||
filterByFocusedLine(projectSuite, options.cliFileFilters);
|
||||
filterByTestIds(projectSuite, options.testIdMatcher);
|
||||
|
||||
const grepMatcher = createTitleMatcher(project.grep);
|
||||
const grepInvertMatcher = project.grepInvert ? createTitleMatcher(project.grepInvert) : null;
|
||||
const grepMatcher = createTitleMatcher(project.project.grep);
|
||||
const grepInvertMatcher = project.project.grepInvert ? createTitleMatcher(project.project.grepInvert) : null;
|
||||
|
||||
const titleMatcher = (test: TestCase) => {
|
||||
const grepTitle = test.titlePath().join(' ');
|
||||
|
|
@ -234,7 +235,7 @@ function createDuplicateTitlesErrors(config: FullConfigInternal, fileSuite: Suit
|
|||
const existingTest = testsByFullTitle.get(fullTitle);
|
||||
if (existingTest) {
|
||||
const error: TestError = {
|
||||
message: `Error: duplicate test title "${fullTitle}", first declared in ${buildItemLocation(config.rootDir, existingTest)}`,
|
||||
message: `Error: duplicate test title "${fullTitle}", first declared in ${buildItemLocation(config.config.rootDir, existingTest)}`,
|
||||
location: test.location,
|
||||
};
|
||||
errors.push(error);
|
||||
|
|
@ -259,12 +260,12 @@ async function requireOrImportDefaultFunction(file: string, expectConstructor: b
|
|||
return func;
|
||||
}
|
||||
|
||||
export function loadGlobalHook(config: FullConfigInternal, file: string): Promise<(config: FullConfigInternal) => any> {
|
||||
return requireOrImportDefaultFunction(path.resolve(config.rootDir, file), false);
|
||||
export function loadGlobalHook(config: FullConfigInternal, file: string): Promise<(config: FullConfig) => any> {
|
||||
return requireOrImportDefaultFunction(path.resolve(config.config.rootDir, file), false);
|
||||
}
|
||||
|
||||
export function loadReporter(config: FullConfigInternal, file: string): Promise<new (arg?: any) => Reporter> {
|
||||
return requireOrImportDefaultFunction(path.resolve(config.rootDir, file), true);
|
||||
return requireOrImportDefaultFunction(path.resolve(config.config.rootDir, file), true);
|
||||
}
|
||||
|
||||
function sourceMapSources(file: string, cache: Map<string, string[]>): string[] {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import { serializeConfig } from '../common/ipc';
|
|||
import { ProcessHost } from './processHost';
|
||||
import { Suite } from '../common/test';
|
||||
import { loadTestFile } from '../common/testLoader';
|
||||
import type { FullConfigInternal } from '../common/types';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import { PoolBuilder } from '../common/poolBuilder';
|
||||
import { addToCompilationCache } from '../common/compilationCache';
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ export class InProcessLoaderHost {
|
|||
}
|
||||
|
||||
async loadTestFile(file: string, testErrors: TestError[]): Promise<Suite> {
|
||||
const result = await loadTestFile(file, this._config.rootDir, testErrors);
|
||||
const result = await loadTestFile(file, this._config.config.rootDir, testErrors);
|
||||
this._poolBuilder.buildPools(result, testErrors);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
import { minimatch } from 'playwright-core/lib/utilsBundle';
|
||||
import { promisify } from 'util';
|
||||
import type { FullProjectInternal } from '../common/types';
|
||||
import type { FullProjectInternal } from '../common/config';
|
||||
import { createFileMatcher } from '../util';
|
||||
|
||||
const readFileAsync = promisify(fs.readFile);
|
||||
|
|
@ -35,12 +35,12 @@ export function filterProjects(projects: FullProjectInternal[], projectNames?: s
|
|||
unknownProjects.set(name, n);
|
||||
});
|
||||
const result = projects.filter(project => {
|
||||
const name = project.name.toLocaleLowerCase();
|
||||
const name = project.project.name.toLocaleLowerCase();
|
||||
unknownProjects.delete(name);
|
||||
return projectsToFind.has(name);
|
||||
});
|
||||
if (unknownProjects.size) {
|
||||
const names = projects.map(p => p.name).filter(name => !!name);
|
||||
const names = projects.map(p => p.project.name).filter(name => !!name);
|
||||
if (!names.length)
|
||||
throw new Error(`No named projects are specified in the configuration file`);
|
||||
const unknownProjectNames = Array.from(unknownProjects.values()).map(n => `"${n}"`).join(', ');
|
||||
|
|
@ -58,7 +58,7 @@ export function buildProjectsClosure(projects: FullProjectInternal[]): Map<FullP
|
|||
throw error;
|
||||
}
|
||||
result.set(project, depth ? 'dependency' : 'top-level');
|
||||
project._internal.deps.map(visit.bind(undefined, depth + 1));
|
||||
project.deps.map(visit.bind(undefined, depth + 1));
|
||||
};
|
||||
for (const p of projects)
|
||||
result.set(p, 'top-level');
|
||||
|
|
@ -70,9 +70,9 @@ export function buildProjectsClosure(projects: FullProjectInternal[]): Map<FullP
|
|||
export async function collectFilesForProject(project: FullProjectInternal, fsCache = new Map<string, string[]>()): Promise<string[]> {
|
||||
const extensions = ['.js', '.ts', '.mjs', '.tsx', '.jsx'];
|
||||
const testFileExtension = (file: string) => extensions.includes(path.extname(file));
|
||||
const allFiles = await cachedCollectFiles(project.testDir, project._internal.respectGitIgnore, fsCache);
|
||||
const testMatch = createFileMatcher(project.testMatch);
|
||||
const testIgnore = createFileMatcher(project.testIgnore);
|
||||
const allFiles = await cachedCollectFiles(project.project.testDir, project.respectGitIgnore, fsCache);
|
||||
const testMatch = createFileMatcher(project.project.testMatch);
|
||||
const testIgnore = createFileMatcher(project.project.testIgnore);
|
||||
const testFiles = allFiles.filter(file => {
|
||||
if (!testFileExtension(file))
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
*/
|
||||
|
||||
import path from 'path';
|
||||
import type { Reporter, TestError } from '../../types/testReporter';
|
||||
import type { FullConfig, Reporter, TestError } from '../../types/testReporter';
|
||||
import { formatError } from '../reporters/base';
|
||||
import DotReporter from '../reporters/dot';
|
||||
import EmptyReporter from '../reporters/empty';
|
||||
|
|
@ -27,9 +27,8 @@ import LineReporter from '../reporters/line';
|
|||
import ListReporter from '../reporters/list';
|
||||
import { Multiplexer } from '../reporters/multiplexer';
|
||||
import type { Suite } from '../common/test';
|
||||
import type { FullConfigInternal } from '../common/types';
|
||||
import type { BuiltInReporter, FullConfigInternal } from '../common/config';
|
||||
import { loadReporter } from './loadUtils';
|
||||
import type { BuiltInReporter } from '../common/configLoader';
|
||||
|
||||
export async function createReporter(config: FullConfigInternal, mode: 'list' | 'watch' | 'run' | 'ui', additionalReporters: Reporter[] = []): Promise<Multiplexer> {
|
||||
const defaultReporters: {[key in BuiltInReporter]: new(arg: any) => Reporter} = {
|
||||
|
|
@ -46,9 +45,9 @@ export async function createReporter(config: FullConfigInternal, mode: 'list' |
|
|||
if (mode === 'watch') {
|
||||
reporters.push(new ListReporter());
|
||||
} else {
|
||||
for (const r of config.reporter) {
|
||||
for (const r of config.config.reporter) {
|
||||
const [name, arg] = r;
|
||||
const options = { ...arg, configDir: config._internal.configDir };
|
||||
const options = { ...arg, configDir: config.configDir };
|
||||
if (name in defaultReporters) {
|
||||
reporters.push(new defaultReporters[name as keyof typeof defaultReporters](options));
|
||||
} else {
|
||||
|
|
@ -79,9 +78,9 @@ export async function createReporter(config: FullConfigInternal, mode: 'list' |
|
|||
}
|
||||
|
||||
export class ListModeReporter implements Reporter {
|
||||
private config!: FullConfigInternal;
|
||||
private config!: FullConfig;
|
||||
|
||||
onBegin(config: FullConfigInternal, suite: Suite): void {
|
||||
onBegin(config: FullConfig, suite: Suite): void {
|
||||
this.config = config;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Listing tests:`);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { webServerPluginsForConfig } from '../plugins/webServerPlugin';
|
|||
import { collectFilesForProject, filterProjects } from './projectUtils';
|
||||
import { createReporter } from './reporters';
|
||||
import { TestRun, createTaskRunner, createTaskRunnerForList } from './tasks';
|
||||
import type { FullConfigInternal } from '../common/types';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import { runWatchModeLoop } from './watchMode';
|
||||
import { runUIMode } from './uiMode';
|
||||
|
|
@ -49,11 +49,11 @@ export class Runner {
|
|||
|
||||
async runAllTests(): Promise<FullResult['status']> {
|
||||
const config = this._config;
|
||||
const listOnly = config._internal.listOnly;
|
||||
const deadline = config.globalTimeout ? monotonicTime() + config.globalTimeout : 0;
|
||||
const listOnly = config.listOnly;
|
||||
const deadline = config.config.globalTimeout ? monotonicTime() + config.config.globalTimeout : 0;
|
||||
|
||||
// Legacy webServer support.
|
||||
webServerPluginsForConfig(config).forEach(p => config._internal.plugins.push({ factory: p }));
|
||||
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
||||
|
||||
const reporter = await createReporter(config, listOnly ? 'list' : 'run');
|
||||
const taskRunner = listOnly ? createTaskRunnerForList(config, reporter, 'in-process')
|
||||
|
|
@ -62,7 +62,7 @@ export class Runner {
|
|||
const testRun = new TestRun(config, reporter);
|
||||
reporter.onConfigure(config);
|
||||
|
||||
if (!listOnly && config._internal.ignoreSnapshots) {
|
||||
if (!listOnly && config.ignoreSnapshots) {
|
||||
reporter.onStdOut(colors.dim([
|
||||
'NOTE: running with "ignoreSnapshots" option. All of the following asserts are silently ignored:',
|
||||
'- expect().toMatchSnapshot()',
|
||||
|
|
@ -89,13 +89,13 @@ export class Runner {
|
|||
|
||||
async watchAllTests(): Promise<FullResult['status']> {
|
||||
const config = this._config;
|
||||
webServerPluginsForConfig(config).forEach(p => config._internal.plugins.push({ factory: p }));
|
||||
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
||||
return await runWatchModeLoop(config);
|
||||
}
|
||||
|
||||
async uiAllTests(): Promise<FullResult['status']> {
|
||||
const config = this._config;
|
||||
webServerPluginsForConfig(config).forEach(p => config._internal.plugins.push({ factory: p }));
|
||||
webServerPluginsForConfig(config).forEach(p => config.plugins.push({ factory: p }));
|
||||
return await runUIMode(config);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import type { Multiplexer } from '../reporters/multiplexer';
|
|||
import { createTestGroups, type TestGroup } from '../runner/testGroups';
|
||||
import type { Task } from './taskRunner';
|
||||
import { TaskRunner } from './taskRunner';
|
||||
import type { FullConfigInternal, FullProjectInternal } from '../common/types';
|
||||
import type { FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
import { collectProjectsAndTestFiles, createRootSuite, loadFileSuites, loadGlobalHook } from './loadUtils';
|
||||
import type { Matcher } from '../util';
|
||||
import type { Suite } from '../common/test';
|
||||
|
|
@ -60,7 +60,7 @@ export class TestRun {
|
|||
}
|
||||
|
||||
export function createTaskRunner(config: FullConfigInternal, reporter: Multiplexer): TaskRunner<TestRun> {
|
||||
const taskRunner = new TaskRunner<TestRun>(reporter, config.globalTimeout);
|
||||
const taskRunner = new TaskRunner<TestRun>(reporter, config.config.globalTimeout);
|
||||
addGlobalSetupTasks(taskRunner, config);
|
||||
taskRunner.addTask('load tests', createLoadTask('in-process', true));
|
||||
addRunTasks(taskRunner, config);
|
||||
|
|
@ -81,9 +81,9 @@ export function createTaskRunnerForWatch(config: FullConfigInternal, reporter: M
|
|||
}
|
||||
|
||||
function addGlobalSetupTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal) {
|
||||
for (const plugin of config._internal.plugins)
|
||||
for (const plugin of config.plugins)
|
||||
taskRunner.addTask('plugin setup', createPluginSetupTask(plugin));
|
||||
if (config.globalSetup || config.globalTeardown)
|
||||
if (config.config.globalSetup || config.config.globalTeardown)
|
||||
taskRunner.addTask('global setup', createGlobalSetupTask());
|
||||
taskRunner.addTask('clear output', createRemoveOutputDirsTask());
|
||||
}
|
||||
|
|
@ -91,10 +91,10 @@ function addGlobalSetupTasks(taskRunner: TaskRunner<TestRun>, config: FullConfig
|
|||
function addRunTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal) {
|
||||
taskRunner.addTask('create phases', createPhasesTask());
|
||||
taskRunner.addTask('report begin', async ({ reporter, rootSuite }) => {
|
||||
reporter.onBegin?.(config, rootSuite!);
|
||||
reporter.onBegin?.(config.config, rootSuite!);
|
||||
return () => reporter.onEnd();
|
||||
});
|
||||
for (const plugin of config._internal.plugins)
|
||||
for (const plugin of config.plugins)
|
||||
taskRunner.addTask('plugin begin', createPluginBeginTask(plugin));
|
||||
taskRunner.addTask('start workers', createWorkersTask());
|
||||
taskRunner.addTask('test suite', createRunTestsTask());
|
||||
|
|
@ -102,10 +102,10 @@ function addRunTasks(taskRunner: TaskRunner<TestRun>, config: FullConfigInternal
|
|||
}
|
||||
|
||||
export function createTaskRunnerForList(config: FullConfigInternal, reporter: Multiplexer, mode: 'in-process' | 'out-of-process'): TaskRunner<TestRun> {
|
||||
const taskRunner = new TaskRunner<TestRun>(reporter, config.globalTimeout);
|
||||
const taskRunner = new TaskRunner<TestRun>(reporter, config.config.globalTimeout);
|
||||
taskRunner.addTask('load tests', createLoadTask(mode, false));
|
||||
taskRunner.addTask('report begin', async ({ reporter, rootSuite }) => {
|
||||
reporter.onBegin?.(config, rootSuite!);
|
||||
reporter.onBegin?.(config.config, rootSuite!);
|
||||
return () => reporter.onEnd();
|
||||
});
|
||||
return taskRunner;
|
||||
|
|
@ -117,7 +117,7 @@ function createPluginSetupTask(plugin: TestRunnerPluginRegistration): Task<TestR
|
|||
plugin.instance = await plugin.factory();
|
||||
else
|
||||
plugin.instance = plugin.factory;
|
||||
await plugin.instance?.setup?.(config, config._internal.configDir, reporter);
|
||||
await plugin.instance?.setup?.(config.config, config.configDir, reporter);
|
||||
return () => plugin.instance?.teardown?.();
|
||||
};
|
||||
}
|
||||
|
|
@ -131,13 +131,13 @@ function createPluginBeginTask(plugin: TestRunnerPluginRegistration): Task<TestR
|
|||
|
||||
function createGlobalSetupTask(): Task<TestRun> {
|
||||
return async ({ config }) => {
|
||||
const setupHook = config.globalSetup ? await loadGlobalHook(config, config.globalSetup) : undefined;
|
||||
const teardownHook = config.globalTeardown ? await loadGlobalHook(config, config.globalTeardown) : undefined;
|
||||
const globalSetupResult = setupHook ? await setupHook(config) : undefined;
|
||||
const setupHook = config.config.globalSetup ? await loadGlobalHook(config, config.config.globalSetup) : undefined;
|
||||
const teardownHook = config.config.globalTeardown ? await loadGlobalHook(config, config.config.globalTeardown) : undefined;
|
||||
const globalSetupResult = setupHook ? await setupHook(config.config) : undefined;
|
||||
return async () => {
|
||||
if (typeof globalSetupResult === 'function')
|
||||
await globalSetupResult();
|
||||
await teardownHook?.(config);
|
||||
await teardownHook?.(config.config);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
@ -146,8 +146,8 @@ function createRemoveOutputDirsTask(): Task<TestRun> {
|
|||
return async ({ config }) => {
|
||||
const outputDirs = new Set<string>();
|
||||
for (const p of config.projects) {
|
||||
if (!config._internal.cliProjectFilter || config._internal.cliProjectFilter.includes(p.name))
|
||||
outputDirs.add(p.outputDir);
|
||||
if (!config.cliProjectFilter || config.cliProjectFilter.includes(p.project.name))
|
||||
outputDirs.add(p.project.outputDir);
|
||||
}
|
||||
|
||||
await Promise.all(Array.from(outputDirs).map(outputDir => removeFolderAsync(outputDir).catch(async (error: any) => {
|
||||
|
|
@ -170,24 +170,24 @@ 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._internal.passWithNoTests && !testRun.config.shard)
|
||||
if (!testRun.rootSuite.allTests().length && !testRun.config.passWithNoTests && !testRun.config.config.shard)
|
||||
throw new Error(`No tests found`);
|
||||
};
|
||||
}
|
||||
|
||||
function createPhasesTask(): Task<TestRun> {
|
||||
return async testRun => {
|
||||
testRun.config._internal.maxConcurrentTestGroups = 0;
|
||||
testRun.config.maxConcurrentTestGroups = 0;
|
||||
|
||||
const processed = new Set<FullProjectInternal>();
|
||||
const projectToSuite = new Map(testRun.rootSuite!.suites.map(suite => [suite.project() as FullProjectInternal, suite]));
|
||||
const projectToSuite = new Map(testRun.rootSuite!.suites.map(suite => [suite._projectConfig!, suite]));
|
||||
for (let i = 0; i < projectToSuite.size; i++) {
|
||||
// Find all projects that have all their dependencies processed by previous phases.
|
||||
const phaseProjects: FullProjectInternal[] = [];
|
||||
for (const project of projectToSuite.keys()) {
|
||||
if (processed.has(project))
|
||||
continue;
|
||||
if (project._internal.deps.find(p => !processed.has(p)))
|
||||
if (project.deps.find(p => !processed.has(p)))
|
||||
continue;
|
||||
phaseProjects.push(project);
|
||||
}
|
||||
|
|
@ -201,12 +201,12 @@ function createPhasesTask(): Task<TestRun> {
|
|||
testRun.phases.push(phase);
|
||||
for (const project of phaseProjects) {
|
||||
const projectSuite = projectToSuite.get(project)!;
|
||||
const testGroups = createTestGroups(projectSuite, testRun.config.workers);
|
||||
const testGroups = createTestGroups(projectSuite, testRun.config.config.workers);
|
||||
phase.projects.push({ project, projectSuite, testGroups });
|
||||
testGroupsInPhase += testGroups.length;
|
||||
}
|
||||
debug('pw:test:task')(`created phase #${testRun.phases.length} with ${phase.projects.map(p => p.project.name).sort()} projects, ${testGroupsInPhase} testGroups`);
|
||||
testRun.config._internal.maxConcurrentTestGroups = Math.max(testRun.config._internal.maxConcurrentTestGroups, testGroupsInPhase);
|
||||
debug('pw:test:task')(`created phase #${testRun.phases.length} with ${phase.projects.map(p => p.project.project.name).sort()} projects, ${testGroupsInPhase} testGroups`);
|
||||
testRun.config.maxConcurrentTestGroups = Math.max(testRun.config.maxConcurrentTestGroups, testGroupsInPhase);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -235,11 +235,11 @@ function createRunTestsTask(): Task<TestRun> {
|
|||
for (const { project, testGroups } of projects) {
|
||||
// Inherit extra enviroment variables from dependencies.
|
||||
let extraEnv: Record<string, string | undefined> = {};
|
||||
for (const dep of project._internal.deps)
|
||||
extraEnv = { ...extraEnv, ...extraEnvByProjectId.get(dep._internal.id) };
|
||||
extraEnvByProjectId.set(project._internal.id, extraEnv);
|
||||
for (const dep of project.deps)
|
||||
extraEnv = { ...extraEnv, ...extraEnvByProjectId.get(dep.id) };
|
||||
extraEnvByProjectId.set(project.id, extraEnv);
|
||||
|
||||
const hasFailedDeps = project._internal.deps.some(p => !successfulProjects.has(p));
|
||||
const hasFailedDeps = project.deps.some(p => !successfulProjects.has(p));
|
||||
if (!hasFailedDeps) {
|
||||
phaseTestGroups.push(...testGroups);
|
||||
} else {
|
||||
|
|
@ -263,7 +263,7 @@ function createRunTestsTask(): Task<TestRun> {
|
|||
// projects failed.
|
||||
if (!dispatcher.hasWorkerErrors()) {
|
||||
for (const { project, projectSuite } of projects) {
|
||||
const hasFailedDeps = project._internal.deps.some(p => !successfulProjects.has(p));
|
||||
const hasFailedDeps = project.deps.some(p => !successfulProjects.has(p));
|
||||
if (!hasFailedDeps && !projectSuite.allTests().some(test => !test.ok()))
|
||||
successfulProjects.add(project);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import type { Page } from 'playwright-core/lib/server/page';
|
|||
import { isUnderTest, ManualPromise } from 'playwright-core/lib/utils';
|
||||
import type { FullResult } from '../../reporter';
|
||||
import { clearCompilationCache, collectAffectedTestFiles, dependenciesForTestFile } from '../common/compilationCache';
|
||||
import type { FullConfigInternal } from '../common/types';
|
||||
import type { FullConfigInternal } from '../common/config';
|
||||
import { Multiplexer } from '../reporters/multiplexer';
|
||||
import { TeleReporterEmitter } from '../reporters/teleEmitter';
|
||||
import { createReporter } from './reporters';
|
||||
|
|
@ -42,20 +42,20 @@ class UIMode {
|
|||
constructor(config: FullConfigInternal) {
|
||||
this._config = config;
|
||||
process.env.PW_LIVE_TRACE_STACKS = '1';
|
||||
config._internal.configCLIOverrides.forbidOnly = false;
|
||||
config._internal.configCLIOverrides.globalTimeout = 0;
|
||||
config._internal.configCLIOverrides.repeatEach = 0;
|
||||
config._internal.configCLIOverrides.shard = undefined;
|
||||
config._internal.configCLIOverrides.updateSnapshots = undefined;
|
||||
config._internal.listOnly = false;
|
||||
config._internal.passWithNoTests = true;
|
||||
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;
|
||||
for (const project of config.projects)
|
||||
project._internal.deps = [];
|
||||
project.deps = [];
|
||||
|
||||
for (const p of config.projects)
|
||||
p.retries = 0;
|
||||
config._internal.configCLIOverrides.use = config._internal.configCLIOverrides.use || {};
|
||||
config._internal.configCLIOverrides.use.trace = { mode: 'on', sources: false };
|
||||
p.project.retries = 0;
|
||||
config.configCLIOverrides.use = config.configCLIOverrides.use || {};
|
||||
config.configCLIOverrides.use.trace = { mode: 'on', sources: false };
|
||||
|
||||
this._originalStdoutWrite = process.stdout.write;
|
||||
this._originalStderrWrite = process.stderr.write;
|
||||
|
|
@ -148,8 +148,8 @@ class UIMode {
|
|||
private async _listTests() {
|
||||
const listReporter = new TeleReporterEmitter(e => this._dispatchEvent(e));
|
||||
const reporter = new Multiplexer([listReporter]);
|
||||
this._config._internal.listOnly = true;
|
||||
this._config._internal.testIdMatcher = undefined;
|
||||
this._config.listOnly = true;
|
||||
this._config.testIdMatcher = undefined;
|
||||
const taskRunner = createTaskRunnerForList(this._config, reporter, 'out-of-process');
|
||||
const testRun = new TestRun(this._config, reporter);
|
||||
clearCompilationCache();
|
||||
|
|
@ -159,7 +159,7 @@ class UIMode {
|
|||
|
||||
const projectDirs = new Set<string>();
|
||||
for (const p of this._config.projects)
|
||||
projectDirs.add(p.testDir);
|
||||
projectDirs.add(p.project.testDir);
|
||||
this._globalWatcher.update([...projectDirs], false);
|
||||
}
|
||||
|
||||
|
|
@ -167,8 +167,8 @@ class UIMode {
|
|||
await this._stopTests();
|
||||
|
||||
const testIdSet = testIds ? new Set<string>(testIds) : null;
|
||||
this._config._internal.listOnly = false;
|
||||
this._config._internal.testIdMatcher = id => !testIdSet || testIdSet.has(id);
|
||||
this._config.listOnly = false;
|
||||
this._config.testIdMatcher = id => !testIdSet || testIdSet.has(id);
|
||||
|
||||
const runReporter = new TeleReporterEmitter(e => this._dispatchEvent(e));
|
||||
const reporter = await createReporter(this._config, 'ui', [runReporter]);
|
||||
|
|
@ -180,7 +180,7 @@ class UIMode {
|
|||
const run = taskRunner.run(testRun, 0, stop).then(async status => {
|
||||
await reporter.onExit({ status });
|
||||
this._testRun = undefined;
|
||||
this._config._internal.testIdMatcher = undefined;
|
||||
this._config.testIdMatcher = undefined;
|
||||
return status;
|
||||
});
|
||||
this._testRun = { run, stop };
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import readline from 'readline';
|
||||
import { createGuid, ManualPromise } from 'playwright-core/lib/utils';
|
||||
import type { FullConfigInternal, FullProjectInternal } from '../common/types';
|
||||
import type { FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
import { Multiplexer } from '../reporters/multiplexer';
|
||||
import { createFileMatcher, createFileMatcherFromArguments } from '../util';
|
||||
import type { Matcher } from '../util';
|
||||
|
|
@ -40,15 +40,15 @@ class FSWatcher {
|
|||
private _timer: NodeJS.Timeout | undefined;
|
||||
|
||||
async update(config: FullConfigInternal) {
|
||||
const commandLineFileMatcher = config._internal.cliArgs.length ? createFileMatcherFromArguments(config._internal.cliArgs) : () => true;
|
||||
const projects = filterProjects(config.projects, config._internal.cliProjectFilter);
|
||||
const commandLineFileMatcher = config.cliArgs.length ? createFileMatcherFromArguments(config.cliArgs) : () => true;
|
||||
const projects = filterProjects(config.projects, config.cliProjectFilter);
|
||||
const projectClosure = buildProjectsClosure(projects);
|
||||
const projectFilters = new Map<FullProjectInternal, Matcher>();
|
||||
for (const [project, type] of projectClosure) {
|
||||
const testMatch = createFileMatcher(project.testMatch);
|
||||
const testIgnore = createFileMatcher(project.testIgnore);
|
||||
const testMatch = createFileMatcher(project.project.testMatch);
|
||||
const testIgnore = createFileMatcher(project.project.testIgnore);
|
||||
projectFilters.set(project, file => {
|
||||
if (!file.startsWith(project.testDir) || !testMatch(file) || testIgnore(file))
|
||||
if (!file.startsWith(project.project.testDir) || !testMatch(file) || testIgnore(file))
|
||||
return false;
|
||||
return type === 'dependency' || commandLineFileMatcher(file);
|
||||
});
|
||||
|
|
@ -59,7 +59,7 @@ class FSWatcher {
|
|||
if (this._watcher)
|
||||
await this._watcher.close();
|
||||
|
||||
this._watcher = chokidar.watch([...projectClosure.keys()].map(p => p.testDir), { ignoreInitial: true }).on('all', async (event, file) => {
|
||||
this._watcher = chokidar.watch([...projectClosure.keys()].map(p => p.project.testDir), { ignoreInitial: true }).on('all', async (event, file) => {
|
||||
if (event !== 'add' && event !== 'change')
|
||||
return;
|
||||
|
||||
|
|
@ -108,9 +108,9 @@ class FSWatcher {
|
|||
|
||||
export async function runWatchModeLoop(config: FullConfigInternal): Promise<FullResult['status']> {
|
||||
// Reset the settings that don't apply to watch.
|
||||
config._internal.passWithNoTests = true;
|
||||
config.passWithNoTests = true;
|
||||
for (const p of config.projects)
|
||||
p.retries = 0;
|
||||
p.project.retries = 0;
|
||||
|
||||
// Perform global setup.
|
||||
const reporter = await createReporter(config, 'watch');
|
||||
|
|
@ -123,7 +123,7 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
|
|||
|
||||
// Prepare projects that will be watched, set up watcher.
|
||||
const failedTestIdCollector = new Set<string>();
|
||||
const originalWorkers = config.workers;
|
||||
const originalWorkers = config.config.workers;
|
||||
const fsWatcher = new FSWatcher();
|
||||
await fsWatcher.update(config);
|
||||
|
||||
|
|
@ -165,11 +165,11 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
|
|||
type: 'multiselect',
|
||||
name: 'projectNames',
|
||||
message: 'Select projects',
|
||||
choices: config.projects.map(p => ({ name: p.name })),
|
||||
choices: config.projects.map(p => ({ name: p.project.name })),
|
||||
}).catch(() => ({ projectNames: null }));
|
||||
if (!projectNames)
|
||||
continue;
|
||||
config._internal.cliProjectFilter = projectNames.length ? projectNames : undefined;
|
||||
config.cliProjectFilter = projectNames.length ? projectNames : undefined;
|
||||
await fsWatcher.update(config);
|
||||
await runTests(config, failedTestIdCollector);
|
||||
lastRun = { type: 'regular' };
|
||||
|
|
@ -185,9 +185,9 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
|
|||
if (filePattern === null)
|
||||
continue;
|
||||
if (filePattern.trim())
|
||||
config._internal.cliArgs = filePattern.split(' ');
|
||||
config.cliArgs = filePattern.split(' ');
|
||||
else
|
||||
config._internal.cliArgs = [];
|
||||
config.cliArgs = [];
|
||||
await fsWatcher.update(config);
|
||||
await runTests(config, failedTestIdCollector);
|
||||
lastRun = { type: 'regular' };
|
||||
|
|
@ -203,9 +203,9 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
|
|||
if (testPattern === null)
|
||||
continue;
|
||||
if (testPattern.trim())
|
||||
config._internal.cliGrep = testPattern;
|
||||
config.cliGrep = testPattern;
|
||||
else
|
||||
config._internal.cliGrep = undefined;
|
||||
config.cliGrep = undefined;
|
||||
await fsWatcher.update(config);
|
||||
await runTests(config, failedTestIdCollector);
|
||||
lastRun = { type: 'regular' };
|
||||
|
|
@ -213,10 +213,10 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
|
|||
}
|
||||
|
||||
if (command === 'failed') {
|
||||
config._internal.testIdMatcher = id => failedTestIdCollector.has(id);
|
||||
config.testIdMatcher = id => failedTestIdCollector.has(id);
|
||||
const failedTestIds = new Set(failedTestIdCollector);
|
||||
await runTests(config, failedTestIdCollector, { title: 'running failed tests' });
|
||||
config._internal.testIdMatcher = undefined;
|
||||
config.testIdMatcher = undefined;
|
||||
lastRun = { type: 'failed', failedTestIds };
|
||||
continue;
|
||||
}
|
||||
|
|
@ -228,9 +228,9 @@ export async function runWatchModeLoop(config: FullConfigInternal): Promise<Full
|
|||
} else if (lastRun.type === 'changed') {
|
||||
await runChangedTests(config, failedTestIdCollector, lastRun.dirtyTestFiles!, 're-running tests');
|
||||
} else if (lastRun.type === 'failed') {
|
||||
config._internal.testIdMatcher = id => lastRun.failedTestIds!.has(id);
|
||||
config.testIdMatcher = id => lastRun.failedTestIds!.has(id);
|
||||
await runTests(config, failedTestIdCollector, { title: 're-running tests' });
|
||||
config._internal.testIdMatcher = undefined;
|
||||
config.testIdMatcher = undefined;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
|
@ -259,7 +259,7 @@ async function runChangedTests(config: FullConfigInternal, failedTestIdCollector
|
|||
|
||||
// Collect all the affected projects, follow project dependencies.
|
||||
// Prepare to exclude all the projects that do not depend on this file, as if they did not exist.
|
||||
const projects = filterProjects(config.projects, config._internal.cliProjectFilter);
|
||||
const projects = filterProjects(config.projects, config.cliProjectFilter);
|
||||
const projectClosure = buildProjectsClosure(projects);
|
||||
const affectedProjects = affectedProjectsClosure([...projectClosure.keys()], [...filesByProject.keys()]);
|
||||
const affectsAnyDependency = [...affectedProjects].some(p => projectClosure.get(p) === 'dependency');
|
||||
|
|
@ -305,7 +305,7 @@ function affectedProjectsClosure(projectClosure: FullProjectInternal[], affected
|
|||
const result = new Set<FullProjectInternal>(affected);
|
||||
for (let i = 0; i < projectClosure.length; ++i) {
|
||||
for (const p of projectClosure) {
|
||||
for (const dep of p._internal.deps) {
|
||||
for (const dep of p.deps) {
|
||||
if (result.has(dep))
|
||||
result.add(p);
|
||||
}
|
||||
|
|
@ -379,11 +379,11 @@ let seq = 0;
|
|||
function printConfiguration(config: FullConfigInternal, title?: string) {
|
||||
const tokens: string[] = [];
|
||||
tokens.push('npx playwright test');
|
||||
tokens.push(...(config._internal.cliProjectFilter || [])?.map(p => colors.blue(`--project ${p}`)));
|
||||
if (config._internal.cliGrep)
|
||||
tokens.push(colors.red(`--grep ${config._internal.cliGrep}`));
|
||||
if (config._internal.cliArgs)
|
||||
tokens.push(...config._internal.cliArgs.map(a => colors.bold(a)));
|
||||
tokens.push(...(config.cliProjectFilter || [])?.map(p => colors.blue(`--project ${p}`)));
|
||||
if (config.cliGrep)
|
||||
tokens.push(colors.red(`--grep ${config.cliGrep}`));
|
||||
if (config.cliArgs)
|
||||
tokens.push(...config.cliArgs.map(a => colors.bold(a)));
|
||||
if (title)
|
||||
tokens.push(colors.dim(`(${title})`));
|
||||
if (seq)
|
||||
|
|
@ -407,14 +407,14 @@ ${colors.dim('Waiting for file changes. Press')} ${colors.bold('enter')} ${color
|
|||
|
||||
async function toggleShowBrowser(config: FullConfigInternal, originalWorkers: number) {
|
||||
if (!showBrowserServer) {
|
||||
config.workers = 1;
|
||||
config.config.workers = 1;
|
||||
showBrowserServer = new PlaywrightServer({ path: '/' + createGuid(), maxConnections: 1 });
|
||||
const wsEndpoint = await showBrowserServer.listen();
|
||||
process.env.PW_TEST_REUSE_CONTEXT = '1';
|
||||
process.env.PW_TEST_CONNECT_WS_ENDPOINT = wsEndpoint;
|
||||
process.stdout.write(`${colors.dim('Show & reuse browser:')} ${colors.bold('on')}\n`);
|
||||
} else {
|
||||
config.workers = originalWorkers;
|
||||
config.config.workers = originalWorkers;
|
||||
await showBrowserServer?.close();
|
||||
showBrowserServer = undefined;
|
||||
delete process.env.PW_TEST_REUSE_CONTEXT;
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class JsonStore {
|
|||
const config = currentConfig();
|
||||
if (!config)
|
||||
throw new Error('Cannot access store before config is loaded');
|
||||
return config._internal.storeDir;
|
||||
return config.storeDir;
|
||||
}
|
||||
|
||||
async set<T>(name: string, value: T | undefined) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ import util from 'util';
|
|||
import path from 'path';
|
||||
import url from 'url';
|
||||
import { colors, debug, minimatch, parseStackTraceLine } from 'playwright-core/lib/utilsBundle';
|
||||
import type { TestInfoError, Location } from './common/types';
|
||||
import type { TestInfoError } from './../types/test';
|
||||
import type { Location } from './../types/testReporter';
|
||||
import { calculateSha1, isRegExp, isString } from 'playwright-core/lib/utils';
|
||||
import type { RawStack } from 'playwright-core/lib/utils';
|
||||
|
||||
|
|
|
|||
|
|
@ -15,11 +15,12 @@
|
|||
*/
|
||||
|
||||
import { formatLocation, debugTest } from '../util';
|
||||
import type { Location, WorkerInfo } from '../common/types';
|
||||
import { ManualPromise } from 'playwright-core/lib/utils';
|
||||
import type { TestInfoImpl } from './testInfo';
|
||||
import type { FixtureDescription, TimeoutManager } from './timeoutManager';
|
||||
import { fixtureParameterNames, type FixturePool, type FixtureRegistration, type FixtureScope } from '../common/fixtures';
|
||||
import type { WorkerInfo } from '../../types/test';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
|
||||
class Fixture {
|
||||
runner: FixtureRunner;
|
||||
|
|
|
|||
|
|
@ -17,11 +17,12 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { captureRawStack, monotonicTime, zones } from 'playwright-core/lib/utils';
|
||||
import type { TestInfoError, TestInfo, TestStatus } from '../../types/test';
|
||||
import type { TestInfoError, TestInfo, TestStatus, FullProject, FullConfig } from '../../types/test';
|
||||
import type { StepBeginPayload, StepEndPayload, WorkerInitParams } from '../common/ipc';
|
||||
import type { TestCase } from '../common/test';
|
||||
import { TimeoutManager } from './timeoutManager';
|
||||
import type { Annotation, FullConfigInternal, FullProjectInternal, Location } from '../common/types';
|
||||
import type { Annotation, FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
import { getContainedPath, normalizeAndSaveAttachment, sanitizeForFilePath, serializeError, trimLongString } from '../util';
|
||||
import type * as trace from '@trace/trace';
|
||||
|
||||
|
|
@ -54,6 +55,8 @@ export class TestInfoImpl implements TestInfo {
|
|||
_didTimeout = false;
|
||||
_wasInterrupted = false;
|
||||
_lastStepId = 0;
|
||||
readonly _projectInternal: FullProjectInternal;
|
||||
readonly _configInternal: FullConfigInternal;
|
||||
|
||||
// ------------ TestInfo fields ------------
|
||||
readonly testId: string;
|
||||
|
|
@ -61,8 +64,8 @@ export class TestInfoImpl implements TestInfo {
|
|||
readonly retry: number;
|
||||
readonly workerIndex: number;
|
||||
readonly parallelIndex: number;
|
||||
readonly project: FullProjectInternal;
|
||||
config: FullConfigInternal;
|
||||
readonly project: FullProject;
|
||||
readonly config: FullConfig;
|
||||
readonly title: string;
|
||||
readonly titlePath: string[];
|
||||
readonly file: string;
|
||||
|
|
@ -101,8 +104,8 @@ export class TestInfoImpl implements TestInfo {
|
|||
}
|
||||
|
||||
constructor(
|
||||
config: FullConfigInternal,
|
||||
project: FullProjectInternal,
|
||||
configInternal: FullConfigInternal,
|
||||
projectInternal: FullProjectInternal,
|
||||
workerParams: WorkerInitParams,
|
||||
test: TestCase,
|
||||
retry: number,
|
||||
|
|
@ -120,8 +123,10 @@ export class TestInfoImpl implements TestInfo {
|
|||
this.retry = retry;
|
||||
this.workerIndex = workerParams.workerIndex;
|
||||
this.parallelIndex = workerParams.parallelIndex;
|
||||
this.project = project;
|
||||
this.config = config;
|
||||
this._projectInternal = projectInternal;
|
||||
this.project = projectInternal.project;
|
||||
this._configInternal = configInternal;
|
||||
this.config = configInternal.config;
|
||||
this.title = test.title;
|
||||
this.titlePath = test.titlePath();
|
||||
this.file = test.location.file;
|
||||
|
|
@ -138,8 +143,8 @@ export class TestInfoImpl implements TestInfo {
|
|||
const fullTitleWithoutSpec = test.titlePath().slice(1).join(' ');
|
||||
|
||||
let testOutputDir = trimLongString(sanitizedRelativePath + '-' + sanitizeForFilePath(fullTitleWithoutSpec));
|
||||
if (project._internal.id)
|
||||
testOutputDir += '-' + sanitizeForFilePath(project._internal.id);
|
||||
if (projectInternal.id)
|
||||
testOutputDir += '-' + sanitizeForFilePath(projectInternal.id);
|
||||
if (this.retry)
|
||||
testOutputDir += '-retry' + this.retry;
|
||||
if (this.repeatEachIndex)
|
||||
|
|
@ -345,7 +350,7 @@ export class TestInfoImpl implements TestInfo {
|
|||
const parsedRelativeTestFilePath = path.parse(relativeTestFilePath);
|
||||
const projectNamePathSegment = sanitizeForFilePath(this.project.name);
|
||||
|
||||
const snapshotPath = this.project.snapshotPathTemplate
|
||||
const snapshotPath = (this._projectInternal.snapshotPathTemplate || '')
|
||||
.replace(/\{(.)?testDir\}/g, '$1' + this.project.testDir)
|
||||
.replace(/\{(.)?snapshotDir\}/g, '$1' + this.project.snapshotDir)
|
||||
.replace(/\{(.)?snapshotSuffix\}/g, this.snapshotSuffix ? '$1' + this.snapshotSuffix : '')
|
||||
|
|
@ -358,7 +363,7 @@ export class TestInfoImpl implements TestInfo {
|
|||
.replace(/\{(.)?arg\}/g, '$1' + path.join(parsedSubPath.dir, parsedSubPath.name))
|
||||
.replace(/\{(.)?ext\}/g, parsedSubPath.ext ? '$1' + parsedSubPath.ext : '');
|
||||
|
||||
return path.normalize(path.resolve(this.config._internal.configDir, snapshotPath));
|
||||
return path.normalize(path.resolve(this._configInternal.configDir, snapshotPath));
|
||||
}
|
||||
|
||||
skip(...args: [arg?: any, description?: string]) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
|
||||
import { colors } from 'playwright-core/lib/utilsBundle';
|
||||
import { TimeoutRunner, TimeoutRunnerError } from 'playwright-core/lib/utils';
|
||||
import type { Location, TestInfoError } from '../common/types';
|
||||
import type { TestInfoError } from '../../types/test';
|
||||
import type { Location } from '../../types/testReporter';
|
||||
|
||||
export type TimeSlot = {
|
||||
timeout: number;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import type { TestBeginPayload, TestEndPayload, RunPayload, DonePayload, WorkerI
|
|||
import { setCurrentTestInfo, setIsWorkerProcess } from '../common/globals';
|
||||
import { ConfigLoader } from '../common/configLoader';
|
||||
import type { Suite, TestCase } from '../common/test';
|
||||
import type { Annotation, FullConfigInternal, FullProjectInternal, TestInfoError } from '../common/types';
|
||||
import type { Annotation, FullConfigInternal, FullProjectInternal } from '../common/config';
|
||||
import { FixtureRunner } from './fixtureRunner';
|
||||
import { ManualPromise } from 'playwright-core/lib/utils';
|
||||
import { TestInfoImpl } from './testInfo';
|
||||
|
|
@ -31,6 +31,7 @@ import { loadTestFile } from '../common/testLoader';
|
|||
import { buildFileSuiteForProject, filterTestsRemoveEmptySuites } from '../common/suiteUtils';
|
||||
import { PoolBuilder } from '../common/poolBuilder';
|
||||
import { addToCompilationCache } from '../common/compilationCache';
|
||||
import type { TestInfoError } from '../../types/test';
|
||||
|
||||
const removeFolderAsync = util.promisify(rimraf);
|
||||
|
||||
|
|
@ -150,7 +151,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
|
||||
private async _teardownScopes() {
|
||||
// TODO: separate timeout for teardown?
|
||||
const timeoutManager = new TimeoutManager(this._project.timeout);
|
||||
const timeoutManager = new TimeoutManager(this._project.project.timeout);
|
||||
timeoutManager.setCurrentRunnable({ type: 'teardown' });
|
||||
const timeoutError = await timeoutManager.runWithTimeout(async () => {
|
||||
await this._fixtureRunner.teardownScope('test', timeoutManager);
|
||||
|
|
@ -189,9 +190,8 @@ export class WorkerMain extends ProcessRunner {
|
|||
if (this._config)
|
||||
return;
|
||||
|
||||
const configLoader = await ConfigLoader.deserialize(this._params.config);
|
||||
this._config = configLoader.fullConfig();
|
||||
this._project = this._config.projects.find(p => p._internal.id === this._params.projectId)!;
|
||||
this._config = await ConfigLoader.deserialize(this._params.config);
|
||||
this._project = this._config.projects.find(p => p.id === this._params.projectId)!;
|
||||
this._poolBuilder = PoolBuilder.createForWorker(this._project);
|
||||
}
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
let fatalUnknownTestIds;
|
||||
try {
|
||||
await this._loadIfNeeded();
|
||||
const fileSuite = await loadTestFile(runPayload.file, this._config.rootDir);
|
||||
const fileSuite = await loadTestFile(runPayload.file, this._config.config.rootDir);
|
||||
const suite = buildFileSuiteForProject(this._project, fileSuite, this._params.repeatEachIndex);
|
||||
const hasEntries = filterTestsRemoveEmptySuites(suite, test => entries.has(test.id));
|
||||
if (hasEntries) {
|
||||
|
|
@ -332,7 +332,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
this._extraSuiteAnnotations.set(suite, extraAnnotations);
|
||||
didFailBeforeAllForSuite = suite; // Assume failure, unless reset below.
|
||||
// Separate timeout for each "beforeAll" modifier.
|
||||
const timeSlot = { timeout: this._project.timeout, elapsed: 0 };
|
||||
const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
||||
await this._runModifiersForSuite(suite, testInfo, 'worker', timeSlot, extraAnnotations);
|
||||
}
|
||||
|
||||
|
|
@ -385,7 +385,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
let afterHooksSlot: TimeSlot | undefined;
|
||||
if (testInfo._didTimeout) {
|
||||
// A timed-out test gets a full additional timeout to run after hooks.
|
||||
afterHooksSlot = { timeout: this._project.timeout, elapsed: 0 };
|
||||
afterHooksSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
||||
testInfo._timeoutManager.setCurrentRunnable({ type: 'afterEach', slot: afterHooksSlot });
|
||||
}
|
||||
await testInfo._runAsStep(async step => {
|
||||
|
|
@ -450,7 +450,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
const afterAllError = await this._runAfterAllHooksForSuite(suite, testInfo);
|
||||
firstAfterHooksError = firstAfterHooksError || afterAllError;
|
||||
}
|
||||
const teardownSlot = { timeout: this._project.timeout, elapsed: 0 };
|
||||
const teardownSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
||||
// Attribute to 'test' so that users understand they should probably increate the test timeout to fix this issue.
|
||||
testInfo._timeoutManager.setCurrentRunnable({ type: 'test', slot: teardownSlot });
|
||||
debugTest(`tearing down test scope started`);
|
||||
|
|
@ -476,8 +476,8 @@ export class WorkerMain extends ProcessRunner {
|
|||
setCurrentTestInfo(null);
|
||||
this.dispatchEvent('testEnd', buildTestEndPayload(testInfo));
|
||||
|
||||
const preserveOutput = this._config.preserveOutput === 'always' ||
|
||||
(this._config.preserveOutput === 'failures-only' && testInfo._isFailure());
|
||||
const preserveOutput = this._config.config.preserveOutput === 'always' ||
|
||||
(this._config.config.preserveOutput === 'failures-only' && testInfo._isFailure());
|
||||
if (!preserveOutput)
|
||||
await removeFolderAsync(testInfo.outputDir).catch(e => {});
|
||||
}
|
||||
|
|
@ -512,7 +512,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
debugTest(`${hook.type} hook at "${formatLocation(hook.location)}" started`);
|
||||
try {
|
||||
// Separate time slot for each "beforeAll" hook.
|
||||
const timeSlot = { timeout: this._project.timeout, elapsed: 0 };
|
||||
const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
||||
testInfo._timeoutManager.setCurrentRunnable({ type: 'beforeAll', location: hook.location, slot: timeSlot });
|
||||
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only'), {
|
||||
category: 'hook',
|
||||
|
|
@ -540,7 +540,7 @@ export class WorkerMain extends ProcessRunner {
|
|||
debugTest(`${hook.type} hook at "${formatLocation(hook.location)}" started`);
|
||||
const afterAllError = await testInfo._runFn(async () => {
|
||||
// Separate time slot for each "afterAll" hook.
|
||||
const timeSlot = { timeout: this._project.timeout, elapsed: 0 };
|
||||
const timeSlot = { timeout: this._project.project.timeout, elapsed: 0 };
|
||||
testInfo._timeoutManager.setCurrentRunnable({ type: 'afterAll', location: hook.location, slot: timeSlot });
|
||||
await testInfo._runAsStep(() => this._fixtureRunner.resolveParametersAndRunFunction(hook.fn, testInfo, 'all-hooks-only'), {
|
||||
category: 'hook',
|
||||
|
|
|
|||
Loading…
Reference in a new issue