fix(ios): improve test script reliability and pending notification detection

Fixed multiple issues in iOS test script and added logging for test compatibility:

Test Script Fixes:
- Fixed get_simulator_id() to correctly extract UUID from booted devices
- Fixed get_app_logs() to use log show (historical) instead of log stream (live) to avoid hanging
- Improved check_plugin_configured() with multiple detection methods (app container, app listing, data directory)
- Added ensure_plugin_configured() function matching Android pattern for consistent user interaction flow
- Fixed integer comparison error in booted device check (removed newlines from count)
- Removed 'local' keyword from variables in main script body (local can only be used in functions)
- Fixed APP_BUNDLE_ID to match actual bundle identifier

Pending Notification Detection:
- Improved get_pending_notifications() to parse pendingCount from plugin logs
- Added direct log query without restrictive predicate to catch plugin logs
- Added multiple fallback methods for detecting pending count

Plugin Logging Enhancement:
- Added explicit pendingCount logging in DailyNotificationScheduler after scheduling
- Uses both NSLog() and print() to ensure logs appear in system logs and Xcode console
- Matches Android's alarm count logging pattern for test script compatibility

This resolves script crashes and enables reliable detection of pending notifications
for automated testing.
This commit is contained in:
Jose Olarte III
2025-12-11 20:12:34 +08:00
parent 1bfd87a0e4
commit 527c075941
2 changed files with 146 additions and 27 deletions

View File

