# P5-06 — End-to-end test suite. # # Spec : docs/superpowers/specs/2026-05-07-p5-oss-readiness-design.md # Stack: e2e/compose.e2e.yml (server + agent + rest-server + playwright) # Tests: e2e/playwright/tests/*.spec.ts # # Triggered on every PR into main and on workflow_dispatch. Runs # longer than the unit-test workflow (~3-4 minutes for a clean run); # kept separate so a slow e2e doesn't block the fast lint/test loop. # # Networking note: every interaction with the server (health probe, # Playwright) happens from a container on the compose `rmnet` # network, addressing the server as `http://server:8080`. We can't # rely on `127.0.0.1:8080` because Gitea's runner executes steps # inside its own container, where compose's host port-publish is # not visible. name: e2e on: pull_request: branches: [main] workflow_dispatch: # Force bash as the default shell — see ci.yml header. defaults: run: shell: bash jobs: e2e: name: Playwright vs docker-compose runs-on: ubuntu-latest container: gitea.dcglab.co.uk/steve/ci-runner-go:2026-05-08 timeout-minutes: 15 steps: - uses: actions/checkout@v4 - name: Build the e2e stack # --profile test pulls in the playwright service which is # otherwise gated. --pull refreshes base images so a bump # to the Dockerfile's FROM tag (e.g. mcr.microsoft.com/ # playwright:vX.Y.Z-jammy) isn't masked by a stale runner # cache that still has the old tag's layers. run: docker compose --profile test -f e2e/compose.e2e.yml build --pull - name: Bring up the stack run: docker compose -f e2e/compose.e2e.yml up -d server rest-server source-fixture - name: Wait for server health run: | set -eu for i in $(seq 1 30); do if docker run --rm --network e2e_rmnet curlimages/curl:8.10.1 \ -fsS http://server:8080/api/version >/dev/null 2>&1; then echo "server up"; exit 0 fi sleep 2 done echo "server didn't come up"; docker compose -f e2e/compose.e2e.yml logs server; exit 1 - name: Capture bootstrap token from server logs id: bootstrap run: | set -eu for i in $(seq 1 15); do line=$(docker compose -f e2e/compose.e2e.yml logs server 2>&1 | grep -E 'bootstrap token' -A2 | grep -Eo '[a-zA-Z0-9_-]{40,}' | head -1 || true) if [ -n "$line" ]; then echo "RM_BOOTSTRAP_TOKEN=$line" >> "$GITHUB_ENV" echo "got bootstrap token (${#line} chars)" exit 0 fi sleep 1 done echo "bootstrap token not found in logs" docker compose -f e2e/compose.e2e.yml logs server exit 1 - name: Start the agent run: docker compose -f e2e/compose.e2e.yml up -d agent - name: Run Playwright tests id: playwright env: RM_BOOTSTRAP_TOKEN: ${{ env.RM_BOOTSTRAP_TOKEN }} # --name pins a stable container ID so the next step can # docker cp out of it before tear-down. We deliberately # drop --rm so the container survives the test exit; the # tear-down step removes it. run: docker compose -f e2e/compose.e2e.yml run --name e2e-pw playwright - name: Extract Playwright report if: always() && steps.playwright.outcome != 'skipped' run: | mkdir -p e2e/playwright/playwright-report e2e/playwright/test-results docker cp e2e-pw:/work/playwright-report/. e2e/playwright/playwright-report/ || true docker cp e2e-pw:/work/test-results/. e2e/playwright/test-results/ || true - name: Show Playwright failure context (on failure) if: failure() run: | set +e shopt -s nullglob globstar for f in e2e/playwright/test-results/**/error-context.md; do echo "::group::$f" cat "$f" echo "::endgroup::" done echo "Failure attachments (download via the playwright-report artifact):" find e2e/playwright/test-results \( -name '*.png' -o -name '*.webm' -o -name 'trace.zip' \) -printf ' %p\n' | sort - name: Compose logs (on failure) if: failure() run: | docker compose -f e2e/compose.e2e.yml logs --tail=200 server docker compose -f e2e/compose.e2e.yml logs --tail=200 agent docker compose -f e2e/compose.e2e.yml logs --tail=200 rest-server - name: Upload Playwright report (on failure) if: failure() uses: actions/upload-artifact@v4 with: name: playwright-report path: | e2e/playwright/playwright-report e2e/playwright/test-results retention-days: 7 - name: Tear down if: always() run: | docker rm -f e2e-pw 2>/dev/null || true docker compose -f e2e/compose.e2e.yml down -v