Skip to content

Module 11: Capstone — a full API + UI suite

This is where it all comes together. You’ll assemble a small but complete test suite against TestMarket Lab that uses everything from Modules 0–10: shared fixtures, API happy-path and negative tests, a Page-Object UI flow, data-driven cases, an HTML report, and a CI workflow. Treat this as the project you’d show an interviewer.

Keep TestMarket Lab running at http://localhost:3000, your .venv active, and pytest-playwright + pytest-html installed.

🎬 Video coming soon

Module 11: the capstone suite at a glance — slide guide Slides — opens in a new tab

Build a suite that proves TestMarket Lab works, end to end:

  • API happy path — products list/read, an order with the right total (Modules 3, 5)
  • API negative400/401/404/409 on bad input (Module 4)
  • UI flow — log in through the browser with a page object (Modules 6, 7)
  • Data-driven — a login matrix from a file (Module 8)
  • Structure & isolation — fixtures, reset_db, per-area folders (Modules 2, 5, 9)
  • Reporting & CI — an HTML report, green on every push (Modules 9, 10)

Remember: a professional suite is not one clever test — it’s coverage across layers (API happy + negative, UI) with structure (fixtures, page objects, folders) and automation (report + CI). The capstone is that shape in miniature.


Everything you’ve built, in one layout:

python-sdet/
├── .github/workflows/tests.yml # CI (Module 10)
├── pages/
│ ├── __init__.py
│ └── login_page.py # POM (Module 7)
├── tests/
│ ├── conftest.py # base_url / api / reset_db (Modules 2, 5)
│ ├── data/
│ │ └── login_cases.json # data-driven cases (Module 8)
│ ├── api/
│ │ ├── test_products.py # happy path (Module 3)
│ │ ├── test_orders.py # arrange-via-API (Module 5)
│ │ ├── test_negative.py # 4xx (Module 4)
│ │ └── test_login_matrix.py # data-driven (Module 8)
│ └── ui/
│ └── test_login.py # Playwright + POM (Modules 6, 7)
├── pytest.ini # markers + pythonpath (Modules 2, 7, 9)
└── requirements.txt # pinned deps (Modules 0, 6, 9)

Remember: the layout is the design — shared setup in conftest.py, selectors in pages/, data in data/, tests grouped by area, and config/CI at the root. A reviewer can read the tree and know what the suite does.


The fixtures every test leans on (from Modules 2 and 5):

tests/conftest.py
import pytest
import requests
@pytest.fixture(scope="session")
def base_url():
return "http://localhost:3000"
@pytest.fixture
def api(base_url):
session = requests.Session()
yield session
session.close()
@pytest.fixture
def reset_db(base_url):
requests.post(f"{base_url}/api/reset")
yield

A happy-path order test (arrange via API, assert the computed total) and a negative test (bad login → 401):

tests/api/test_orders.py
import pytest
@pytest.mark.api
@pytest.mark.smoke
def test_order_total(reset_db, base_url, api):
r = api.post(
f"{base_url}/api/orders",
json={"email": "[email protected]", "items": [{"product_id": 1, "quantity": 2}]},
)
assert r.status_code == 201
assert r.json()["total"] == pytest.approx(29.99 * 2)
tests/api/test_negative.py
import pytest
@pytest.mark.api
def test_login_rejects_bad_password(base_url, api):
r = api.post(
f"{base_url}/api/auth/login",
json={"email": "[email protected]", "password": "wrong"},
)
assert r.status_code == 401

Remember: cover both directions — the happy path and the failure. A suite that only proves success misses where real bugs live.


The login flow, driven through LoginPage (Module 7):

pages/login_page.py
class LoginPage:
def __init__(self, page, base_url):
self.page = page
self.base_url = base_url
self.email = page.get_by_label("Email")
self.password = page.get_by_label("Password")
self.submit = page.get_by_test_id("login-submit")
def goto(self):
self.page.goto(f"{self.base_url}/auth/login")
return self
def login(self, email, password):
self.email.fill(email)
self.password.fill(password)
self.submit.click()
tests/ui/test_login.py
import pytest
from playwright.sync_api import expect
from pages.login_page import LoginPage
@pytest.mark.ui
def test_login_flow(page, base_url):
LoginPage(page, base_url).goto().login("[email protected]", "customer123")
expect(page.get_by_test_id("nav-logout")).to_be_visible()

