What does Playwright's webServer config option do before the first test runs?
answer
- Config key that boots the app
- Waiting before the first test
- Two ways to prove readiness
- Different answer locally and in CI
basics
~10 sPlaywright's webServer option spawns a command before the suite and waits until the url or port it names answers, then runs the tests and stops the process it started when the run ends.
solid answer
~40 s`webServer` in `playwright.config.ts` names a `command` Playwright spawns before any test runs, plus either a `url` it polls until the response status is 2xx, 3xx, 400, 401, 402 or 403, or a `port` it waits to accept connections. Only when that readiness check passes does the runner start tests, so the internal admin console is guaranteed to be up. `reuseExistingServer: !process.env.CI` is the usual setting: locally it attaches to a dev server you already have running, while in CI it insists on starting its own. `cwd`, `env`, `stdout`, `stderr` and `gracefulShutdown` shape the child process, and `timeout` bounds the wait -- exceeding it fails the run before any test executes. Playwright kills the process it started at the end, and since 1.43 the key also accepts an array when a suite needs several services.
code
typescript · 12 linesimport { defineConfig } from '@playwright/test';
export default defineConfig({
use: { baseURL: 'http://localhost:4173' },
webServer: {
command: 'pnpm run start:admin-console',
url: 'http://localhost:4173/health',
timeout: 120_000,
reuseExistingServer: !process.env.CI,
stdout: 'pipe',
},
});go deeper
Know that this config key starts the app for you and waits for it, and be able to point at the command plus url or port pair in a config you are shown.
Explain the readiness check itself: which statuses count as up, how port differs from url, what the timeout bounds, and why the process is killed when the run finishes.
Show the CI judgment: reuse locally but never in CI, pipe server logs so a startup failure is diagnosable, and know that each shard starts its own server.
Own the choice of whether the suite starts the application at all, versus testing a deployed environment, and be clear about what each option costs in reproducibility.
## What the option actually does Playwright's `webServer` block in `playwright.config.ts` hands the lifecycle of the application under test to the test runner. You give it a `command` -- the same shell line you would type by hand to start the internal admin console -- and Playwright spawns that command as a child process before it starts executing tests. It then blocks, polling for a readiness signal, and only once that signal arrives does the first test file run. When the run ends, Playwright terminates the process it started. No test code has to know that the server was started for it; the suite simply finds the app already listening. ## Two ways to say "ready" Exactly one of `url` or `port` is required, and the choice decides how Playwright probes for readiness. | Option | Readiness check | When to reach for it | |---|---|---| | `url` | Requests that URL until the response status is 2xx, 3xx, 400, 401, 402 or 403 | An HTTP app, where a health route -- or even a redirect to sign-in -- proves the process is answering | | `port` | Waits until that port on `localhost` accepts a TCP connection | A service with no convenient HTTP route to poll | The accepted status list matters in practice. A `401` from the admin console's protected root still means "the server is answering", so you do not have to add an unauthenticated health endpoint just to satisfy the runner. A `500`, by contrast, does **not** count as ready: Playwright keeps waiting until `webServer.timeout` expires and then fails the whole run before a single test has executed. ## The rest of the options - `command` -- required; the shell command Playwright spawns. - `cwd` -- the working directory for that command; resolved relative to the config file by default. - `env` -- extra environment variables handed to the child process only. - `timeout` -- how long to wait for the readiness signal before failing the run. - `reuseExistingServer` -- when `true` and something already answers at the `url` or `port`, Playwright uses it instead of spawning a second one. - `stdout` and `stderr` -- `'pipe'` to surface the server's logs in the run output, `'ignore'` to silence them. - `ignoreHTTPSErrors` -- for a dev server presenting a self-signed certificate. - `gracefulShutdown` -- for example `{ signal: 'SIGTERM', timeout: 500 }`, to ask the process to stop before it is killed. ## Local versus CI 1. On a developer machine the admin console is usually already running under a dev server, and restarting it for every run would be slow and would throw away hot reload. 2. On CI nothing is running, and silently attaching to a stray process would make the run test something other than the commit under build. 3. `reuseExistingServer: !process.env.CI` encodes both rules in one line: attach locally, insist on a fresh process in CI, and fail loudly there if the port is already taken. ## Where it sits in the run order - Web server processes are started before `globalSetup` runs and before any project's tests, including a setup project, so a precondition step can already talk to the app. - Since Playwright 1.43 the key also accepts an **array** of server definitions, which is how a suite boots the admin console and a stub for a downstream service together. - Every invocation of `playwright test` owns its own server, so a sharded CI matrix starts one server per shard rather than sharing a single instance. ## What it is not - It is not a per-test hook. One process serves the entire run and every worker in it, so it cannot reset application state between tests. - It is not a health model for production; the readiness probe only proves the process answers, not that background migrations or caches have finished. - It is not required. Teams that deploy the console to a shared environment before the suite runs simply omit `webServer` and point `use.baseURL` at that environment instead. ## Diagnosing a start-up failure When the run dies before any test, the message names the readiness check that never passed, and the useful next steps are mechanical: 1. Run the `command` by hand in the same directory and watch what it prints. 2. Set `stdout: 'pipe'` so the server's own log reaches the run output instead of being swallowed. 3. Confirm the probe target: a `url` on the wrong path answers `404`, which is not in the accepted list. 4. Raise `timeout` only after establishing that the server really is slow to boot rather than broken.
- The admin console's root returns 401 until you sign in. Can it still be used as the webServer url?Yes. Playwright treats 2xx, 3xx, 400, 401, 402 and 403 as proof the server is answering, so a protected root is a perfectly good readiness probe. Only a connection refusal or a 5xx keeps it waiting, so you do not need to add an unauthenticated health route just to satisfy the runner.
- Why would reuseExistingServer be wrong to leave true on CI?On CI it would let the run silently attach to whatever already occupies the port -- a leftover process from an earlier job, or a different build. The suite would then test something other than the commit under build and pass for the wrong reason. Setting it to false makes a busy port a loud failure instead.
saying these in an interview costs you the question
- Thinks tests start immediately and race the server's startup
- Says reuseExistingServer should stay true in CI
- Believes the server must be started by hand in globalSetup
- Assumes Playwright leaves the spawned server running afterwards
- Thinks a url answering 500 counts as ready