@@ -18,6 +18,7 @@ vi.mock('../../../platforms/ios/runner-client.ts', async (importOriginal) => {
1818 return {
1919 ...actual ,
2020 prewarmIosRunnerSession : vi . fn ( ) ,
21+ runIosRunnerCommand : vi . fn ( async ( ) => ( { currentUptimeMs : 42 } ) ) ,
2122 stopIosRunnerSession : vi . fn ( async ( ) => { } ) ,
2223 } ;
2324} ) ;
@@ -96,6 +97,7 @@ import { ensureDeviceReady } from '../../device-ready.ts';
9697import { applyRuntimeHintsToApp , clearRuntimeHintsFromApp } from '../../runtime-hints.ts' ;
9798import {
9899 prewarmIosRunnerSession ,
100+ runIosRunnerCommand ,
99101 stopIosRunnerSession ,
100102} from '../../../platforms/ios/runner-client.ts' ;
101103import { runMacOsAlertAction } from '../../../platforms/ios/macos-helper.ts' ;
@@ -118,6 +120,7 @@ const mockEnsureDeviceReady = vi.mocked(ensureDeviceReady);
118120const mockApplyRuntimeHints = vi . mocked ( applyRuntimeHintsToApp ) ;
119121const mockClearRuntimeHints = vi . mocked ( clearRuntimeHintsFromApp ) ;
120122const mockPrewarmIosRunnerSession = vi . mocked ( prewarmIosRunnerSession ) ;
123+ const mockRunIosRunnerCommand = vi . mocked ( runIosRunnerCommand ) ;
121124const mockStopIosRunner = vi . mocked ( stopIosRunnerSession ) ;
122125const mockDismissMacOsAlert = vi . mocked ( runMacOsAlertAction ) ;
123126const mockSettleSimulator = vi . mocked ( settleIosSimulator ) ;
@@ -148,6 +151,8 @@ beforeEach(() => {
148151 mockClearRuntimeHints . mockReset ( ) ;
149152 mockClearRuntimeHints . mockResolvedValue ( undefined ) ;
150153 mockPrewarmIosRunnerSession . mockReset ( ) ;
154+ mockRunIosRunnerCommand . mockReset ( ) ;
155+ mockRunIosRunnerCommand . mockResolvedValue ( { currentUptimeMs : 42 } ) ;
151156 mockStopIosRunner . mockReset ( ) ;
152157 mockStopIosRunner . mockResolvedValue ( undefined ) ;
153158 mockDismissMacOsAlert . mockReset ( ) ;
@@ -2094,6 +2099,118 @@ test('open iOS URL without app bundle id skips runner prewarm', async () => {
20942099 expect ( mockPrewarmIosRunnerSession ) . not . toHaveBeenCalled ( ) ;
20952100} ) ;
20962101
2102+ test ( 'prepare ios-runner starts the XCTest runner on an explicit iOS selector' , async ( ) => {
2103+ const sessionStore = makeSessionStore ( ) ;
2104+ const sessionName = 'prepare-ios-runner' ;
2105+ mockResolveTargetDevice . mockResolvedValue ( {
2106+ platform : 'ios' ,
2107+ id : 'sim-1' ,
2108+ name : 'iPhone 17 Pro' ,
2109+ kind : 'simulator' ,
2110+ booted : true ,
2111+ } ) ;
2112+
2113+ const response = await handleSessionCommands ( {
2114+ req : {
2115+ token : 't' ,
2116+ session : sessionName ,
2117+ command : 'prepare' ,
2118+ positionals : [ 'ios-runner' ] ,
2119+ flags : { platform : 'ios' , udid : 'sim-1' , timeoutMs : 240000 } ,
2120+ meta : { requestId : 'prepare-request' } ,
2121+ } ,
2122+ sessionName,
2123+ logPath : path . join ( os . tmpdir ( ) , 'daemon.log' ) ,
2124+ sessionStore,
2125+ invoke : noopInvoke ,
2126+ } ) ;
2127+
2128+ expect ( response ) . toBeTruthy ( ) ;
2129+ expect ( response ?. ok ) . toBe ( true ) ;
2130+ expect ( mockEnsureDeviceReady ) . toHaveBeenCalledWith (
2131+ expect . objectContaining ( { platform : 'ios' , id : 'sim-1' } ) ,
2132+ ) ;
2133+ expect ( mockRunIosRunnerCommand ) . toHaveBeenCalledTimes ( 1 ) ;
2134+ expect ( mockRunIosRunnerCommand ) . toHaveBeenCalledWith (
2135+ expect . objectContaining ( { platform : 'ios' , id : 'sim-1' } ) ,
2136+ { command : 'uptime' } ,
2137+ expect . objectContaining ( {
2138+ logPath : expect . stringMatching ( / d a e m o n \. l o g $ / ) ,
2139+ requestId : 'prepare-request' ,
2140+ } ) ,
2141+ ) ;
2142+ expect ( ( response as any ) . data ) . toMatchObject ( {
2143+ action : 'ios-runner' ,
2144+ platform : 'ios' ,
2145+ deviceId : 'sim-1' ,
2146+ deviceName : 'iPhone 17 Pro' ,
2147+ kind : 'simulator' ,
2148+ runner : { currentUptimeMs : 42 } ,
2149+ message : 'Prepared iOS runner: iPhone 17 Pro' ,
2150+ } ) ;
2151+ expect ( sessionStore . get ( sessionName ) ) . toBeUndefined ( ) ;
2152+ } ) ;
2153+
2154+ test ( 'prepare ios-runner rejects non-iOS devices' , async ( ) => {
2155+ const sessionStore = makeSessionStore ( ) ;
2156+ mockResolveTargetDevice . mockResolvedValue ( {
2157+ platform : 'android' ,
2158+ id : 'emulator-5554' ,
2159+ name : 'Pixel 9 Pro XL' ,
2160+ kind : 'emulator' ,
2161+ booted : true ,
2162+ } ) ;
2163+
2164+ const response = await handleSessionCommands ( {
2165+ req : {
2166+ token : 't' ,
2167+ session : 'prepare-android' ,
2168+ command : 'prepare' ,
2169+ positionals : [ 'ios-runner' ] ,
2170+ flags : { platform : 'android' , serial : 'emulator-5554' } ,
2171+ } ,
2172+ sessionName : 'prepare-android' ,
2173+ logPath : path . join ( os . tmpdir ( ) , 'daemon.log' ) ,
2174+ sessionStore,
2175+ invoke : noopInvoke ,
2176+ } ) ;
2177+
2178+ expect ( response ) . toBeTruthy ( ) ;
2179+ expect ( response ?. ok ) . toBe ( false ) ;
2180+ if ( response && ! response . ok ) {
2181+ expect ( response . error . code ) . toBe ( 'UNSUPPORTED_OPERATION' ) ;
2182+ expect ( response . error . message ) . toBe ( 'prepare ios-runner is only supported on iOS' ) ;
2183+ }
2184+ expect ( mockRunIosRunnerCommand ) . not . toHaveBeenCalled ( ) ;
2185+ } ) ;
2186+
2187+ test ( 'prepare requires the ios-runner subcommand' , async ( ) => {
2188+ const sessionStore = makeSessionStore ( ) ;
2189+
2190+ const response = await handleSessionCommands ( {
2191+ req : {
2192+ token : 't' ,
2193+ session : 'prepare-invalid' ,
2194+ command : 'prepare' ,
2195+ positionals : [ ] ,
2196+ flags : { platform : 'ios' } ,
2197+ } ,
2198+ sessionName : 'prepare-invalid' ,
2199+ logPath : path . join ( os . tmpdir ( ) , 'daemon.log' ) ,
2200+ sessionStore,
2201+ invoke : noopInvoke ,
2202+ } ) ;
2203+
2204+ expect ( response ) . toBeTruthy ( ) ;
2205+ expect ( response ?. ok ) . toBe ( false ) ;
2206+ if ( response && ! response . ok ) {
2207+ expect ( response . error . code ) . toBe ( 'INVALID_ARGS' ) ;
2208+ expect ( response . error . message ) . toBe ( 'prepare requires a subcommand: ios-runner' ) ;
2209+ }
2210+ expect ( mockResolveTargetDevice ) . not . toHaveBeenCalled ( ) ;
2211+ expect ( mockRunIosRunnerCommand ) . not . toHaveBeenCalled ( ) ;
2212+ } ) ;
2213+
20972214test ( 'open web URL on iOS device session without active app falls back to Safari' , async ( ) => {
20982215 const sessionStore = makeSessionStore ( ) ;
20992216 const sessionName = 'ios-device-session' ;
0 commit comments