Remember: the UI test is a scenario — the page object hides the selectors, and the test reads as “log in, then I’m signed in.” That’s the payoff of the POM structure.


The login validation matrix from a JSON file (Module 8) — tests/data/login_cases.json:

[
{ "email": "[email protected]", "password": "customer123", "expected": 200 },
{ "email": "[email protected]", "password": "wrong", "expected": 401 },
{ "email": "[email protected]", "password": "whatever", "expected": 401 },
{ "email": "[email protected]", "password": "", "expected": 400 }
]
tests/api/test_login_matrix.py
import json
from pathlib import Path
import pytest
CASES = json.loads((Path(__file__).parent.parent / "data" / "login_cases.json").read_text())
@pytest.mark.api
@pytest.mark.parametrize("case", CASES, ids=[f"{c['email']}->{c['expected']}" for c in CASES])
def test_login_matrix(case, base_url, api):
r = api.post(
f"{base_url}/api/auth/login",
json={"email": case["email"], "password": case["password"]},
)
assert r.status_code == case["expected"]

Note the path: this test lives in tests/api/, so it reaches up two levels (.parent.parent) to tests/data/.

Remember: one JSON file drives many cases; the path is anchored with Path(__file__).parent so it works from any directory — mind how many levels up data/ is from the test.


The config that ties the suite together:

pytest.ini
[pytest]
testpaths = tests
pythonpath = .
markers =
smoke: a fast, critical subset to run first
api: tests that hit the REST API
ui: tests that drive the browser
# requirements.txt (pin everything you've installed)
pytest
requests
pytest-playwright
pytest-html

Regenerate requirements.txt any time with pip freeze > requirements.txt, and reuse the CI workflow from Module 10 unchanged — it already installs these, starts the app, and runs the suite.

Remember: pytest.ini (markers + pythonpath), a complete requirements.txt, and the CI workflow are the connective tissue. Get them right once and every new test just drops into place.


Terminal window
pytest -m api # just the API layer
pytest -m ui # just the browser layer
pytest -ra --html=report.html --self-contained-html # everything, with a report

Push it to GitHub with the Module 10 workflow and the whole thing runs on every PR, report and all. That’s a suite you can hand to a team — or a hiring manager.

Remember: run a slice by marker while developing (-m api, -m ui), the whole suite with a report before you push, and let CI run it on every change. Fast feedback locally, full coverage in CI.


Assemble the capstone in your python-sdet project. Run with pytest -v.

Exercise 1 — Build the structure (20 min)

Section titled “Exercise 1 — Build the structure (20 min)”

Set up the folder layout above (pages/, tests/api/, tests/ui/, tests/data/, pytest.ini, requirements.txt). Move your existing tests into tests/api/ and tests/ui/. Confirm pytest still collects and passes everything.

Make sure you have at least: one API happy-path test, one API negative test, one data-driven matrix, and one UI flow — each with the right marker (api/ui, and smoke on the fastest).

Run pytest -m smoke --html=report.html --self-contained-html and open the report. Then run pytest -m "api and not ui" and confirm only API tests run.

Add the Module 10 workflow, push to GitHub, and confirm the suite runs green in Actions with the report uploaded as an artifact. Make it a required check.

Add a product create → read → update → delete UI or API lifecycle test, and a negative UI test (wrong password shows flash-error). Then point the whole suite at your own app by swapping base_url and the selectors — the structure carries over unchanged.


From pip install pytest to a CI-gated API + UI suite, you now have the toolkit real SDET roles use every day: pytest, requests, Playwright, fixtures, the Page Object Model, data-driven tests, reporting, and CI — all exercised against a real application. Take this capstone, extend it, and point it at your own projects. That suite is your portfolio piece.

Want one more senior skill on top? Module 12 — Database verification shows you how to prove an action persisted correctly by reading the database directly — the “but did it actually save?” check that most suites, and most courses, skip.