Chrome Extension alarms testing needs more than checking that a callback fires once. Manifest V3 service workers are short-lived, devices sleep, browsers restart, alarm delivery may be delayed, and repeating schedules can resume differently than a wall-clock job. A robust QA plan validates user-visible outcomes and persistent state across those transitions.
This tutorial builds a synthetic reminder extension. Each alarm records a run identifier, scheduled time, actual delivery time, and completion marker in extension storage. It never calls production services. That makes missed, late, duplicate, and recovered executions safe to reproduce.
What Chrome officially documents
The chrome.alarms reference says alarms schedule work for a future time or periodic interval and require the alarms permission. Chrome may delay delivery beyond the requested time. During device sleep, alarms continue conceptually but do not wake the device; missed alarms fire after wake. A repeating alarm fires at most once after wake and is then rescheduled from that wake-time delivery.
The service-worker lifecycle guide says Chrome normally terminates an extension worker after inactivity and recommends persisting important data instead of relying on globals. The storage reference provides asynchronous extension-scoped storage that service workers can use.
Google also publishes a Puppeteer termination test and broader end-to-end testing guidance. These sources support deterministic lifecycle tests without claiming alarm timing is exact.
Define the contract before automation
Use four separate outcomes: delivered on time within tolerance, delivered late, recovered after wake or restart, and missing. Do not fail a test merely because delivery is a few milliseconds late. Set a business tolerance appropriate to the extension and record both requested and observed timestamps.
Give every logical schedule window an idempotency key. Before performing work, read the completion record from storage. After success, update it atomically enough for your workflow. Duplicate callbacks must not create duplicate notifications, uploads, or records.
Step 1: create a lifecycle-safe fixture
Build an unpacked test extension with one named alarm, one status page, and a service worker. Declare only alarms and storage. Store the expected alarm configuration and last completed run in chrome.storage.local; never depend on a top-level variable surviving worker termination.
On every worker start, query the named alarm. If it should exist but is absent, recreate it. Feature-detect optional alarm properties and test fallback behavior on every supported browser version rather than assuming a new property exists everywhere.
Step 2: verify creation, replacement, and clearing
- Create the named alarm and assert it appears in
get()andgetAll(). - Create the same name again and verify the earlier schedule is replaced.
- Clear it and verify the returned result and absence from the list.
- Clear all alarms in an isolated profile and verify none remain.
- Reload or update the unpacked extension and check the documented recovery logic.
Step 3: terminate the service worker deliberately
Follow Chrome’s Puppeteer pattern to locate and stop the extension service worker, then trigger an event that wakes it. Verify the alarm handler can immediately read its required state from extension storage and produce the same result as before termination.
Seed a broken version that stores configuration only in a global variable. The test should pass before termination and fail afterward. Replace the global with persisted state and rerun both modes. This proves the test can detect the defect rather than merely exercise the happy path.
Step 4: test sleep and wake semantics
Use a controlled machine or virtual environment. Schedule an alarm, put the device to sleep past its target, then wake it. Assert one recovery delivery, not one delivery for every missed period. Record the late duration and verify the next repeating occurrence is based on the resumed schedule behavior.
If real sleep automation is unstable in CI, keep one hardware or VM test and supplement it with unit tests around a clock abstraction. Do not claim a mocked clock proves operating-system wake behavior.
Step 5: inject delay and overlap
Block the worker with a controlled long task or apply system load so the event is late. Ensure the handler classifies lateness, completes once, and does not overlap the next logical window. Test two callbacks racing for the same idempotency key and a crash between starting and completing work.
| Scenario | Expected result | Evidence |
|---|---|---|
| Normal delivery | One completed run | Requested and actual time |
| Worker terminated | State restored | Storage record and user-visible result |
| Device wakes late | One recovery run | Late duration and next schedule |
| Duplicate callback | Second attempt skipped | Shared idempotency key |
| Storage failure | No false success | Error state and unchanged completion marker |
Step 6: test persistence boundaries
Restart the browser, reload the extension, disable and re-enable it, and install an update in disposable profiles. Verify both the alarm and its supporting state according to the capabilities of the tested Chrome version. State that must survive belongs in an appropriate persistent store; session storage is intentionally cleared on several lifecycle transitions.
Also test storage quota errors, delayed writes, corrupted values, schema migration, and concurrent updates. A recovered alarm with unusable state should enter a visible repair path instead of running with defaults silently.
Step 7: avoid misleading automation
Chrome’s E2E guide notes that some frameworks can change service-worker behavior. Selenium’s debugger attachment can prevent workers from stopping naturally, so a Selenium-only green result is not enough for termination coverage. Keep a Puppeteer termination suite or another method that genuinely stops the worker.
Assert user-visible behavior where possible. Inspect internal storage only for lifecycle evidence, and avoid tightly coupling every E2E test to implementation details.
Negative test matrix
- Missing
alarmspermission. - Invalid or past schedule input.
- Duplicate alarm name.
- Worker termination before reading state.
- Worker termination after starting but before completion.
- Sleep across several repeat periods.
- Browser restart and extension reload.
- Storage quota or write failure.
- Corrupted stored schedule.
- Clock or timezone change.
Screenshot plan
Capture the manifest permissions, alarm creation, expected timing matrix, worker target before termination, termination action, restored storage state, sleep/wake delivery evidence, duplicate suppression, restart recovery, and final QA checklist. Remove usernames, local paths, extension IDs, and private browsing data.
Release checklist
- Timing tolerance is explicit.
- Important state is not kept only in globals.
- The named alarm is checked on worker startup.
- Termination is tested with a tool that truly stops the worker.
- Sleep/wake produces at most one recovery action per logical window.
- Idempotency prevents duplicate side effects.
- Restart, reload, disable, and update paths are covered.
- Storage failure never becomes false success.
- Feature support is detected across the browser matrix.
- A human reviews timing and recovery evidence before release.
Limits
Alarms are not real-time timers. Chrome may delay them, sleeping devices are not awakened, and test frameworks can affect worker lifetime. Design the feature around eventual, idempotent work and communicate timing tolerance to users.
The reliable pattern is persistent state, one logical run key, deliberate worker termination, real sleep/wake coverage, user-visible assertions, and human review of the evidence.
Official sources
- chrome.alarms API
- Extension service-worker lifecycle
- chrome.storage API
- Test service-worker termination with Puppeteer
- End-to-end testing for Chrome Extensions
