E2E Tests¶
End-to-end tests (E2E) verify complete user workflows in a real browser — from the click through frontend and backend down to the database. They sit at the top of the test pyramid: the least numerous, the slowest, but the most realistic. E2E tests use Selenium WebDriver with the page-object pattern and produce Markdown test protocols with screenshots (internal reference: NFR-008).
What this level verifies¶
- End-to-end flows: e.g. login → create record → save → find again, across all layers.
- Real interplay: real browser, real frontend (nginx), real backend, real database.
Tested areas at a glance¶
E2E suites are organized by requirement (REQ). Grouped thematically:
| Area | Example workflows | Extent |
|---|---|---|
| Master data & lifecycle | species/cultivars, phase control, planting run | extensive |
| Watering & feeding | nutrient solution, feeding, tank management | moderate |
| Tasks & care | task queue, care dashboard, reminders | moderate |
| Harvest & post-harvest | harvest readiness, harvest list, post-harvest | moderate |
| Plant protection | pests/diseases, photo recognition, diagnosis | moderate |
| Platform | login, tenants, privacy, light mode | extensive |
| Others | dashboard, calendar, onboarding, companion planting, notifications, AI assistant, print views | moderate |
Curated test-case specifications
Detailed, numbered test cases live as Markdown documents under spec/e2e-testcases/ (TC-REQ-*, TC-NFR-*) alongside COVERAGE-REPORT.md. Per requirement they describe preconditions, steps, and expected results.
Tooling & location¶
| Value | |
|---|---|
| Tooling | Selenium WebDriver, pytest, page-object pattern |
| Location | tests/e2e/ |
| Locator | data-testid attributes (never CSS structure) |
CI: smoke gate per PR + nightly full run
The suite also runs in GitHub Actions — using the same Docker Compose stack as local runs: the e2e-smoke workflow runs the fast smoke profile on every pull request and on pushes to develop. It was a required check on develop from ADR-011 until 2026-07-29 and is advisory since: it still runs and still reports on every pull request, but it no longer blocks the merge — read it before you merge. The rollback was driven by merge latency of roughly 11 minutes per run; the nightly full-matrix run now carries the coverage. Whether the suite actually runs is still decided by a job inside the workflow rather than by a path filter on the trigger: a required workflow skipped by path filtering never reports a result and blocks the pull request indefinitely — that construction stays in place so the check can be re-armed at any time. The e2e-nightly workflow runs the complete suite nightly as a matrix over the compose profiles light, full, mobile, tablet, and full-mobile; a failing run no longer opens a GitHub issue — the run status, the per-profile rendered check run, and the artifacts carry everything the automatic issue merely restated. Test protocol, screenshots, and container logs are attached to every run as workflow artifacts.
CI test reports¶
Every run writes a JUnit XML report (junit-<profile>.xml) alongside the protocol and screenshots, carrying each test's TC-ID as a tc_id property. In GitHub Actions, the e2e-smoke workflow (per pull request) and each profile in the e2e-nightly workflow additionally render this report via dorny/test-reporter as a GitHub check run and a job-summary table — with the concrete failure message (assertion text plus a short traceback) per failed test, instead of just a green/red overall status.
Fork pull requests: no rendered check
On pull requests from forks, the render step cannot create a check run with the restricted GITHUB_TOKEN and is skipped (continue-on-error). e2e-smoke detects this and then writes the result overview plus the failed tests from the test protocol into the job summary; otherwise the job summary is only a pointer at the artifact, so the same numbers do not appear twice. The junit-*.xml artifact is available either way.
The rendered check run is a CI convenience — it does not replace the Markdown test protocol (protokoll.md) with its embedded screenshots, which remains the human-readable audit trail (NFR-008 §4.4).
Running¶
# Local against a running app (Chrome headless, localhost:5173)
pytest tests/e2e/ -v
# Dedicated, isolated Docker stack (app + Selenium Grid)
./scripts/run-e2e.sh # full light suite
./scripts/run-e2e.sh --smoke # smoke suite (~7 min)
./scripts/run-e2e.sh --profile mobile # a single compose profile
Reports and screenshots land under test-reports/e2e/<timestamp>/, including the JUnit XML report — see CI test reports. The full stack, fixtures, and protocol format are in the testing concept → E2E Tests.
Test-case traceability (mandatory gate)¶
Every E2E test must name the specified test case it verifies — as a @TC-<id> tag on the Gherkin scenario, or as a TC-ID in the classic test's docstring. scripts/check_bdd_traceability.py checks this mechanically and runs as a pre-commit hook inside the required static job:
task test:e2e:traceability # exit 0 / 1, no stack needed
task test:e2e:traceability -- --list-unimplemented # + cases with no test yet
Four defect classes, two per channel:
| Channel | Defect |
|---|---|
| Gherkin | a tag no test-case document declares |
| Gherkin | a scenario with no tag |
| docstring | a TC-ID no test-case document declares |
| docstring | a test function with no TC-ID |
The reverse direction is not a defect: a specified test case without a test is simply not automated yet and is only counted.
The did you mean …? hint is not a match
When the check finds an unresolvable ID it suggests the closest known one. That is string proximity, not a semantic match. TC-REQ-027-001 → TC-027-001 looks like a typo but describes different behaviour. #839 checked three such suggestions against the spec text — all three were wrong.
If no test case describes the behaviour under test, the honest outcome is a new test case in spec/e2e-testcases/, not a forced mapping. A wrong mapping is worse than none: it claims coverage that does not exist. In #839, 66 of 99 cases needed exactly that.
Conventions¶
- Every page object inherits from
BasePageand encapsulates exactly one screen. - Address elements exclusively via
data-testidlocators — never via brittle CSS paths. - Take screenshots explicitly at meaningful checkpoints; on failure they are captured automatically.
- Every new test names its TC-ID — see Test-case traceability.