Bugfix/e2e stress test fixes #2
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
| # Runs lint, unit tests, and E2E suites to gate deployments | |
| name: Test | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| jobs: | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| if: ${{ !startsWith(github.head_ref || github.ref_name, 'hotfix/') }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22.x' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci --legacy-peer-deps | |
| - name: Sync Astro types | |
| run: npm run sync | |
| - name: Run Astro check | |
| run: npm run check | |
| - name: Run lint | |
| run: npm run lint:base | |
| - name: Run Actions lint | |
| run: npm run lint:actions | |
| unit-test: | |
| name: Unit Tests | |
| runs-on: ubuntu-latest | |
| needs: lint | |
| if: ${{ !startsWith(github.head_ref || github.ref_name, 'hotfix/') }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22.x' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci --legacy-peer-deps | |
| - name: Sync Astro types | |
| run: npm run sync | |
| - name: Run Astro check | |
| run: npm run check | |
| - name: Run unit tests (Vitest — coverage with GitHub Actions reporter) | |
| id: vitest | |
| run: npm run test:coverage | |
| - name: Report Coverage | |
| if: steps.vitest.outcome == 'success' && hashFiles('coverage/coverage-summary.json') != '' | |
| uses: davelosert/vitest-coverage-report-action@v2.9.0 | |
| with: | |
| json-summary-path: './coverage/coverage-summary.json' | |
| json-final-path: './coverage/coverage-final.json' | |
| - name: Upload test coverage | |
| if: always() && hashFiles('coverage/coverage-summary.json') != '' | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: test-coverage | |
| path: coverage/ | |
| retention-days: 30 | |
| e2e-test: | |
| name: E2E Tests | |
| runs-on: ubuntu-latest | |
| needs: unit-test | |
| if: ${{ !startsWith(github.head_ref || github.ref_name, 'hotfix/') }} | |
| env: | |
| CONVERTKIT_API_KEY: ${{ secrets.CONVERTKIT_API_KEY }} | |
| CONVERTKIT_FORM_ID: ${{ secrets.CONVERTKIT_FORM_ID }} | |
| CRON_SECRET: ${{ secrets.CRON_SECRET }} | |
| RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SUPABASE_URL: ${{ secrets.SUPABASE_URL }} | |
| SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} | |
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} | |
| SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} | |
| SUPABASE_PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }} | |
| SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }} | |
| KV_URL: ${{ secrets.KV_URL }} | |
| KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }} | |
| KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }} | |
| KV_REST_API_READ_ONLY_TOKEN: ${{ secrets.KV_REST_API_READ_ONLY_TOKEN }} | |
| REDIS_URL: ${{ secrets.REDIS_URL }} | |
| WEBMENTION_IO_TOKEN: ${{ secrets.WEBMENTION_IO_TOKEN }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22.x' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci --legacy-peer-deps | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3.11.1 | |
| - name: Build Upstash mock image | |
| run: docker buildx build --load -t wb/upstash-redis-local:test test/containers/upstash/local-proxy | |
| - name: Generate container env file | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p test/containers | |
| : "${SUPABASE_SERVICE_ROLE_KEY:?SUPABASE_SERVICE_ROLE_KEY secret is required}" | |
| cat <<EOF_ENV > test/containers/.env | |
| COMPOSE_PROJECT_NAME=wb-e2e | |
| CONVERTKIT_HTTP_PORT=9010 | |
| RESEND_HTTP_PORT=9011 | |
| UPSTASH_HTTP_PORT=${UPSTASH_HTTP_PORT:-8079} | |
| UPSTASH_REDIS_PORT=${UPSTASH_REDIS_PORT:-6380} | |
| UPSTASH_TOKEN=${UPSTASH_TOKEN:-local-dev-token} | |
| SUPABASE_HEALTH_TIMEOUT=${SUPABASE_HEALTH_TIMEOUT:-240} | |
| SUPABASE_URL=${SUPABASE_URL:-http://127.0.0.1:54321} | |
| SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY} | |
| EOF_ENV | |
| - name: Start mock containers | |
| run: npm run containers:up | |
| - name: Wait for mock services | |
| run: npm run containers:wait | |
| - name: Start Supabase stack | |
| run: npm run containers:supabase:start | |
| - name: Apply Supabase migrations (local container) | |
| run: | | |
| set -euo pipefail | |
| echo "::group::Supabase migrations" | |
| npm run containers:supabase:db-push | |
| echo "::endgroup::" | |
| echo "::group::Supabase schema snapshot" | |
| FORCE_COLOR=1 dotenv -e test/containers/.env -- bash -c 'npx supabase db dump --local --schema-only --workdir suprabase' > /tmp/supabase-schema.sql | |
| head -n 200 /tmp/supabase-schema.sql || true | |
| echo "(full schema saved to /tmp/supabase-schema.sql)" | |
| echo "::endgroup::" | |
| echo "::group::Restart Supabase REST container" | |
| bash -euo pipefail -c ' | |
| project_id=$(grep -m1 "^project_id" suprabase/config.toml | awk -F '"' "{print \$2}") | |
| if [ -z "${project_id}" ]; then | |
| echo "Unable to determine Supabase project_id" >&2 | |
| exit 1 | |
| fi | |
| sanitize() { | |
| echo "$1" | tr "[:upper:]" "[:lower:]" | sed "s/[^a-z0-9]/-/g" | |
| } | |
| for candidate in "${project_id}" "$(sanitize "${project_id}")"; do | |
| rest_container=$(docker ps --filter "label=com.docker.compose.project=${candidate}" --format "{{.ID}} {{.Names}}" | grep -E "rest" | head -n 1 || true) | |
| if [ -n "${rest_container}" ]; then | |
| container_id=$(echo "${rest_container}" | awk "{print \$1}") | |
| container_name=$(echo "${rest_container}" | awk "{print \$2}") | |
| echo "Restarting ${container_name} (${container_id}) to refresh schema cache" | |
| docker restart "${container_id}" | |
| break | |
| fi | |
| done | |
| if [ -z "${rest_container:-}" ]; then | |
| echo "Supabase REST container not found; skipping restart" >&2 | |
| fi | |
| ' | |
| echo "::endgroup::" | |
| echo "::group::Supabase container logs (last 200 lines)" | |
| bash -euo pipefail -c ' | |
| project_id=$(grep -m1 "^project_id" suprabase/config.toml | awk -F '"' "{print \$2}") | |
| if [ -z "${project_id}" ]; then | |
| echo "Unable to determine Supabase project_id" >&2 | |
| exit 1 | |
| fi | |
| sanitize() { | |
| echo "$1" | tr "[:upper:]" "[:lower:]" | sed "s/[^a-z0-9]/-/g" | |
| } | |
| containers="" | |
| for candidate in "${project_id}" "$(sanitize "${project_id}")"; do | |
| containers=$(docker ps --filter "label=com.docker.compose.project=${candidate}" --format "{{.ID}} {{.Names}}" || true) | |
| if [ -n "${containers}" ]; then | |
| break | |
| fi | |
| done | |
| if [ -z "${containers}" ]; then | |
| echo "No Supabase containers found" >&2 | |
| exit 0 | |
| fi | |
| echo "Capturing logs for project ${project_id}" | |
| echo "${containers}" | while read -r container_id container_name; do | |
| [ -z "${container_id}" ] && continue | |
| echo "--- ${container_name} (tail -n 200) ---" | |
| docker logs --tail 200 "${container_id}" || true | |
| done | |
| ' | |
| echo "::endgroup::" | |
| - name: Start Astro dev server | |
| run: | | |
| npm run dev -- --host 0.0.0.0 > /tmp/astro-dev.log 2>&1 & | |
| echo $! > /tmp/astro-dev.pid | |
| - name: Wait for dev server | |
| run: | | |
| for attempt in $(seq 1 60); do | |
| if curl -fsS http://127.0.0.1:4321 >/dev/null; then | |
| echo "✅ Dev server is responding" | |
| exit 0 | |
| fi | |
| sleep 2 | |
| done | |
| echo "❌ Dev server failed to start" >&2 | |
| if [ -f /tmp/astro-dev.log ]; then | |
| echo '--- Astro dev server log ---' | |
| cat /tmp/astro-dev.log | |
| fi | |
| exit 1 | |
| - name: Run Playwright E2E tests | |
| run: npx playwright test | |
| env: | |
| CI: '1' | |
| FORCE_COLOR: '1' | |
| E2E_MOCKS: '1' | |
| - name: Stop Astro dev server | |
| if: always() | |
| run: | | |
| if [ -f /tmp/astro-dev.pid ]; then | |
| kill $(cat /tmp/astro-dev.pid) || true | |
| rm /tmp/astro-dev.pid | |
| fi | |
| - name: Upload Playwright report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report | |
| path: playwright-report/ | |
| retention-days: 30 | |
| - name: Stop Supabase stack | |
| if: always() | |
| run: npm run containers:supabase:stop || true | |
| - name: Stop mock containers | |
| if: always() | |
| run: npm run containers:down || true | |
| hotfix-bypass: | |
| name: Hotfix Bypass Notice | |
| runs-on: ubuntu-latest | |
| if: ${{ startsWith(github.head_ref || github.ref_name, 'hotfix/') }} | |
| steps: | |
| - name: Skip testing for hotfix branch | |
| run: echo "hotfix/* branch detected; skipping lint, unit, and e2e workflows but allowing deployments." |