name: CI on: push: branches: [master] pull_request: branches: [master] jobs: backend: name: Backend tests runs-on: docker defaults: run: working-directory: backend steps: - uses: actions/checkout@v4 - name: Install uv run: | curl -LsSf https://astral.sh/uv/install.sh | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Set up Python run: uv python install 3.13 - name: Sync dependencies run: uv sync - name: Run pytest run: uv run pytest -v frontend-lint: name: Frontend lint runs-on: docker defaults: run: working-directory: frontend steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' cache: npm cache-dependency-path: frontend/package-lock.json - name: Install dependencies run: npm ci - name: Run ESLint run: npm run lint frontend-test: name: Frontend unit tests runs-on: docker defaults: run: working-directory: frontend steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' cache: npm cache-dependency-path: frontend/package-lock.json - name: Install dependencies run: npm ci - name: Run Vitest run: npm test frontend-build: name: Frontend build runs-on: docker defaults: run: working-directory: frontend steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' cache: npm cache-dependency-path: frontend/package-lock.json - name: Install dependencies run: npm ci - name: Build production bundle run: npm run build e2e: name: Playwright e2e runs-on: docker needs: [backend, frontend-build] steps: - uses: actions/checkout@v4 - name: Ensure Docker CLI run: | set -eux if ! command -v docker >/dev/null 2>&1; then DOCKER_VERSION=27.5.1 curl -fsSL "https://download.docker.com/linux/static/stable/$(uname -m)/docker-${DOCKER_VERSION}.tgz" \ | tar -xz -C /usr/local/bin --strip-components=1 docker/docker fi if ! docker compose version >/dev/null 2>&1; then COMPOSE_VERSION=v2.32.4 mkdir -p /usr/local/lib/docker/cli-plugins curl -fsSL "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" \ -o /usr/local/lib/docker/cli-plugins/docker-compose chmod +x /usr/local/lib/docker/cli-plugins/docker-compose fi docker --version docker compose version - name: Start stack run: docker compose -p life-towers -f docker-compose.dev.yml up --build -d - name: Wait for /api/v1/health run: | set -e cid=$(docker compose -p life-towers -f docker-compose.dev.yml ps -q life-towers) for i in $(seq 1 60); do status=$(docker inspect -f '{{.State.Health.Status}}' "$cid" 2>/dev/null || echo starting) if [ "$status" = healthy ]; then echo "stack healthy after ${i} attempts" exit 0 fi sleep 2 done echo "stack failed to become healthy" >&2 docker compose -p life-towers -f docker-compose.dev.yml logs >&2 exit 1 - name: Run Playwright run: | set -eux # Sibling-container (Docker-out-of-Docker) setup: the host daemon can't # see this job's filesystem, so `-v "$(pwd)/frontend:/work"` would mount # an empty dir. Instead create the container, copy the source IN, run, # then copy artifacts back OUT — all via the CLI, which reads/writes the # job container's filesystem. cid=$(docker create \ --network life-towers_default \ -e PLAYWRIGHT_BASE_URL=http://life-towers:8000 \ -e CI=true \ mcr.microsoft.com/playwright:v1.60.0-noble \ sh -c 'cd /work && npm ci && npx playwright test') # /work does not exist yet, so cp creates it from frontend's contents. docker cp ./frontend "$cid":/work # Run, but always copy artifacts back even when tests fail. set +e docker start -a "$cid" code=$? set -e docker cp "$cid":/work/playwright-report ./frontend/ || true docker cp "$cid":/work/visuals ./frontend/ || true docker rm -f "$cid" >/dev/null 2>&1 || true exit "$code" - name: Upload Playwright report if: always() uses: actions/upload-artifact@v3 with: name: playwright-report path: frontend/playwright-report if-no-files-found: ignore retention-days: 14 - name: Upload visual screenshots if: always() uses: actions/upload-artifact@v3 with: name: playwright-visuals path: frontend/visuals if-no-files-found: ignore retention-days: 14 - name: Dump container logs on failure if: failure() run: docker compose -p life-towers -f docker-compose.dev.yml logs - name: Tear down stack if: always() run: docker compose -p life-towers -f docker-compose.dev.yml down -v