diff --git a/docs/src/test-api/class-testconfig.md b/docs/src/test-api/class-testconfig.md index 69c10560b4..404c987a19 100644 --- a/docs/src/test-api/class-testconfig.md +++ b/docs/src/test-api/class-testconfig.md @@ -584,8 +584,8 @@ export default defineConfig({ * since: v1.10 - type: ?<[Object]|[Array]<[Object]>> - `command` <[string]> Shell command to start. For example `npm run start`.. - - `port` ?<[int]> The port that your http server is expected to appear on. It does wait until it accepts connections. Exactly one of `port` or `url` is required. - - `url` ?<[string]> The url on your http server that is expected to return a 2xx, 3xx, 400, 401, 402, or 403 status code when the server is ready to accept connections. Redirects (3xx status codes) are being followed and the new location is checked. Exactly one of `port` or `url` is required. + - `port` ?<[int]> The port that your http server is expected to appear on. It does wait until it accepts connections. Either `port` or `url` should be specified. + - `url` ?<[string]> The url on your http server that is expected to return a 2xx, 3xx, 400, 401, 402, or 403 status code when the server is ready to accept connections. Redirects (3xx status codes) are being followed and the new location is checked. Either `port` or `url` should be specified. - `ignoreHTTPSErrors` ?<[boolean]> Whether to ignore HTTPS errors when fetching the `url`. Defaults to `false`. - `timeout` ?<[int]> How long to wait for the process to start up and be available in milliseconds. Defaults to 60000. - `reuseExistingServer` ?<[boolean]> If true, it will re-use an existing server on the `port` or `url` when available. If no server is running on that `port` or `url`, it will run the command to start a new server. If `false`, it will throw if an existing process is listening on the `port` or `url`. This should be commonly set to `!process.env.CI` to allow the local dev server when running tests locally. diff --git a/packages/playwright-test/src/plugins/webServerPlugin.ts b/packages/playwright-test/src/plugins/webServerPlugin.ts index 5443ff9736..6a518d9f98 100644 --- a/packages/playwright-test/src/plugins/webServerPlugin.ts +++ b/packages/playwright-test/src/plugins/webServerPlugin.ts @@ -28,7 +28,7 @@ import type { ReporterV2 } from '../reporters/reporterV2'; export type WebServerPluginOptions = { command: string; - url: string; + url?: string; ignoreHTTPSErrors?: boolean; timeout?: number; reuseExistingServer?: boolean; @@ -45,7 +45,7 @@ const DEFAULT_ENVIRONMENT_VARIABLES = { const debugWebServer = debug('pw:webserver'); export class WebServerPlugin implements TestRunnerPlugin { - private _isAvailable?: () => Promise; + private _isAvailableCallback?: () => Promise; private _killProcess?: () => Promise; private _processExitedPromise!: Promise; private _options: WebServerPluginOptions; @@ -60,7 +60,7 @@ export class WebServerPlugin implements TestRunnerPlugin { public async setup(config: FullConfig, configDir: string, reporter: ReporterV2) { this._reporter = reporter; - this._isAvailable = getIsAvailableFunction(this._options.url, this._checkPortOnly, !!this._options.ignoreHTTPSErrors, this._reporter.onStdErr?.bind(this._reporter)); + this._isAvailableCallback = this._options.url ? getIsAvailableFunction(this._options.url, this._checkPortOnly, !!this._options.ignoreHTTPSErrors, this._reporter.onStdErr?.bind(this._reporter)) : undefined; this._options.cwd = this._options.cwd ? path.resolve(configDir, this._options.cwd) : configDir; try { await this._startProcess(); @@ -79,12 +79,12 @@ export class WebServerPlugin implements TestRunnerPlugin { let processExitedReject = (error: Error) => { }; this._processExitedPromise = new Promise((_, reject) => processExitedReject = reject); - const isAlreadyAvailable = await this._isAvailable!(); + const isAlreadyAvailable = await this._isAvailableCallback?.(); if (isAlreadyAvailable) { debugWebServer(`WebServer is already available`); if (this._options.reuseExistingServer) return; - const port = new URL(this._options.url); + const port = new URL(this._options.url!).port; throw new Error(`${this._options.url ?? `http://localhost${port ? ':' + port : ''}`} is already used, make sure that nothing is running on the port/url or set reuseExistingServer:true in config.webServer.`); } @@ -121,21 +121,21 @@ export class WebServerPlugin implements TestRunnerPlugin { } private async _waitForProcess() { + if (!this._isAvailableCallback) { + this._processExitedPromise.catch(() => {}); + return; + } debugWebServer(`Waiting for availability...`); - await this._waitForAvailability(); - debugWebServer(`WebServer available`); - } - - private async _waitForAvailability() { const launchTimeout = this._options.timeout || 60 * 1000; const cancellationToken = { canceled: false }; const { timedOut } = (await Promise.race([ - raceAgainstDeadline(() => waitFor(this._isAvailable!, cancellationToken), monotonicTime() + launchTimeout), + raceAgainstDeadline(() => waitFor(this._isAvailableCallback!, cancellationToken), monotonicTime() + launchTimeout), this._processExitedPromise, ])); cancellationToken.canceled = true; if (timedOut) throw new Error(`Timed out waiting ${launchTimeout}ms from config.webServer.`); + debugWebServer(`WebServer available`); } } @@ -214,15 +214,17 @@ export const webServerPluginsForConfig = (config: FullConfigInternal): TestRunne const shouldSetBaseUrl = !!config.config.webServer; const webServerPlugins = []; for (const webServerConfig of config.webServers) { - if ((!webServerConfig.port && !webServerConfig.url) || (webServerConfig.port && webServerConfig.url)) - throw new Error(`Exactly one of 'port' or 'url' is required in config.webServer.`); + if (webServerConfig.port && webServerConfig.url) + throw new Error(`Either 'port' or 'url' should be specified in config.webServer.`); - const url = webServerConfig.url || `http://localhost:${webServerConfig.port}`; - - // We only set base url when only the port is given. That's a legacy mode we have regrets about. - if (shouldSetBaseUrl && !webServerConfig.url) - process.env.PLAYWRIGHT_TEST_BASE_URL = url; + let url: string | undefined; + if (webServerConfig.port || webServerConfig.url) { + url = webServerConfig.url || `http://localhost:${webServerConfig.port}`; + // We only set base url when only the port is given. That's a legacy mode we have regrets about. + if (shouldSetBaseUrl && !webServerConfig.url) + process.env.PLAYWRIGHT_TEST_BASE_URL = url; + } webServerPlugins.push(new WebServerPlugin({ ...webServerConfig, url }, webServerConfig.port !== undefined)); } diff --git a/packages/playwright-test/types/test.d.ts b/packages/playwright-test/types/test.d.ts index 5c24dd1c33..a46e570539 100644 --- a/packages/playwright-test/types/test.d.ts +++ b/packages/playwright-test/types/test.d.ts @@ -6735,15 +6735,15 @@ interface TestConfigWebServer { command: string; /** - * The port that your http server is expected to appear on. It does wait until it accepts connections. Exactly one of - * `port` or `url` is required. + * The port that your http server is expected to appear on. It does wait until it accepts connections. Either `port` + * or `url` should be specified. */ port?: number; /** * The url on your http server that is expected to return a 2xx, 3xx, 400, 401, 402, or 403 status code when the * server is ready to accept connections. Redirects (3xx status codes) are being followed and the new location is - * checked. Exactly one of `port` or `url` is required. + * checked. Either `port` or `url` should be specified. */ url?: string;