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