Featured project 6 · Python · API + UI
Python pytest + Playwright Framework
API, UI and hybrid test automation in Python with pytest, Playwright, requests and pydantic: 46 tests of a notes app in about 1 m 45 s with 4 parallel workers, on Chromium, Firefox and WebKit in GitHub Actions.
Purpose
Shows a Python test stack for one application with a web UI and a REST API on the same backend: the public Expand Testing Notes app. The API carries most of the checks, the browser checks what a user sees, and hybrid tests check that the two sides agree.
Technology stack
| Area | Tools |
|---|---|
| Language | Python 3.12 |
| Test runner | pytest 9 (fixtures, markers, parametrize, hooks) |
| Browser automation | Playwright for Python 1.63 with pytest-playwright (Chromium, Firefox, WebKit) |
| API | requests 2.34 (one Session per client) |
| Contracts | pydantic 2 models with extra fields forbidden |
| Parallel runs and report | pytest-xdist, pytest-html (one self-contained file), Playwright traces and videos |
| Code quality and CI | ruff (lint + format), GitHub Actions: lint gate, API job, browser matrix, nightly run |
Architecture overview
tests/api, tests/ui, tests/e2e what is checked
│ fixtures (tests/conftest.py): throwaway user per test, logged-in client,
│ page with ads blocked, page already logged in, report hooks
│
framework/api NotesApiClient (requests) + pydantic models
framework/pages LoginPage, NotesPage (data-testid locators, no assertions)
framework/data unique users and notes (uuid)
│
framework/config URLs and timeouts from environment variablesKey engineering decisions
- Every test registers its own user through the API and deletes it in teardown, so tests are independent and safe with pytest-xdist on a shared public site
- UI tests log in by putting the API token into localStorage before the page loads; the login form itself is tested once
- The API client returns raw responses and never asserts, so the same client serves positive and negative tests
- pydantic models with extra="forbid": an added or renamed field fails a test at once instead of passing silently
- Page objects hold data-testid locators and actions; tests assert with Playwright's retrying expect, with no sleeps anywhere
- Demo tests that fail, skip and xfail on purpose carry their own marker and stay out of normal runs
Implemented features
- 32 API tests: registration and validation, login, the same 401 for a wrong password and an unknown user, token checks, profile, password change, logout, notes CRUD with PUT and PATCH, bad ids, one user cannot see another user's notes
- 9 UI tests: login form, adding, completing, deleting and searching notes
- 5 hybrid tests: data created through the API shown in the app, changes made in the app checked through the API
- HTML report with an embedded screenshot for every failed UI test; Playwright screenshot, video and trace kept for failures
- Framework design notes and 86 interview questions with answers linked to the code (Markdown + PDF)
Setup and execution
python -m venv .venv && .venv\Scripts\activate
pip install -r requirements.txt
playwright install chromium firefox webkit
pytest -n 4 # all 46 tests
pytest -m api # API only
pytest -m "ui or e2e" --browser firefox # browser tests on Firefox
pytest -m demo # report demo: 2 failed, 1 skipped, 1 xfailedSample test results


CI workflow
On every push, pull request and night: ruff lint and format check first, then the API tests with 4 workers and the UI + hybrid tests on Chromium, Firefox and WebKit as three parallel jobs. Each job writes a pass/fail table to the run summary and keeps its HTML report and traces as an artifact.
Known limitations and future improvements
Limitations
- Runs against a shared public demo site, whose availability affects results
- Visual and accessibility checks are not included yet
Next steps
- Accessibility checks with axe-core
- Publish the HTML report to GitHub Pages
