diff --git a/README.md b/README.md index cc50e9c3..135fbb93 100644 --- a/README.md +++ b/README.md @@ -788,32 +788,42 @@ Core workflow: ## Integrations -### iOS Simulator - -Control real Mobile Safari in the iOS Simulator for authentic mobile web testing. Requires macOS with Xcode. - -**Setup:** - -```bash -# Install Appium and XCUITest driver -npm install -g appium -appium driver install xcuitest -``` +### iOS Safari (via Appium) + +agent-browser supports iOS Safari automation via Appium and the XCUITest driver. + +**Prerequisites:** +1. macOS with Xcode installed +2. Install Appium and XCUITest driver: + ```bash + npm install -g appium + appium driver install xcuitest + ``` +3. Build WebDriverAgent for simulator: + ```bash + cd ~/.appium/node_modules/appium-xcuitest-driver/node_modules/appium-webdriveragent + xcodebuild build-for-testing -project WebDriverAgent.xcodeproj -scheme WebDriverAgentRunner -sdk iphonesimulator -derivedDataPath ./DerivedData CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO + ``` **Usage:** ```bash -# List available iOS simulators -agent-browser device list +# List available devices +agent-browser -p ios device list -# Launch Safari on a specific device +# Open a URL on iOS Simulator agent-browser -p ios --device "iPhone 16 Pro" open https://example.com +# Take a screenshot +agent-browser -p ios screenshot ./ios-screenshot.png + +# Run JavaScript +agent-browser -p ios eval "navigator.userAgent" + # Same commands as desktop agent-browser -p ios snapshot -i agent-browser -p ios tap @e1 agent-browser -p ios fill @e2 "text" -agent-browser -p ios screenshot mobile.png # Mobile-specific commands agent-browser -p ios swipe up @@ -823,6 +833,8 @@ agent-browser -p ios swipe down 500 agent-browser -p ios close ``` +**Supported commands:** navigate, click, type, fill, screenshot, eval, scroll, swipe, wait, and more (27+ commands). + Or use environment variables: ```bash @@ -841,6 +853,11 @@ agent-browser open https://example.com **Note:** The iOS provider boots the simulator, starts Appium, and controls Safari. First launch takes ~30-60 seconds; subsequent commands are fast. +**Limitations:** +- Tab/window management not supported (Safari mobile limitation) +- No PDF generation +- No screencast/video recording (requires CDP) + #### Real Device Support Appium also supports real iOS devices connected via USB. This requires additional one-time setup: diff --git a/src/daemon.ts b/src/daemon.ts index 36d5acc4..ed184ace 100644 --- a/src/daemon.ts +++ b/src/daemon.ts @@ -381,7 +381,6 @@ export async function startDaemon(options?: { ) { if (isIOS && manager instanceof IOSManager) { // Auto-launch iOS Safari - // Check for device in command first (for reused daemons), then fall back to env vars const cmd = parseResult.command as { iosDevice?: string }; const iosDevice = cmd.iosDevice || process.env.AGENT_BROWSER_IOS_DEVICE; await manager.launch({ diff --git a/src/protocol.ts b/src/protocol.ts index cb543ede..db54c3ed 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -56,6 +56,7 @@ const navigateSchema = baseCommandSchema.extend({ url: z.string().min(1), waitUntil: z.enum(['load', 'domcontentloaded', 'networkidle']).optional(), headers: z.record(z.string()).optional(), + iosDevice: z.string().optional(), }); const clickSchema = baseCommandSchema.extend({ diff --git a/test/ios-manager.test.ts b/test/ios-manager.test.ts new file mode 100644 index 00000000..df338ab5 --- /dev/null +++ b/test/ios-manager.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from 'vitest'; +import { parseCommand } from '../src/protocol.js'; +import { IOSManager } from '../src/ios-manager.js'; + +// Helper to create command JSON string +const cmd = (obj: object) => JSON.stringify(obj); + +describe('iOS provider', () => { + describe('IOSManager instantiation', () => { + it('should be instantiable', () => { + const manager = new IOSManager(); + expect(manager).toBeDefined(); + expect(manager).toBeInstanceOf(IOSManager); + }); + }); + + describe('iosDevice schema parsing', () => { + it('should preserve iosDevice in navigate command', () => { + const result = parseCommand( + cmd({ + id: 'test-1', + action: 'navigate', + url: 'https://example.com', + iosDevice: 'iPhone 16 Pro', + }) + ); + expect(result.success).toBe(true); + if (result.success) { + expect((result.command as any).iosDevice).toBe('iPhone 16 Pro'); + } + }); + + it('should accept navigate without iosDevice', () => { + const result = parseCommand( + cmd({ + id: 'test-2', + action: 'navigate', + url: 'https://example.com', + }) + ); + expect(result.success).toBe(true); + if (result.success) { + expect((result.command as any).iosDevice).toBeUndefined(); + } + }); + }); +});