Skip to content

test: add Playwright + Vitest harness with first E2E and unit suites#1

Merged
ivanmaierg merged 16 commits into
mainfrom
test/playwright-setup
May 9, 2026
Merged

test: add Playwright + Vitest harness with first E2E and unit suites#1
ivanmaierg merged 16 commits into
mainfrom
test/playwright-setup

Conversation

@ivanmaierg
Copy link
Copy Markdown
Owner

@ivanmaierg ivanmaierg commented May 9, 2026

Summary

Bootstraps an end-to-end testing harness for the MV3 extension using Playwright (real Chromium with the unpacked extension loaded), plus a small Vitest unit suite for pure scheduling logic. Adds a CI e2e job that runs after build.

  • 6 E2E specs covering popup, banner injection + dismiss, banner Refresh button → real tab reload, and SW message-passing
  • 12 unit assertions on decideRefresh / decideRemind
  • New CI job downloads the existing dist artifact (no double build) and runs pnpm test:e2e --headless=new

Why now

There was zero test infrastructure. The recent banner font fix (bfc20cc) is exactly the kind of regression a screenshot test catches; this PR adds that net plus enough behavioral coverage to ship changes with confidence.

Architecture decisions

  • Two-pipeline split. Playwright for everything that touches the browser; Vitest for pure logic only (no parallel test universe). Alarm-driven flows aren't E2E-testable because chrome.alarms minimum period is 1 minute.
  • Helper extraction. decideRefresh and decideRemind moved from inline branches in service-worker.ts into src/background/scheduler.ts as pure functions. Makes the unit suite possible without mocking chrome.*.
  • Extension ID at runtime. manifest.config.ts has no key field, so the fixture parses the ID from serviceWorker.url() after waiting on context.waitForEvent('serviceworker').
  • github.com mocked via page.route. Chromium matches content_scripts by URL not response origin, so the content script injects normally on a stub HTML response.
  • Snapshot strategy. Linux baseline is committed in the repo (generated via Playwright Docker image); CI verifies against it. macOS *-darwin.png variants are gitignored.

Bundled SW changes (heads-up)

a6d398a is labeled "extract scheduling helpers" but the diff also carries pre-existing in-progress work that was on the working tree:

  • ensureAlarm() helper — alarm creation is now idempotent (checks chrome.alarms.get first)
  • Alarm cadence changed from periodInMinutes: 0.5 to 1 (matches the chrome.alarms minimum)
  • log(...) debug logging added throughout (tick, alarm fire, tab activation, blur, reload)

These are real product improvements, not testing scaffolding. I bundled them into the extraction commit because they were already in the working tree when SDD apply ran. Open to splitting into a separate feat(background): idempotent alarm + structured logs commit if reviewer prefers — say the word.

Test coverage

Area Tests
Popup renders heading + Enabled toggle; toggling persists to chrome.storage and shows Saved
Banner injects on mocked github.com; Dismiss hides it; Refresh now click triggers real tab reload via SW handler (sentinel-based assertion)
Banner styling toHaveScreenshot Linux baseline committed; macOS/Windows local-only
SW alarm gh-refresh-tick registered after init
SW messages prefs-updated ack; refresh-now from non-tab sender no-ops but acks
Scheduler unit 12 assertions on refresh/remind decision math

Known gaps (intentional, not blockers)

  1. Alarm-driven tick() not exercised end-to-end — unit tests cover the decision math, message-passing test covers the wiring, but the integration "alarm fires → walk all tabs → reload the right ones" is unverified. Closing this needs tick() exported for direct invocation. Filed as follow-up.
  2. Snapshot regen is manual — when banner styling changes, regenerate the canonical Linux baseline via the Docker recipe in tests/README.md. CI does not auto-update.
  3. Notifications + webNavigation.onHistoryStateUpdated are untested. Lower priority.

Test plan

  • CI build job passes (existing)
  • CI e2e job runs and passes against the committed Linux baseline
  • If banner styling changed and the snapshot diff fails: regenerate the Linux baseline via the Docker recipe in tests/README.md, commit the updated reminder-banner-chromium-linux.png
  • pnpm typecheck && pnpm lint && pnpm build green
  • pnpm test:unit 12/12
  • pnpm test:e2e 7/7 locally on macOS

Files

New:

  • playwright.config.ts, vitest.config.ts
  • src/background/scheduler.ts
  • tests/e2e/fixtures/extension.ts, tests/e2e/fixtures/github-stub.html
  • tests/e2e/popup.spec.ts, tests/e2e/banner.spec.ts, tests/e2e/service-worker.spec.ts
  • tests/unit/scheduler.spec.ts, tests/unit/setup.ts
  • tests/README.md

Modified:

  • src/background/service-worker.ts (helper imports + bundled improvements above)
  • package.json (devDeps: @playwright/test, vitest; scripts: test, test:unit, test:e2e, test:e2e:ui)
  • eslint.config.js (tests/ override for react-hooks/rules-of-hooks on Playwright fixtures)
  • .github/workflows/ci.yml (new e2e job)
  • .gitignore (test artifacts + OS-specific snapshots)
  • CHANGELOG.md

ivanmaierg added 16 commits May 8, 2026 22:31
…en recipe

Replace stale "CI auto-generates linux baseline on first run" copy in
tests/README.md and the inline banner.spec.ts comment with the current
approach: the committed Linux PNG is the source of truth, CI only verifies,
and contributors must use the Playwright Docker image to regenerate it.
Also adds .pnpm-store/ to .gitignore (created by the Docker recipe).
@ivanmaierg ivanmaierg merged commit b303077 into main May 9, 2026
2 checks passed
@ivanmaierg ivanmaierg deleted the test/playwright-setup branch May 9, 2026 12:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant