diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2f05a8f..df9440f 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -29,10 +29,34 @@ on: jobs: e2e: - name: E2E integration tests (MSW mocked) + name: E2E (${{ matrix.project }}) runs-on: ubuntu-latest timeout-minutes: 30 + # fail-fast: false is the point of this matrix — if WebKit breaks we + # still want Chromium/Firefox/mobile results in the same run, each + # clearly attributed to its own job rather than one aggregate pass/fail. + strategy: + fail-fast: false + matrix: + include: + # `project` maps 1:1 to a project name in e2e/playwright.config.ts + # and is passed to `--project=`. + # `browser` is the underlying engine Playwright needs installed — + # the two mobile projects emulate a device on top of an existing + # desktop engine, so they reuse that engine's binary rather than + # needing a separate download. + - project: chromium + browser: chromium + - project: firefox + browser: firefox + - project: webkit + browser: webkit + - project: mobile-chrome + browser: chromium + - project: mobile-safari + browser: webkit + defaults: run: working-directory: e2e @@ -66,14 +90,14 @@ jobs: run: npm ci working-directory: e2e - - name: Install Playwright browsers - run: npx playwright install --with-deps chromium + - name: Install Playwright browser (${{ matrix.browser }}) + run: npx playwright install --with-deps ${{ matrix.browser }} working-directory: e2e # ── Run tests ──────────────────────────────────────────────────────────── - - name: Run E2E integration tests - run: npx playwright test --reporter=github + - name: Run E2E tests — ${{ matrix.project }} + run: npx playwright test --project=${{ matrix.project }} --reporter=github working-directory: e2e env: CI: true @@ -86,12 +110,14 @@ jobs: E2E_API_BASE_URL: ${{ vars.E2E_API_BASE_URL || '' }} # ── Artifacts ──────────────────────────────────────────────────────────── + # Artifact names include the project so a reviewer can jump straight + # to (e.g.) the mobile-safari report without guessing which run it was. - name: Upload Playwright report if: always() uses: actions/upload-artifact@v4 with: - name: playwright-report-${{ github.run_id }} + name: playwright-report-${{ matrix.project }}-${{ github.run_id }} path: | frontend/test-results/ e2e/playwright-report/ @@ -101,6 +127,6 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: playwright-traces-${{ github.run_id }} + name: playwright-traces-${{ matrix.project }}-${{ github.run_id }} path: frontend/test-results/**/*.zip - retention-days: 7 + retention-days: 7 \ No newline at end of file diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index 7b9cb2b..3458bf9 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -11,6 +11,11 @@ * cd e2e * npx playwright test * + * ## Running a single browser engine locally + * + * npx playwright test --project=firefox + * npx playwright test --project=mobile-safari + * * ## Running against a real bridgelet-sdk instance * * E2E_API_BASE_URL=http://localhost:3001 \ @@ -47,8 +52,10 @@ export default defineConfig({ retries: process.env['CI'] ? 2 : 0, /** - * GitHub Actions reporter annotates the PR check directly; - * the list reporter is easier to read in a local terminal. + * GitHub Actions reporter annotates the PR check directly, and includes + * the project (browser) name in each annotation — this is what gives us + * per-engine failure attribution in the CI output. The list reporter is + * easier to read in a local terminal. */ reporter: process.env['CI'] ? 'github' : 'list', @@ -91,14 +98,40 @@ export default defineConfig({ }, /** - * Only run Chromium in CI to keep the scheduled job fast. - * Add more projects locally if cross-browser coverage is needed. + * Cross-browser + mobile-viewport matrix. + * + * Desktop engines cover the three major rendering engines (Chromium, + * Gecko, WebKit). The mobile projects emulate real device viewports, + * touch input, and UA strings — claim links are frequently opened from + * SMS/mail apps on a phone, so a desktop-viewport-only pass would miss + * layout and touch-target regressions on that path. + * + * Each project name is what CI's matrix strategy passes to + * `--project=`, and what shows up in the GitHub Actions job name + * and Playwright's `--reporter=github` annotations — so a failure is + * always attributed to a specific engine/device, not just "e2e failed". */ projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] }, }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + { + name: 'mobile-chrome', + use: { ...devices['Pixel 7'] }, + }, + { + name: 'mobile-safari', + use: { ...devices['iPhone 14'] }, + }, ], /** @@ -122,4 +155,4 @@ export default defineConfig({ }, outputDir: '../frontend/test-results', -}); +}); \ No newline at end of file diff --git a/frontend/components/dev-toolbar.tsx b/frontend/components/dev-toolbar.tsx index a3afbb0..a92cce1 100644 --- a/frontend/components/dev-toolbar.tsx +++ b/frontend/components/dev-toolbar.tsx @@ -54,6 +54,18 @@ export function DevToolbar() { // Avoid rendering until hydrated so the radio group shows the correct value. if (!mounted) return null; + // Skip rendering under browser automation (Playwright, Selenium, Puppeteer, + // etc. all set navigator.webdriver = true; real users never do). This + // toolbar is dev-only tooling for manually switching MSW scenarios — the + // E2E suite selects its scenario via the URL token instead + // (e.g. /claim/expired-e2e-test-token), so the toolbar isn't needed + // during automated runs. Without this check, the fixed bottom-4 right-4 + // panel renders during E2E too (next dev still has + // NODE_ENV === 'development'), and on the shorter mobile viewports it + // overlaps and intercepts clicks on real submit buttons (e.g. "Review + // Payment" / "Continue"), causing spurious mobile-only E2E failures. + if (typeof navigator !== 'undefined' && navigator.webdriver) return null; + if (collapsed) { return (