chore: try serializing the config instead of requiring it in the worker (#13839)

This commit is contained in:
Pavel Feldman 2022-04-29 15:05:08 -08:00 committed by GitHub
parent 4b682f9f13
commit a1b10c3856
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 131 additions and 73 deletions

View file

@ -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;

View file

@ -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) {

View file

@ -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 };

View file

@ -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.

View file

@ -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;

View file

@ -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 = {

View file

@ -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);
}); });
}, },

View file

@ -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'],

View file

@ -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': `

View file

@ -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',