Project Goals
3 min- Write a fixture that runs your Flask app on a real port for the test.
- Reset to a fresh DB per run for deterministic E2E.
- Cover 2-3 golden user journeys with Playwright.
- Run headless in CI, headed when debugging.
Warm-Up · You Need a Live Server
5 minUnlike the test client (Lesson 36, in-process), E2E needs the app actually running at a URL so the browser can hit it. The professional move: a pytest fixture that starts the server in a background thread before the tests and stops it after.
A live-server fixture makes E2E self-contained: tests boot the app, drive it through the browser, then tear it down — no manual "start the server first" step. Combined with a fresh DB, the suite is reproducible and CI-friendly.
Plan · Live Server + Fresh DB
14 minThe live-server fixture
# conftest.py import threading, pytest from werkzeug.serving import make_server from myapp import create_app, init_db @pytest.fixture(scope="session") def live_server(): app = create_app(database="e2e_test.db") with app.app_context(): init_db() # fresh schema server = make_server("127.0.0.1", 5099, app) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() yield "http://127.0.0.1:5099" server.shutdown() # stop it after the session
Fresh data per test (reset between journeys)
@pytest.fixture def fresh_db(): from myapp import init_db init_db() # wipe + recreate before each E2E test → isolation yield
A journey test using the fixtures
def test_publish_journey(page, live_server, fresh_db): page.goto(live_server + "/new") page.get_by_label("Title").fill("E2E Post") page.get_by_label("Body").fill("Created by Playwright.") page.get_by_role("button", name="Publish").click() assert page.get_by_role("heading", name="E2E Post").is_visible()
Headless vs headed
pytest # headless (no window) — fast, for CI pytest --headed # shows the browser — for debugging pytest --headed --slowmo 500 # slow it down to watch each step pytest --tracing on # record a trace to debug failures later
Build · The Golden Journeys
12 min# test_journeys.py — 3 critical journeys, each isolated import pytest def test_home_loads(page, live_server, fresh_db): page.goto(live_server) assert page.get_by_text("No posts yet").is_visible() def test_publish_and_read(page, live_server, fresh_db): page.goto(live_server + "/new") page.get_by_label("Title").fill("First") page.get_by_label("Body").fill("Hello there, world!") page.get_by_role("button", name="Publish").click() # redirected to the post assert page.get_by_role("heading", name="First").is_visible() # and listed on home page.goto(live_server) assert page.get_by_text("First").is_visible() def test_validation_blocks_empty(page, live_server, fresh_db): page.goto(live_server + "/new") page.get_by_role("button", name="Publish").click() # empty form assert page.get_by_text("Title too short").is_visible() # should NOT have navigated away assert "/new" in page.url
$ pytest test_journeys.py -v test_home_loads PASSED test_publish_and_read PASSED test_validation_blocks_empty PASSED 3 passed in 6.1s ← a few real journeys, self-contained server + DB
Read the diff
Three golden journeys — empty home, the publish-and-read happy path, and a validation block — each starting from a fresh DB thanks to the fresh_db fixture. The live_server fixture boots and tears down the app automatically, so the whole suite runs with one pytest command, in CI, with no manual setup. That's a real, shippable E2E layer.
Build Yours
13 minAdd the live_server fixture to a Flask app of yours and write one E2E test that loads the home page through it.
Add the fresh_db fixture. Write two journeys that both create data; confirm they don't interfere (each starts clean).
E2E the sign-up → login → access-protected-page flow. Use user-facing selectors and a fresh DB.
Stretch · Debug a Flake
8 minIntroduce a deliberate flake (a test that depends on data from a previous test). Watch it fail intermittently when run in different orders (-p no:randomly vs random order). Fix it with the fresh_db fixture. Then use --tracing on to capture a trace of a failing run.
Recap
3 minA real E2E suite needs a live server (fixture that boots the app on a port) and a fresh DB per test for isolation. Cover 2-3 golden journeys with Playwright, using user-facing selectors. Run headless in CI, headed (with slowmo/tracing) to debug. Keep the count low — E2E is the slow tip of the pyramid. That completes the testing techniques; next, static analysis.
Homework
4 minBuild an E2E suite for a Flask app: a live_server + fresh_db fixture and 3 golden-journey tests (including one with login). Confirm a single pytest command runs them with no manual server start. Submit the conftest + tests and a note on which journeys you chose and why.
Use the conftest.py live-server + fresh-db fixtures and model journeys on test_journeys.py. Requires the app-factory pattern from Lesson 36.