mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-02-28 12:22:43 -08:00
241 lines
6.5 KiB
Python
241 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
iOS Push Notification Simulator
|
|
|
|
Send simulated push notifications to test notification handling.
|
|
Supports custom payloads and test tracking.
|
|
|
|
Usage: python scripts/push_notification.py --bundle-id com.app --title "Alert" --body "Message"
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
from common import resolve_udid
|
|
|
|
|
|
class PushNotificationSender:
|
|
"""Sends simulated push notifications to iOS simulator."""
|
|
|
|
def __init__(self, udid: str | None = None):
|
|
"""Initialize push notification sender.
|
|
|
|
Args:
|
|
udid: Optional device UDID (auto-detects booted simulator if None)
|
|
"""
|
|
self.udid = udid
|
|
|
|
def send(
|
|
self,
|
|
bundle_id: str,
|
|
payload: dict | str,
|
|
_test_name: str | None = None,
|
|
_expected_behavior: str | None = None,
|
|
) -> bool:
|
|
"""
|
|
Send push notification to app.
|
|
|
|
Args:
|
|
bundle_id: Target app bundle ID
|
|
payload: Push payload (dict or JSON string) or path to JSON file
|
|
test_name: Test scenario name for tracking
|
|
expected_behavior: Expected behavior after notification arrives
|
|
|
|
Returns:
|
|
Success status
|
|
"""
|
|
# Handle different payload formats
|
|
if isinstance(payload, str):
|
|
# Check if it's a file path
|
|
payload_path = Path(payload)
|
|
if payload_path.exists():
|
|
with open(payload_path) as f:
|
|
payload_data = json.load(f)
|
|
else:
|
|
# Try to parse as JSON string
|
|
try:
|
|
payload_data = json.loads(payload)
|
|
except json.JSONDecodeError:
|
|
print(f"Error: Invalid JSON payload: {payload}")
|
|
return False
|
|
else:
|
|
payload_data = payload
|
|
|
|
# Ensure payload has aps dictionary
|
|
if "aps" not in payload_data:
|
|
payload_data = {"aps": payload_data}
|
|
|
|
# Create temp file with payload
|
|
try:
|
|
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
json.dump(payload_data, f)
|
|
temp_payload_path = f.name
|
|
|
|
# Build simctl command
|
|
cmd = ["xcrun", "simctl", "push"]
|
|
|
|
if self.udid:
|
|
cmd.append(self.udid)
|
|
else:
|
|
cmd.append("booted")
|
|
|
|
cmd.extend([bundle_id, temp_payload_path])
|
|
|
|
# Send notification
|
|
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
|
|
# Clean up temp file
|
|
Path(temp_payload_path).unlink()
|
|
|
|
return True
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
print(f"Error sending push notification: {e.stderr}")
|
|
return False
|
|
except Exception as e:
|
|
print(f"Error: {e}")
|
|
return False
|
|
|
|
def send_simple(
|
|
self,
|
|
bundle_id: str,
|
|
title: str | None = None,
|
|
body: str | None = None,
|
|
badge: int | None = None,
|
|
sound: bool = True,
|
|
) -> bool:
|
|
"""
|
|
Send simple push notification with common parameters.
|
|
|
|
Args:
|
|
bundle_id: Target app bundle ID
|
|
title: Alert title
|
|
body: Alert body
|
|
badge: Badge number
|
|
sound: Whether to play sound
|
|
|
|
Returns:
|
|
Success status
|
|
"""
|
|
payload = {}
|
|
|
|
if title or body:
|
|
alert = {}
|
|
if title:
|
|
alert["title"] = title
|
|
if body:
|
|
alert["body"] = body
|
|
payload["alert"] = alert
|
|
|
|
if badge is not None:
|
|
payload["badge"] = badge
|
|
|
|
if sound:
|
|
payload["sound"] = "default"
|
|
|
|
# Wrap in aps
|
|
full_payload = {"aps": payload}
|
|
|
|
return self.send(bundle_id, full_payload)
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
parser = argparse.ArgumentParser(description="Send simulated push notification to iOS app")
|
|
|
|
# Required
|
|
parser.add_argument(
|
|
"--bundle-id", required=True, help="Target app bundle ID (e.g., com.example.app)"
|
|
)
|
|
|
|
# Simple payload options
|
|
parser.add_argument("--title", help="Alert title (for simple notifications)")
|
|
parser.add_argument("--body", help="Alert body message")
|
|
parser.add_argument("--badge", type=int, help="Badge number")
|
|
parser.add_argument("--no-sound", action="store_true", help="Don't play notification sound")
|
|
|
|
# Custom payload
|
|
parser.add_argument(
|
|
"--payload",
|
|
help="Custom JSON payload file or inline JSON string",
|
|
)
|
|
|
|
# Test tracking
|
|
parser.add_argument("--test-name", help="Test scenario name for tracking")
|
|
parser.add_argument(
|
|
"--expected",
|
|
help="Expected behavior after notification",
|
|
)
|
|
|
|
# Device
|
|
parser.add_argument(
|
|
"--udid",
|
|
help="Device UDID (auto-detects booted simulator if not provided)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Resolve UDID with auto-detection
|
|
try:
|
|
udid = resolve_udid(args.udid)
|
|
except RuntimeError as e:
|
|
print(f"Error: {e}")
|
|
sys.exit(1)
|
|
|
|
sender = PushNotificationSender(udid=udid)
|
|
|
|
# Send notification
|
|
if args.payload:
|
|
# Custom payload mode
|
|
success = sender.send(args.bundle_id, args.payload)
|
|
else:
|
|
# Simple notification mode
|
|
success = sender.send_simple(
|
|
args.bundle_id,
|
|
title=args.title,
|
|
body=args.body,
|
|
badge=args.badge,
|
|
sound=not args.no_sound,
|
|
)
|
|
|
|
if success:
|
|
# Token-efficient output
|
|
output = "Push notification sent"
|
|
|
|
if args.test_name:
|
|
output += f" (test: {args.test_name})"
|
|
|
|
print(output)
|
|
|
|
if args.expected:
|
|
print(f"Expected: {args.expected}")
|
|
|
|
print()
|
|
print("Notification details:")
|
|
if args.title:
|
|
print(f" Title: {args.title}")
|
|
if args.body:
|
|
print(f" Body: {args.body}")
|
|
if args.badge:
|
|
print(f" Badge: {args.badge}")
|
|
|
|
print()
|
|
print("Verify notification handling:")
|
|
print("1. Check app log output: python scripts/log_monitor.py --app " + args.bundle_id)
|
|
print(
|
|
"2. Capture state: python scripts/app_state_capture.py --app-bundle-id "
|
|
+ args.bundle_id
|
|
)
|
|
|
|
else:
|
|
print("Failed to send push notification")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|