chore: allow web server w/o waiting (#24609)

This commit is contained in:
Pavel Feldman 2023-08-04 12:05:16 -07:00 committed by GitHub
parent b3ce913551
commit 8fde110c61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 25 additions and 23 deletions

View file

@ -584,8 +584,8 @@ export default defineConfig({
* since: v1.10 * since: v1.10
- type: ?<[Object]|[Array]<[Object]>> - type: ?<[Object]|[Array]<[Object]>>
- `command` <[string]> Shell command to start. For example `npm run start`.. - `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. - `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. 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. Either `port` or `url` should be specified.
- `ignoreHTTPSErrors` ?<[boolean]> Whether to ignore HTTPS errors when fetching the `url`. Defaults to `false`. - `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. - `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. - `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.

View file

@ -28,7 +28,7 @@ import type { ReporterV2 } from '../reporters/reporterV2';
export type WebServerPluginOptions = { export type WebServerPluginOptions = {
command: string; command: string;
url: string; url?: string;
ignoreHTTPSErrors?: boolean; ignoreHTTPSErrors?: boolean;
timeout?: number; timeout?: number;
reuseExistingServer?: boolean; reuseExistingServer?: boolean;
@ -45,7 +45,7 @@ const DEFAULT_ENVIRONMENT_VARIABLES = {
const debugWebServer = debug('pw:webserver'); const debugWebServer = debug('pw:webserver');
export class WebServerPlugin implements TestRunnerPlugin { export class WebServerPlugin implements TestRunnerPlugin {
private _isAvailable?: () => Promise<boolean>; private _isAvailableCallback?: () => Promise<boolean>;
private _killProcess?: () => Promise<void>; private _killProcess?: () => Promise<void>;
private _processExitedPromise!: Promise<any>; private _processExitedPromise!: Promise<any>;
private _options: WebServerPluginOptions; private _options: WebServerPluginOptions;
@ -60,7 +60,7 @@ export class WebServerPlugin implements TestRunnerPlugin {
public async setup(config: FullConfig, configDir: string, reporter: ReporterV2) { public async setup(config: FullConfig, configDir: string, reporter: ReporterV2) {
this._reporter = reporter; 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; this._options.cwd = this._options.cwd ? path.resolve(configDir, this._options.cwd) : configDir;
try { try {
await this._startProcess(); await this._startProcess();
@ -79,12 +79,12 @@ export class WebServerPlugin implements TestRunnerPlugin {
let processExitedReject = (error: Error) => { }; let processExitedReject = (error: Error) => { };
this._processExitedPromise = new Promise((_, reject) => processExitedReject = reject); this._processExitedPromise = new Promise((_, reject) => processExitedReject = reject);
const isAlreadyAvailable = await this._isAvailable!(); const isAlreadyAvailable = await this._isAvailableCallback?.();
if (isAlreadyAvailable) { if (isAlreadyAvailable) {
debugWebServer(`WebServer is already available`); debugWebServer(`WebServer is already available`);
if (this._options.reuseExistingServer) if (this._options.reuseExistingServer)
return; 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.`); 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() { private async _waitForProcess() {
if (!this._isAvailableCallback) {
this._processExitedPromise.catch(() => {});
return;
}
debugWebServer(`Waiting for availability...`); debugWebServer(`Waiting for availability...`);
await this._waitForAvailability();
debugWebServer(`WebServer available`);
}
private async _waitForAvailability() {
const launchTimeout = this._options.timeout || 60 * 1000; const launchTimeout = this._options.timeout || 60 * 1000;
const cancellationToken = { canceled: false }; const cancellationToken = { canceled: false };
const { timedOut } = (await Promise.race([ const { timedOut } = (await Promise.race([
raceAgainstDeadline(() => waitFor(this._isAvailable!, cancellationToken), monotonicTime() + launchTimeout), raceAgainstDeadline(() => waitFor(this._isAvailableCallback!, cancellationToken), monotonicTime() + launchTimeout),
this._processExitedPromise, this._processExitedPromise,
])); ]));
cancellationToken.canceled = true; cancellationToken.canceled = true;
if (timedOut) if (timedOut)
throw new Error(`Timed out waiting ${launchTimeout}ms from config.webServer.`); 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 shouldSetBaseUrl = !!config.config.webServer;
const webServerPlugins = []; const webServerPlugins = [];
for (const webServerConfig of config.webServers) { for (const webServerConfig of config.webServers) {
if ((!webServerConfig.port && !webServerConfig.url) || (webServerConfig.port && webServerConfig.url)) if (webServerConfig.port && webServerConfig.url)
throw new Error(`Exactly one of 'port' or 'url' is required in config.webServer.`); throw new Error(`Either 'port' or 'url' should be specified in config.webServer.`);
const url = webServerConfig.url || `http://localhost:${webServerConfig.port}`; let url: string | undefined;
if (webServerConfig.port || webServerConfig.url) {
// We only set base url when only the port is given. That's a legacy mode we have regrets about. url = webServerConfig.url || `http://localhost:${webServerConfig.port}`;
if (shouldSetBaseUrl && !webServerConfig.url)
process.env.PLAYWRIGHT_TEST_BASE_URL = url;
// 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)); webServerPlugins.push(new WebServerPlugin({ ...webServerConfig, url }, webServerConfig.port !== undefined));
} }

View file

@ -6735,15 +6735,15 @@ interface TestConfigWebServer {
command: string; command: string;
/** /**
* The port that your http server is expected to appear on. It does wait until it accepts connections. Exactly one of * The port that your http server is expected to appear on. It does wait until it accepts connections. Either `port`
* `port` or `url` is required. * or `url` should be specified.
*/ */
port?: number; 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 * 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 * 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; url?: string;