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
1022 lines
38 KiB
Markdown
1022 lines
38 KiB
Markdown
# iOS Prefetch Testing Guide
|
||
|
||
**Purpose:** How to test background prefetch for DailyNotificationPlugin on iOS (simulator + device)
|
||
|
||
**Version:** 1.0.1
|
||
**Scope:** Phase 1 Prefetch MVP
|
||
**Next Target:** Phase 2 (Rolling Window + TTL Telemetry)
|
||
**Maintainer:** Matthew Raymer
|
||
**Last Updated:** 2025-11-15
|
||
**Status:** 🎯 **ACTIVE** - Testing guide for Phase 1+ implementation
|
||
|
||
**Note:** This guide assumes the iOS test app is implemented as described in `doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.md`.
|
||
|
||
**Glossary:** See `doc/test-app-ios/IOS_PREFETCH_GLOSSARY.md` for terminology definitions.
|
||
|
||
**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; no hard guarantee of 5-min prefetch
|
||
|
||
---
|
||
|
||
## Non-Goals (Phase 1)
|
||
|
||
**Out of scope for Phase 1 testing:**
|
||
|
||
- ❌ No testing of silent push notifications
|
||
- ❌ No testing of advanced rolling-window policies beyond "schedule one daily notification"
|
||
- ❌ No guarantees of exact N-minute timing (iOS heuristics only)
|
||
- ❌ No testing of multi-day rolling windows (deferred to Phase 2)
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Goals](#goals)
|
||
2. [Assumptions](#assumptions)
|
||
3. [Simulator Test Plan (Logic-Correctness)](#simulator-test-plan)
|
||
4. [Real Device Test Plan (Timing & Reliability)](#real-device-test-plan)
|
||
5. [Log Checklist](#log-checklist)
|
||
6. [Behavior Classification](#behavior-classification)
|
||
7. [Test Harness Reference](#test-harness-reference)
|
||
|
||
---
|
||
|
||
## Goals
|
||
|
||
Verify that the **iOS prefetch path actually runs**:
|
||
|
||
- ✅ BGTask is registered
|
||
- ✅ BGTask is scheduled
|
||
- ✅ Fetch executes
|
||
- ✅ Data is persisted & used by the notification
|
||
|
||
**Separate concerns:**
|
||
- **"Does the logic work?"** → Test in Simulator
|
||
- **"Does timing behave?"** → Test on Real Device
|
||
|
||
## Time & T-Lead Rules
|
||
|
||
**Critical:** These rules prevent bugs when tweaking prefetch timing.
|
||
|
||
### Time Storage
|
||
|
||
- **`notificationTime` is stored in UTC** (not local time)
|
||
- All internal calculations use UTC timestamps
|
||
- Display to users may convert to local time, but storage remains UTC
|
||
|
||
### Prefetch Lead Calculation
|
||
|
||
- **Prefetch lead:** `prefetchTime = notificationTime - leadMinutes`
|
||
- **Default lead:** 5 minutes (300 seconds)
|
||
- **Minimum lead:** 1 minute (60 seconds) - iOS requires at least 1 minute in future
|
||
- **Maximum lead:** No hard limit, but iOS heuristics favor shorter windows
|
||
|
||
### Edge Cases
|
||
|
||
**Negative/Too-Small Lead Times:**
|
||
- If `notificationTime - leadMinutes < now + 60 seconds`:
|
||
- Cap prefetch at `now + 60 seconds` (minimum iOS requirement)
|
||
- Log: `[DNP-FETCH] Prefetch lead too small, capping at minimum (60s)`
|
||
- Or disable prefetch if notification is too soon
|
||
|
||
**DST (Daylight Saving Time) Changes:**
|
||
- UTC storage prevents DST issues
|
||
- Test case: Schedule notification at 2:00 AM on DST change day
|
||
- Verify: Prefetch and notification times remain correct (UTC-based)
|
||
|
||
**Timezone Changes:**
|
||
- Test case: User changes timezone between schedule and fire
|
||
- Verify: Notification fires at correct UTC time (not local time)
|
||
- Log: `[DNP-SCHEDULER] Timezone changed, but UTC schedule unchanged`
|
||
|
||
### Test Cases
|
||
|
||
1. **DST Change:**
|
||
- Schedule notification for 2:00 AM on DST transition day
|
||
- Verify prefetch and notification use UTC, not local time
|
||
- Check logs show UTC timestamps
|
||
|
||
2. **Timezone Change:**
|
||
- Schedule notification
|
||
- Change device timezone
|
||
- Verify notification fires at correct UTC time
|
||
- Check logs confirm UTC-based scheduling
|
||
|
||
3. **Too-Small Lead:**
|
||
- Schedule notification 30 seconds in future
|
||
- Verify prefetch is capped at 60 seconds minimum
|
||
- Check logs show capping behavior
|
||
|
||
## If You Only Have 30 Minutes
|
||
|
||
Quick validation checklist (each step links to log verification):
|
||
|
||
1. Build test app (per `IOS_TEST_APP_REQUIREMENTS.md`)
|
||
2. Run on iOS Simulator
|
||
3. Schedule notification 5 minutes in future → **See Log Checklist → Notification Scheduled**
|
||
4. Background app (Home button / Cmd+Shift+H)
|
||
5. Xcode → Debug → Simulate Background Fetch → **See Log Checklist → BGTask Fired**
|
||
6. Confirm logs show: Registration → Scheduling → BGTask handler → Fetch success → Task completed
|
||
7. Wait for notification time or manually trigger → **See Log Checklist → Notification Delivery**
|
||
8. Verify notification uses cached content (check logs for `[DNP-FETCH] Found cached content`) → **See Log Checklist → Notification Delivery**
|
||
|
||
---
|
||
|
||
## Assumptions
|
||
|
||
- Using **BGTaskScheduler** (`BGAppRefreshTask`) for prefetch
|
||
- App has **background modes** enabled:
|
||
- Background fetch
|
||
- Background processing (if using `BGProcessingTask`)
|
||
- Info.plist has:
|
||
- `BGTaskSchedulerPermittedIdentifiers` array with task identifier: `com.timesafari.dailynotification.fetch`
|
||
- Plugin exposes:
|
||
- `scheduleDailyNotification()` method that schedules both prefetch and notification
|
||
|
||
### Architecture at a Glance
|
||
|
||
**Flow Overview:**
|
||
```
|
||
JS/HTML Test App → Capacitor Bridge → DailyNotificationPlugin (iOS)
|
||
↓
|
||
BGTaskScheduler (BGAppRefreshTask)
|
||
↓
|
||
HTTP API (JWT, ETag, etc.)
|
||
↓
|
||
Cache/DB (UserDefaults/CoreData)
|
||
↓
|
||
Notification Delivery
|
||
(uses cached content if valid,
|
||
falls back to live fetch)
|
||
```
|
||
|
||
**Key Components:**
|
||
- **Test App:** Provides UI to trigger plugin methods (see `IOS_TEST_APP_REQUIREMENTS.md`)
|
||
- **Plugin:** Handles scheduling, fetching, caching, notification delivery
|
||
- **BGTaskScheduler:** iOS background execution for prefetch
|
||
- **Storage:** Persists schedules and cached content
|
||
- **Notification System:** Delivers notifications using prefetched or live content
|
||
|
||
**Verification Points:**
|
||
- BGTask registered at app startup
|
||
- BGTask scheduled 5 minutes before notification time
|
||
- BGTask executes and fetches content
|
||
- Content persisted to cache/DB
|
||
- Notification delivery uses cached content
|
||
|
||
---
|
||
|
||
## Simulator Test Plan (Logic-Correctness)
|
||
|
||
**Objective:** Confirm that when a background task fires, your prefetch code runs end-to-end.
|
||
|
||
**Cross-Reference:** Corresponds to `IOS_TEST_APP_REQUIREMENTS.md – Testing Scenarios → Basic Functionality [SIM+DEV]`
|
||
|
||
### 1. Harden Logging (One-Time Prep)
|
||
|
||
Add structured logs at key points:
|
||
|
||
**On app startup:**
|
||
```
|
||
[DNP-FETCH] Registering BGTaskScheduler task (id=com.timesafari.dailynotification.fetch)
|
||
```
|
||
|
||
**When scheduling:**
|
||
```
|
||
[DNP-FETCH] Scheduling BGAppRefreshTask (earliestBeginDate=2025-11-14T05:48:00Z)
|
||
[DNP-FETCH] Prefetch scheduled 5 minutes before notification (notificationTime=2025-11-14T05:53:00Z)
|
||
```
|
||
|
||
**When BGTask handler fires:**
|
||
```
|
||
[DNP-FETCH] BGTask handler invoked (task.identifier=com.timesafari.dailynotification.fetch)
|
||
```
|
||
|
||
**Inside prefetch logic:**
|
||
```
|
||
[DNP-FETCH] Starting fetch (notificationTime=2025-11-14T05:53:00Z, scheduleId=...)
|
||
[DNP-FETCH] Fetch success: status=200, items=1, ttl=86400
|
||
[DNP-FETCH] Fetch failed: error=..., willRetry=false
|
||
```
|
||
|
||
**When integrating with notifications:**
|
||
```
|
||
[DNP-FETCH] Using cached content for notification at 2025-11-14T05:53:00Z
|
||
[DNP-FETCH] No cached content, falling back to on-demand fetch
|
||
```
|
||
|
||
### 2. Start App in Simulator
|
||
|
||
1. Clean build, run on iPhone 15 simulator (or similar)
|
||
2. Verify console logs:
|
||
- ✅ `[DNP-FETCH] Registering BGTaskScheduler task...` at startup
|
||
|
||
### 3. Schedule a Notification with Prefetch
|
||
|
||
1. Call plugin's `scheduleDailyNotification()` from JS layer
|
||
2. Check logs:
|
||
- ✅ `[DNP-FETCH] Scheduling BGAppRefreshTask...` with sensible `earliestBeginDate`
|
||
- ✅ `[DNP-SCHEDULER] Scheduling notification for 2025-11-14T05:53:00Z`
|
||
- ✅ Any DB/state writes for the schedule
|
||
|
||
**Note:** For a full happy-path log example, see `IOS_TEST_APP_REQUIREMENTS.md – Testing Scenarios → Basic Functionality`.
|
||
|
||
### 4. Simulate Background Fetch
|
||
|
||
With app running:
|
||
|
||
1. **Background the app** (Home button / Cmd+Shift+H)
|
||
2. In Xcode menu:
|
||
- **Debug → Simulate Background Fetch** or
|
||
- **Debug → Simulate Background Refresh**
|
||
|
||
**Expected logs (in order):**
|
||
```
|
||
[DNP-FETCH] BGTask handler invoked (task.identifier=...)
|
||
[DNP-FETCH] Starting fetch (notificationTime=..., scheduleId=...)
|
||
[DNP-FETCH] API call: GET /api/notifications (JWT present, ETag=...)
|
||
[DNP-FETCH] Fetch success: status=200, bytes=1234, ttl=86400
|
||
[DNP-FETCH] Cached content for scheduleId=...
|
||
[DNP-FETCH] Task completed (success=true)
|
||
```
|
||
|
||
**See Sample Prefetch Response & Mapping below for expected API response structure.**
|
||
|
||
**Copy-Paste Commands:**
|
||
|
||
```bash
|
||
# In Xcode LLDB console (simulator only):
|
||
e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"com.timesafari.dailynotification.fetch"]
|
||
|
||
# Or use test app UI button: "Simulate BGTask Now"
|
||
```
|
||
|
||
### 5. Trigger or Wait for Notification
|
||
|
||
**Option A: Wait for scheduled time**
|
||
- Wait until scheduled time
|
||
- Look for:
|
||
- `[DNP-FETCH] Using cached content for notification at ...`
|
||
- `[DNP-SCHEDULER] Notification delivered (id=..., title=...)`
|
||
|
||
**Option B: Manual trigger**
|
||
- Use test app UI to trigger notification test path
|
||
- Confirm notification shows content derived from prefetch, not fallback
|
||
|
||
### 6. Sample Prefetch Response & Mapping
|
||
|
||
**Example API Response:**
|
||
|
||
When prefetch succeeds, the API returns JSON like:
|
||
|
||
```json
|
||
{
|
||
"id": "notif-2025-11-15",
|
||
"title": "Your daily update",
|
||
"body": "Here is today's summary…",
|
||
"ttl": 86400,
|
||
"scheduled_for": "2025-11-15T05:53:00Z"
|
||
}
|
||
```
|
||
|
||
**Response Mapping:**
|
||
|
||
- `id` → Used for notification identification and deduplication
|
||
- `title` → Notification title (displayed to user)
|
||
- `body` → Notification body (displayed to user)
|
||
- `ttl` → Cache validity check at delivery time (seconds)
|
||
- `scheduled_for` → Cross-check with `notificationTime` to ensure alignment
|
||
|
||
**Log Verification:**
|
||
|
||
When you see `[DNP-FETCH] Fetch success (status=200, bytes=1234, ttl=86400)`, the `ttl=86400` should match the `ttl` field in the JSON response. The `scheduled_for` timestamp should match the `notificationTime` used in scheduling.
|
||
|
||
**Copy-Paste Commands:**
|
||
|
||
```swift
|
||
// In test app or test harness:
|
||
// View cached payload
|
||
let cachedContent = UserDefaults.standard.data(forKey: "DNP_CachedContent_\(scheduleId)")
|
||
let json = try? JSONSerialization.jsonObject(with: cachedContent ?? Data())
|
||
|
||
// Simulate time warp (testing only)
|
||
DailyNotificationBackgroundTaskTestHarness.simulateTimeWarp(minutesForward: 60)
|
||
|
||
// Force reschedule all tasks
|
||
DailyNotificationBackgroundTaskTestHarness.forceRescheduleAll()
|
||
```
|
||
|
||
### 7. Negative-Path Tests
|
||
|
||
**Network failure:**
|
||
1. Turn off network in Simulator (Settings → Network)
|
||
2. Trigger background fetch
|
||
3. Expect:
|
||
- `[DNP-FETCH] Fetch failed: error=NetworkError, willRetry=false`
|
||
- Then either:
|
||
- Cached content used if available, or
|
||
- Fallback path logged: `[DNP-FETCH] No cached content, falling back to on-demand fetch`
|
||
|
||
**Permission denied:**
|
||
1. Deny notification permissions
|
||
2. Attempt to schedule
|
||
3. Expect:
|
||
- `[DNP-PLUGIN] notifications_denied for scheduleDailyNotification`
|
||
- Error returned: `{ error: "notifications_denied", ... }`
|
||
|
||
**Storage unavailable:**
|
||
1. Simulate DB write failure (test harness can inject this)
|
||
2. Attempt to cache prefetched content
|
||
3. Expect:
|
||
- `[DNP-STORAGE] Cache write failed: error=...`
|
||
- Fallback to on-demand fetch at notification time
|
||
- Log: `[DNP-FETCH] Storage unavailable, will fetch on-demand`
|
||
|
||
**JWT expiration:**
|
||
1. Simulate expired JWT token (test harness can inject this)
|
||
2. Trigger background fetch
|
||
3. Expect:
|
||
- `[DNP-FETCH] Fetch failed: error=AuthError, httpStatus=401, willRetry=false`
|
||
- Telemetry: `dnp_prefetch_failure_total{reason="AUTH"}`
|
||
- Log: `[DNP-FETCH] JWT expired, authentication failed`
|
||
|
||
**Timezone drift:**
|
||
1. Schedule notification
|
||
2. Change device timezone (Settings → General → Date & Time)
|
||
3. Verify:
|
||
- Notification fires at correct UTC time (not local time)
|
||
- Log: `[DNP-SCHEDULER] Timezone changed, but UTC schedule unchanged`
|
||
- Prefetch timing remains correct (UTC-based)
|
||
|
||
**Corrupted cache:**
|
||
1. Manually tamper with stored cache (invalid JSON or remove entry)
|
||
2. Wait for notification to fire
|
||
3. Expect:
|
||
- `[DNP-FETCH] No cached content available, falling back to on-demand fetch`
|
||
- No crash on reading bad cache (error handling wraps cache read)
|
||
- Fallback fetch executes successfully
|
||
|
||
**BGTask execution failure:**
|
||
1. Simulate internal failure in BGTask handler (test harness can inject exception)
|
||
2. Verify expiration handler or completion still gets called
|
||
3. Expect:
|
||
- Task marked complete even on exception (defer guard ensures completion)
|
||
- Log: `[DNP-FETCH] Task marked complete (success=false) due to error`
|
||
|
||
**Repeated scheduling calls:**
|
||
1. Call `scheduleDailyNotification()` multiple times rapidly
|
||
2. Verify no duplicate scheduling for same time
|
||
3. Expect:
|
||
- Only one BGTask is actually submitted (one active task rule)
|
||
- Logs show single scheduling, not duplicates
|
||
|
||
**Permission revoked mid-run:**
|
||
1. Schedule and fetch (ensure success)
|
||
2. Go to Settings and disable notifications before notification fires
|
||
3. Expect:
|
||
- Notification won't show (iOS blocks it)
|
||
- Plugin logs absence of delivery (telemetry or log note)
|
||
- No crash if user toggles permissions
|
||
|
||
---
|
||
|
||
## Known OS Limitations
|
||
|
||
**Critical:** These are iOS system limitations, not plugin bugs. If these conditions occur, it is **not a plugin bug**; confirm logs and document behavior.
|
||
|
||
**BGTaskScheduler Simulator Limitation:**
|
||
- **BGTaskSchedulerErrorDomain Code=1 (notPermitted) is EXPECTED on simulator**
|
||
- BGTaskScheduler doesn't work reliably on iOS Simulator - this is a known iOS limitation
|
||
- Background fetch scheduling will fail on simulator with Code=1 error
|
||
- **This is NOT a plugin bug** - notification scheduling still works correctly
|
||
- Prefetch won't run on simulator, but will work on real devices with Background App Refresh enabled
|
||
- Error handling logs: "Background fetch scheduling failed (expected on simulator)"
|
||
- **Testing:** Use Xcode → Debug → Simulate Background Fetch for simulator testing
|
||
- **See also:** `doc/directives/0003-iOS-Android-Parity-Directive.md` for implementation details
|
||
|
||
**iOS may NOT run BGTasks if:**
|
||
- App has been force-quit by the user (iOS won't run BGTask for force-quit apps)
|
||
- Background App Refresh is disabled in Settings → [Your App]
|
||
- Device is in Low Power Mode and idle
|
||
- Device battery is critically low
|
||
- **On Simulator:** BGTaskScheduler generally doesn't work (Code=1 error is expected)
|
||
|
||
**iOS timing heuristics:**
|
||
- iOS may delay or batch tasks; prefetch might run **much later** than `earliestBeginDate` (up to 15+ minutes)
|
||
- iOS adapts BGTask frequency based on user behavior, app usage, and energy constraints
|
||
- Notification delivery can drift by ±180 seconds (this is expected, not a bug)
|
||
|
||
**What to do:**
|
||
- Check logs to confirm plugin logic executed correctly
|
||
- Document the OS-imposed delay in test results
|
||
- Verify fallback mechanisms work when prefetch is delayed
|
||
|
||
---
|
||
|
||
## Real Device Test Plan (Timing & Reliability)
|
||
|
||
**Objective:** Confirm that **prefetch happens near the intended time** (e.g., 5 minutes before) in realistic conditions.
|
||
|
||
**Cross-Reference:** Corresponds to `IOS_TEST_APP_REQUIREMENTS.md – Testing Scenarios → Background Tasks [DEV-ONLY]`
|
||
|
||
### 1. Install Dev Build on iPhone
|
||
|
||
- Same BGTask + background modes config as simulator
|
||
- Confirm logs visible via:
|
||
- Xcode → Devices & Simulators → device → open console, or
|
||
- `os_log` aggregator / remote logging
|
||
|
||
### 2. Baseline Run
|
||
|
||
1. Open app
|
||
2. Schedule a notification for **30–60 minutes in the future** with prefetch
|
||
3. Lock the phone and leave it idle, **plugged in** (best-case for iOS)
|
||
|
||
### 3. Monitor Logs
|
||
|
||
Look for:
|
||
|
||
- BGTask scheduled with appropriate `earliestBeginDate`
|
||
- Some time before the notification:
|
||
- BGTask handler invoked
|
||
- Prefetch fetch executes
|
||
- At notification fire:
|
||
- Notification uses prefetched data
|
||
|
||
### 4. Edge Case Scenarios and Environment Conditions
|
||
|
||
Test different conditions and edge cases:
|
||
|
||
| Scenario / Condition | Test Strategy | Expected Outcome & Plugin Behavior |
|
||
|---------------------|---------------|-----------------------------------|
|
||
| **Background Refresh Off** | Disable Background App Refresh in iOS Settings, schedule notification, leave device idle | BGTask will not run (iOS suppresses it). Plugin logs warning (notPermitted). Notification appears using live fetch at fire time. No crash or hang. Telemetry records system failure reason. |
|
||
| **Low Power Mode On** | Enable Low Power Mode, schedule notification, idle device (battery vs plugged) | iOS may delay or skip BGTask. If skipped: plugin falls back gracefully. If runs later: notification uses cached data if valid. |
|
||
| **App Force-Quit** | Schedule notification, kill app (swipe up), keep device idle until notification time | iOS won't run BGTask (app force-quit). No prefetch occurs. Notification fires with fallback fetch. Plugin detects on next launch that prefetch didn't happen. |
|
||
| **Device Timezone Change** | Schedule notification for 12:00 UTC, change device timezone before it fires | Notification fires at correct UTC time (not local). Logs show UTC timestamp unchanged. Prefetch runs at UTC-5min regardless of timezone. |
|
||
| **DST Transition** | Schedule notification on DST change day (e.g., just after "lost hour") | Prefetch and notification align correctly in UTC. No double-firing or misses. Logs show consistent UTC times. |
|
||
| **Multi-Day Scheduling** (Phase 2) | Schedule two daily notifications (today and tomorrow) | Plugin handles multiple schedules. Prefetch today's content today, schedules BGTask for tomorrow. After first day, second day's prefetch still occurs. |
|
||
| **Device Reboot** | Schedule notification, reboot device before it fires, launch app | Schedule persists. BGTask reschedules after reboot (or app detects on launch). Startup log shows `hasPendingSchedules=true`. Notification still delivered. |
|
||
|
||
**Additional Variations:**
|
||
- **Device on battery** vs plugged in
|
||
- **App force-quit** vs just backgrounded
|
||
- **Multiple days in a row**: iOS will adapt its heuristics; see if prefetch becomes more or less reliable
|
||
- **Low Power Mode**: May delay/disable background tasks
|
||
- **Background App Refresh disabled**: Should fail gracefully
|
||
|
||
### 5. Success Criteria
|
||
|
||
In "good" conditions (plugged in, WiFi, not force-quit):
|
||
|
||
- ✅ Prefetch runs at least once before the notification in the majority of tests
|
||
- ✅ Failure modes are logged clearly:
|
||
- So you know when **timing failed because of iOS**, not your code
|
||
- ✅ **For at least one full test cycle, logs and telemetry counts confirm that the sequence: scheduled → executed → success → used is coherent**
|
||
|
||
**Acceptable outcomes:**
|
||
- Prefetch runs within 15 minutes of `earliestBeginDate` (iOS heuristic window)
|
||
- If prefetch misses, fallback to on-demand fetch works
|
||
- All failures are logged with clear reasons
|
||
|
||
### Test Run Result Template
|
||
|
||
Use this template when recording test runs (especially on real devices):
|
||
|
||
```text
|
||
Date:
|
||
Device model:
|
||
iOS version:
|
||
App build / commit:
|
||
Lead time (T-Lead - see glossary):
|
||
Scenario ID(s) tested:
|
||
Conditions: (plugged vs battery, Wi-Fi vs LTE, Low Power Mode on/off, Background App Refresh on/off)
|
||
Outcome summary:
|
||
- Prefetch scheduled at:
|
||
- BGTask executed at:
|
||
- Notification fired at:
|
||
- Cached content used: yes/no
|
||
- Actual execution time vs scheduled: (e.g., scheduled 5min before, executed 2min before = within heuristics)
|
||
Telemetry counters:
|
||
- dnp_prefetch_scheduled_total:
|
||
- dnp_prefetch_executed_total:
|
||
- dnp_prefetch_success_total:
|
||
- dnp_prefetch_used_for_notification_total:
|
||
State verification:
|
||
- Content hash match: (fetch hash vs delivery hash)
|
||
- Schedule hash match: (scheduling hash vs execution hash)
|
||
- Cache persistence: (verified after notification)
|
||
Failures observed (if any) and key log lines:
|
||
Notes / follow-ups:
|
||
```
|
||
|
||
**Persistent Test Artifacts:**
|
||
|
||
Test app can save this summary to file for later review. Access via "Export Test Results" button or UserDefaults key `DNP_TestRunArtifact`.
|
||
|
||
---
|
||
|
||
## Log Checklist
|
||
|
||
When everything is wired correctly, one full cycle should produce:
|
||
|
||
### 1. App Launch
|
||
|
||
**Expected logs:**
|
||
```
|
||
[DNP-FETCH] Registering BGTaskScheduler task (id=com.timesafari.dailynotification.fetch)
|
||
[DNP-PLUGIN] Startup complete (hasPendingSchedules=true|false)
|
||
```
|
||
|
||
**If you see registration but not startup:**
|
||
- Check plugin initialization in AppDelegate
|
||
- Verify Capacitor plugin registration
|
||
- Check for initialization errors in logs
|
||
|
||
**If you don't see registration:**
|
||
- Check Info.plist has `BGTaskSchedulerPermittedIdentifiers`
|
||
- Verify task registered in AppDelegate before app finishes launching
|
||
- Check for registration errors in logs
|
||
|
||
### 2. Notification Scheduled (from JS / plugin)
|
||
|
||
**Expected logs:**
|
||
```
|
||
[DNP-FETCH] Scheduling prefetch for notification at 2025-11-14T05:53:00Z
|
||
[DNP-FETCH] BGAppRefreshTask scheduled (earliestBeginDate=2025-11-14T05:48:00Z)
|
||
[DNP-SCHEDULER] Scheduling notification for 2025-11-14T05:53:00Z
|
||
[DNP-STORAGE] Persisted schedule to DB (id=..., type=DAILY, ...)
|
||
```
|
||
|
||
**Note:** For a full happy-path log example, see `IOS_TEST_APP_REQUIREMENTS.md – Testing Scenarios → Basic Functionality`.
|
||
|
||
**If you see scheduling but not BGTask scheduled:**
|
||
- Check BGTaskScheduler submission succeeded
|
||
- Verify `earliestBeginDate` is at least 1 minute in future
|
||
- Check for BGTaskScheduler errors in logs
|
||
- **On Simulator:** BGTaskSchedulerErrorDomain Code=1 (notPermitted) is expected - see "Known OS Limitations"
|
||
|
||
**If you see BGTask scheduled but not persisted:**
|
||
- Check database write operations
|
||
- Verify storage layer is initialized
|
||
- Check for storage errors in logs
|
||
|
||
### 3. BGTask Fired (simulator or device)
|
||
|
||
**Expected logs:**
|
||
```
|
||
[DNP-FETCH] BGTask handler invoked (id=com.timesafari.dailynotification.fetch)
|
||
[DNP-FETCH] Resolved next notification needing content (time=..., scheduleId=...)
|
||
[DNP-FETCH] Starting fetch from <URL> (notificationTime=..., jwtPresent=true)
|
||
[DNP-FETCH] Fetch success (status=200, bytes=1234, ttl=86400)
|
||
[DNP-FETCH] Cached content for scheduleId=...
|
||
[DNP-FETCH] Task completed (success=true)
|
||
```
|
||
|
||
**If you see "Registering BGTaskScheduler task" but never see "BGTask handler invoked" on device:**
|
||
- Check Background App Refresh enabled in Settings → [Your App]
|
||
- Verify app not force-quit (iOS won't run BGTask for force-quit apps)
|
||
- Check Info.plist identifiers match code exactly (case-sensitive)
|
||
- Verify task was actually scheduled (check `earliestBeginDate` in logs)
|
||
- On Simulator: Use Xcode → Debug → Simulate Background Fetch
|
||
|
||
**If you see handler invoked but not fetch starting:**
|
||
- Check notification resolution logic
|
||
- Verify schedule exists in database
|
||
- Check for resolution errors in logs
|
||
|
||
**If you see fetch starting but not success:**
|
||
- Check network connectivity
|
||
- Verify API endpoint is accessible
|
||
- Check JWT/authentication
|
||
- Look for HTTP error codes in logs
|
||
|
||
### 4. Notification Delivery
|
||
|
||
**Expected logs:**
|
||
```
|
||
[DNP-SCHEDULER] Preparing content for notification at 2025-11-14T05:53:00Z
|
||
[DNP-FETCH] Found cached content (age=300s, source=prefetch)
|
||
[DNP-SCHEDULER] Notification built (title=..., body=..., id=...)
|
||
[DNP-SCHEDULER] Notification scheduled/delivered
|
||
```
|
||
|
||
**If you see "Preparing content" but not "Found cached content":**
|
||
- Check if prefetch actually ran (see BGTask logs)
|
||
- Verify cache/DB read operations
|
||
- Check TTL validation (content may have expired)
|
||
- Look for fallback to on-demand fetch
|
||
|
||
**If you see "Found cached content" but not "Notification built":**
|
||
- Check notification building logic
|
||
- Verify notification permissions granted
|
||
- Check for notification construction errors
|
||
|
||
### 5. Failure Cases
|
||
|
||
**Expected logs:**
|
||
```
|
||
[DNP-FETCH] Fetch failed (error=NetworkError, httpStatus=0, willRetry=false)
|
||
[DNP-FETCH] No cached content available, falling back to on-demand fetch
|
||
[DNP-FETCH] Task completed (success=false, reason=NETWORK)
|
||
```
|
||
|
||
**If you see fetch failures repeatedly:**
|
||
- Check network connectivity
|
||
- Verify API endpoint is correct
|
||
- Check authentication (JWT) is valid
|
||
- Look for specific error reasons in logs (NETWORK, AUTH, SYSTEM)
|
||
|
||
**If you see "No cached content" but prefetch should have run:**
|
||
- Check if BGTask actually executed (see BGTask logs)
|
||
- Verify cache/DB write operations succeeded
|
||
- Check for cache expiration (TTL may have passed)
|
||
- Verify fallback to on-demand fetch works
|
||
|
||
**If you can walk through a log and trace all steps like this, you're in good shape.**
|
||
|
||
---
|
||
|
||
## Behavior Classification
|
||
|
||
Testable matrix of deterministic vs heuristic behavior:
|
||
|
||
| Bucket | Component / Method | Deterministic? | Test on | Notes |
|
||
|--------|-------------------|----------------|---------|-------|
|
||
| A | `BGTaskScheduler.shared.register` | Yes | Sim + Dev | Registration must always log & succeed/fail deterministically |
|
||
| A | `configure()`, `getLastNotification()`, `cancelAllNotifications()`, `getNotificationStatus()`, `updateSettings()` | Yes | Sim + Dev | Logic & I/O-only, no timing dependencies |
|
||
| A | `checkPermissionStatus()`, `requestNotificationPermissions()` | Yes | Sim + Dev | Permission state reading and requests, deterministic |
|
||
| A | `isChannelEnabled(channelId?)`, `openChannelSettings(channelId?)` | Yes | Sim + Dev | Channel status and settings (iOS: app-wide, not per-channel) |
|
||
| A | `getBatteryStatus()`, `getPowerState()`, `getRollingWindowStats()` | Yes | Sim + Dev | State reading, deterministic |
|
||
| A | `testJWTGeneration()`, `testEndorserAPI()` | Yes | Sim + Dev | API call logic, deterministic |
|
||
| A | Fetch function logic (HTTP calls, DB writes, JSON parsing) | Yes | Sim + Dev | Code path is deterministic |
|
||
| A | Error handling, retry logic, fallbacks | Yes | Sim + Dev | Logic is deterministic |
|
||
| A | Log emission / structured logging | Yes | Sim + Dev | Logging is deterministic |
|
||
| A | Notification building logic (given data) | Yes | Sim + Dev | Title/body/payload construction is deterministic |
|
||
| A | TTL validation at delivery time | Yes | Sim + Dev | Validation logic is deterministic |
|
||
| B | `scheduleDailyNotification()` | Logic yes, timing no | Sim + Dev | Use logs to verify scheduling but not run time |
|
||
| B | `maintainRollingWindow()` | Logic yes, timing no | Sim + Dev | Logic deterministic, but when iOS allows execution is heuristic |
|
||
| B | BGTaskScheduler scheduling (`earliestBeginDate`) | Logic yes, timing no | Sim + Dev | `earliestBeginDate` is a hint, not a guarantee |
|
||
| B | Notification trigger + prefetch relationship | Logic yes, timing no | Sim + Dev | "If cached content exists, use it" is deterministic; whether prefetch ran in time is heuristic |
|
||
| C | Time between `earliestBeginDate` and task execution | No | Device | Purely heuristic, iOS controls when tasks run |
|
||
| C | Background task execution timing (BGTaskScheduler) | No | Device | iOS heuristics control execution timing |
|
||
| C | Notification delivery timing (UNUserNotificationCenter) | No | Device | iOS controls delivery timing (±180s tolerance) |
|
||
| C | Reboot recovery detection (uptime comparison) | No | Device | May vary based on system state |
|
||
|
||
**Bucket Summary:**
|
||
- **🟢 Bucket A (Deterministic):** Test in Simulator and Device - Logic correctness
|
||
- **🟡 Bucket B (Partially Deterministic):** Test flow in Simulator, timing on Device
|
||
- **🔴 Bucket C (Heuristic):** Test on Real Device only - Timing and reliability
|
||
|
||
---
|
||
|
||
## Test Harness Reference
|
||
|
||
**File:** `ios/Plugin/DailyNotificationBackgroundTaskTestHarness.swift`
|
||
|
||
**Purpose:** Use this harness if the plugin code is broken or being refactored; it isolates BGTask behavior from plugin wiring.
|
||
|
||
**What it demonstrates:**
|
||
- Task registration
|
||
- Task scheduling
|
||
- Task handler implementation
|
||
- Expiration handling
|
||
- Completion reporting
|
||
|
||
**Usage:**
|
||
- Reference implementation when building actual prefetch logic
|
||
- Standalone testing of BGTaskScheduler behavior
|
||
- Debugging BGTask registration/scheduling issues
|
||
- See file for detailed usage examples and testing instructions
|
||
|
||
---
|
||
|
||
## Integration with Main Directive
|
||
|
||
This testing guide supports:
|
||
|
||
- **Phase 1:** Core prefetch functionality testing
|
||
- **Phase 2:** Advanced features testing (rolling window, TTL)
|
||
- **Phase 3:** TimeSafari integration testing (JWT, ETag)
|
||
- **Validation Matrix:** Cross-platform feature validation
|
||
|
||
## Telemetry and Monitoring
|
||
|
||
**Expected Counters (even if not yet implemented):**
|
||
|
||
Define counters you expect the runtime to emit:
|
||
|
||
- `dnp_prefetch_scheduled_total` - Total prefetch tasks scheduled
|
||
- `dnp_prefetch_executed_total` - Total prefetch tasks executed
|
||
- `dnp_prefetch_success_total` - Total successful prefetch executions
|
||
- `dnp_prefetch_failure_total{reason="NETWORK|AUTH|SYSTEM"}` - Total failed prefetch executions by reason
|
||
- `dnp_prefetch_used_for_notification_total` - Total notifications using prefetched content
|
||
|
||
**Telemetry Pipeline:**
|
||
|
||
These counters MUST be emitted via the same pipeline as Android (e.g., structlog → rsyslog → Prometheus/Influx/Loki). If telemetry is not yet wired on iOS, mark tests that rely on counters as **P2** and fall back to log inspection for Phase 1.
|
||
|
||
**Telemetry JSON Schema (Phase 2 Ready):**
|
||
|
||
Reserve placeholder JSON fields for telemetry events in the log schema:
|
||
|
||
```json
|
||
{
|
||
"event": "prefetch_success",
|
||
"scheduleId": "notif-2025-11-15",
|
||
"duration_ms": 2432,
|
||
"ttl": 86400,
|
||
"timestamp": "2025-11-15T05:48:32Z",
|
||
"telemetry": {
|
||
"dnp_prefetch_scheduled_total": 1,
|
||
"dnp_prefetch_executed_total": 1,
|
||
"dnp_prefetch_success_total": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
Even if not wired yet, this ensures Phase 2 code can emit compatible structured logs.
|
||
|
||
**Telemetry Validation:**
|
||
|
||
Use optional console log validation:
|
||
|
||
```swift
|
||
// In test harness or plugin
|
||
logTelemetrySnapshot(prefix: "DNP-")
|
||
```
|
||
|
||
This captures telemetry counters from structured logs for Phase 2 validation. Verify increment patterns (`scheduled → executed → success → used`).
|
||
|
||
**Structured Log Output (JSON):**
|
||
|
||
Log important events in structured format for automated parsing:
|
||
|
||
**Success Event:**
|
||
```json
|
||
{
|
||
"event": "prefetch_success",
|
||
"id": "notif-123",
|
||
"ttl": 86400,
|
||
"fetchDurationMs": 1200,
|
||
"scheduledFor": "2025-11-15T05:53:00Z",
|
||
"timestamp": "2025-11-15T05:48:32Z"
|
||
}
|
||
```
|
||
|
||
**Failure Event:**
|
||
```json
|
||
{
|
||
"event": "prefetch_failure",
|
||
"reason": "NETWORK",
|
||
"scheduledFor": "2025-11-15T05:53:00Z",
|
||
"timestamp": "2025-11-15T05:48:32Z"
|
||
}
|
||
```
|
||
|
||
**Cycle Complete Summary:**
|
||
```json
|
||
{
|
||
"event": "prefetch_cycle_complete",
|
||
"id": "notif-123",
|
||
"fetched": true,
|
||
"usedCached": true,
|
||
"timestamp": "2025-11-15T05:53:00Z"
|
||
}
|
||
```
|
||
|
||
These structured logs can be parsed by validation scripts and analytics backends.
|
||
|
||
**Success Criteria:**
|
||
- For at least one full test cycle, logs and telemetry counts confirm that the sequence: scheduled → executed → success → used is coherent
|
||
- Counters increment as expected through the prefetch lifecycle
|
||
- Failure counters provide clear reason categorization
|
||
|
||
---
|
||
|
||
## Test Campaign Sign-Off Checklist (Phase 1)
|
||
|
||
**Use this checklist to verify Phase 1 prefetch testing is complete:**
|
||
|
||
- [ ] Simulator: one full happy-path cycle verified (Schedule → BGTask → Fetch → Delivery)
|
||
- [ ] Device: at least one "best-case" run (plugged in, Wi-Fi, not force-quit) where prefetch runs before notification and cached content is used
|
||
- [ ] At least one failure path logged (NETWORK or PERMISSIONS) and handled gracefully
|
||
- [ ] Time & T-Lead edge-case tests run (too-small lead, timezone change) or explicitly deferred
|
||
- [ ] Telemetry counters either wired and validated, or marked **P2** with log-based fallback documented
|
||
|
||
**See also:**
|
||
- `doc/directives/0003-iOS-Android-Parity-Directive.md` - Main implementation directive
|
||
- `doc/test-app-ios/IOS_TEST_APP_REQUIREMENTS.md` - Test app setup requirements
|
||
|
||
---
|
||
|
||
## Glossary
|
||
|
||
**See:** `doc/test-app-ios/IOS_PREFETCH_GLOSSARY.md` for complete terminology definitions.
|
||
|
||
**Quick Reference:**
|
||
- **BGTaskScheduler** – iOS framework for background task scheduling (see glossary)
|
||
- **T-Lead** – Lead time between prefetch and notification (see glossary)
|
||
- **Bucket A/B/C** – Deterministic vs heuristic classification (see glossary)
|
||
- **UTC** – Coordinated Universal Time for timestamp storage (see glossary)
|
||
|
||
---
|
||
|
||
**Status:** 🎯 **READY FOR USE**
|
||
**Next Steps:** Use this guide when implementing and testing Phase 1+ prefetch functionality
|
||
|
||
---
|
||
|
||
## Automated Testing Strategies
|
||
|
||
### Unit Tests (Swift)
|
||
|
||
Write unit tests for plugin logic that can be tested in isolation:
|
||
|
||
**Time Calculations:**
|
||
- Test prefetchTime calculation: `prefetchTime = notificationTime - leadMinutes`
|
||
- Test edge conditions: exactly 60s lead, less than 60s lead (should cap at 60s)
|
||
- Test timezone handling: UTC storage vs local display
|
||
|
||
**TTL Validation:**
|
||
- Test cache validity check with simulated clock
|
||
- Test just-before-expiry vs just-after-expiry
|
||
- Test TTL expiration at notification delivery time
|
||
|
||
**JSON Mapping:**
|
||
- Test API response parsing with sample JSON
|
||
- Verify all fields map correctly (id, title, body, ttl, scheduled_for)
|
||
- Test error handling for malformed JSON
|
||
|
||
**Permission Check Flow:**
|
||
- Mock UNUserNotificationCenter returning .denied
|
||
- Verify plugin returns proper error (`notifications_denied`)
|
||
- Verify BGTask is not scheduled when permission denied
|
||
|
||
**BGTask Scheduling Logic:**
|
||
- Abstract scheduling method for dependency injection
|
||
- Inject fake BGTaskScheduler to verify correct identifier and earliestBeginDate
|
||
- Test one active task rule (cancel existing before scheduling new)
|
||
|
||
### Integration Tests
|
||
|
||
**Xcode UI Tests:**
|
||
- Launch test app, tap "Schedule Notification"
|
||
- Background app (simulate Home button)
|
||
- Use debug simulation command to trigger BGTask
|
||
- Bring app to foreground and verify UI/logs indicate success
|
||
|
||
**Log Sequence Validation:**
|
||
- Run app in simulator via CLI
|
||
- Use simctl or LLDB to trigger events
|
||
- Collect logs and analyze with script
|
||
- Assert all expected log lines appear in order
|
||
|
||
**Mocking and Dependency Injection:**
|
||
- Network calls: Use URLProtocol to intercept HTTP and return canned responses
|
||
- Notification scheduling: Provide dummy UNUserNotificationCenter
|
||
- BGTask invocation: Call handler method directly with dummy BGTask object
|
||
- Time travel: Use injectable time source (Clock.now) for TTL/multi-day tests
|
||
|
||
### Coverage of BGTask Expiration
|
||
|
||
Test expiration handler invocation:
|
||
- Set up BGTask that intentionally sleeps 40+ seconds on real device
|
||
- Monitor logs for "expirationHandler invoked" message
|
||
- Validate cancellation logic works correctly
|
||
|
||
---
|
||
|
||
## Validation and Verification Enhancements
|
||
|
||
### Structured Logging and Automated Log Analysis
|
||
|
||
**Distinct Log Markers:**
|
||
- Continue using prefixes: `[DNP-FETCH]`, `[DNP-SCHEDULER]`, `[DNP-PLUGIN]`, `[DNP-STORAGE]`
|
||
- Log concise summary line at end of successful cycle:
|
||
- `[DNP-PLUGIN] Prefetch cycle complete (id=XYZ, fetched=true, usedCached=true)`
|
||
- This gives one line to grep for overall success
|
||
|
||
**Log Validation Script:**
|
||
|
||
Develop `validate-ios-logs.sh` to parse device logs and verify sequence automatically:
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# validate-ios-logs.sh - Validates prefetch log sequence
|
||
|
||
LOG_FILE="${1:-device.log}"
|
||
|
||
# Expected sequence markers
|
||
REGISTRATION="Registering BGTaskScheduler task"
|
||
SCHEDULING="BGAppRefreshTask scheduled"
|
||
HANDLER="BGTask handler invoked"
|
||
FETCH_START="Starting fetch"
|
||
FETCH_SUCCESS="Fetch success"
|
||
TASK_COMPLETE="Task completed"
|
||
NOTIFICATION="Notification delivered"
|
||
|
||
# Check sequence
|
||
if grep -q "$REGISTRATION" "$LOG_FILE" && \
|
||
grep -q "$SCHEDULING" "$LOG_FILE" && \
|
||
grep -q "$HANDLER" "$LOG_FILE" && \
|
||
grep -q "$FETCH_START" "$LOG_FILE" && \
|
||
grep -q "$FETCH_SUCCESS" "$LOG_FILE" && \
|
||
grep -q "$TASK_COMPLETE" "$LOG_FILE" && \
|
||
grep -q "$NOTIFICATION" "$LOG_FILE"; then
|
||
echo "✅ Log sequence validated: All steps present"
|
||
exit 0
|
||
else
|
||
echo "❌ Log sequence incomplete: Missing steps"
|
||
exit 1
|
||
fi
|
||
```
|
||
|
||
**Usage:**
|
||
```bash
|
||
grep -E "\[DNP-(FETCH|SCHEDULER|PLUGIN)\]" device.log | ./scripts/validate-ios-logs.sh
|
||
```
|
||
|
||
### Enhanced Verification Signals
|
||
|
||
**Telemetry Counters:**
|
||
|
||
Monitor telemetry counters for prefetch operations:
|
||
- `dnp_prefetch_scheduled_total` - Increment when BGTask successfully scheduled
|
||
- `dnp_prefetch_executed_total` - Increment when BGTask handler runs
|
||
- `dnp_prefetch_success_total` - Increment on successful fetch
|
||
- `dnp_prefetch_failure_total{reason="NETWORK|AUTH|SYSTEM"}` - Increment on failure
|
||
- `dnp_prefetch_used_for_notification_total` - Increment when notification uses cached content
|
||
|
||
**State Integrity Checks:**
|
||
|
||
**Content Hash Verification:**
|
||
- Compute MD5 hash of cached content after fetch
|
||
- Log: `[DNP-FETCH] Cached content MD5: abcdef`
|
||
- At notification time, verify hash matches: `[DNP-FETCH] Using cached content MD5: abcdef`
|
||
- Mismatch indicates content changed or wasn't used properly
|
||
|
||
**Schedule Hash Verification:**
|
||
- Hash scheduling data (notificationTime + lead + scheduleId)
|
||
- Print hash when scheduling and when executing
|
||
- Confirm BGTask is executing for correct schedule
|
||
|
||
**Persistence Verification:**
|
||
- After notification fires, verify cache state (empty if cleared, present if within TTL)
|
||
- Flag any discrepancy in logs
|
||
|
||
**Persistent Test Artifacts:**
|
||
|
||
Test app saves summary of each test run to file:
|
||
|
||
```json
|
||
{
|
||
"testRun": {
|
||
"date": "2025-11-15T10:00:00Z",
|
||
"device": "iPhone 15 Pro",
|
||
"iosVersion": "17.1",
|
||
"outcome": {
|
||
"prefetchScheduledAt": "2025-11-15T05:48:00Z",
|
||
"bgTaskExecutedAt": "2025-11-15T05:50:00Z",
|
||
"notificationFiredAt": "2025-11-15T05:53:00Z",
|
||
"cachedContentUsed": true,
|
||
"errors": []
|
||
},
|
||
"telemetry": {
|
||
"scheduled": 1,
|
||
"executed": 1,
|
||
"success": 1,
|
||
"used": 1
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Access via test app UI button "Export Test Results" or UserDefaults key `DNP_TestRunArtifact`.
|
||
|
||
---
|
||
|
||
## Phase 2 Forward Plan
|
||
|
||
**Planned enhancements for Phase 2:**
|
||
|
||
- Implement rolling window validation
|
||
- Integrate Prometheus metrics collector
|
||
- Add automated CI pipeline for simulator validation
|
||
- Verify TTL and cache invalidation logic
|
||
- Wire telemetry counters to production pipeline
|
||
- Add automated log sequence validation
|
||
- Implement persistent schedule snapshot for post-run verification
|
||
- Add in-app log viewer/export for QA use
|
||
- Test multi-day scenarios with varying TTL values
|
||
- Validate content reuse across days when TTL allows
|
||
|
||
**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)
|
||
|