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-extensionand 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.