End-to-end browser tests
The E2E suite uses Python Playwright, Chromium, pytest-django, and Django's
transactional live_server. It is intentionally separate from fast unit and view tests.
Structure
tests/e2e/
├── config.py # environment-driven browser settings
├── conftest.py # browser lifecycle, isolated contexts, failure artifacts
├── pages/ # reusable Page Objects and stable UI actions
│ ├── base.py
│ ├── auth.py
│ └── tickets.py
├── test_chat_coach.py # user-visible workflow examples
└── test_pwa_versioning.py
Keep business setup in the test/database layer and browser interactions in Page Objects. Tests should describe user outcomes, not repeat selectors or login steps.
Installation
.\.venv\Scripts\python.exe -m pip install -r requirements-dev.txt
.\.venv\Scripts\python.exe -m playwright install chromium
Running tests
# All browser tests
.\.venv\Scripts\python.exe -m pytest tests\e2e -m e2e -q
# One scenario
.\.venv\Scripts\python.exe -m pytest tests\e2e\test_chat_coach.py::test_customer_sees_chat_guidance_after_two_rapid_messages -q
# Only service-worker/PWA scenarios
.\.venv\Scripts\python.exe -m pytest tests\e2e -m pwa -q
# Visible browser with slowed interactions for debugging
$env:E2E_HEADLESS="false"
$env:E2E_SLOW_MO="250"
.\.venv\Scripts\python.exe -m pytest tests\e2e -m e2e -s
Supported settings are E2E_HEADLESS, E2E_SLOW_MO, E2E_ACTION_TIMEOUT_MS,
E2E_NAVIGATION_TIMEOUT_MS, E2E_VIEWPORT_WIDTH, E2E_VIEWPORT_HEIGHT, and
E2E_ARTIFACT_DIR.
QA / staging / live smoke checks
The destructive workflow suite always uses Django's isolated live_server. A separate,
read-only smoke layer can target a deployed environment without modifying real tickets:
$env:E2E_BASE_URL="https://qa.example.com"
$env:E2E_ENVIRONMENT="qa"
$env:E2E_CUSTOMER_MOBILE="0912..."
$env:E2E_CUSTOMER_PASSWORD="..."
$env:E2E_STAFF_USERNAME="qa-staff"
$env:E2E_STAFF_PASSWORD="..."
.\.venv\Scripts\python.exe -m pytest tests\e2e\test_external_environment_smoke.py -m external_e2e -q
BASE_URL is accepted as an alias for E2E_BASE_URL. Missing URLs or credentials are
reported as skipped tests, never as a successful external run. Use only controlled accounts.
Failure diagnostics
Every test receives a fresh Chromium process and BrowserContext. This keeps Django's synchronous
database lifecycle isolated from Playwright's event loop and prevents cross-test browser state.
On failure, the fixture automatically saves these files under test-results/e2e/<test-name>/:
failure.png: full-page screenshot at failure time;trace.zip: DOM snapshots, screenshots, network timing and browser actions.
Open a trace with:
.\.venv\Scripts\python.exe -m playwright show-trace test-results\e2e\<test-name>\trace.zip
JavaScript page errors also fail the test and produce the same artifacts. Ordinary UI tests
block service workers. A PWA test must use browser_page(service_workers="allow") and the
pwa marker.
Adding a scenario
- Mark the module with
e2eanddjango_db(transaction=True). - Create only the minimum database state required by the scenario.
- Open an isolated page using
with browser_page() as page:. - Use an existing Page Object; add reusable selectors/actions there if needed.
- Assert a user-visible outcome with Playwright's auto-waiting
expectAPI. - Avoid
sleep; wait on URLs, elements, responses, or application state.
Minimal pattern:
pytestmark = [pytest.mark.e2e, pytest.mark.django_db(transaction=True)]
def test_example(live_server, browser_page, customer):
with browser_page() as page:
AuthenticationPage(page, live_server.url).login_customer(customer, PASSWORD)
tickets = TicketConversationPage(page, live_server.url)
tickets.goto("/tickets/")
expect(page.get_by_role("heading", name="درخواستهای من")).to_be_visible()
The fixture prefers Playwright's bundled Chromium and falls back to installed Google Chrome.