#!/usr/bin/env python3 """ Fastbot Runner for iOS Runs Fastbot testing and monitors status via HTTP API. """ import time import logging import subprocess from typing import Optional import sys import os from pathlib import Path # Setup path ROOT = Path(__file__).resolve().parent sys.path.insert(0, str(ROOT)) logger = logging.getLogger(__name__) class FastbotTestRunner: """Fastbot test runner with status monitoring""" def __init__(self, udid: str, fastbot_port: int = 9197): self.udid = udid self.fastbot_port = fastbot_port def monitor_status(self, interval: int = 5, process: Optional[subprocess.Popen] = None) -> None: """ Monitor Fastbot running status via HTTP API. :param interval: Status check interval in seconds :param process: Fastbot process to monitor """ try: import requests except ImportError: logger.warning("requests not available, skipping status monitoring") if process: process.wait() return url = f"http://127.0.0.1:{self.fastbot_port}/status" logger.info(f"Monitoring Fastbot status at {url}") last_status = None while True: # Check if process has ended if process and process.poll() is not None: logger.info("Fastbot process has ended") break try: response = requests.get(url, timeout=2) if response.status_code == 200: data = response.json() running = data.get("runingStatus", False) # Note: typo in API # Log status changes if running != last_status: if running: logger.info("[STATUS] Fastbot is running ✓") else: logger.info("[STATUS] Fastbot has stopped") break last_status = running else: logger.warning(f"[STATUS] HTTP {response.status_code}") except requests.RequestException as e: if last_status is not None: # Only warn if we were connected before logger.warning(f"[STATUS] Cannot reach status endpoint: {e}") except Exception as e: logger.error(f"[STATUS] Unexpected error: {e}") time.sleep(interval) logger.info("Status monitoring completed") def run_fastbot( self, target_bundle_id: str, duration: int = 600, throttle: int = 1000, output_dir: Optional[str] = None ) -> tuple: """ Run Fastbot test with status monitoring. :param target_bundle_id: App to test :param duration: Test duration in seconds :param throttle: Event throttle in milliseconds :param output_dir: Output directory for logs :return: (exit_code, stdout) """ from utils_ios.go_ios.go_ios_runner import GoIOSRunner logger.info(f"Starting Fastbot test for {target_bundle_id}") logger.info(f"Duration: {duration}s, Throttle: {throttle}ms") runner = GoIOSRunner(udid=self.udid) try: # Start infrastructure (tunnel not needed, but port forward required) logger.info("Starting infrastructure...") if not runner.start_infrastructure(wda_port=8100, fastbot_port=self.fastbot_port): logger.error("Failed to start infrastructure") return 1, "" logger.info("Infrastructure started, waiting for stabilization...") time.sleep(3) # Start Fastbot logger.info("Starting Fastbot...") fastbot_process = runner.start_fastbot( target_bundle_id=target_bundle_id, duration=duration, throttle=throttle ) logger.info(f"Fastbot started with PID: {fastbot_process.pid}") # Monitor status self.monitor_status(interval=5, process=fastbot_process) # Wait for completion fastbot_process.wait() returncode = fastbot_process.returncode logger.info(f"Fastbot completed with exit code: {returncode}") return returncode, "" except KeyboardInterrupt: logger.info("User interrupted Fastbot test") return 130, "" # SIGINT exit code finally: logger.info("Cleaning up...") runner.stop_all() if __name__ == "__main__": logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') # Quick test runner = FastbotTestRunner(udid="00008101-0016601022C0001E") runner.run_fastbot("com.apple.AppStore", duration=30, throttle=1000)