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:
@@ -188,7 +188,11 @@ class DailyNotificationScheduler {
|
|||||||
self.scheduledNotifications.insert(content.id)
|
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
|
return true
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ if [ -f "${SCRIPT_DIR}/ios-test-lib.sh" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Phase 1 specific configuration
|
# Phase 1 specific configuration
|
||||||
APP_BUNDLE_ID="com.timesafari.ios-test-app"
|
APP_BUNDLE_ID="com.timesafari.dailynotification.test"
|
||||||
SIMULATOR_DEVICE="iPhone 15"
|
SIMULATOR_DEVICE="iPhone 15"
|
||||||
LOG_PREFIX="DNP"
|
LOG_PREFIX="DNP"
|
||||||
|
|
||||||
@@ -70,13 +70,47 @@ wait_for_ui_action() {
|
|||||||
|
|
||||||
# iOS-specific helper functions
|
# iOS-specific helper functions
|
||||||
get_simulator_id() {
|
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() {
|
get_app_logs() {
|
||||||
local device_id=$1
|
local device_id=$1
|
||||||
local lines=${2:-50}
|
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() {
|
check_plugin_configured() {
|
||||||
@@ -88,16 +122,32 @@ check_plugin_configured() {
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if app has been launched (indicates configuration may exist)
|
# Check multiple ways to determine if app is installed/configured:
|
||||||
local app_data=$(xcrun simctl get_app_container "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || echo "")
|
# 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
|
# 2. Check if app is listed in simulator (installed)
|
||||||
print_success "App data exists (plugin may be configured)"
|
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:"
|
print_info "Please verify in the app UI that you see:"
|
||||||
echo " ⚙️ Plugin Settings: ✅ Configured"
|
echo " ⚙️ Plugin Settings: ✅ Configured"
|
||||||
echo " 🔌 Native Fetcher: ✅ Configured"
|
echo " 🔌 Native Fetcher: ✅ Configured"
|
||||||
echo ""
|
echo ""
|
||||||
echo "If both show ✅, the plugin is configured and you can skip configuration."
|
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
|
return 0
|
||||||
else
|
else
|
||||||
print_info "Plugin not configured (no app data found)"
|
print_info "Plugin not configured (no app data found)"
|
||||||
@@ -106,6 +156,48 @@ check_plugin_configured() {
|
|||||||
fi
|
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() {
|
check_permissions() {
|
||||||
print_info "Checking notification permissions..."
|
print_info "Checking notification permissions..."
|
||||||
|
|
||||||
@@ -152,7 +244,9 @@ launch_app() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Boot simulator if not running
|
# 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
|
if [ "${booted}" -eq "0" ]; then
|
||||||
print_info "Booting simulator..."
|
print_info "Booting simulator..."
|
||||||
xcrun simctl boot "${device_id}" 2>/dev/null || true
|
xcrun simctl boot "${device_id}" 2>/dev/null || true
|
||||||
@@ -181,11 +275,38 @@ get_pending_notifications() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Note: iOS doesn't provide direct command-line access to pending notifications
|
# Note: iOS doesn't provide direct command-line access to pending notifications like Android's dumpsys alarm
|
||||||
# This would need to be implemented via a plugin method
|
# We check logs for the explicit pendingCount that the plugin now logs after scheduling
|
||||||
# For now, we'll check logs for notification scheduling
|
# Get logs directly without predicate to catch plugin logs (plugin runs in app process)
|
||||||
local count=$(get_app_logs "${device_id}" 100 | grep -c "scheduled notification" || echo "0")
|
local logs=$(xcrun simctl spawn "${device_id}" log show --last 2m --style=compact 2>/dev/null | grep -iE "(DailyNotificationScheduler|pendingCount|dailynotification)" | tail -100 || echo "")
|
||||||
echo "${count}"
|
|
||||||
|
# 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() {
|
should_run_test() {
|
||||||
@@ -223,13 +344,7 @@ if should_run_test "0" "$@"; then
|
|||||||
|
|
||||||
print_step "1" "Schedule a test notification for near-future..."
|
print_step "1" "Schedule a test notification for near-future..."
|
||||||
launch_app
|
launch_app
|
||||||
check_plugin_configured || {
|
ensure_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."
|
|
||||||
}
|
|
||||||
|
|
||||||
INITIAL_COUNT=$(get_pending_notifications)
|
INITIAL_COUNT=$(get_pending_notifications)
|
||||||
print_info "Current pending notifications: ${INITIAL_COUNT}"
|
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_step "2" "Terminate app (simulate cold start)..."
|
||||||
print_info "Terminating app to simulate cold start scenario..."
|
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
|
xcrun simctl terminate "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || true
|
||||||
|
|
||||||
print_info "App terminated. Waiting 5 seconds..."
|
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..."
|
print_info "Checking logs for recovery activity..."
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
local device_id=$(get_simulator_id)
|
device_id=$(get_simulator_id)
|
||||||
local logs=$(get_app_logs "${device_id}" 100)
|
logs=$(get_app_logs "${device_id}" 100)
|
||||||
|
|
||||||
if echo "${logs}" | grep -q "DNP-REACTIVATION\|recovery\|missed"; then
|
if echo "${logs}" | grep -q "DNP-REACTIVATION\|recovery\|missed"; then
|
||||||
print_success "Recovery activity detected in logs"
|
print_success "Recovery activity detected in logs"
|
||||||
@@ -355,7 +470,7 @@ if should_run_test "2" "$@"; then
|
|||||||
print_step "2" "Terminate app..."
|
print_step "2" "Terminate app..."
|
||||||
print_info "Terminating app (simulating swipe from app switcher)..."
|
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
|
xcrun simctl terminate "${device_id}" "${APP_BUNDLE_ID}" 2>/dev/null || true
|
||||||
|
|
||||||
print_info "App terminated. Waiting 3 seconds..."
|
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..."
|
print_info "Checking logs for error handling..."
|
||||||
sleep 2
|
sleep 2
|
||||||
|
|
||||||
local device_id=$(get_simulator_id)
|
device_id=$(get_simulator_id)
|
||||||
local logs=$(get_app_logs "${device_id}" 50)
|
logs=$(get_app_logs "${device_id}" 50)
|
||||||
|
|
||||||
if echo "${logs}" | grep -q "error\|invalid\|Error"; then
|
if echo "${logs}" | grep -q "error\|invalid\|Error"; then
|
||||||
print_success "Error handling detected in logs"
|
print_success "Error handling detected in logs"
|
||||||
|
|||||||
Reference in New Issue
Block a user