Point at a running app
Importplaywright from @e2edev/playwright and put this in the exported
config object:
Config fragment
app.open('/path') and every relative navigation resolve against url. The
runner reads no APP_URL of its own; the scaffolded config reads it so you
can override the address per run. A missing scheme becomes https://, or
http:// for a loopback host.
Let the runner start it
Addcommand and the runner spawns the dev server, waits until url
answers, and stops it when the run ends, fails, or is interrupted:
e2e.config.ts
reuseExisting: trueattaches to a dev server you already have running instead of failing withAPP_ALREADY_RUNNING. CI ignores it, so a leftover server on a shared runner is still an error there.logkeeps what the server printed. Without it the runner discards the output, and a dev server that crashes on boot leaves nothing behind butAPP_UNREACHABLE. The file is unredacted; keep it in an ignored directory. Withlogset, a startup failure (an exit before the URL answered, orstartupTimeoutspent) ends itsAPP_UNREACHABLEmessage with the last 20 lines appended to the log since the command started, eachcommand.envvalue replaced by its<secret:NAME>marker, so the report alone shows what the process was doing. A wait that passes half ofstartupTimeoutprints one notice,target "web" command still starting after 30s: waiting for http://localhost:3000/; log: .e2e/logs/app.log, once that half is at least 5 s.envpasses what the app needs. The child inherits onlyPATH,HOME, the temp-directory variables, and on WindowsSystemRootandCOMSPEC. Model keys and CI tokens are not inherited automatically. Values you put incommand.envare forwarded to the child, including any keys or tokens.
executable is resolved on PATH
and args are passed verbatim. startupTimeout (default 60 s) bounds the
wait for url; set readyUrl when the health endpoint is not the base URL.
Every option is in the config reference.
Start dependencies first
A database, a migration, an auth emulator: declare them asservices. They
start in order before the app command, each ready before the next begins,
and stop in reverse when the run ends.
Target fragment
readyUrl answers, or, with waitForExit, when
the process exits 0. Each one takes the same log, env, and cwd options
as command. The list reporter shows each service starting with a ticking
clock and splits the startup time out of the run’s duration. A service still
not ready at half its startupTimeout prints one notice (service "postgres" still starting after 90s: waiting for it to exit), provided that half is at
least 5 s; a service that exits non-zero or runs out of budget ends its
APP_UNREACHABLE with the lines appended to its log since it started, so a
docker compose up --wait that hangs on Image postgres Pulling says so in
the report. Services that share one log file also share that tail: an
earlier readyUrl service still running can add lines to it.
Several targets
Two browsers on one app are two targets that each name it:e2e.config.ts
command share one process. Every test runs
on every target unless it is scoped with platforms or requires;
--target <name> selects at run time. A device target sits beside a browser
target in the same list; see the agent-device reference.
Protected previews
The app under test is often not public: a Vercel preview behind deployment protection, a tunnel with an interstitial page, a staging host behind HTTP basic authentication. The engine declares how to get through, and every path onto the page goes through with it.Target fragment
headers go on every request the browser sends to an allowed origin and on
no request to any other, so a bypass secret never reaches a CDN or an
identity provider the page also talks to. The same option skips ngrok’s
interstitial with 'ngrok-skip-browser-warning': '1'.
A preview origin changes with every deploy. identity keeps cache and
session entries keyed by what the app is, so they survive a redeploy.
A host behind HTTP basic authentication takes basicAuth. The browser
answers a 401 from an allowed origin with the credentials and a challenge
from any other origin with nothing.
Target fragment
What
headers costsInjecting headers routes every request through the runner, which turns the
browser’s HTTP cache off and blocks service workers for that target. A
Playwright trace records request headers, so the trace of a protected preview
carries the bypass secret. Share it as you would the secret.Config reference
command, services, and every app option.Playwright reference
Browser, viewport,
connect, headers, basicAuth.