Skip to main content
All failure patterns
Environment or infrastructurePlaywright · CI

Playwright webServer never became ready: Error: Timed out waiting 60000ms from config.webServer.

Playwright ran the webServer command, then polled the configured url or port until the timeout without getting an answer it counts as ready. It stops there, before running a single test.

MINIMAL REPRO

Reproduce it

Run on 2026-10-23 with @playwright/test 1.62.1, Python 3.14.5 (http.server as the app), macOS, arm64. The output below is what it printed.

playwright.config.ts
import { defineConfig } from "@playwright/test"

export default defineConfig({
  testDir: "./tests",
  use: { baseURL: "http://127.0.0.1:4790" },
  webServer: {
    // Listens on 4790, but readiness is checked on 4791.
    command: "python3 -m http.server 4790 --bind 127.0.0.1",
    url: "http://127.0.0.1:4791",
    timeout: 5_000,
  },
})
command
DEBUG=pw:webserver npx playwright test
output
pw:webserver HTTP GET: http://127.0.0.1:4791/
pw:webserver Error while checking if http://127.0.0.1:4791/ is available: connect ECONNREFUSED 127.0.0.1:4791
pw:webserver Waiting 1000ms
pw:webserver Terminating the WebServer
pw:webserver Terminated the WebServer
Error: Timed out waiting 5000ms from config.webServer.

# Timestamps and the earlier polls removed. Without DEBUG, the run prints only the Error line.

RANKED DIAGNOSIS

What usually causes it

  1. 01

    The url or port is not where the server listens

    Readiness is checked only on the configured url (or port). If the app binds 4790 and the config waits on 4791, or the app reads PORT from an env var CI sets differently, Playwright polls an empty port until the timeout. That is the repro above.

    DIAGNOSE

    Rerun with `DEBUG=pw:webserver`. Each poll is logged as HTTP GET: <url> followed by the result, so you can see connect ECONNREFUSED on the port it is actually checking.

    FIX

    Derive the listen port, `webServer.url` and `use.baseURL` from one value.

  2. 02

    The readiness URL answers, but with 404 or a 5xx

    Playwright counts 2xx, 3xx, 400, 401, 402 and 403 as ready. A 404, or a 500 from an app missing its CI environment, is treated as not up yet. In 1.62.1 a 404 on / is retried once as /index.html and otherwise ignored. We pointed url at /healthz on a server with no such path: the debug log showed HTTP Status: 404 on every poll, then the same timeout.

    DIAGNOSE

    Read the HTTP Status lines in the pw:webserver log, or curl the url yourself from inside the job while the server is starting.

    FIX

    Point url at a route that exists and returns 200 without auth, and give the app the environment it needs to render that route in CI.

  3. 03

    The server needs longer than the timeout to start

    The default timeout is 60000 ms. A dev server that compiles on first request, or a cold container pulling dependencies, can take longer on a shared CI runner than it does locally.

    DIAGNOSE

    Time the command by hand in the CI image, and check whether the pw:webserver log shows connection errors (still booting) or HTTP statuses (up but not ready).

    FIX

    Build once and run the production server in CI (for example `next build` in an earlier step, `next start` as the command), and raise `timeout` only to the measured start time plus a margin.

VERIFIED FIX

The fix, and how it was checked

Make the readiness url the address the server actually serves, on a path that returns 200.

playwright.config.ts (webServer)
webServer: {
  command: "python3 -m http.server 4790 --bind 127.0.0.1",
  url: "http://127.0.0.1:4790",
  timeout: 5_000,
},

Re-run on 2026-10-23 (@playwright/test 1.62.1, Python 3.14.5): with url moved to port 4790, where http.server listens, a one-test spec printed 1 passed (1.2s). Pointing url at /healthz on the same server logged HTTP Status: 404 on all seven polls before the 5000ms timeout, which matches isURLAvailable in 1.62.1's playwright-core, where only a status from 200 to 403 counts as ready.

When this is not the cause

The command crashed
If the command exits before it is ready, Playwright does not wait out the timeout. It fails at once with Process from config.webServer was not able to start. Exit code: N (we saw Exit code: 3 from a command that exits with 3). Read the server's own output for that one.
Something else already holds the port
That is a different message: <url> is already used, make sure that nothing is running on the port/url or set reuseExistingServer:true in config.webServer. An http.server we left running on 4790 before the test run produced exactly that line.
localhost resolving to IPv6
Often blamed, but 1.62.1's port check tries both 127.0.0.1 and ::1, and in our test a url of http://localhost:4790 became ready against a server bound only to ::1. Check the port and the status code first.

Prevent the repeat

  • →Keep one source for the app port, webServer.url and baseURL.
  • →Give the app a cheap health route that returns 200 without auth and use it as webServer.url.
  • →Run with DEBUG=pw:webserver on CI retries so the poll results are in the log.

Primary references

Run this against your evidence

The browser triage workflow uses the same stable pattern slug and does not upload your paste.

Open local triage

Related failure patterns