Browser extensions live inside someone else's DOM. The Everhour Chrome extension can pass every test and still break in production when the host app ships a redesign and renames a CSS class. This watchdog exists to catch that within minutes, not after a user report.

What it does

  • Real-product checks: loads the actual extension into Chromium via --load-extension and opens real Asana pages — List view, Board view, and Task Details.
  • 20+ control assertions: verifies that every injected UI control is rendered, on every page type, every 10 minutes.
  • Auto-login with 2FA: automated Asana authentication, including OTP via otplib.
  • Slack alerts with evidence: on failure, sends a webhook alert with a pixel-diff screenshot showing exactly what disappeared. Alerts are deduplicated so the channel does not cry wolf.
  • Dashboard: a real-time HTTP dashboard on port 8080 with a group/check/control hierarchy.
  • History in SQLite: persistent check results with 30-day detail retention and 90-day aggregates.

Reliability engineering

The interesting part is not the selectors — it is keeping a headed browser alive 24/7 in a container:

  • Classified retries: errors are classified (SessionLost, NetworkError, Timeout) and retried with exponential backoff; only unknown states alert immediately.
  • Watchdog for the watcher: a separate process monitors a heartbeat file and restarts the main process if it hangs.
  • Memory guard: Chrome restarts automatically when RSS exceeds a configurable limit.
  • Headed in Docker: Chromium runs headed under Xvfb — no physical display, but a real compositor, so extension rendering matches a user machine.

Stack

Layer Tools
Browser automation Playwright, headed Chromium, Xvfb
Runtime Docker, docker-compose (VPS, Tailscale, ZimaOS profiles)
Dashboard Express + static UI on :8080
Storage SQLite (check history, aggregates)
Alerts Slack webhook with deduplication
Auth Asana auto-login, OTP via otplib
Language TypeScript

Architecture

Watchdog process — monitors heartbeat, restarts main on hang
|
Main process
|   Browser manager — Chrome lifecycle, memory guard
|   Checker — page checks, 20+ selectors per page type
|   Scheduler — 10-minute cycle
|   Auth provider — Asana login + OTP
|   Alerts — Slack webhook, dedup, pixel-diff screenshots
|   Storage — SQLite history
|
Dashboard (Express) — :8080

What I learned

Synthetic monitoring for a browser extension is mostly failure-mode engineering: sessions expire, host apps deploy at night, Chrome leaks memory, networks flap. Each failure class needs its own response — retry, re-login, restart, or alert — and confusing them either pages you at 3 AM for nothing or hides a real outage.

The full technical walkthrough is in the blog article: Monitoring a Chrome Extension in Production: Building a 24/7 Playwright Watchdog.