ci: upload frontend Playwright test reports as workflow artifacts #999
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Onboarding Smoke Test | |
| # Verifies the contributor setup path: | |
| # fresh clone → ./setup.sh → ./start.sh → services respond | |
| # Catches onboarding regressions before they hit contributors. | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "setup.sh" | |
| - "start.sh" | |
| - "backend/**" | |
| - "frontend/**" | |
| - "backend/requirements.txt" | |
| - "backend/requirements-dev.txt" | |
| - ".github/workflows/smoke-test.yml" | |
| pull_request: | |
| branches: [main] | |
| paths: | |
| - "setup.sh" | |
| - "start.sh" | |
| - "backend/**" | |
| - "frontend/**" | |
| - "backend/requirements.txt" | |
| - "backend/requirements-dev.txt" | |
| - ".github/workflows/smoke-test.yml" | |
| workflow_dispatch: | |
| jobs: | |
| shell-smoke: | |
| name: start.sh shell smoke | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Validate start.sh syntax | |
| run: bash -n start.sh | |
| - name: Verify frontend failure message | |
| run: | | |
| mv frontend frontend_backup | |
| trap 'mv frontend_backup frontend' EXIT | |
| if bash start.sh > output.log 2>&1; then | |
| echo "Expected start.sh to fail" | |
| exit 1 | |
| fi | |
| grep -q "ERROR: frontend directory not found." output.log \ | |
| || { echo "Expected frontend error message not found"; cat output.log; exit 1; } | |
| smoke-test: | |
| name: Fresh-clone smoke test (Ubuntu) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| # ── 1. Checkout ──────────────────────────────────────────────────────── | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| # ── 2. Set up Python 3.11 ────────────────────────────────────────────── | |
| # setup.sh requires Python 3.11+. We pin it explicitly so the runner | |
| # never silently falls back to an older system Python. | |
| - name: Set up Python 3.11 | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| # ── 3. Set up Node.js ────────────────────────────────────────────────── | |
| # setup.sh checks for node + npm; frontend uses Vite on port 5173. | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| # ── 4. Make scripts executable ───────────────────────────────────────── | |
| # Git on some clients strips execute bits — enforce them explicitly. | |
| - name: Make scripts executable | |
| run: chmod +x setup.sh start.sh | |
| # ── 5. Run setup.sh ──────────────────────────────────────────────────── | |
| # setup.sh: creates venv, pip installs backend/requirements.txt + | |
| # httpx[cli], npm installs frontend, writes .env, creates data/logs dirs. | |
| # lsof is used by start.sh; install it here so setup.sh can't fail on it. | |
| - name: Install system dependencies | |
| run: sudo apt-get install -y lsof libcairo2-dev pkg-config python3-dev | |
| - name: Run setup.sh | |
| run: | | |
| echo "::group::setup.sh output" | |
| bash setup.sh | |
| echo "::endgroup::" | |
| # ── 6. Verify setup produced expected artifacts ──────────────────────── | |
| # Fails fast with a clear message if setup.sh silently skipped something. | |
| - name: Verify setup artifacts | |
| run: | | |
| echo "Checking venv..." | |
| test -f venv/bin/python3 || { echo "FAIL: venv/bin/python3 missing"; exit 1; } | |
| test -f venv/bin/activate || { echo "FAIL: venv/bin/activate missing"; exit 1; } | |
| echo "Checking backend deps..." | |
| source venv/bin/activate | |
| python3 -c "import fastapi" || { echo "FAIL: fastapi not installed"; exit 1; } | |
| python3 -c "import uvicorn" || { echo "FAIL: uvicorn not installed"; exit 1; } | |
| python3 -c "import httpx" || { echo "FAIL: httpx not installed"; exit 1; } | |
| deactivate | |
| echo "Checking frontend node_modules..." | |
| test -f frontend/node_modules/.bin/vite \ | |
| || { echo "FAIL: frontend/node_modules/.bin/vite missing"; exit 1; } | |
| echo "Checking directories..." | |
| for d in data data/raw data/reports logs wordlists; do | |
| test -d "$d" || { echo "FAIL: directory '$d' missing"; exit 1; } | |
| done | |
| echo "Checking .env..." | |
| test -f .env || { echo "FAIL: .env not created"; exit 1; } | |
| grep -q "SECUSCAN_BIND_PORT=8000" .env \ | |
| || { echo "FAIL: expected port config missing from .env"; exit 1; } | |
| echo "All artifact checks passed." | |
| # ── 7. Start services via start.sh ───────────────────────────────────── | |
| # start.sh launches uvicorn on 127.0.0.1:8000 and Vite on 127.0.0.1:5173. | |
| # We background the whole script and capture its PID for cleanup. | |
| - name: Start services via start.sh | |
| run: | | |
| bash start.sh & | |
| echo "START_SH_PID=$!" >> "$GITHUB_ENV" | |
| # ── 8. Wait for backend (uvicorn on :8000) ───────────────────────────── | |
| # start.sh starts uvicorn on 127.0.0.1:8000. | |
| # /openapi.json is always present in FastAPI without any auth — safer | |
| # than /health which may not exist. | |
| - name: Wait for backend to be ready | |
| run: | | |
| MAX_WAIT=60 | |
| INTERVAL=3 | |
| ELAPSED=0 | |
| URL="http://127.0.0.1:8000/openapi.json" | |
| echo "Polling $URL ..." | |
| until curl --silent --fail --max-time 2 "$URL" > /dev/null 2>&1; do | |
| if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then | |
| echo "ERROR: Backend did not become ready within ${MAX_WAIT}s" | |
| echo "--- Active processes ---" | |
| ps aux | grep -E "uvicorn|python" || true | |
| echo "--- Port 8000 status ---" | |
| lsof -i :8000 || true | |
| exit 1 | |
| fi | |
| echo " Not ready (${ELAPSED}s elapsed) — retrying in ${INTERVAL}s ..." | |
| sleep "$INTERVAL" | |
| ELAPSED=$((ELAPSED + INTERVAL)) | |
| done | |
| echo "Backend ready after ${ELAPSED}s." | |
| # ── 9. Wait for frontend (Vite on :5173) ────────────────────────────── | |
| - name: Wait for frontend to be ready | |
| run: | | |
| MAX_WAIT=60 | |
| INTERVAL=3 | |
| ELAPSED=0 | |
| URL="http://127.0.0.1:5173" | |
| echo "Polling $URL ..." | |
| until curl --silent --fail --max-time 2 "$URL" > /dev/null 2>&1; do | |
| if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then | |
| echo "ERROR: Frontend did not become ready within ${MAX_WAIT}s" | |
| echo "--- Active processes ---" | |
| ps aux | grep -E "vite|node" || true | |
| echo "--- Port 5173 status ---" | |
| lsof -i :5173 || true | |
| exit 1 | |
| fi | |
| echo " Not ready (${ELAPSED}s elapsed) — retrying in ${INTERVAL}s ..." | |
| sleep "$INTERVAL" | |
| ELAPSED=$((ELAPSED + INTERVAL)) | |
| done | |
| echo "Frontend ready after ${ELAPSED}s." | |
| # ── 10. Smoke-check backend API ──────────────────────────────────────── | |
| # /openapi.json must contain "openapi" and "SecuScan" (the app title). | |
| # /docs must return HTTP 200 (Swagger UI). | |
| # These require zero auth and prove the real app stack loaded correctly. | |
| - name: Smoke-check backend API | |
| run: | | |
| echo "--- GET /openapi.json ---" | |
| OAS=$(curl --silent --fail --max-time 5 "http://127.0.0.1:8000/openapi.json") | |
| echo "$OAS" | python3 -c "import sys, json; d=json.load(sys.stdin); assert 'openapi' in d, 'missing openapi key'" \ | |
| || { echo "FAIL: /openapi.json invalid JSON or missing openapi key"; exit 1; } | |
| echo "openapi.json OK" | |
| echo "--- GET /docs ---" | |
| curl --silent --fail --max-time 5 "http://127.0.0.1:8000/docs" > /dev/null \ | |
| || { echo "FAIL: /docs did not return 200"; exit 1; } | |
| echo "/docs OK" | |
| echo "Backend smoke checks passed." | |
| # ── 11. Smoke-check frontend ─────────────────────────────────────────── | |
| - name: Smoke-check frontend | |
| run: | | |
| echo "--- GET http://127.0.0.1:5173 ---" | |
| BODY=$(curl --silent --fail --max-time 5 "http://127.0.0.1:5173") | |
| echo "$BODY" | grep -qi "html" \ | |
| || { echo "FAIL: frontend did not return an HTML page"; exit 1; } | |
| echo "Frontend smoke check passed." | |
| # ── 12. Teardown ─────────────────────────────────────────────────────── | |
| - name: Stop services | |
| if: always() | |
| run: | | |
| [ -n "${START_SH_PID:-}" ] && kill "$START_SH_PID" 2>/dev/null || true | |
| pkill -f "uvicorn" 2>/dev/null || true | |
| pkill -f "vite" 2>/dev/null || true | |
| pkill -f "npm" 2>/dev/null || true | |
| echo "Teardown complete." | |
| # ── 13. Upload logs on failure ───────────────────────────────────────── | |
| - name: Upload logs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: smoke-test-logs | |
| path: | | |
| logs/ | |
| **/*.log | |
| nohup.out | |
| if-no-files-found: ignore | |
| retention-days: 7 |