@@ -188,7 +188,11 @@ class DailyNotificationScheduler {
self.scheduledNotifications.insert(content.id)
}
print("\(Self.TAG): Notification scheduled successfully for \(scheduledDate)")
// Log pending count for test scripts (matches Android's alarm count logging)
// Use NSLog to ensure it appears in system logs (print() may not always be captured)
let pendingCount = await getPendingNotificationCount()
NSLog("\(Self.TAG): Notification scheduled successfully for \(scheduledDate), id=\(content.id), pendingCount=\(pendingCount)")
print("\(Self.TAG): Notification scheduled successfully for \(scheduledDate), id=\(content.id), pendingCount=\(pendingCount)")
return true
} catch {

View File

@@ -13,7 +13,7 @@ if [ -f "${SCRIPT_DIR}/ios-test-lib.sh" ]; then
fi
# Phase 1 specific configuration
APP_BUNDLE_ID="com.timesafari.ios-test-app"
APP_BUNDLE_ID="com.timesafari.dailynotification.test"
SIMULATOR_DEVICE="iPhone 15"
LOG_PREFIX="DNP"
@@ -70,13 +70,47 @@ wait_for_ui_action() {
# iOS-specific helper functions
get_simulator_id() {
xcrun simctl list devices available | grep "${SIMULATOR_DEVICE}" | head -1 | sed -E 's/.*\(([^)]+)\).*/\1/'
# First try to find a booted device matching the name
# Extract UUID from line like: " iPhone 15 (6514F1D6-80C2-4D0E-8CB4-6F561C8EA1F1) (Booted)"
local booted_id=$(xcrun simctl list devices | grep "${SIMULATOR_DEVICE}" | grep "Booted" | head -1 | sed -E 's/.*\(([A-F0-9-]{36})\).*\(Booted\).*/\1/')
if [ -n "${booted_id}" ] && [ "${booted_id}" != "Booted" ]; then
echo "${booted_id}"
return 0
fi
# If no booted device, try available devices
local available_id=$(xcrun simctl list devices available | grep "${SIMULATOR_DEVICE}" | head -1 | sed -E 's/.*\(([A-F0-9-]{36})\).*/\1/')
if [ -n "${available_id}" ]; then
echo "${available_id}"
return 0
fi
# Last resort: try any device with similar name (handles "iPhone 15" vs "iPhone 15 Pro")
# Prefer booted devices
local any_id=$(xcrun simctl list devices | grep -i "iphone.*15" | grep "Booted" | head -1 | sed -E 's/.*\(([A-F0-9-]{36})\).*\(Booted\).*/\1/')
if [ -n "${any_id}" ] && [ "${any_id}" != "Booted" ]; then
echo "${any_id}"
return 0
fi
# Try any iPhone 15 device (not necessarily booted)
any_id=$(xcrun simctl list devices | grep -i "iphone.*15" | head -1 | sed -E 's/.*\(([A-F0-9-]{36})\).*/\1/')
if [ -n "${any_id}" ]; then
echo "${any_id}"
return 0
fi
return 1
}
get_app_logs() {
local device_id=$1
local lines=${2:-50}
xcrun simctl spawn "${device_id}" log stream --level=debug --predicate 'processImagePath contains "ios-test-app"' --style=compact 2>/dev/null | head -n "${lines}" || echo ""
# Use log show (historical) instead of log stream (live) to avoid hanging
# This matches Android's approach of using logcat -d (historical logs)
# log stream can block indefinitely waiting for new logs
# Remove predicate to catch all logs (plugin logs may not match processImagePath predicate)
xcrun simctl spawn "${device_id}" log show --last 2m --style=compact 2>/dev/null | grep -iE "(dailynotification|ios-test-app|App)" | tail -n "${lines}" || echo ""
}
check_plugin_configured() {
@@ -88,16 +122,32 @@ check_plugin_configured() {
return 1
fi
# Check if app has been launched (indicates configuration may exist)
local app_data=$(xcrun simctl get_app_container "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || echo "")
# Check multiple ways to determine if app is installed/configured:
# 1. Check if app container exists (most reliable for installed apps)
local app_container=$(xcrun simctl get_app_container "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || echo "")
if [ -n "${app_data}" ]; then
print_success "App data exists (plugin may be configured)"
# 2. Check if app is listed in simulator (installed)
local app_listed=$(xcrun simctl listapps "${device_id}" 2>/dev/null | grep -c "${APP_BUNDLE_ID}" || echo "0")
app_listed=$(echo "${app_listed}" | tr -d '\n' | head -1)
app_listed=${app_listed:-0}
# 3. Check if app data directory exists (indicates app has been launched)
local data_root="$HOME/Library/Developer/CoreSimulator/Devices/${device_id}/data/Containers/Data/Application"
local app_data_exists=""
if [ -d "${data_root}" ]; then
# Check if any app data directory exists (app has been launched at least once)
app_data_exists=$(find "${data_root}" -maxdepth 1 -type d 2>/dev/null | head -1 || echo "")
fi
# If any check indicates app exists, consider it potentially configured
if [ -n "${app_container}" ] || [ "${app_listed}" -gt "0" ] || [ -n "${app_data_exists}" ]; then
print_success "App detected (plugin may be configured)"
print_info "Please verify in the app UI that you see:"
echo " ⚙️ Plugin Settings: ✅ Configured"
echo " 🔌 Native Fetcher: ✅ Configured"
echo ""
echo "If both show ✅, the plugin is configured and you can skip configuration."
echo "If not configured, you'll need to click 'Configure Plugin' in the app UI."
return 0
else
print_info "Plugin not configured (no app data found)"
@@ -106,6 +156,48 @@ check_plugin_configured() {
fi
}
ensure_plugin_configured() {
if check_plugin_configured; then
# Plugin might be configured, but let user verify (matches Android pattern)
wait_for_ui_action "Please check the Plugin Status section at the top of the app.
If you see:
- ⚙️ Plugin Settings: ✅ Configured
- 🔌 Native Fetcher: ✅ Configured
- 🔔 Notifications: ✅ Granted (or similar)
Then the plugin is already configured - just press Enter to continue.
If any show ❌ or 'Not configured':
- Click 'Request Permissions' if notifications are not granted
- Click 'Configure Plugin' if settings/fetcher are not configured
- Wait for all to show ✅, then press Enter."
# Give a moment for any configuration that just happened
sleep 2
print_success "Continuing with tests (plugin configuration verified or skipped)"
return 0
else
# Plugin definitely needs configuration
print_info "Plugin needs configuration"
# First ensure permissions
ensure_permissions
wait_for_ui_action "Click the 'Configure Plugin' button in the app UI.
Wait for the status to update:
- ⚙️ Plugin Settings: Should change to ✅ Configured
- 🔌 Native Fetcher: Should change to ✅ Configured
Once both show ✅, press Enter to continue."
# Verify configuration completed
sleep 2
print_success "Plugin configuration completed (or verified)"
fi
}
check_permissions() {
print_info "Checking notification permissions..."
@@ -152,7 +244,9 @@ launch_app() {
fi
# Boot simulator if not running
local booted=$(xcrun simctl list devices | grep "${device_id}" | grep -c "Booted" || echo "0")
local booted=$(xcrun simctl list devices | grep "${device_id}" | grep -c "Booted" 2>/dev/null || echo "0")
booted=$(echo "${booted}" | tr -d '\n' | head -1) # Remove newlines and take first value
booted=${booted:-0} # Default to 0 if empty
if [ "${booted}" -eq "0" ]; then
print_info "Booting simulator..."
xcrun simctl boot "${device_id}" 2>/dev/null || true
@@ -181,11 +275,38 @@ get_pending_notifications() {
return
fi
# Note: iOS doesn't provide direct command-line access to pending notifications
# This would need to be implemented via a plugin method
# For now, we'll check logs for notification scheduling
local count=$(get_app_logs "${device_id}" 100 | grep -c "scheduled notification" || echo "0")
echo "${count}"
# Note: iOS doesn't provide direct command-line access to pending notifications like Android's dumpsys alarm
# We check logs for the explicit pendingCount that the plugin now logs after scheduling
# Get logs directly without predicate to catch plugin logs (plugin runs in app process)
local logs=$(xcrun simctl spawn "${device_id}" log show --last 2m --style=compact 2>/dev/null | grep -iE "(DailyNotificationScheduler|pendingCount|dailynotification)" | tail -100 || echo "")
# Method 1: Look for explicit pendingCount in scheduling logs (most reliable)
# The plugin logs: "Notification scheduled successfully for ..., id=..., pendingCount=X"
# Match case-insensitive and extract the number after pendingCount=
local pending_count=$(echo "${logs}" | grep -iE "pendingCount[=:][[:space:]]*[0-9]+" | tail -1 | grep -oE "[0-9]+" | head -1 || echo "")
# Method 2: Look for "pending" count in status responses
# The plugin's getNotificationStatus returns "pending": count
local status_count=$(echo "${logs}" | grep -E "\"pending\"[[:space:]]*:[[:space:]]*[0-9]+" | tail -1 | grep -oE "[0-9]+" | head -1 || echo "")
# Method 3: Count unique notification IDs that were scheduled (fallback)
# Extract notification IDs from scheduling logs (format: "id=daily_...")
local notification_ids=$(echo "${logs}" | grep -oE "id=[a-zA-Z0-9_.-]+" | sed 's/id=//' | sort -u | wc -l | tr -d ' ')
notification_ids=${notification_ids:-0}
# Prefer explicit pendingCount from scheduling log (most reliable)
if [ -n "${pending_count}" ] && [ "${pending_count}" -ge "0" ] 2>/dev/null && [ "${pending_count}" -le "64" ] 2>/dev/null; then
echo "${pending_count}"
# Otherwise use status count if available
elif [ -n "${status_count}" ] && [ "${status_count}" -ge "0" ] 2>/dev/null && [ "${status_count}" -le "64" ] 2>/dev/null; then
echo "${status_count}"
# Fallback to counting unique notification IDs
elif [ "${notification_ids}" -gt "0" ]; then
echo "${notification_ids}"
# Default to 0 if nothing found
else
echo "0"
fi
}
should_run_test() {
@@ -223,13 +344,7 @@ if should_run_test "0" "$@"; then
print_step "1" "Schedule a test notification for near-future..."
launch_app
check_plugin_configured || {
wait_for_ui_action "In the app UI, click 'Configure Plugin' to set up the plugin.
This will initialize the database and storage.
Once configured, press Enter to continue."
}
ensure_plugin_configured
INITIAL_COUNT=$(get_pending_notifications)
print_info "Current pending notifications: ${INITIAL_COUNT}"
@@ -302,7 +417,7 @@ This creates a notification that should persist across app termination."
print_step "2" "Terminate app (simulate cold start)..."
print_info "Terminating app to simulate cold start scenario..."
local device_id=$(get_simulator_id)
device_id=$(get_simulator_id)
xcrun simctl terminate "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || true
print_info "App terminated. Waiting 5 seconds..."
@@ -314,8 +429,8 @@ This creates a notification that should persist across app termination."
print_info "Checking logs for recovery activity..."
sleep 3
local device_id=$(get_simulator_id)
local logs=$(get_app_logs "${device_id}" 100)
device_id=$(get_simulator_id)
logs=$(get_app_logs "${device_id}" 100)
if echo "${logs}" | grep -q "DNP-REACTIVATION\|recovery\|missed"; then
print_success "Recovery activity detected in logs"
@@ -355,7 +470,7 @@ if should_run_test "2" "$@"; then
print_step "2" "Terminate app..."
print_info "Terminating app (simulating swipe from app switcher)..."
local device_id=$(get_simulator_id)
device_id=$(get_simulator_id)
xcrun simctl terminate "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || true
print_info "App terminated. Waiting 3 seconds..."
@@ -399,8 +514,8 @@ The app should show an error message and NOT crash."
print_info "Checking logs for error handling..."
sleep 2
local device_id=$(get_simulator_id)
local logs=$(get_app_logs "${device_id}" 50)
device_id=$(get_simulator_id)
logs=$(get_app_logs "${device_id}" 50)
if echo "${logs}" | grep -q "error\|invalid\|Error"; then
print_success "Error handling detected in logs"