Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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=<name>`.
# `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
Expand Down Expand Up @@ -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
Expand All @@ -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/
Expand All @@ -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
43 changes: 38 additions & 5 deletions e2e/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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',

Expand Down Expand Up @@ -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=<name>`, 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'] },
},
],

/**
Expand All @@ -122,4 +155,4 @@ export default defineConfig({
},

outputDir: '../frontend/test-results',
});
});
14 changes: 13 additions & 1 deletion frontend/components/dev-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<button
Expand Down Expand Up @@ -143,4 +155,4 @@ export function DevToolbar() {
<p className="mt-2 text-[10px] text-slate-400">Dev only · persisted to sessionStorage</p>
</aside>
);
}
}
Loading