It Only Fails in CI
The worst failures happen on a machine you can't watch. Shrink the difference between your laptop and the runner — and make the pipeline hand you evidence, not theories.
The usual suspects
- Headless rendering — animations, focus and fonts differ; "visible" headed may not be headless.
- Viewport — the runner's window is smaller; a button below the fold gets its click intercepted.
- Timezone and locale — the runner runs UTC; your date and currency assertions don't.
- Docker's /dev/shm — too small by default; Chrome crashes with no useful message.
- Network speed — the office network hid the wait you never wrote.
- Parallel workers — two jobs share one test account and ruin each other's data.
Reproduce the delta, not the mystery
▸ Make local look like the runner
# same headless mode, same viewport as CI — suddenly it fails here too
pytest --headed=false \
--browser chromium tests/ \
-o addopts="" --tb=short
# better still: run the CI image itself
docker run --rm -it -v $PWD:/work -w /work \
mcr.microsoft.com/playwright/python:v1.44.0 pytest tests/Make the pipeline hand you evidence
A trace is a time machine — DOM, network, console and screenshots at every step, recorded on the runner, replayed on your laptop:
▸ pytest-playwright: keep traces for failures
pytest --tracing retain-on-failure --video retain-on-failure tests/
# CI: upload test-results/ as a build artifact.
# Open a trace locally with: playwright show-trace trace.zipGrounded in the pytest-playwright tracing options and common CI runner behaviour