From 23c3a0d9e6aec6acd8b5f1e65c16a48539a17bd5 Mon Sep 17 00:00:00 2001 From: Lukas Date: Mon, 4 May 2026 08:29:12 +0200 Subject: [PATCH] feat: add e2e test suite with Robot Framework Add e2e/ dir with Robot Framework tests for page loading, ban records, blocklist import, config edit. Add requirements.txt. Update Makefile with test commands. Update .gitignore, backend docs, testing requirements docs. --- .gitignore | 3 +++ Docs/Backend-Development.md | 1 + Docs/Testing-Requirements.md | 25 ++++++++++++++++++++++- Makefile | 17 +++++++++++++++- e2e/requirements.txt | 2 ++ e2e/resources/auth.resource | 31 +++++++++++++++++++++++++++++ e2e/resources/common.resource | 25 +++++++++++++++++++++++ e2e/tests/01_page_loading.robot | 13 ++++++++++++ e2e/tests/02_ban_records.robot | 16 +++++++++++++++ e2e/tests/03_blocklist_import.robot | 15 ++++++++++++++ e2e/tests/04_config_edit.robot | 15 ++++++++++++++ 11 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 e2e/requirements.txt create mode 100644 e2e/resources/auth.resource create mode 100644 e2e/resources/common.resource create mode 100644 e2e/tests/01_page_loading.robot create mode 100644 e2e/tests/02_ban_records.robot create mode 100644 e2e/tests/03_blocklist_import.robot create mode 100644 e2e/tests/04_config_edit.robot diff --git a/.gitignore b/.gitignore index 6d90837..ead8ab5 100644 --- a/.gitignore +++ b/.gitignore @@ -112,3 +112,6 @@ Docker/fail2ban-dev-config/** *.tmp *.bak *.orig + +# ── E2E test results ─────────────────────────── +e2e/results/ diff --git a/Docs/Backend-Development.md b/Docs/Backend-Development.md index f765891..fc8185f 100644 --- a/Docs/Backend-Development.md +++ b/Docs/Backend-Development.md @@ -3010,6 +3010,7 @@ atomic_write(path, updated_content) # Atomic write, auto-cleanup on error - Every merge request must pass: ruff, type checker, all tests. - Do not merge with failing CI. - Keep pull requests small and focused — one feature or fix per PR. +- **E2E test results** (`e2e/results/`) are gitignored — never commit test outputs or HTML reports. --- diff --git a/Docs/Testing-Requirements.md b/Docs/Testing-Requirements.md index aa213f9..e95ebee 100644 --- a/Docs/Testing-Requirements.md +++ b/Docs/Testing-Requirements.md @@ -36,4 +36,27 @@ Requires codecov.io integration with repository. - Follow pattern: `test___` - Mock external dependencies (fail2ban socket, aiohttp calls) - Test happy path AND error/edge cases -- See `Docs/Backend-Development.md §9` for detailed testing guide \ No newline at end of file +- See `Docs/Backend-Development.md §9` for detailed testing guide + +## E2E Testing + +An end-to-end test suite using **Robot Framework** with the Browser library (Playwright-backed) exercises the full running stack: frontend → backend → fail2ban → database. + +### Running E2E Tests + +```bash +make e2e +``` + +Requires: +- `BANGUI_SESSION_SECRET` env var must be set (see [Backend-Development.md](Backend-Development.md) for setup) +- Stack must be startable via `make up` (Docker/Podman + compose installed) +- `rfbrowser init` is run automatically by the `e2e` target (Playwright browsers downloaded on first run; re-run after `robotframework-browser` version changes) + +### HTML Report + +After a run, open `e2e/results/report.html` in a browser to view the detailed HTML report with screenshots on failure. + +### Writing New E2E Tests + +Place new `.robot` files in `e2e/tests/`. Use `e2e/resources/common.resource` for shared variables and setup/teardown, and `e2e/resources/auth.resource` for the `Login As Admin` keyword. \ No newline at end of file diff --git a/Makefile b/Makefile index 69b0e80..bc5b0af 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,7 @@ # make logs — tail logs for all debug services # make restart — restart the debug stack # make dev-ban-test — one-command smoke test of the ban pipeline +# make e2e — run the Robot Framework E2E test suite # ────────────────────────────────────────────────────────────── COMPOSE_FILE := Docker/compose.debug.yml @@ -39,7 +40,7 @@ COMPOSE := $(shell command -v podman-compose 2>/dev/null \ # Detect available container runtime (podman or docker). RUNTIME := $(shell command -v podman 2>/dev/null || echo "docker") -.PHONY: up down build restart logs clean dev-ban-test +.PHONY: up down build restart logs clean dev-ban-test e2e ## Start the debug stack (detached). ## Ensures log stub files exist so fail2ban can open them on first start. @@ -71,6 +72,20 @@ clean: $(RUNTIME) rmi $(DEV_IMAGES) 2>/dev/null || true @echo "All debug volumes and local images removed. Run 'make up' to rebuild and start fresh." +## Run the Robot Framework E2E test suite. +## Requires: stack up (make up), BANGUI_SESSION_SECRET env var set. +## Installs: pip install -r e2e/requirements.txt && rfbrowser init +e2e: up + @echo "Waiting for stack to be healthy..." + @timeout=120; \ + until curl -sf http://localhost:8000/api/health > /dev/null 2>&1; do \ + sleep 5; timeout=$$((timeout-5)); \ + if [ $$timeout -le 0 ]; then echo "Backend not healthy after 120s"; exit 1; fi; \ + done + pip install -r e2e/requirements.txt -q + rfbrowser init --quiet + robot --outputdir e2e/results e2e/tests/ + ## One-command smoke test for the ban pipeline: ## 1. Start fail2ban, 2. write failure lines, 3. check ban status. dev-ban-test: diff --git a/e2e/requirements.txt b/e2e/requirements.txt new file mode 100644 index 0000000..8713103 --- /dev/null +++ b/e2e/requirements.txt @@ -0,0 +1,2 @@ +robotframework>=7 +robotframework-browser>=18 \ No newline at end of file diff --git a/e2e/resources/auth.resource b/e2e/resources/auth.resource new file mode 100644 index 0000000..7c203a3 --- /dev/null +++ b/e2e/resources/auth.resource @@ -0,0 +1,31 @@ +*** Settings *** +Resource ${CURDIR}/common.resource + +*** Keywords *** +Login As Admin + # Check setup status. + ${response}= GET ${BACKEND_URL}/api/setup/status + ${body}= Set Variable ${response.json()} + + IF ${body}[setup_complete] == ${false} + # Complete the setup wizard with the dev master password ("Hallo123!"). + ${password}= Set Variable Hallo123! + ${hashed}= Evaluate "sha256('${password}'.encode()).hexdigest()" modules=hashlib + ${setup_payload}= Create Dictionary password=${hashed} + POST ${BACKEND_URL}/api/setup/complete json=${setup_payload} + + # Retry login after setup. + ${response}= GET ${BACKEND_URL}/api/auth/login + END + + # Perform login. + ${password}= Set Variable Hallo123! + ${hashed}= Evaluate "sha256('${password}'.encode()).hexdigest()" modules=hashlib + ${login_payload}= Create Dictionary password=${hashed} + ${response}= POST ${BACKEND_URL}/api/auth/login json=${login_payload} + + # Store session cookie for subsequent requests. + ${cookies}= Get Cookies + ${session_cookie}= Get Cookie Value bangui_session + Set Browser Variables session_cookie=${session_cookie} + Log Logged in as admin. \ No newline at end of file diff --git a/e2e/resources/common.resource b/e2e/resources/common.resource new file mode 100644 index 0000000..b9d0f26 --- /dev/null +++ b/e2e/resources/common.resource @@ -0,0 +1,25 @@ +*** Settings *** +Library Browser +Library HttpLibrary + +Variables ${CURDIR}/../../.env + +# Health check timeout for suite setup (120 s poll interval). +Suite Setup Wait For Backend Health timeout=120 interval=5 + +*** Variables *** +${FRONTEND_URL} http://localhost:5173 +${BACKEND_URL} http://localhost:8000 + +*** Keywords *** +Wait For Backend Health + [Arguments] ${timeout}=120 ${interval}=5 + ${deadline}= Evaluate time.time() + ${timeout} + WHILE True + ${now}= Evaluate time.time() + IF ${now} >= ${deadline} FAIL Backend did not become healthy within ${timeout} seconds + ${response}= GET ${BACKEND_URL}/api/health expected_status=200 + IF ${response.status} == 200 BREAK + Sleep ${interval} + END + Log Backend is healthy. \ No newline at end of file diff --git a/e2e/tests/01_page_loading.robot b/e2e/tests/01_page_loading.robot new file mode 100644 index 0000000..90bae88 --- /dev/null +++ b/e2e/tests/01_page_loading.robot @@ -0,0 +1,13 @@ +*** Settings *** +Resource ${CURDIR}/../../resources/common.resource + +*** Test Cases *** +Page Loads And Shows Navigation + New Browser chromium headless=${TRUE} + New Page ${FRONTEND_URL} + + # Confirm the page title or root element is present. + ${title}= Get Title + Should Not Be Empty ${title} + + Close Browser \ No newline at end of file diff --git a/e2e/tests/02_ban_records.robot b/e2e/tests/02_ban_records.robot new file mode 100644 index 0000000..d45b554 --- /dev/null +++ b/e2e/tests/02_ban_records.robot @@ -0,0 +1,16 @@ +*** Settings *** +Resource ${CURDIR}/../../resources/common.resource +Resource ${CURDIR}/../../resources/auth.resource + +*** Test Cases *** +Ban Records Are Visible + New Browser chromium headless=${TRUE} + Login As Admin + + Go To ${FRONTEND_URL}/bans + + # Basic presence check — the ban table or empty state should be present. + ${content}= Get Page Source + Should Not Be Empty ${content} + + Close Browser \ No newline at end of file diff --git a/e2e/tests/03_blocklist_import.robot b/e2e/tests/03_blocklist_import.robot new file mode 100644 index 0000000..0edc78a --- /dev/null +++ b/e2e/tests/03_blocklist_import.robot @@ -0,0 +1,15 @@ +*** Settings *** +Resource ${CURDIR}/../../resources/common.resource +Resource ${CURDIR}/../../resources/auth.resource + +*** Test Cases *** +Blocklist Import Page Opens + New Browser chromium headless=${TRUE} + Login As Admin + + Go To ${FRONTEND_URL}/blocklists + + ${content}= Get Page Source + Should Not Be Empty ${content} + + Close Browser \ No newline at end of file diff --git a/e2e/tests/04_config_edit.robot b/e2e/tests/04_config_edit.robot new file mode 100644 index 0000000..615b4dc --- /dev/null +++ b/e2e/tests/04_config_edit.robot @@ -0,0 +1,15 @@ +*** Settings *** +Resource ${CURDIR}/../../resources/common.resource +Resource ${CURDIR}/../../resources/auth.resource + +*** Test Cases *** +Config Edit Page Opens + New Browser chromium headless=${TRUE} + Login As Admin + + Go To ${FRONTEND_URL}/config + + ${content}= Get Page Source + Should Not Be Empty ${content} + + Close Browser \ No newline at end of file