Files
roof 0d32038150 feat: add comprehensive E2E test suite (Playwright + WireGuard + API)
Adds tests/e2e/ with three layers of E2E coverage:
- API layer (tests/e2e/api/): unauthenticated access, admin endpoints,
  peer endpoints, access control enforcement — 24 tests
- Playwright UI (tests/e2e/ui/): login flows, admin navigation, peer
  dashboard/services, role-based ACL, password change — 60+ tests
- WireGuard connectivity (tests/e2e/wg/): tunnel up/down, DNS resolution
  through VPN, service ACL enforcement via iptables, full-tunnel routing
Shared helpers: PicAPIClient, WGInterface, playwright_login, cleanup.
Makefile targets: test-e2e-api, test-e2e-ui, test-e2e-wg, test-e2e.
Adds scripts/reset_admin_password.py for test bootstrap.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 16:41:13 -04:00

80 lines
2.4 KiB
Python

"""
Playwright fixtures for PIC WebUI E2E tests.
Session/function-scoped browser fixtures live here. All infrastructure
fixtures (webui_base, admin_user, admin_password, make_peer, admin_client)
are provided by the parent conftest at tests/e2e/conftest.py and are
automatically discovered by pytest.
"""
import sys
import os
import pytest
try:
from playwright.sync_api import sync_playwright
except ImportError:
pytest.skip('playwright not installed — run: make test-e2e-deps', allow_module_level=True)
# Make the helpers package importable when pytest is invoked from any cwd.
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
# ---------------------------------------------------------------------------
# Browser / context / page fixtures
# ---------------------------------------------------------------------------
@pytest.fixture(scope='session')
def browser_instance():
"""A single Chromium browser process shared across the whole test session."""
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
yield browser
browser.close()
@pytest.fixture
def context(browser_instance):
"""A fresh browser context (isolated cookies/storage) for each test."""
ctx = browser_instance.new_context()
yield ctx
ctx.close()
@pytest.fixture
def page(context):
"""A fresh browser page for each test."""
p = context.new_page()
yield p
p.close()
# ---------------------------------------------------------------------------
# Logged-in page fixtures
# ---------------------------------------------------------------------------
@pytest.fixture
def admin_page(page, webui_base, admin_user, admin_password):
"""
A page already logged in as the admin user.
Returns the page object directly (not a tuple).
"""
from helpers.playwright_login import do_login
do_login(page, webui_base, admin_user, admin_password)
return page
@pytest.fixture
def peer_page(page, webui_base, make_peer):
"""
A page already logged in as a freshly created peer.
Returns (page, peer_info) where peer_info is the dict from make_peer.
The peer is cleaned up automatically after the test via make_peer's finalizer.
"""
from helpers.playwright_login import do_login
peer = make_peer('e2etest-ui-peer')
do_login(page, webui_base, peer['name'], peer['password'])
return page, peer