End-to-End Tests Across Chrome, Firefox, and Mobile Chrome for a Side Project

165 Playwright specs covering public pages, auth, dashboard, and admin — but CI only runs the Chromium project, and that tradeoff is deliberate, not an oversight.

End-to-End Tests Across Chrome, Firefox, and Mobile Chrome for a Side Project

Commit 9bc1b8b — "feat: Add comprehensive E2E testing suite with Playwright" — introduced a test directory that now spans public pages, authentication, the full dashboard (log trip, view data, plan trip, predictions), the admin panel, and the API layer directly. Commit d2d6f0f, a few weeks later, is titled "Simplify E2E tests: Chrome only, fix all 165 tests, add CI workflow" — and that title tells you almost everything about how a solo maintainer's relationship with test breadth actually evolves.

One suite, three browser projects
One suite, three browser projects

CI runs chromium only for speed; firefox and mobile-chrome are opt-in outside CI.

The suite is organized by user journey, not by page

e2e/tests/
├── public/public-pages.spec.js
├── auth/authentication.spec.js
├── dashboard/dashboard.spec.js
├── dashboard/logtrip.spec.js
├── admin/admin.spec.js
└── api/api.spec.js

logtrip.spec.js gets its own file separate from the rest of the dashboard suite because logging a trip is the single most important user action in the app — the one thing that has to work every time, filling location, fishing type, catch details, bait, and notes, then verifying the row appears in View Data and survives a page refresh. Splitting it out means a failure there is unambiguous in the CI summary, rather than buried inside a generic "dashboard.spec.js failed" line.

Three browser projects, one used in CI

playwright.config.js defines chromium, firefox, and mobile-chrome as separate projects — a real cross-browser matrix, on paper. In practice, commit f2364f8 ("Optimize CI: cache Playwright browsers and skip firefox/mobile in CI") made a deliberate call: CI runs chromium only.

projects: process.env.CI
  ? [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }]
  : [
      { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
      { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
      { name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
    ],

The tradeoff is explicit: full cross-browser coverage exists and is runnable locally or on demand, but every push and every PR only pays the Chromium cost. For a small team where "the CI feedback loop needs to stay under a few minutes" outweighs "catch a Firefox-only rendering bug on every commit," that's the right default — the coverage isn't gone, it's just not on the critical path of every push.

Auth-dependent projects vs. stateless projects

A subtler split, from commit 92c85eb ("separate test projects - auth/api/public tests don't need storage state"): not every spec needs a logged-in session, and forcing all of them through the same global storageState setup wastes time and creates a shared dependency that doesn't need to exist.

projects: [
  { name: 'setup', testMatch: /global\.setup\.js/ },
  { name: 'public-and-auth', testMatch: /(public|auth)\/.*\.spec\.js/ },
  { name: 'authenticated', testMatch: /(dashboard|admin)\/.*\.spec\.js/,
    use: { storageState: 'playwright/.auth/user.json' }, dependencies: ['setup'] },
]

Public-page and login-flow specs run without any dependency on the auth setup project completing first; dashboard and admin specs declare setup as an explicit dependency and reuse the stored session. That split alone cut a meaningful chunk of runtime, because logging in through the UI once per test file (rather than once per test) stopped happening implicitly.

The runbook is the test documentation

TEST_DOCUMENTATION.md pairs every spec with manual reproduction steps — not because the tests aren't trusted, but because a solo maintainer debugging a CI failure at midnight benefits enormously from "here's exactly how a human would reproduce this in the browser" sitting right next to the automated version.

Series: Fishing Tracker Pro. Next: the isolated Docker stack that lets these tests run without ever touching production data.