Apply comprehensive enhancements to iOS prefetch plugin testing and validation system per directive requirements. Technical Correctness Improvements: - Enhanced BGTask scheduling with validation (60s minimum lead time) - Implemented one active task rule (cancel existing before scheduling) - Added graceful simulator error handling (Code=1 expected) - Follow Apple best practice: schedule next task immediately at execution - Ensure task completion even on expiration with guard flag - Improved error handling and structured logging Testing Coverage Expansion: - Added edge case scenarios table (7 scenarios: Background Refresh Off, Low Power Mode, Force-Quit, Timezone Change, DST, Multi-Day, Reboot) - Expanded failure injection tests (8 new negative-path scenarios) - Documented automated testing strategies (unit and integration tests) Validation Enhancements: - Added structured JSON logging schema for events - Provided log validation script (validate-ios-logs.sh) - Enhanced test run template with telemetry and state verification - Documented state integrity checks (content hash, schedule hash) - Added UI indicators and persistent test artifacts requirements Documentation Updates: - Enhanced IOS_PREFETCH_TESTING.md with comprehensive test strategies - Added Technical Correctness Requirements to IOS_TEST_APP_REQUIREMENTS.md - Expanded error handling test cases from 2 to 7 scenarios - Created ENHANCEMENTS_APPLIED.md summary document Files modified: - ios/Plugin/DailyNotificationBackgroundTaskTestHarness.swift: Enhanced with technical correctness improvements - doc/test-app-ios/IOS_PREFETCH_TESTING.md: Expanded testing coverage - doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.md: Added technical requirements - doc/test-app-ios/ENHANCEMENTS_APPLIED.md: New summary document
29 KiB
iOS Test App Requirements
Purpose: What the iOS test app must provide so that the testing guide can be executed with parity vs Android
Version: 1.0.1
Scope: Phase 1 Prefetch MVP
Next Target: Phase 2 (Rolling Window + TTL Telemetry)
Maintainer: Matthew Raymer
Status: 📋 REQUIRED FOR PHASE 1
Date: 2025-11-15
Directive Reference: doc/directives/0003-iOS-Android-Parity-Directive.md
Note: This app exists to support the prefetch testing scenarios in doc/test-app-ios/IOS_PREFETCH_TESTING.md.
Android parity: Behavior is aligned with test-apps/android-test-app where platform constraints allow. Timing and BGTask heuristics will differ from Android's exact alarms:
- Android: Exact alarms via AlarmManager / WorkManager
- iOS: Heuristic BGTaskScheduler (see glossary); no hard guarantee of 5-min prefetch
Glossary: See doc/test-app-ios/IOS_PREFETCH_GLOSSARY.md for complete terminology definitions.
Overview
This document defines the requirements for the iOS test app (test-apps/ios-test-app/) that must be created as part of Phase 1 implementation. The iOS test app must provide UI parity with the Android test app (test-apps/android-test-app/) while respecting iOS-specific constraints and capabilities.
Non-Goals (Phase 1)
Out of scope for Phase 1:
- ❌ No full UX polish (color, branding)
- ❌ No localization / accessibility guarantees (text only for internal QA)
- ❌ No production signing / App Store deployment
- ❌ No advanced UI features beyond basic functionality testing
Security & Privacy Constraints
Critical requirements for test app implementation:
- Test app MUST use non-production endpoints and credentials
- JWT / API keys used here are test-only and may be rotated or revoked at any time
- Logs MUST NOT include real user PII (names, emails, phone numbers)
- Any screenshots or shared logs should be scrubbed of secrets before external sharing
- Test app should clearly indicate it's a development/testing build (not production)
If You Only Have 30 Minutes
Quick setup checklist:
- Copy HTML/JS from Android test app (
test-apps/android-test-app/app/src/main/assets/public/index.html) - Wire plugin into Capacitor (
capacitor.config.json) - Add Info.plist keys (BGTask identifiers, background modes, notification permissions)
- Build/run (
./scripts/build-ios-test-app.sh --simulatoror Xcode) - Press buttons: Check Plugin Status → Request Permissions → Schedule Test Notification
- See logs with prefixes
DNP-PLUGIN,DNP-FETCH,DNP-SCHEDULER
UI Parity Requirements
HTML/JS UI
The iOS test app MUST use the same HTML/JS UI as the Android test app to ensure consistent testing experience across platforms.
Source: Copy from test-apps/android-test-app/app/src/main/assets/public/index.html
Required UI Elements:
- Plugin registration status indicator
- Permission status display (✅/❌ indicators)
- Test notification button
- Check permissions button
- Request permissions button
- Channel management buttons (Check Channel Status, Open Channel Settings)
- Status display area
- Log output area (optional, for debugging)
UI Functionality
The test app UI must support:
-
Plugin Status Check
- Display plugin availability status
- Show "Plugin is loaded and ready!" when available
-
Permission Management
- Display current permission status
- Request permissions button
- Check permissions button
- Show ✅/❌ indicators for each permission
-
Channel Management (iOS parity with Android)
- Check channel status button (iOS: checks app-wide notification authorization)
- Open channel settings button (iOS: opens app Settings, not per-channel)
- Note: iOS doesn't have per-channel control like Android; these methods provide app-wide equivalents
-
Notification Testing
- Schedule test notification button
- Display scheduled time
- Show notification status
-
Status Display
- Show last notification time
- Show pending notification count
- Display error messages if any
UI Elements to Plugin Methods Mapping
| UI Element / Button | Plugin Method / API Call | Notes |
|---|---|---|
| "Check Plugin Status" | DailyNotification.configure() or status call |
Verify plugin load & config |
| "Check Permissions" | checkPermissionStatus() |
Returns current notification permission status |
| "Request Permissions" | requestNotificationPermissions() |
Requests notification permissions (shows system dialog) |
| "Schedule Test Notification" | scheduleDailyNotification() |
Should schedule prefetch + notify |
| "Show Last Notification" | getLastNotification() |
Uses deterministic path (Bucket A) |
| "Cancel All Notifications" | cancelAllNotifications() |
Uses deterministic path (Bucket A) |
| "Get Notification Status" | getNotificationStatus() |
Uses deterministic path (Bucket A) |
| "Check Channel Status" | isChannelEnabled(channelId?) |
Checks if notifications enabled (iOS: app-wide) |
| "Open Channel Settings" | openChannelSettings(channelId?) |
Opens notification settings (iOS: app Settings) |
See IOS_PREFETCH_TESTING.md Behavior Classification for deterministic vs heuristic methods.
iOS Permissions Configuration
Info.plist Requirements
The test app's Info.plist MUST include:
<!-- Background Task Identifiers -->
<!-- These identifiers MUST match exactly the values used in IOS_PREFETCH_TESTING.md and the plugin Swift code, otherwise BGTaskScheduler (see glossary) will silently fail -->
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.timesafari.dailynotification.fetch</string> <!-- Prefetch task (BGAppRefreshTask - see glossary) -->
<string>com.timesafari.dailynotification.notify</string> <!-- Notification maintenance task (if applicable) -->
</array>
<!-- Background Modes -->
<key>UIBackgroundModes</key>
<array>
<string>background-fetch</string>
<string>background-processing</string>
<string>remote-notification</string>
</array>
<!-- Notification Permissions -->
<key>NSUserNotificationsUsageDescription</key>
<string>This app uses notifications to deliver daily updates and reminders.</string>
Background App Refresh
- Background App Refresh must be enabled in Settings
- Test app should check and report Background App Refresh status
- User should be guided to enable Background App Refresh if disabled
Build Options
Xcode GUI Build
-
Open Workspace:
cd test-apps/ios-test-app open App.xcworkspace # or App.xcodeproj -
Select Target:
- Choose iOS Simulator (iPhone 15, iPhone 15 Pro, etc.)
- Or physical device (requires signing)
-
Build and Run:
- Press Cmd+R
- Or Product → Run
Command-Line Build
Use the build script:
# From repo root
./scripts/build-ios-test-app.sh --simulator
# Or for device
./scripts/build-ios-test-app.sh --device
Phase 2 Enhancement: Refactor into modular subcommands:
./scripts/build-ios-test-app.sh setup # pod install + sync
./scripts/build-ios-test-app.sh run-sim # build + run simulator
./scripts/build-ios-test-app.sh device # build + deploy device
Copy-Paste Commands:
# Setup (first time or after dependency changes)
cd test-apps/ios-test-app
pod install
npx cap sync ios
# Build for simulator
xcodebuild -workspace App.xcworkspace \
-scheme ios-test-app \
-configuration Debug \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 15' \
build
# Run on simulator
xcodebuild -workspace App.xcworkspace \
-scheme ios-test-app \
-configuration Debug \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 15' \
test
Future CI Integration (Optional)
Note for Phase 2+: Consider adding xcodebuild-based CI job that:
- Builds
test-apps/ios-test-appfor simulator - Runs a minimal UI test that:
- Launches app
- Calls
configure()andgetNotificationStatus()
- Validates log sequence + successful fetch simulation via LLDB trigger
- This ensures test app remains buildable as plugin evolves
Phase 1: Manual testing only; CI integration is out of scope.
CI Readiness (Phase 2):
- Add
xcodebuildtarget for "Prefetch Integration Test" - Validate log sequence + successful fetch simulation via LLDB trigger
- Use log validation script (
validate-ios-logs.sh) for automated sequence checking
Build Requirements
Required Tools:
- Xcode: 15.0 or later
- macOS: 13.0 (Ventura) or later
- iOS Deployment Target: iOS 15.0 or later
- CocoaPods: >= 1.13 (must run
pod installbefore first build) - Node.js: 20.x (recommended)
- npm: Latest stable (comes with Node.js)
- Xcode Command Line Tools: Must run
xcode-select --installif not already installed
Note: Mismatched versions are out of scope for Phase 1 support. Use recommended versions to avoid compatibility issues.
Capacitor Configuration
Plugin Registration
The test app MUST register the DailyNotification plugin:
capacitor.config.json or capacitor.config.ts:
{
"plugins": {
"DailyNotification": {
"enabled": true
}
}
}
Plugin Path
The plugin must be accessible from the test app:
- Development: Plugin source at
../../ios/Plugin/ - Production: Plugin installed via npm/CocoaPods
Sync Command
After making changes to plugin or web assets:
cd test-apps/ios-test-app
npx cap sync ios
Debugging Strategy
When executing the scenarios in IOS_PREFETCH_TESTING.md, use the following commands while looking for logs with prefixes DNP-PLUGIN, DNP-FETCH, DNP-SCHEDULER.
Xcode Debugger
Check Pending Notifications:
po UNUserNotificationCenter.current().pendingNotificationRequests()
Check Permission Status:
po await UNUserNotificationCenter.current().notificationSettings()
Manually Trigger BGTask (Simulator Only):
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
Copy-Paste Commands:
// In Xcode LLDB console:
// Check pending notifications
po UNUserNotificationCenter.current().pendingNotificationRequests()
// Check permission status
po await UNUserNotificationCenter.current().notificationSettings()
// Manually trigger BGTask (simulator only)
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
// Force reschedule all tasks (test harness)
po DailyNotificationBackgroundTaskTestHarness.forceRescheduleAll()
// Simulate time warp (test harness)
po DailyNotificationBackgroundTaskTestHarness.simulateTimeWarp(minutesForward: 60)
Console.app Logging
- Open Console.app (Applications → Utilities)
- Select device/simulator
- Filter by:
DNP-orDailyNotification
Key Log Prefixes:
DNP-PLUGIN:- Main plugin operationsDNP-FETCH:- Background fetch operationsDNP-SCHEDULER:- Scheduling operationsDNP-STORAGE:- Storage operations
Structured Logging (Swift Logger):
The plugin uses Swift Logger categories for structured logging:
com.timesafari.dailynotification.plugin- Plugin operationscom.timesafari.dailynotification.fetch- Fetch operationscom.timesafari.dailynotification.scheduler- Scheduling operationscom.timesafari.dailynotification.storage- Storage operations
Filter in Console.app by subsystem: com.timesafari.dailynotification
Phase 2: Log Validation Script
Add helper script (validate-ios-logs.sh) to grep for required sequence markers:
grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log | ./scripts/validate-ios-logs.sh
This confirms that all critical log steps occurred in proper order and flags missing or out-of-order events automatically.
Common Debugging Scenarios
Scenario: BGTask not running when expected → follow this checklist:
-
BGTask Not Running:
- Check Info.plist has
BGTaskSchedulerPermittedIdentifiers - Verify identifiers match code exactly (case-sensitive)
- Verify task registered in AppDelegate before app finishes launching
- Check Background App Refresh enabled in Settings → [Your App]
- Verify app not force-quit (iOS won't run BGTask for force-quit apps)
- Use simulator-only LLDB command to manually trigger
- See also:
IOS_PREFETCH_TESTING.md – Log Checklist: Section 3
- Check Info.plist has
-
Notifications Not Delivering:
- Check notification permissions:
UNUserNotificationCenter.current().getNotificationSettings() - Verify notification scheduled:
UNUserNotificationCenter.current().getPendingNotificationRequests() - Check notification category registered
- See also:
IOS_PREFETCH_TESTING.md – Log Checklist: Section 4
- Check notification permissions:
-
BGTaskScheduler Fails on Simulator:
- Expected Behavior: BGTaskSchedulerErrorDomain Code=1 (notPermitted) is normal on simulator
- BGTaskScheduler doesn't work reliably on simulator - this is an iOS limitation, not a plugin bug
- Notification scheduling still works; prefetch won't run on simulator but will work on real devices
- Error handling logs clear message: "Background fetch scheduling failed (expected on simulator)"
- See also:
IOS_PREFETCH_TESTING.md – Known OS Limitationsfor details - Testing: Use Xcode → Debug → Simulate Background Fetch for simulator testing
-
Plugin Not Discovered:
- Check plugin class conforms to
CAPBridgedPluginprotocol (required for Capacitor discovery) - Verify
@objc extension DailyNotificationPlugin: CAPBridgedPluginexists in plugin code - Check plugin framework is force-loaded in AppDelegate before Capacitor initializes
- Verify
pluginMethodsarray includes all@objcmethods - See also:
doc/directives/0003-iOS-Android-Parity-Directive.md – Plugin Discovery Issuefor detailed troubleshooting
- Check plugin class conforms to
-
Build Failures:
- Run
pod install - Clean build folder (Cmd+Shift+K)
- Verify Capacitor plugin path
- Run
Test App Implementation Checklist
Setup
- Create
test-apps/ios-test-app/directory - Initialize Capacitor iOS project
- Copy HTML/JS UI from Android test app
- Configure Info.plist with BGTask identifiers
- Configure Info.plist with background modes
- Add notification permission description
Plugin Integration
- Register DailyNotification plugin in Capacitor config
- Ensure plugin path is correct
- Run
npx cap sync ios - Verify plugin loads in test app
UI Implementation
- Copy HTML/JS from Android test app
- Test plugin status display
- Test permission status display
- Test notification scheduling UI
- Test status display
Build & Test
- Build script works (
./scripts/build-ios-test-app.sh) - App builds in Xcode
- App runs on simulator
- Plugin methods work from UI
- Notifications deliver correctly
- BGTask executes (with manual trigger in simulator)
File Structure
test-apps/ios-test-app/
├── App.xcworkspace # Xcode workspace (if using CocoaPods)
├── App.xcodeproj # Xcode project
├── App/ # Main app directory
│ ├── App/
│ │ ├── AppDelegate.swift
│ │ ├── SceneDelegate.swift
│ │ ├── Info.plist # Must include BGTask identifiers
│ │ └── Assets.xcassets
│ └── Public/ # Web assets (HTML/JS)
│ └── index.html # Same as Android test app
├── Podfile # CocoaPods dependencies
├── capacitor.config.json # Capacitor configuration
└── package.json # npm dependencies (if any)
Testing Scenarios
Basic Functionality
Happy Path Example:
When everything is working correctly, your first run should produce logs like this:
[DNP-PLUGIN] configure() called
[DNP-PLUGIN] status = ready
[DNP-PLUGIN] requestPermissions() → granted
[DNP-SCHEDULER] scheduleDailyNotification() → notificationTime=2025-11-15T05:53:00Z
[DNP-FETCH] Scheduling prefetch for notification at 2025-11-15T05:53:00Z
[DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=2025-11-15T05:48:00Z)
[DNP-SCHEDULER] Scheduling notification for 2025-11-15T05:53:00Z
[DNP-STORAGE] Persisted schedule to DB (id=..., type=DAILY, ...)
If your first run doesn't look roughly like this, fix that before proceeding to BGTask tests.
-
Plugin Registration
[P1-Core][SIM+DEV]- Launch app
- Verify plugin status shows "Plugin is loaded and ready!"
- See also:
IOS_PREFETCH_TESTING.md – Simulator Test Plan: Step 2
-
Permission Management
[P1-Core][SIM+DEV]- Check permissions
- Request permissions
- Verify permissions granted
- See also:
IOS_PREFETCH_TESTING.md – Simulator Test Plan: Step 6 (Negative-Path Tests)
-
Notification Scheduling
[P1-Prefetch][SIM+DEV]- Schedule test notification
- Verify notification scheduled
- Wait for notification to appear
- See also:
IOS_PREFETCH_TESTING.md – Simulator Test Plan: Steps 3-5
Background Tasks
Sim vs Device Behavior:
[SIM+DEV]– can be fully tested on simulator and device (logic correctness)[DEV-ONLY]– timing / heuristic behavior, must be verified on real hardware
-
BGTask Scheduling
[P1-Prefetch][SIM+DEV]- Schedule notification with prefetch
- Verify BGTask scheduled 5 minutes before notification
- Manually trigger BGTask (simulator only)
- Verify content fetched
- Note: Logic and logs on sim; real timing on device
- See also:
IOS_PREFETCH_TESTING.md – Simulator Test Plan: Steps 3-4
-
BGTask Miss Detection
[P1-Prefetch][DEV-ONLY]- Schedule notification
- Wait 15+ minutes
- Launch app
- Verify BGTask rescheduled
- Note: Only meaningful on device (heuristic timing)
- See also:
IOS_PREFETCH_TESTING.md – Real Device Test Plan: Step 4
Error Handling
-
Permission Denied
[P1-Core][SIM+DEV]- Deny notification permissions
- Try to schedule notification
- Verify error returned
- See also:
IOS_PREFETCH_TESTING.md – Simulator Test Plan: Step 7 (Negative-Path Tests)
-
Invalid Parameters
[P1-Core][SIM+DEV]- Try to schedule with invalid time format
- Verify error returned
-
Network Failures
[P1-Prefetch][SIM+DEV]- Turn off network during fetch
- Verify graceful fallback to on-demand fetch
- Test recovery: network off → fail → network on → retry succeeds
-
Server Errors / Auth Expiry
[P1-Prefetch][SIM+DEV]- Simulate HTTP 401 or 500 error
- Verify plugin logs failure with reason (AUTH, NETWORK)
- Confirm telemetry counter increments:
dnp_prefetch_failure_total{reason="AUTH"} - Verify fallback to live fetch at notification time
-
Corrupted Cache
[P1-Prefetch][SIM+DEV]- Manually tamper with stored cache (invalid JSON or remove entry)
- Verify plugin detects invalid cache and falls back
- Ensure no crash on reading bad cache (error handling wraps cache read)
-
BGTask Execution Failure
[P1-Prefetch][SIM+DEV]- Simulate internal failure in BGTask handler
- Verify expiration handler or completion still gets called
- Ensure task marked complete even on exception
-
Repeated Scheduling Calls
[P1-Prefetch][SIM+DEV]- Call
scheduleDailyNotification()multiple times rapidly - Verify no duplicate scheduling (one active task rule enforced)
- Confirm only one BGTask is actually submitted
- Call
Advanced Features [P2-Advanced]
-
Rolling Window
[P2-Advanced]- Schedule multiple notifications
- Verify rolling window maintenance
- Check notification limits (64 max on iOS)
-
TTL Enforcement
[P2-Advanced]- Schedule notification with prefetch
- Wait for TTL to expire
- Verify stale content discarded at delivery
Developer/Test Harness Features (Optional but Recommended)
Since this test app is for internal testing, it can expose more tools:
Dev-Only Toggles
| Button | Action | Purpose |
|---|---|---|
| "Simulate BGTask Now" | Calls LLDB trigger | Quick sanity test |
| "Schedule 1-min Notification" | Auto schedules T-Lead=1 | Edge case testing |
| "Simulate DST Shift" | Adds +1 hr offset | DST handling check |
| "Show Cached Payload" | Displays JSON cache | Prefetch validation |
| "Force Reschedule All" | Calls forceRescheduleAll() |
BGTask recovery testing |
| "Time Warp +N Minutes" | Calls simulateTimeWarp() |
Accelerated TTL/T-Lead tests |
-
Force Schedule Notification N Minutes from Now
- Bypass normal scheduling UI
- Directly call
scheduleDailyNotification()with calculated time - Useful for quick testing scenarios
-
Force "Prefetch-Only" Task
- Trigger BGTask without scheduling notification
- Useful for testing prefetch logic in isolation
- Display raw JSON returned from API (last fetched payload)
-
Display Raw API Response
- Show last fetched payload as JSON
- Useful for debugging API responses
- Can be referenced from
IOS_PREFETCH_TESTING.mdas shortcuts for specific scenarios
-
Manual BGTask Trigger (Dev Build Only)
- Button to manually trigger BGTask (simulator only)
- Wraps the LLDB command in UI for convenience
-
UI Feedback Enhancements
- Add persistent toast/log area showing step-by-step plugin state ("Registered", "Scheduled", "BGTask fired", etc.)
- Include color-coded state indicators for each stage (🟢 OK, 🟡 Pending, 🔴 Error)
These features can then be referenced from IOS_PREFETCH_TESTING.md as shortcuts for specific test scenarios.
Persistent Schedule Snapshot
Phase 2 Enhancement: Store a simple JSON of the last prefetch state for post-run verification:
{
"last_schedule": "2025-11-15T05:48:00Z",
"last_prefetch": "2025-11-15T05:50:00Z",
"last_notification": "2025-11-15T05:53:00Z",
"prefetch_success": true,
"cached_content_used": true,
"contentHash": "abcdef123456",
"scheduleHash": "xyz789"
}
This can be used for post-run verification and telemetry aggregation. Access via test app UI button "Show Schedule Snapshot" or via UserDefaults key DNP_ScheduleSnapshot.
UI Indicators for Tests
Status Display:
- Status label/icon: 🟢 green when last notification was fetched and delivered from cache
- 🔴 red if something failed (network error, etc.)
- 🟡 yellow if pending/unknown state
Last Operation Summary:
- Display last fetch time
- Show whether cached content was used
- Display any error message
- Show telemetry counter values
Dump Prefetch Status Button:
- Triggers plugin to report all relevant info
- Shows pending tasks, cache status, telemetry snapshot
- Displays in scrollable text view
- Useful on devices where attaching debugger is inconvenient
In-App Log Viewer (Phase 2)
For QA Use:
- Read app's unified logging (OSLog) for entries with subsystem
com.timesafari.dailynotification - Present logs on screen or allow export to file
- Capture Logger output into text buffer during app session
- Security: Only enable in test builds, not production
Export Test Results:
- Save test run summary to file (JSON format)
- Include timestamps, outcomes, telemetry counters
- Access via "Export Test Results" button
- Collect from devices via Xcode or CI pipelines
Risks & Gotchas
Common pitfalls when working with the test app:
-
Accidentally editing shared HTML/JS in iOS test app instead of Android canonical source
- Always edit Android test app HTML/JS first, then copy to iOS
- Keep iOS test app HTML/JS as a copy, not the source of truth
-
Forgetting
npx cap sync iosafter plugin or asset changes- Run
npx cap sync iosafter any plugin code changes - Run
npx cap sync iosafter any web asset changes - Check
capacitor.config.jsonis up to date
- Run
-
Running with stale Pods
- Run
pod repo updateperiodically - Run
pod installafter dependency changes - Clean build folder (Cmd+Shift+K) if Pods seem stale
- Run
-
Changing BGTask identifiers in one place but not the other
- Info.plist
BGTaskSchedulerPermittedIdentifiersmust match Swift code exactly (case-sensitive) - Check both places when updating identifiers
- See
IOS_PREFETCH_TESTING.mdfor identifier requirements
- Info.plist
-
Mismatched tooling versions
- Use recommended versions (Node 20.x, CocoaPods >= 1.13, Xcode 15.0+)
- Mismatched versions are out of scope for Phase 1 support
References
- Directive:
doc/directives/0003-iOS-Android-Parity-Directive.md - Testing Guide:
doc/test-app-ios/IOS_PREFETCH_TESTING.md- Comprehensive prefetch testing procedures - Android Test App:
test-apps/android-test-app/ - Build Script:
scripts/build-ios-test-app.sh
Review & Sign-Off Checklist (Phase 1)
Use this checklist to verify Phase 1 iOS test app is complete:
- Test app builds on simulator with recommended toolchain (Node 20.x, CocoaPods >= 1.13, Xcode 15.0+)
- Test app builds on at least one real device
- All UI buttons map to plugin methods as per the UI Elements to Plugin Methods Mapping table
- Happy-path log sequence matches the example in Testing Scenarios → Basic Functionality
- BGTask identifiers are consistent (Info.plist ↔ Swift ↔ docs)
- Risks & Gotchas section has been read and acknowledged by the implementer
- Security & Privacy Constraints have been followed (non-production endpoints, no PII in logs)
Technical Correctness Requirements
BGTask Scheduling & Lifecycle
Validation Requirements:
- Verify
earliestBeginDateis at least 60 seconds in future (iOS requirement) - Log and handle scheduling errors gracefully (Code=1 on simulator is expected)
- Cancel existing pending task before scheduling new (one active task rule)
- Use
BGTaskScheduler.shared.getPendingTaskRequests()in debug to verify only one task pending
Schedule Next Task at Execution:
- Adopt Apple's best practice: schedule next task IMMEDIATELY at start of BGTask handler
- This ensures continuity even if app is terminated shortly after
- Pattern: Schedule next → Initiate async work → Mark complete → Use expiration handler
Expiration Handler and Completion:
- Implement expiration handler to cancel ongoing operations if iOS terminates task (~30 seconds)
- Always call
task.setTaskCompleted(success:)exactly once - Use
success: falseif fetch didn't complete (system may reschedule sooner) - Use
success: trueif all went well - Re-schedule next task after marking completion (for recurring use cases)
Error Handling & Retry:
- Distinguish recoverable errors (transient network) vs permanent failures
- For network failures: log failure reason, set
success: false, consider cached data if available - For logic errors: log clear message, call
setTaskCompleted(success: false), exit cleanly - Ensure fallback to on-demand fetch at notification time if prefetch fails
Data Consistency & Cleanup:
- Cross-check
notificationTimematches payload'sscheduled_forfield - Validate TTL on cached content at notification time (discard if expired)
- Ensure content is saved to persistent store before marking BGTask complete
- Implement cache cleanup for outdated data
- Handle permission changes gracefully (detect failure at delivery, log outcome)
Scheduling and Notification Coordination
Unified Scheduling Logic:
- Atomically schedule both UNNotificationRequest and BGAppRefreshTaskRequest
- If one fails, cancel the other or report partial failure
- Return clear status/error code to JS layer
BGTask Identifier Constants:
- Verify identifier in code exactly matches Info.plist (case-sensitive)
- Test harness should verify on app launch (logs show successful registration)
Concurrency Considerations:
- Handle potentially overlapping schedules (Phase 2: multiple notifications)
- Use one BGTask to fetch for next upcoming notification only
- Store next notification's schedule ID and time in shared place
- Use locks or dispatch synchronization to avoid race conditions
OS Limits:
- Acknowledge force-quit prevents BGTask execution (can't circumvent)
- Tolerate running slightly later than
earliestBeginDate(iOS heuristics) - Log actual execution time vs scheduled time for analysis
Phase 2 Forward Plan
Planned enhancements for Phase 2:
- Add quick scenario buttons (Simulate BGTask Now, Schedule 1-min Notification, Simulate DST Shift, Show Cached Payload)
- Implement persistent schedule snapshot (JSON of last prefetch state)
- Add color-coded UI feedback (🟢 OK, 🟡 Pending, 🔴 Error)
- Refactor build script into modular subcommands (
setup,run-sim,device) - Integrate CI pipeline with
xcodebuildtarget for "Prefetch Integration Test" - Add log validation script (
validate-ios-logs.sh) for automated sequence checking - Implement rolling window & TTL validation
- Add telemetry verification for multi-day scenarios
- Test on different device models and iOS versions
- Add in-app log viewer/export for QA use
See also: doc/directives/0003-iOS-Android-Parity-Directive.md for Phase 2 implementation details.
Changelog (high-level)
- 2025-11-15 — Initial Phase 1 version (prefetch MVP, Android parity)
Status: 📋 REQUIRED FOR PHASE 1
Last Updated: 2025-11-15