- Add TestEventsPlugin for receiving ADB broadcast intents
- Create operator console UI (console/index.html, console.css, console.js)
- Add test plan structure (plan.json) with phases, tests, and steps
- Wire all test scripts (phase1, phase2, phase3) with step context and events
- Add event emission helpers to alarm-test-lib.sh (step_start, step_pass, etc.)
- Update test-phase1.sh with comprehensive prerequisite verification
- Register TestEventsPlugin in capacitor.plugins.json
- Add console documentation (CONSOLE-USAGE.md, CONSOLE-REMAINING-WORK.md)
- Add test implementation alignment tracking (TEST-IMPLEMENTATION-ALIGNMENT.md)
This enables real-time test progress tracking via structured events
from shell scripts to the operator console UI.
- Normalize alarm time seconds to :00 for consistent comparison
- Compare dates (YYYY-MM-DD) instead of full timestamps to detect rollover
- Expand logcat search patterns to catch all rollover logs (DN|RESCHEDULE, etc.)
- Add 5-second wait after notification fire to allow rollover processing
- UI: Normalize seconds display to :00 in all time displays
- UI: Add auto-refresh mechanism that detects nextNotificationTime changes
- UI: Poll every 3 seconds and force refresh when rollover detected
- UI: Initialize tracking variable on page load for change detection
Fixes issue where test passed but alarm time didn't actually change,
and UI wasn't updating to show rescheduled notification time after rollover.
Implement P0-P5 directives for operator clarity, consistent outcomes, and
easy evidence capture across all test phases.
Changes:
- alarm-test-lib.sh: Add evidence collection (capture_alarms, capture_logcat,
capture_screenshot), verdict functions (verdict_pass/warn/fail), run directory
management, and release gating support (RELEASE_GATE_PHASE3)
- test-phase1.sh: Refactor to unified framework with CLI modes (--setup,
--run, --smoke, --all, --ci), micro-prompts, evidence capture, and verdict
blocks for all 5 tests
- test-phase2.sh: Add evidence capture, verdict blocks, and STRICTNESS policy
(soft/hard) for warn vs fail behavior
- test-phase3.sh: Add evidence capture, verdict blocks, release gating
(--gate-phase3), and fatigue reduction (time estimates, automation hints)
- RUNBOOK-TESTING.md: New comprehensive operator guide (669 lines) covering
prerequisites, all phases, evidence locations, verdict interpretation,
common failures, and troubleshooting
All test scripts now use consistent UI helpers (section, substep, info, ok,
warn, error), standardized evidence collection, and clear verdict reporting.
Evidence is saved to timestamped run directories (runs/<RUN_ID>/) with alarms,
logs, and screenshots organized by test phase and scenario.
Tests pass with consistent presentation and reproducible evidence collection.
Boot recovery runs twice on Android (both LOCKED_BOOT_COMPLETED and
BOOT_COMPLETED fire). The first run marks missed alarms (missed=1), while
the second run only reschedules future alarms (missed=0).
TEST 2 was incorrectly extracting the last entry (missed=0) instead of
the first entry (missed=1), causing false test failures.
Changes:
- Extract first missed count using head -n1 instead of tail -n1
- Add comment explaining why first entry is needed
- Test now correctly validates missed alarm detection
Fixes test validation for past alarm detection during boot recovery.
Implements force stop scenario detection and comprehensive alarm recovery.
Adds scenario differentiation (FORCE_STOP, BOOT, COLD_START, NONE) to route
recovery logic appropriately.
Changes:
- Add RecoveryScenario enum and detectScenario() method
- Add performForceStopRecovery() for force stop scenario
- Add alarmsExist() helper to check AlarmManager state
- Update isBootRecovery() to ignore flags < 1 second old (emulator quirk fix)
- Update BootReceiver to only set boot flag for actual boot events
- Add test script step to clear boot flag before testing
- Fix compiler warnings (remove unused parameters)
Fixes false BOOT detection when alarms are cleared after force stop.
Boot flag age validation prevents emulator quirks from triggering BOOT
scenario during app launch.
Implements: Plugin Requirements §3.1.4 - Force Stop Recovery
Implements automated testing for TEST 4 (Invalid Data Handling) to verify
recovery gracefully handles invalid database entries without crashing.
Changes:
- Add injectInvalidTestData plugin method for injecting invalid test data
(empty schedule IDs, null nextRunAt, empty notification IDs)
- Make test app debuggable to enable direct database access
- Enhance test-phase1.sh with automated database injection and verification:
* Detect debuggable app status (check for DEBUGGABLE flag)
* Inject invalid data via direct SQL (schedules and notifications)
* Handle WAL mode with checkpoint
* Verify data injection success
* Trigger recovery and check logs for "Skipping invalid" messages
* Report pass/fail/inconclusive results
Fixes database constraint issues discovered during testing:
- Include jitterMs and backoffPolicy in schedule inserts
- Include priority, vibration_enabled, sound_enabled in notification inserts
Test results: ✅ PASSED
- Invalid data successfully injected
- Cold start recovery correctly skips invalid entries
- Recovery completes without crashing
- Boot recovery processes invalid data (follow-up improvement needed)
This enables automated verification that recovery handles corrupted or
invalid database entries gracefully, preventing crashes in production.
Fix duplicate alarm bug where updating schedule time created multiple
schedules in database, violating "one notification per day" contract.
Plugin Changes:
- Use stable scheduleId "daily_notification" instead of timestamp-based IDs
- Delete all existing notification schedules before creating new one
- Cancel alarms in AlarmManager before database deletion
- Add detailed logging for cleanup operations
- Make scheduleDailyReminder delegate to scheduleDailyNotification
Test Harness Changes:
- Make TEST 2 fail when alarm count > 1 after schedule update
- Make TEST 2 fail when alarm count > 1 after recovery
- Add clear failure messages explaining "one per day" violation
- Add final verdict section with detailed failure summary
Results:
- Before: 2-3 alarms, 2 schedules in DB, "Pending: 2" in UI
- After: 1 alarm, 1 schedule in DB, "Pending: 1" in UI
- TEST 2 now correctly passes with proper validation
This ensures that updating schedule time maintains exactly one alarm
per day, preventing duplicate notifications and database bloat.
Add automatic app state reset for TEST 1 to ensure clean starting state when
lingering alarms from TEST 0 are detected. Create PHASE1_TEST1_GOLDEN.md with
actual values from successful run.
TEST 1 Auto-Reset:
- Detect lingering plugin alarms before TEST 1 starts
- Automatically uninstall/reinstall app to clear alarms
- Verify clean state (0 alarms) before proceeding
- Gracefully skip TEST 1 if clean state cannot be achieved
- Take failure screenshots when reset fails
- Wrap all TEST 1 steps in conditional to skip on reset failure
Documentation:
- Create PHASE1_TEST1_GOLDEN.md with actual values from passing run
- Document auto-reset behavior in golden run steps
- Add cross-references between TEST 0 and TEST 1 golden docs
- Include actual timestamps, scheduleIds, and recovery metrics
This ensures TEST 1 always starts from a known clean state, making test
results reliable and reproducible. The golden doc serves as a baseline for
comparing future TEST 1 runs.
Fix alarm counting to correctly parse dumpsys output where app ID and
action appear on different lines. Add screenshot capture for test
diagnostics and create golden run documentation.
Test Harness Improvements:
- Fix get_plugin_alarm_count() to track app ID and action separately
across alarm block lines (fixes false 0-count bug)
- Add show_plugin_alarms_compact() to display complete alarm blocks
- Add wait_for_stable_plugin_alarm_count() polling helper to reduce
race condition false negatives
- Add take_screenshot() and take_failure_screenshot() helpers for
automatic test state capture
- Integrate screenshots into TEST 0 at key checkpoints
- Update TEST 0 messaging to handle race conditions gracefully
- Add screenshots/ to .gitignore
Documentation:
- Create PHASE1_TEST0_GOLDEN.md with actual values from successful run
- Document expected script output, UI state, dumpsys shape, and logcat
patterns
- Include pass/fail checklist for future test runs
This fixes the issue where alarm counting always returned 0 because the
AWK logic required app ID and action on the same line, but dumpsys
output has them on separate lines (header line has app ID, tag line
has action).
Centralize all notification alarm scheduling through NotifyReceiver.scheduleExactNotification()
with idempotence checks to prevent duplicate alarms. Implement one-alarm policy using
setAlarmClock() only. Fix test harness alarm counting to deduplicate by Alarm handle.
Plugin Changes:
- Add ScheduleSource enum to track scheduling paths (INITIAL_SETUP, ROLLOVER_ON_FIRE, etc.)
- Add DB-level idempotence check before scheduling (prevents logical duplicates)
- Add explicit alarm cancellation before scheduling (safety net)
- Implement one-alarm policy: use setAlarmClock() only, no setExact* fallbacks for same event
- Add deep logging for all AlarmManager calls (variant, requestCode, pendingIntentHash)
- Update all rollover paths (DailyNotificationReceiver, DailyNotificationWorker) to use
centralized function with ROLLOVER_ON_FIRE source
- Add @JvmStatic annotation to scheduleExactNotification for Java interop
Test Harness Changes:
- Fix get_plugin_alarm_count() to deduplicate by Alarm handle (prevents double-counting
same alarm in main list and "Next wake from idle" section)
- Update TEST 0 messaging: treat 0 alarms as race condition (inconclusive, not failure)
- Make post-rollover check the authoritative assertion point (only fails on >1 or 0 alarms)
- Remove redundant "Found 0 alarms - test may not be accurate" messages
This fixes the duplicate alarm bug where two distinct AlarmManager entries were created
for the same daily notification, violating the "one notification per day" contract.
Remove the 'List Alarms' button and alarm list display functionality
from the Android test app UI. This feature was added for testing but
is no longer needed as alarm verification is handled by the test scripts.
Removed:
- '📋 List Alarms' button
- alarmListContainer div and alarm list display
- loadAlarmList() JavaScript function
- getSchedulesWithStatus() API call usage
The getSchedulesWithStatus() plugin method remains available for
programmatic use if needed in the future.
Extract common helper functions from test-phase1.sh, test-phase2.sh,
and test-phase3.sh into a shared library (alarm-test-lib.sh) to reduce
code duplication and improve maintainability.
Changes:
- Create alarm-test-lib.sh with shared configuration, UI helpers, ADB
helpers, log parsing, and test selection logic
- Refactor all three phase test scripts to source the shared library
- Remove ~200 lines of duplicated code across the three scripts
- Preserve all existing behavior, CLI arguments, and test semantics
- Maintain Phase 1 compatibility (print_* functions, VERIFY_FIRE flag)
- Update all adb references to use $ADB_BIN variable
- Standardize alarm counting to use shared count_alarms() function
Benefits:
- Single source of truth for shared helpers
- Easier maintenance (fix once, benefits all scripts)
- Consistent behavior across all test phases
- No functional changes to test execution or results
Adds ability to list alarms with AlarmManager status in web interface.
Changes:
- Add getSchedulesWithStatus() method to DailyNotificationPlugin
- Add ScheduleWithStatus TypeScript interface with isActuallyScheduled flag
- Add alarm list UI to android-test-app with status indicators
Backend:
- getSchedulesWithStatus() returns schedules with AlarmManager verification
- Checks isAlarmScheduled() for each notify schedule with nextRunAt
- Returns isActuallyScheduled boolean flag for each schedule
TypeScript:
- New ScheduleWithStatus interface extending Schedule
- Method signature with JSDoc and usage examples
UI:
- New "📋 List Alarms" button in test app
- Color-coded alarm cards (green=scheduled, orange=not scheduled)
- Shows schedule ID, next run time, pattern, and AlarmManager status
- Useful for debugging recovery scenarios and verifying alarm state
Use case:
- Verify which alarms are in database vs actually scheduled
- Debug Phase 1/2/3 recovery scenarios
- Visual confirmation of alarm state after app launch/boot
Related:
- Enhances: android-test-app for Phase 1-3 testing
- Supports: Recovery verification and debugging
Adds documentation and test harness for Phase 2 (Force Stop Detection & Recovery).
Changes:
- Add PHASE2-EMULATOR-TESTING.md with detailed test procedures
- Add PHASE2-VERIFICATION.md with test matrix and verification template
- Add test-phase2.sh automated test harness
Test harness features:
- 3 test cases: force stop with cleared alarms, intact alarms, empty DB
- Automatic force stop simulation via adb
- Log parsing for scenario detection and recovery results
- UI prompts for plugin configuration and scheduling
Related:
- Directive: android-implementation-directive-phase2.md
- Requirements: docs/alarms/03-plugin-requirements.md §3.1.4
- Testing: docs/alarms/PHASE2-EMULATOR-TESTING.md
- Verification: docs/alarms/PHASE2-VERIFICATION.md
- Add visual indicator when notification is received (shows for 30s)
- Poll notification status every 5 seconds to detect recent deliveries
- Add helpful message in test notification success about checking top of screen
- Improve user feedback for notification testing workflow
Fix configure() method to read parameters directly from CAPPluginCall
instead of expecting nested options object, matching Android implementation.
Improve build process to ensure canonical UI is always copied:
- iOS build script: Copy www/index.html to test app before build
- Android build.gradle: Add copyCanonicalUI task to run before build
- Ensures test apps always use latest UI from www/index.html
This fixes the issue where configure() was returning 'Configuration
options required' error because it expected a nested options object
when Capacitor passes parameters directly on the call object.
Fixed iOS 13.0 compatibility issue in test harness by replacing Logger
(iOS 14+) with os_log (iOS 13+). Fixed build script to correctly detect
and sync Capacitor config from App subdirectory. Unified both Android
and iOS test app UIs to use www/index.html as the canonical source.
Changes:
- DailyNotificationBackgroundTaskTestHarness: Replace Logger with os_log
for iOS 13.0 deployment target compatibility
- build-ios-test-app.sh: Fix Capacitor sync path detection to check
both current directory and App/ subdirectory for config files
- test-apps: Update both Android and iOS test apps to use www/index.html
as the canonical UI source for consistency
This ensures the plugin builds on iOS 13.0+ and both test apps provide
the same testing experience across platforms.
- Fix isChannelEnabled() to create channel if missing and re-fetch from system
to get actual state (handles previously blocked channels)
- Use correct channel ID 'timesafari.daily' instead of 'daily_notification_channel'
- Add detailed logging for channel status checks
- Fix UI to refresh channel status after notification permissions are granted
- Channel status now correctly reflects both app-level and channel-level settings
Static reminders scheduled via scheduleDailyNotification() with
isStaticReminder=true were being skipped because they don't have content
in storage - title/body are in Intent extras. Fixed by:
- DailyNotificationReceiver: Extract static reminder extras from Intent
and pass to WorkManager as input data
- DailyNotificationWorker: Check for static reminder flag in input data
and create NotificationContent from input data instead of loading from
storage
- DailyNotificationWorker: Ensure notification channel exists before
displaying (fixes "No Channel found" errors)
Also updated prefetch timing from 5 minutes to 2 minutes before notification
time in plugin code and web UI.
Restructure Android project from nested module layout to standard
Capacitor plugin structure following community conventions.
Structure Changes:
- Move plugin code from android/plugin/ to android/src/main/java/
- Move test app from android/app/ to test-apps/android-test-app/app/
- Remove nested android/plugin module structure
- Remove nested android/app test app structure
Build Infrastructure:
- Add Gradle wrapper files (gradlew, gradlew.bat, gradle/wrapper/)
- Transform android/build.gradle from root project to library module
- Update android/settings.gradle for standalone plugin builds
- Add android/gradle.properties with AndroidX configuration
- Add android/consumer-rules.pro for ProGuard rules
Configuration Updates:
- Add prepare script to package.json for automatic builds on npm install
- Update package.json version to 1.0.1
- Update android/build.gradle to properly resolve Capacitor dependencies
- Update test-apps/android-test-app/settings.gradle with correct paths
- Remove android/variables.gradle (hardcode values in build.gradle)
Documentation:
- Update BUILDING.md with new structure and build process
- Update INTEGRATION_GUIDE.md to reflect standard structure
- Update README.md to remove path fix warnings
- Add test-apps/BUILD_PROCESS.md documenting test app build flows
Test App Configuration:
- Fix android-test-app to correctly reference plugin and Capacitor
- Remove capacitor-cordova-android-plugins dependency (not needed)
- Update capacitor.settings.gradle path verification in fix script
BREAKING CHANGE: Plugin now uses standard Capacitor Android structure.
Consuming apps must update their capacitor.settings.gradle to reference
android/ instead of android/plugin/. This is automatically handled by
Capacitor CLI for apps using standard plugin installation.