diff --git a/packages/playwright/src/common/configLoader.ts b/packages/playwright/src/common/configLoader.ts index 08a9c0a48d..de568114a1 100644 --- a/packages/playwright/src/common/configLoader.ts +++ b/packages/playwright/src/common/configLoader.ts @@ -286,7 +286,16 @@ function validateProject(file: string, project: Project, title: string) { } } -export function resolveConfigFile(configFileOrDirectory: string): string | undefined { +export function resolveConfigLocation(configFile: string | undefined): ConfigLocation { + const configFileOrDirectory = configFile ? path.resolve(process.cwd(), configFile) : process.cwd(); + const resolvedConfigFile = resolveConfigFile(configFileOrDirectory); + return { + resolvedConfigFile, + configDir: resolvedConfigFile ? path.dirname(resolvedConfigFile) : configFileOrDirectory, + }; +} + +function resolveConfigFile(configFileOrDirectory: string): string | undefined { const resolveConfig = (configFile: string) => { if (fs.existsSync(configFile)) return configFile; @@ -309,22 +318,15 @@ export function resolveConfigFile(configFileOrDirectory: string): string | undef return configFile; // If there is no config, assume this as a root testing directory. return undefined; - } else { - // When passed a file, it must be a config file. - const configFile = resolveConfig(configFileOrDirectory); - return configFile!; } + // When passed a file, it must be a config file. + return configFileOrDirectory!; } export async function loadConfigFromFileRestartIfNeeded(configFile: string | undefined, overrides?: ConfigCLIOverrides, ignoreDeps?: boolean): Promise { - const configFileOrDirectory = configFile ? path.resolve(process.cwd(), configFile) : process.cwd(); - const resolvedConfigFile = resolveConfigFile(configFileOrDirectory); - if (restartWithExperimentalTsEsm(resolvedConfigFile)) + const location = resolveConfigLocation(configFile); + if (restartWithExperimentalTsEsm(location.resolvedConfigFile)) return null; - const location: ConfigLocation = { - configDir: resolvedConfigFile ? path.dirname(resolvedConfigFile) : configFileOrDirectory, - resolvedConfigFile, - }; return await loadConfig(location, overrides, ignoreDeps); } diff --git a/packages/playwright/src/runner/testServer.ts b/packages/playwright/src/runner/testServer.ts index da1584de96..19a43beed0 100644 --- a/packages/playwright/src/runner/testServer.ts +++ b/packages/playwright/src/runner/testServer.ts @@ -21,7 +21,7 @@ import { ManualPromise, gracefullyProcessExitDoNotHang, isUnderTest } from 'play import type { Transport, HttpServer } from 'playwright-core/lib/utils'; import type * as reporterTypes from '../../types/testReporter'; import { collectAffectedTestFiles, dependenciesForTestFile } from '../transform/compilationCache'; -import type { FullConfigInternal } from '../common/config'; +import type { ConfigLocation, FullConfigInternal } from '../common/config'; import { InternalReporter } from '../reporters/internalReporter'; import { createReporterForTestServer, createReporters } from './reporters'; import { TestRun, createTaskRunnerForList, createTaskRunnerForTestServer, createTaskRunnerForWatchSetup, createTaskRunnerForListFiles } from './tasks'; @@ -33,7 +33,7 @@ import { Watcher } from '../fsWatcher'; import type { ReportEntry, TestServerInterface, TestServerInterfaceEventEmitters } from '../isomorphic/testServerInterface'; import { Runner } from './runner'; import type { ConfigCLIOverrides } from '../common/ipc'; -import { loadConfig, resolveConfigFile, restartWithExperimentalTsEsm } from '../common/configLoader'; +import { loadConfig, resolveConfigLocation, restartWithExperimentalTsEsm } from '../common/configLoader'; import { webServerPluginsForConfig } from '../plugins/webServerPlugin'; import type { TraceViewerRedirectOptions, TraceViewerServerOptions } from 'playwright-core/lib/server/trace/viewer/traceViewer'; import type { TestRunnerPluginRegistration } from '../plugins'; @@ -44,15 +44,15 @@ const originalStdoutWrite = process.stdout.write; const originalStderrWrite = process.stderr.write; class TestServer { - private _configFile: string | undefined; + private _configLocation: ConfigLocation; private _dispatcher: TestServerDispatcher | undefined; - constructor(configFile: string | undefined) { - this._configFile = configFile; + constructor(configLocation: ConfigLocation) { + this._configLocation = configLocation; } async start(options: { host?: string, port?: number }): Promise { - this._dispatcher = new TestServerDispatcher(this._configFile); + this._dispatcher = new TestServerDispatcher(this._configLocation); return await startTraceViewerServer({ ...options, transport: this._dispatcher.transport }); } @@ -63,7 +63,7 @@ class TestServer { } class TestServerDispatcher implements TestServerInterface { - private _configFile: string | undefined; + private _configLocation: ConfigLocation; private _globalWatcher: Watcher; private _testWatcher: Watcher; private _testRun: { run: Promise, stop: ManualPromise } | undefined; @@ -77,8 +77,8 @@ class TestServerDispatcher implements TestServerInterface { private _closeOnDisconnect = false; private _devServerHandle: (() => Promise) | undefined; - constructor(configFile: string | undefined) { - this._configFile = configFile; + constructor(configLocation: ConfigLocation) { + this._configLocation = configLocation; this.transport = { dispatch: (method, params) => (this as any)[method](params), onclose: () => { @@ -145,7 +145,7 @@ class TestServerDispatcher implements TestServerInterface { await this.runGlobalTeardown(); const { reporter, report } = await this._collectingReporter(); - const { config, error } = await this._loadConfig(this._configFile); + const { config, error } = await this._loadConfig(); if (!config) { reporter.onError(error!); return { status: 'failed', report }; @@ -178,7 +178,7 @@ class TestServerDispatcher implements TestServerInterface { if (this._devServerHandle) return { status: 'failed', report: [] }; const { reporter, report } = await this._collectingReporter(); - const { config, error } = await this._loadConfig(this._configFile); + const { config, error } = await this._loadConfig(); if (!config) { reporter.onError(error!); return { status: 'failed', report }; @@ -212,14 +212,14 @@ class TestServerDispatcher implements TestServerInterface { } async clearCache(params: Parameters[0]): ReturnType { - const { config } = await this._loadConfig(this._configFile); + const { config } = await this._loadConfig(); if (config) await clearCacheAndLogToConsole(config); } async listFiles(params: Parameters[0]): ReturnType { const { reporter, report } = await this._collectingReporter(); - const { config, error } = await this._loadConfig(this._configFile); + const { config, error } = await this._loadConfig(); if (!config) { reporter.onError(error!); return { status: 'failed', report }; @@ -250,7 +250,7 @@ class TestServerDispatcher implements TestServerInterface { retries: 0, }; const { reporter, report } = await this._collectingReporter(); - const { config, error } = await this._loadConfig(this._configFile, overrides); + const { config, error } = await this._loadConfig(overrides); if (!config) { reporter.onError(error!); return { report: [], status: 'failed' }; @@ -315,7 +315,7 @@ class TestServerDispatcher implements TestServerInterface { else process.env.PW_LIVE_TRACE_STACKS = undefined; - const { config, error } = await this._loadConfig(this._configFile, overrides); + const { config, error } = await this._loadConfig(overrides); if (!config) { const wireReporter = await this._wireReporter(e => this._dispatchEvent('report', e)); wireReporter.onError(error!); @@ -359,7 +359,7 @@ class TestServerDispatcher implements TestServerInterface { } async findRelatedTestFiles(params: Parameters[0]): ReturnType { - const { config, error } = await this._loadConfig(this._configFile); + const { config, error } = await this._loadConfig(); if (error) return { testFiles: [], errors: [error] }; const runner = new Runner(config!); @@ -393,11 +393,9 @@ class TestServerDispatcher implements TestServerInterface { gracefullyProcessExitDoNotHang(0); } - private async _loadConfig(configFile: string | undefined, overrides?: ConfigCLIOverrides): Promise<{ config: FullConfigInternal | null, error?: reporterTypes.TestError }> { - const configFileOrDirectory = configFile ? path.resolve(process.cwd(), configFile) : process.cwd(); - const resolvedConfigFile = resolveConfigFile(configFileOrDirectory); + private async _loadConfig(overrides?: ConfigCLIOverrides): Promise<{ config: FullConfigInternal | null, error?: reporterTypes.TestError }> { try { - const config = await loadConfig({ resolvedConfigFile, configDir: resolvedConfigFile === configFileOrDirectory ? path.dirname(resolvedConfigFile) : configFileOrDirectory }, overrides); + const config = await loadConfig(this._configLocation, overrides); // Preserve plugin instances between setup and build. if (!this._plugins) this._plugins = config.plugins || []; @@ -411,7 +409,8 @@ class TestServerDispatcher implements TestServerInterface { } export async function runUIMode(configFile: string | undefined, options: TraceViewerServerOptions & TraceViewerRedirectOptions): Promise { - return await innerRunTestServer(configFile, options, async (server: HttpServer, cancelPromise: ManualPromise) => { + const configLocation = resolveConfigLocation(configFile); + return await innerRunTestServer(configLocation, options, async (server: HttpServer, cancelPromise: ManualPromise) => { await installRootRedirect(server, [], { ...options, webApp: 'uiMode.html' }); if (options.host !== undefined || options.port !== undefined) { await openTraceInBrowser(server.urlPrefix()); @@ -428,23 +427,24 @@ export async function runUIMode(configFile: string | undefined, options: TraceVi } export async function runTestServer(configFile: string | undefined, options: { host?: string, port?: number }): Promise { - return await innerRunTestServer(configFile, options, async server => { + const configLocation = resolveConfigLocation(configFile); + return await innerRunTestServer(configLocation, options, async server => { // eslint-disable-next-line no-console console.log('Listening on ' + server.urlPrefix().replace('http:', 'ws:') + '/' + server.wsGuid()); }); } -async function innerRunTestServer(configFile: string | undefined, options: { host?: string, port?: number }, openUI: (server: HttpServer, cancelPromise: ManualPromise) => Promise): Promise { +async function innerRunTestServer(configLocation: ConfigLocation, options: { host?: string, port?: number }, openUI: (server: HttpServer, cancelPromise: ManualPromise, configLocation: ConfigLocation) => Promise): Promise { if (restartWithExperimentalTsEsm(undefined, true)) return 'restarted'; - const testServer = new TestServer(configFile); + const testServer = new TestServer(configLocation); const cancelPromise = new ManualPromise(); const sigintWatcher = new SigIntWatcher(); process.stdin.on('close', () => gracefullyProcessExitDoNotHang(0)); void sigintWatcher.promise().then(() => cancelPromise.resolve()); try { const server = await testServer.start(options); - await openUI(server, cancelPromise); + await openUI(server, cancelPromise, configLocation); await cancelPromise; } finally { await testServer.stop();