diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 40e1bce0..eca6860f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,16 +4,38 @@ on: push: tags: - 'v*' + workflow_dispatch: + inputs: + skip_tests: + description: "Skip full E2E (emergency hotfix only)" + type: boolean + default: false permissions: contents: write + issues: write jobs: + full-test: + if: ${{ github.event.inputs.skip_tests != 'true' }} + uses: ./.github/workflows/test-full.yml + permissions: + contents: read + issues: write + create-release: + needs: [full-test] + if: ${{ always() && (needs.full-test.result == 'success' || needs.full-test.result == 'skipped') }} runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + - name: Warn if tests were skipped + if: ${{ needs.full-test.result == 'skipped' }} + run: | + echo "::warning::Release created with skip_tests=true — full E2E tests were bypassed" + echo "⚠️ **Tests bypassed**: This release was created with \`skip_tests=true\`. Full E2E tests were not run." >> $GITHUB_STEP_SUMMARY + - name: Extract version number id: version run: | diff --git a/.github/workflows/test-full.yml b/.github/workflows/test-full.yml new file mode 100644 index 00000000..332716ff --- /dev/null +++ b/.github/workflows/test-full.yml @@ -0,0 +1,78 @@ +name: Full E2E Tests + +on: + schedule: + - cron: "0 6 * * 1" # Monday 6am UTC + workflow_call: + workflow_dispatch: + +concurrency: + group: test-full-${{ github.ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + issues: write + +jobs: + full-test: + name: full-test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Check compose parity + run: bash scripts/check-compose-persist-parity.sh + + - name: Setup Node.js + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 + with: + node-version: "22" + cache: "npm" + + - name: Install npm 11 + run: npm install -g npm@11 + + - name: Install dependencies + run: npm ci + + - name: Run linter + run: npm run lint + + - name: Run unit tests + run: npm run test:run + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium webkit + + - name: Run full E2E tests + run: npm run test:e2e + + - name: Upload Playwright report + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 + + - name: Notify on failure + if: failure() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + EXISTING=$(gh issue list --label ci-failure --state open --limit 1 --json number --jq '.[0].number // empty') + if [ -n "$EXISTING" ]; then + gh issue comment "$EXISTING" --body "Full E2E failed again: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} (${{ github.event_name }}, $(date -u +%Y-%m-%dT%H:%M:%SZ))" + else + gh issue create \ + --title "Full E2E test suite failing" \ + --label "automated,ci-failure" \ + --body "The weekly full E2E test suite failed. + + **Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + **Trigger:** ${{ github.event_name }} + **Ref:** ${{ github.ref }} + + Check the Playwright report artifact for details." + fi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 518fb8c6..64f415eb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -3,8 +3,6 @@ name: Test on: pull_request: branches: [main] - push: - branches: [main] workflow_dispatch: workflow_call: @@ -43,10 +41,10 @@ jobs: run: npm run test:run - name: Install Playwright browsers - run: npx playwright install --with-deps chromium webkit + run: npx playwright install --with-deps chromium - - name: Run E2E tests (production build) - run: npm run test:e2e + - name: Run smoke E2E tests + run: npm run test:e2e:smoke - name: Upload Playwright report uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 diff --git a/playwright.smoke.config.ts b/playwright.smoke.config.ts index 082aebaa..250f13f7 100644 --- a/playwright.smoke.config.ts +++ b/playwright.smoke.config.ts @@ -1,37 +1,33 @@ import { defineConfig, devices } from "@playwright/test"; +const smokeTestUrl = process.env.SMOKE_TEST_URL; + /** - * Playwright configuration for post-deploy smoke tests. - * - * Runs against live URLs (d.racku.la or count.racku.la) to verify - * deployment succeeded and the app is working. + * Playwright configuration for smoke tests. * - * Environment variables: - * - SMOKE_TEST_URL: Target URL to test (default: https://d.racku.la) + * Two modes: + * - Local mode (no SMOKE_TEST_URL): builds locally and serves on port 4173 + * - Deploy mode (SMOKE_TEST_URL set): tests against a live URL * * @example - * # Test dev environment + * # Local/CI smoke tests (local build) * npm run test:e2e:smoke * * # Test production * SMOKE_TEST_URL=https://count.racku.la npm run test:e2e:smoke */ export default defineConfig({ - // No webServer - we're testing a deployed URL testDir: "e2e", - // Only run smoke tests - not the full E2E suite - testMatch: "smoke.spec.ts", + testMatch: ["smoke.spec.ts", "basic-workflow.spec.ts"], fullyParallel: true, - // More retries for live environment retries: 2, - // Longer timeouts for network latency timeout: 60000, expect: { timeout: 15000, }, reporter: [["html", { open: "never" }]], use: { - baseURL: process.env.SMOKE_TEST_URL || "https://d.racku.la", + baseURL: smokeTestUrl || "http://localhost:4173", trace: "on-first-retry", screenshot: "only-on-failure", }, @@ -40,6 +36,15 @@ export default defineConfig({ name: "chromium", use: { ...devices["Desktop Chrome"] }, }, - // Only chromium for smoke tests - speed over coverage ], + ...(smokeTestUrl + ? {} + : { + webServer: { + command: "npm run build && npm run preview", + port: 4173, + timeout: 120_000, + reuseExistingServer: !process.env.CI, + }, + }), });