chore: try serializing the config instead of requiring it in the worker (#13839)
This commit is contained in:
parent
4b682f9f13
commit
a1b10c3856
|
|
@ -16,12 +16,15 @@
|
||||||
|
|
||||||
import type { TestError } from '../types/testReporter';
|
import type { TestError } from '../types/testReporter';
|
||||||
import type { ConfigCLIOverrides } from './runner';
|
import type { ConfigCLIOverrides } from './runner';
|
||||||
import type { TestStatus } from './types';
|
import type { FullConfigInternal, TestStatus } from './types';
|
||||||
|
|
||||||
export type SerializedLoaderData = {
|
export type SerializedLoaderData = {
|
||||||
overrides: ConfigCLIOverrides;
|
config: FullConfigInternal;
|
||||||
configFile: { file: string } | { configDir: string };
|
configFile: string | undefined;
|
||||||
|
configDir: string;
|
||||||
|
overridesForLegacyConfigMode?: ConfigCLIOverrides;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WorkerInitParams = {
|
export type WorkerInitParams = {
|
||||||
workerIndex: number;
|
workerIndex: number;
|
||||||
parallelIndex: number;
|
parallelIndex: number;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import * as path from 'path';
|
||||||
import * as url from 'url';
|
import * as url from 'url';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import { ProjectImpl } from './project';
|
|
||||||
import type { BuiltInReporter, ConfigCLIOverrides } from './runner';
|
import type { BuiltInReporter, ConfigCLIOverrides } from './runner';
|
||||||
import type { Reporter } from '../types/testReporter';
|
import type { Reporter } from '../types/testReporter';
|
||||||
import { builtInReporters } from './runner';
|
import { builtInReporters } from './runner';
|
||||||
|
|
@ -45,7 +44,6 @@ export class Loader {
|
||||||
private _fullConfig: FullConfigInternal;
|
private _fullConfig: FullConfigInternal;
|
||||||
private _configDir: string = '';
|
private _configDir: string = '';
|
||||||
private _configFile: string | undefined;
|
private _configFile: string | undefined;
|
||||||
private _projects: ProjectImpl[] = [];
|
|
||||||
|
|
||||||
constructor(configCLIOverrides?: ConfigCLIOverrides) {
|
constructor(configCLIOverrides?: ConfigCLIOverrides) {
|
||||||
this._configCLIOverrides = configCLIOverrides || {};
|
this._configCLIOverrides = configCLIOverrides || {};
|
||||||
|
|
@ -53,12 +51,20 @@ export class Loader {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async deserialize(data: SerializedLoaderData): Promise<Loader> {
|
static async deserialize(data: SerializedLoaderData): Promise<Loader> {
|
||||||
const loader = new Loader(data.overrides);
|
if (process.env.PLAYWRIGHT_LEGACY_CONFIG_MODE) {
|
||||||
if ('file' in data.configFile)
|
const loader = new Loader(data.overridesForLegacyConfigMode);
|
||||||
await loader.loadConfigFile(data.configFile.file);
|
if (data.configFile)
|
||||||
else
|
await loader.loadConfigFile(data.configFile);
|
||||||
await loader.loadEmptyConfig(data.configFile.configDir);
|
else
|
||||||
return loader;
|
await loader.loadEmptyConfig(data.configDir);
|
||||||
|
return loader;
|
||||||
|
} else {
|
||||||
|
const loader = new Loader();
|
||||||
|
loader._configFile = data.configFile;
|
||||||
|
loader._configDir = data.configDir;
|
||||||
|
loader._fullConfig = data.config;
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadConfigFile(file: string): Promise<FullConfigInternal> {
|
async loadConfigFile(file: string): Promise<FullConfigInternal> {
|
||||||
|
|
@ -107,6 +113,8 @@ export class Loader {
|
||||||
config.projects = takeFirst(this._configCLIOverrides.projects, config.projects as any);
|
config.projects = takeFirst(this._configCLIOverrides.projects, config.projects as any);
|
||||||
config.workers = takeFirst(this._configCLIOverrides.workers, config.workers);
|
config.workers = takeFirst(this._configCLIOverrides.workers, config.workers);
|
||||||
config.use = mergeObjects(config.use, this._configCLIOverrides.use);
|
config.use = mergeObjects(config.use, this._configCLIOverrides.use);
|
||||||
|
for (const project of config.projects || [])
|
||||||
|
this._applyCLIOverridesToProject(project);
|
||||||
|
|
||||||
// 3. Run configure plugins phase.
|
// 3. Run configure plugins phase.
|
||||||
for (const plugin of config.plugins || [])
|
for (const plugin of config.plugins || [])
|
||||||
|
|
@ -155,11 +163,7 @@ export class Loader {
|
||||||
this._fullConfig.workers = takeFirst(config.workers, baseFullConfig.workers);
|
this._fullConfig.workers = takeFirst(config.workers, baseFullConfig.workers);
|
||||||
this._fullConfig.webServer = takeFirst(config.webServer, baseFullConfig.webServer);
|
this._fullConfig.webServer = takeFirst(config.webServer, baseFullConfig.webServer);
|
||||||
this._fullConfig._plugins = takeFirst(config.plugins, baseFullConfig._plugins);
|
this._fullConfig._plugins = takeFirst(config.plugins, baseFullConfig._plugins);
|
||||||
|
this._fullConfig.projects = (config.projects || [config]).map(p => this._resolveProject(config, p, throwawayArtifactsPath));
|
||||||
const projects: Project[] = this._configCLIOverrides.projects || config.projects || [config];
|
|
||||||
for (const project of projects)
|
|
||||||
this._addProject(config, project, throwawayArtifactsPath);
|
|
||||||
this._fullConfig.projects = this._projects.map(p => p.config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadTestFile(file: string, environment: 'runner' | 'worker') {
|
async loadTestFile(file: string, environment: 'runner' | 'worker') {
|
||||||
|
|
@ -228,18 +232,28 @@ export class Loader {
|
||||||
return this._fullConfig;
|
return this._fullConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
projects() {
|
|
||||||
return this._projects;
|
|
||||||
}
|
|
||||||
|
|
||||||
serialize(): SerializedLoaderData {
|
serialize(): SerializedLoaderData {
|
||||||
return {
|
const result: SerializedLoaderData = {
|
||||||
configFile: this._configFile ? { file: this._configFile } : { configDir: this._configDir },
|
configFile: this._configFile,
|
||||||
overrides: this._configCLIOverrides,
|
configDir: this._configDir,
|
||||||
|
config: this._fullConfig,
|
||||||
};
|
};
|
||||||
|
if (process.env.PLAYWRIGHT_LEGACY_CONFIG_MODE)
|
||||||
|
result.overridesForLegacyConfigMode = this._configCLIOverrides;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addProject(config: Config, projectConfig: Project, throwawayArtifactsPath: string) {
|
private _applyCLIOverridesToProject(projectConfig: Project) {
|
||||||
|
projectConfig.fullyParallel = takeFirst(this._configCLIOverrides.fullyParallel, projectConfig.fullyParallel);
|
||||||
|
projectConfig.grep = takeFirst(this._configCLIOverrides.grep, projectConfig.grep);
|
||||||
|
projectConfig.grepInvert = takeFirst(this._configCLIOverrides.grepInvert, projectConfig.grepInvert);
|
||||||
|
projectConfig.outputDir = takeFirst(this._configCLIOverrides.outputDir, projectConfig.outputDir);
|
||||||
|
projectConfig.repeatEach = takeFirst(this._configCLIOverrides.repeatEach, projectConfig.repeatEach);
|
||||||
|
projectConfig.retries = takeFirst(this._configCLIOverrides.retries, projectConfig.retries);
|
||||||
|
projectConfig.timeout = takeFirst(this._configCLIOverrides.timeout, projectConfig.timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _resolveProject(config: Config, projectConfig: Project, throwawayArtifactsPath: string): FullProjectInternal {
|
||||||
// Resolve all config dirs relative to configDir.
|
// Resolve all config dirs relative to configDir.
|
||||||
if (projectConfig.testDir !== undefined)
|
if (projectConfig.testDir !== undefined)
|
||||||
projectConfig.testDir = path.resolve(this._configDir, projectConfig.testDir);
|
projectConfig.testDir = path.resolve(this._configDir, projectConfig.testDir);
|
||||||
|
|
@ -250,21 +264,13 @@ export class Loader {
|
||||||
if (projectConfig.snapshotDir !== undefined)
|
if (projectConfig.snapshotDir !== undefined)
|
||||||
projectConfig.snapshotDir = path.resolve(this._configDir, projectConfig.snapshotDir);
|
projectConfig.snapshotDir = path.resolve(this._configDir, projectConfig.snapshotDir);
|
||||||
|
|
||||||
projectConfig.fullyParallel = takeFirst(this._configCLIOverrides.fullyParallel, projectConfig.fullyParallel);
|
|
||||||
projectConfig.grep = takeFirst(this._configCLIOverrides.grep, projectConfig.grep);
|
|
||||||
projectConfig.grepInvert = takeFirst(this._configCLIOverrides.grepInvert, projectConfig.grepInvert);
|
|
||||||
projectConfig.outputDir = takeFirst(this._configCLIOverrides.outputDir, projectConfig.outputDir);
|
|
||||||
projectConfig.repeatEach = takeFirst(this._configCLIOverrides.repeatEach, projectConfig.repeatEach);
|
|
||||||
projectConfig.retries = takeFirst(this._configCLIOverrides.retries, projectConfig.retries);
|
|
||||||
projectConfig.timeout = takeFirst(this._configCLIOverrides.timeout, projectConfig.timeout);
|
|
||||||
|
|
||||||
const testDir = takeFirst(projectConfig.testDir, config.testDir, this._configDir);
|
const testDir = takeFirst(projectConfig.testDir, config.testDir, this._configDir);
|
||||||
|
|
||||||
const outputDir = takeFirst(projectConfig.outputDir, config.outputDir, path.join(throwawayArtifactsPath, 'test-results'));
|
const outputDir = takeFirst(projectConfig.outputDir, config.outputDir, path.join(throwawayArtifactsPath, 'test-results'));
|
||||||
const snapshotDir = takeFirst(projectConfig.snapshotDir, config.snapshotDir, testDir);
|
const snapshotDir = takeFirst(projectConfig.snapshotDir, config.snapshotDir, testDir);
|
||||||
const name = takeFirst(projectConfig.name, config.name, '');
|
const name = takeFirst(projectConfig.name, config.name, '');
|
||||||
const screenshotsDir = takeFirst((projectConfig as any).screenshotsDir, (config as any).screenshotsDir, path.join(testDir, '__screenshots__', process.platform, name));
|
const screenshotsDir = takeFirst((projectConfig as any).screenshotsDir, (config as any).screenshotsDir, path.join(testDir, '__screenshots__', process.platform, name));
|
||||||
const fullProject: FullProjectInternal = {
|
return {
|
||||||
_fullyParallel: takeFirst(projectConfig.fullyParallel, config.fullyParallel, undefined),
|
_fullyParallel: takeFirst(projectConfig.fullyParallel, config.fullyParallel, undefined),
|
||||||
_expect: takeFirst(projectConfig.expect, config.expect, undefined),
|
_expect: takeFirst(projectConfig.expect, config.expect, undefined),
|
||||||
grep: takeFirst(projectConfig.grep, config.grep, baseFullConfig.grep),
|
grep: takeFirst(projectConfig.grep, config.grep, baseFullConfig.grep),
|
||||||
|
|
@ -282,7 +288,6 @@ export class Loader {
|
||||||
timeout: takeFirst(projectConfig.timeout, config.timeout, defaultTimeout),
|
timeout: takeFirst(projectConfig.timeout, config.timeout, defaultTimeout),
|
||||||
use: mergeObjects(config.use, projectConfig.use),
|
use: mergeObjects(config.use, projectConfig.use),
|
||||||
};
|
};
|
||||||
this._projects.push(new ProjectImpl(fullProject, this._projects.length));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _requireOrImport(file: string) {
|
private async _requireOrImport(file: string) {
|
||||||
|
|
|
||||||
|
|
@ -32,9 +32,9 @@ export class ProjectImpl {
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildTestTypePool(testType: TestTypeImpl): FixturePool {
|
private _buildTestTypePool(testType: TestTypeImpl): FixturePool {
|
||||||
if (!this.testTypePools.has(testType)) {
|
if (!this.testTypePools.has(testType)) {
|
||||||
const fixtures = this.resolveFixtures(testType, this.config.use);
|
const fixtures = this._resolveFixtures(testType, this.config.use);
|
||||||
const pool = new FixturePool(fixtures);
|
const pool = new FixturePool(fixtures);
|
||||||
this.testTypePools.set(testType, pool);
|
this.testTypePools.set(testType, pool);
|
||||||
}
|
}
|
||||||
|
|
@ -42,9 +42,9 @@ export class ProjectImpl {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: we can optimize this function by building the pool inline in cloneSuite
|
// TODO: we can optimize this function by building the pool inline in cloneSuite
|
||||||
private buildPool(test: TestCase): FixturePool {
|
private _buildPool(test: TestCase): FixturePool {
|
||||||
if (!this.testPools.has(test)) {
|
if (!this.testPools.has(test)) {
|
||||||
let pool = this.buildTestTypePool(test._testType);
|
let pool = this._buildTestTypePool(test._testType);
|
||||||
|
|
||||||
const parents: Suite[] = [];
|
const parents: Suite[] = [];
|
||||||
for (let parent: Suite | undefined = test.parent; parent; parent = parent.parent)
|
for (let parent: Suite | undefined = test.parent; parent; parent = parent.parent)
|
||||||
|
|
@ -88,7 +88,7 @@ export class ProjectImpl {
|
||||||
to._entries.pop();
|
to._entries.pop();
|
||||||
to.tests.pop();
|
to.tests.pop();
|
||||||
} else {
|
} else {
|
||||||
const pool = this.buildPool(entry);
|
const pool = this._buildPool(entry);
|
||||||
test._workerHash = `run${this.index}-${pool.digest}-repeat${repeatEachIndex}`;
|
test._workerHash = `run${this.index}-${pool.digest}-repeat${repeatEachIndex}`;
|
||||||
test._pool = pool;
|
test._pool = pool;
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +104,7 @@ export class ProjectImpl {
|
||||||
return this._cloneEntries(suite, result, repeatEachIndex, filter, '') ? result : undefined;
|
return this._cloneEntries(suite, result, repeatEachIndex, filter, '') ? result : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveFixtures(testType: TestTypeImpl, configUse: Fixtures): FixturesWithLocation[] {
|
private _resolveFixtures(testType: TestTypeImpl, configUse: Fixtures): FixturesWithLocation[] {
|
||||||
return testType.fixtures.map(f => {
|
return testType.fixtures.map(f => {
|
||||||
const configKeys = new Set(Object.keys(configUse || {}));
|
const configKeys = new Set(Object.keys(configUse || {}));
|
||||||
const resolved = { ...f.fixtures };
|
const resolved = { ...f.fixtures };
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,8 @@ import JSONReporter from './reporters/json';
|
||||||
import JUnitReporter from './reporters/junit';
|
import JUnitReporter from './reporters/junit';
|
||||||
import EmptyReporter from './reporters/empty';
|
import EmptyReporter from './reporters/empty';
|
||||||
import HtmlReporter from './reporters/html';
|
import HtmlReporter from './reporters/html';
|
||||||
import type { ProjectImpl } from './project';
|
import { ProjectImpl } from './project';
|
||||||
import type { Config } from './types';
|
import type { Config, FullProjectInternal } from './types';
|
||||||
import type { FullConfigInternal } from './types';
|
import type { FullConfigInternal } from './types';
|
||||||
import { raceAgainstTimeout } from 'playwright-core/lib/utils/timeoutRunner';
|
import { raceAgainstTimeout } from 'playwright-core/lib/utils/timeoutRunner';
|
||||||
import { SigIntWatcher } from './sigIntWatcher';
|
import { SigIntWatcher } from './sigIntWatcher';
|
||||||
|
|
@ -194,8 +194,8 @@ export class Runner {
|
||||||
};
|
};
|
||||||
for (const [project, files] of filesByProject) {
|
for (const [project, files] of filesByProject) {
|
||||||
report.projects.push({
|
report.projects.push({
|
||||||
name: project.config.name,
|
name: project.name,
|
||||||
testDir: path.resolve(configFile, project.config.testDir),
|
testDir: path.resolve(configFile, project.testDir),
|
||||||
files: files
|
files: files
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -207,7 +207,7 @@ export class Runner {
|
||||||
return await this._runFiles(list, filesByProject, testFileReFilters);
|
return await this._runFiles(list, filesByProject, testFileReFilters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _collectFiles(testFileReFilters: FilePatternFilter[], projectNames?: string[]): Promise<Map<ProjectImpl, string[]>> {
|
private async _collectFiles(testFileReFilters: FilePatternFilter[], projectNames?: string[]): Promise<Map<FullProjectInternal, string[]>> {
|
||||||
const testFileFilter = testFileReFilters.length ? createFileMatcher(testFileReFilters.map(e => e.re)) : () => true;
|
const testFileFilter = testFileReFilters.length ? createFileMatcher(testFileReFilters.map(e => e.re)) : () => true;
|
||||||
let projectsToFind: Set<string> | undefined;
|
let projectsToFind: Set<string> | undefined;
|
||||||
let unknownProjects: Map<string, string> | undefined;
|
let unknownProjects: Map<string, string> | undefined;
|
||||||
|
|
@ -220,26 +220,27 @@ export class Runner {
|
||||||
unknownProjects!.set(name, n);
|
unknownProjects!.set(name, n);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const projects = this._loader.projects().filter(project => {
|
const fullConfig = this._loader.fullConfig();
|
||||||
|
const projects = fullConfig.projects.filter(project => {
|
||||||
if (!projectsToFind)
|
if (!projectsToFind)
|
||||||
return true;
|
return true;
|
||||||
const name = project.config.name.toLocaleLowerCase();
|
const name = project.name.toLocaleLowerCase();
|
||||||
unknownProjects!.delete(name);
|
unknownProjects!.delete(name);
|
||||||
return projectsToFind.has(name);
|
return projectsToFind.has(name);
|
||||||
});
|
});
|
||||||
if (unknownProjects && unknownProjects.size) {
|
if (unknownProjects && unknownProjects.size) {
|
||||||
const names = this._loader.projects().map(p => p.config.name).filter(name => !!name);
|
const names = fullConfig.projects.map(p => p.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(', ');
|
||||||
throw new Error(`Project(s) ${unknownProjectNames} not found. Available named projects: ${names.map(name => `"${name}"`).join(', ')}`);
|
throw new Error(`Project(s) ${unknownProjectNames} not found. Available named projects: ${names.map(name => `"${name}"`).join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const files = new Map<ProjectImpl, string[]>();
|
const files = new Map<FullProjectInternal, string[]>();
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
const allFiles = await collectFiles(project.config.testDir);
|
const allFiles = await collectFiles(project.testDir);
|
||||||
const testMatch = createFileMatcher(project.config.testMatch);
|
const testMatch = createFileMatcher(project.testMatch);
|
||||||
const testIgnore = createFileMatcher(project.config.testIgnore);
|
const testIgnore = createFileMatcher(project.testIgnore);
|
||||||
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 testFiles = allFiles.filter(file => !testIgnore(file) && testMatch(file) && testFileFilter(file) && testFileExtension(file));
|
const testFiles = allFiles.filter(file => !testIgnore(file) && testMatch(file) && testFileFilter(file) && testFileExtension(file));
|
||||||
|
|
@ -248,7 +249,7 @@ export class Runner {
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _runFiles(list: boolean, filesByProject: Map<ProjectImpl, string[]>, testFileReFilters: FilePatternFilter[]): Promise<FullResult> {
|
private async _runFiles(list: boolean, filesByProject: Map<FullProjectInternal, string[]>, testFileReFilters: FilePatternFilter[]): Promise<FullResult> {
|
||||||
const allTestFiles = new Set<string>();
|
const allTestFiles = new Set<string>();
|
||||||
for (const files of filesByProject.values())
|
for (const files of filesByProject.values())
|
||||||
files.forEach(file => allTestFiles.add(file));
|
files.forEach(file => allTestFiles.add(file));
|
||||||
|
|
@ -293,19 +294,20 @@ export class Runner {
|
||||||
const outputDirs = new Set<string>();
|
const outputDirs = new Set<string>();
|
||||||
const rootSuite = new Suite('');
|
const rootSuite = new Suite('');
|
||||||
for (const [project, files] of filesByProject) {
|
for (const [project, files] of filesByProject) {
|
||||||
const grepMatcher = createTitleMatcher(project.config.grep);
|
const projectImpl = new ProjectImpl(project, config.projects.indexOf(project));
|
||||||
const grepInvertMatcher = project.config.grepInvert ? createTitleMatcher(project.config.grepInvert) : null;
|
const grepMatcher = createTitleMatcher(project.grep);
|
||||||
const projectSuite = new Suite(project.config.name);
|
const grepInvertMatcher = project.grepInvert ? createTitleMatcher(project.grepInvert) : null;
|
||||||
projectSuite._projectConfig = project.config;
|
const projectSuite = new Suite(project.name);
|
||||||
if (project.config._fullyParallel)
|
projectSuite._projectConfig = project;
|
||||||
|
if (project._fullyParallel)
|
||||||
projectSuite._parallelMode = 'parallel';
|
projectSuite._parallelMode = 'parallel';
|
||||||
rootSuite._addSuite(projectSuite);
|
rootSuite._addSuite(projectSuite);
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const fileSuite = fileSuites.get(file);
|
const fileSuite = fileSuites.get(file);
|
||||||
if (!fileSuite)
|
if (!fileSuite)
|
||||||
continue;
|
continue;
|
||||||
for (let repeatEachIndex = 0; repeatEachIndex < project.config.repeatEach; repeatEachIndex++) {
|
for (let repeatEachIndex = 0; repeatEachIndex < project.repeatEach; repeatEachIndex++) {
|
||||||
const cloned = project.cloneFileSuite(fileSuite, repeatEachIndex, test => {
|
const cloned = projectImpl.cloneFileSuite(fileSuite, repeatEachIndex, test => {
|
||||||
const grepTitle = test.titlePath().join(' ');
|
const grepTitle = test.titlePath().join(' ');
|
||||||
if (grepInvertMatcher?.(grepTitle))
|
if (grepInvertMatcher?.(grepTitle))
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -315,7 +317,7 @@ export class Runner {
|
||||||
projectSuite._addSuite(cloned);
|
projectSuite._addSuite(cloned);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
outputDirs.add(project.config.outputDir);
|
outputDirs.add(project.outputDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Fail when no tests.
|
// 7. Fail when no tests.
|
||||||
|
|
|
||||||
|
|
@ -85,12 +85,13 @@ export class TestInfoImpl implements TestInfo {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
loader: Loader,
|
loader: Loader,
|
||||||
|
projectImpl: ProjectImpl,
|
||||||
workerParams: WorkerInitParams,
|
workerParams: WorkerInitParams,
|
||||||
test: TestCase,
|
test: TestCase,
|
||||||
retry: number,
|
retry: number,
|
||||||
addStepImpl: (data: Omit<TestStepInternal, 'complete'>) => TestStepInternal,
|
addStepImpl: (data: Omit<TestStepInternal, 'complete'>) => TestStepInternal,
|
||||||
) {
|
) {
|
||||||
this._projectImpl = loader.projects()[workerParams.projectIndex];
|
this._projectImpl = projectImpl;
|
||||||
this._test = test;
|
this._test = test;
|
||||||
this._addStepImpl = addStepImpl;
|
this._addStepImpl = addStepImpl;
|
||||||
this._startTime = monotonicTime();
|
this._startTime = monotonicTime();
|
||||||
|
|
@ -113,10 +114,10 @@ export class TestInfoImpl implements TestInfo {
|
||||||
this._timeoutManager = new TimeoutManager(this.project.timeout);
|
this._timeoutManager = new TimeoutManager(this.project.timeout);
|
||||||
|
|
||||||
this.outputDir = (() => {
|
this.outputDir = (() => {
|
||||||
const sameName = loader.projects().filter(project => project.config.name === this.project.name);
|
const sameName = loader.fullConfig().projects.filter(project => project.name === this.project.name);
|
||||||
let uniqueProjectNamePathSegment: string;
|
let uniqueProjectNamePathSegment: string;
|
||||||
if (sameName.length > 1)
|
if (sameName.length > 1)
|
||||||
uniqueProjectNamePathSegment = this.project.name + (sameName.indexOf(this._projectImpl) + 1);
|
uniqueProjectNamePathSegment = this.project.name + (sameName.indexOf(this._projectImpl.config) + 1);
|
||||||
else
|
else
|
||||||
uniqueProjectNamePathSegment = this.project.name;
|
uniqueProjectNamePathSegment = this.project.name;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import { setCurrentTestInfo } from './globals';
|
||||||
import { Loader } from './loader';
|
import { Loader } from './loader';
|
||||||
import type { Suite, TestCase } from './test';
|
import type { Suite, TestCase } from './test';
|
||||||
import type { Annotation, TestError, TestStepInternal } from './types';
|
import type { Annotation, TestError, TestStepInternal } from './types';
|
||||||
import type { ProjectImpl } from './project';
|
import { ProjectImpl } from './project';
|
||||||
import { FixtureRunner } from './fixtures';
|
import { FixtureRunner } from './fixtures';
|
||||||
import { ManualPromise } from 'playwright-core/lib/utils/manualPromise';
|
import { ManualPromise } from 'playwright-core/lib/utils/manualPromise';
|
||||||
import { TestInfoImpl } from './testInfo';
|
import { TestInfoImpl } from './testInfo';
|
||||||
|
|
@ -151,7 +151,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this._loader = await Loader.deserialize(this._params.loader);
|
this._loader = await Loader.deserialize(this._params.loader);
|
||||||
this._project = this._loader.projects()[this._params.projectIndex];
|
this._project = new ProjectImpl(this._loader.fullConfig().projects[this._params.projectIndex], this._params.projectIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
async runTestGroup(runPayload: RunPayload) {
|
async runTestGroup(runPayload: RunPayload) {
|
||||||
|
|
@ -207,7 +207,7 @@ export class WorkerRunner extends EventEmitter {
|
||||||
|
|
||||||
private async _runTest(test: TestCase, retry: number, nextTest: TestCase | undefined) {
|
private async _runTest(test: TestCase, retry: number, nextTest: TestCase | undefined) {
|
||||||
let lastStepId = 0;
|
let lastStepId = 0;
|
||||||
const testInfo = new TestInfoImpl(this._loader, this._params, test, retry, data => {
|
const testInfo = new TestInfoImpl(this._loader, this._project, this._params, test, retry, data => {
|
||||||
const stepId = `${data.category}@${data.title}@${++lastStepId}`;
|
const stepId = `${data.category}@${data.title}@${++lastStepId}`;
|
||||||
let callbackHandled = false;
|
let callbackHandled = false;
|
||||||
const step: TestStepInternal = {
|
const step: TestStepInternal = {
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,7 @@ type RunOptions = {
|
||||||
cwd?: string,
|
cwd?: string,
|
||||||
};
|
};
|
||||||
type Fixtures = {
|
type Fixtures = {
|
||||||
|
legacyConfigLoader: boolean;
|
||||||
writeFiles: (files: Files) => Promise<string>;
|
writeFiles: (files: Files) => Promise<string>;
|
||||||
runInlineTest: (files: Files, params?: Params, env?: Env, options?: RunOptions, beforeRunPlaywrightTest?: ({ baseDir }: { baseDir: string }) => Promise<void>) => Promise<RunResult>;
|
runInlineTest: (files: Files, params?: Params, env?: Env, options?: RunOptions, beforeRunPlaywrightTest?: ({ baseDir }: { baseDir: string }) => Promise<void>) => Promise<RunResult>;
|
||||||
runTSC: (files: Files) => Promise<TSCResult>;
|
runTSC: (files: Files) => Promise<TSCResult>;
|
||||||
|
|
@ -224,15 +225,19 @@ export const test = base
|
||||||
.extend<CommonFixtures>(commonFixtures)
|
.extend<CommonFixtures>(commonFixtures)
|
||||||
.extend<ServerFixtures, ServerWorkerOptions>(serverFixtures)
|
.extend<ServerFixtures, ServerWorkerOptions>(serverFixtures)
|
||||||
.extend<Fixtures>({
|
.extend<Fixtures>({
|
||||||
|
legacyConfigLoader: [false, { option: true }],
|
||||||
|
|
||||||
writeFiles: async ({}, use, testInfo) => {
|
writeFiles: async ({}, use, testInfo) => {
|
||||||
await use(files => writeFiles(testInfo, files));
|
await use(files => writeFiles(testInfo, files));
|
||||||
},
|
},
|
||||||
|
|
||||||
runInlineTest: async ({ childProcess }, use, testInfo: TestInfo) => {
|
runInlineTest: async ({ childProcess, legacyConfigLoader }, use, testInfo: TestInfo) => {
|
||||||
await use(async (files: Files, params: Params = {}, env: Env = {}, options: RunOptions = {}, beforeRunPlaywrightTest?: ({ baseDir: string }) => Promise<void>) => {
|
await use(async (files: Files, params: Params = {}, env: Env = {}, options: RunOptions = {}, beforeRunPlaywrightTest?: ({ baseDir: string }) => Promise<void>) => {
|
||||||
const baseDir = await writeFiles(testInfo, files);
|
const baseDir = await writeFiles(testInfo, files);
|
||||||
if (beforeRunPlaywrightTest)
|
if (beforeRunPlaywrightTest)
|
||||||
await beforeRunPlaywrightTest({ baseDir });
|
await beforeRunPlaywrightTest({ baseDir });
|
||||||
|
if (legacyConfigLoader)
|
||||||
|
env = { ...env, PLAYWRIGHT_LEGACY_CONFIG_MODE: '1' };
|
||||||
return await runPlaywrightTest(childProcess, baseDir, params, env, options);
|
return await runPlaywrightTest(childProcess, baseDir, params, env, options);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,13 @@ const config: Config = {
|
||||||
workers: process.env.CI ? 1 : undefined,
|
workers: process.env.CI ? 1 : undefined,
|
||||||
preserveOutput: process.env.CI ? 'failures-only' : 'always',
|
preserveOutput: process.env.CI ? 'failures-only' : 'always',
|
||||||
projects: [
|
projects: [
|
||||||
{ name: 'playwright-test' },
|
{
|
||||||
|
name: 'playwright-test'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'playwright-test-legacy-config',
|
||||||
|
use: { legacyConfigLoader: true },
|
||||||
|
} as any,
|
||||||
],
|
],
|
||||||
reporter: process.env.CI ? [
|
reporter: process.env.CI ? [
|
||||||
['dot'],
|
['dot'],
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
|
|
||||||
import { test, expect } from './playwright-test-fixtures';
|
import { test, expect } from './playwright-test-fixtures';
|
||||||
|
|
||||||
test('should work with connectOptions', async ({ runInlineTest }) => {
|
test('should work with connectOptions (legacy)', async ({ runInlineTest, legacyConfigLoader }) => {
|
||||||
|
test.skip(!legacyConfigLoader, 'Not supported in the new mode');
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'playwright.config.js': `
|
'playwright.config.js': `
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
@ -49,6 +50,42 @@ test('should work with connectOptions', async ({ runInlineTest }) => {
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should work with connectOptions', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'playwright.config.js': `
|
||||||
|
module.exports = { plugins: [require('./plugin')] };
|
||||||
|
`,
|
||||||
|
'plugin.js': `
|
||||||
|
let server;
|
||||||
|
module.exports = {
|
||||||
|
configure: async (config) => {
|
||||||
|
server = await pwt.chromium.launchServer();
|
||||||
|
config.use = {
|
||||||
|
connectOptions: {
|
||||||
|
wsEndpoint: server.wsEndpoint()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
teardown: async () => {
|
||||||
|
await server.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
const { test } = pwt;
|
||||||
|
test.use({ locale: 'fr-CH' });
|
||||||
|
test('pass', async ({ page }) => {
|
||||||
|
await page.setContent('<div>PASS</div>');
|
||||||
|
await expect(page.locator('div')).toHaveText('PASS');
|
||||||
|
expect(await page.evaluate(() => navigator.language)).toBe('fr-CH');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
test('should throw with bad connectOptions', async ({ runInlineTest }) => {
|
test('should throw with bad connectOptions', async ({ runInlineTest }) => {
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'playwright.config.js': `
|
'playwright.config.js': `
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,8 @@
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import { test, expect } from './playwright-test-fixtures';
|
import { test, expect } from './playwright-test-fixtures';
|
||||||
|
|
||||||
test('event order', async ({ runInlineTest }, testInfo) => {
|
test('event order', async ({ runInlineTest, legacyConfigLoader }, testInfo) => {
|
||||||
|
test.skip(legacyConfigLoader);
|
||||||
const log = testInfo.outputPath('logs.txt');
|
const log = testInfo.outputPath('logs.txt');
|
||||||
const result = await runInlineTest({
|
const result = await runInlineTest({
|
||||||
'log.ts': `
|
'log.ts': `
|
||||||
|
|
@ -87,8 +88,6 @@ test('event order', async ({ runInlineTest }, testInfo) => {
|
||||||
'a setup',
|
'a setup',
|
||||||
'b setup',
|
'b setup',
|
||||||
'globalSetup',
|
'globalSetup',
|
||||||
'a configure',
|
|
||||||
'b configure',
|
|
||||||
'baseURL a | b | ',
|
'baseURL a | b | ',
|
||||||
'globalTeardown',
|
'globalTeardown',
|
||||||
'b teardown',
|
'b teardown',
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue