1414// - IOS_SWIFT_VERSION overrides default Swift version (default: 5.0).
1515// - IOS_COMMAND_TIMEOUT_MS overrides timeout for build/install/simctl commands (default: 3 minutes).
1616// - IOS_SIMCTL_QUERY_TIMEOUT_MS overrides timeout for polling simctl queries (default: 10 seconds).
17+ // - IOS_APP_CONTAINER_READY_TIMEOUT_MS overrides how long to wait for simctl to expose app containers (default: 60 seconds).
1718// - IOS_BUILD_TIMEOUT_MS overrides timeout for xcodebuild app build (default: IOS_COMMAND_TIMEOUT_MS).
1819// - IOS_COMMAND_MAX_BUFFER_BYTES overrides spawnSync maxBuffer for captured command output (default: 64 MiB).
1920// - IOS_TEST_TIMEOUT_MS overrides max test runtime (default: 10 minutes).
@@ -100,6 +101,10 @@ const simctlQueryTimeoutMs = parseTimeoutMs(
100101 "IOS_SIMCTL_QUERY_TIMEOUT_MS" ,
101102 Math . min ( commandTimeoutMs , 10 * 1000 )
102103) ;
104+ const appContainerReadyTimeoutMs = parseTimeoutMs (
105+ "IOS_APP_CONTAINER_READY_TIMEOUT_MS" ,
106+ Math . max ( 60 * 1000 , simctlQueryTimeoutMs * 6 )
107+ ) ;
103108const commandMaxBufferBytes = parsePositiveInt ( "IOS_COMMAND_MAX_BUFFER_BYTES" , 64 * 1024 * 1024 ) ;
104109const testTimeoutMs = Number ( process . env . IOS_TEST_TIMEOUT_MS || 10 * 60 * 1000 ) ;
105110const inactivityTimeoutMs = Number ( process . env . IOS_TEST_INACTIVITY_TIMEOUT_MS || 2 * 60 * 1000 ) ;
@@ -641,16 +646,31 @@ function buildTestRunnerApp(destination, swiftVersion) {
641646
642647const appContainerPathCache = new Map ( ) ;
643648
644- function getAppContainerPath ( udid , containerType ) {
649+ function getAppContainerPath ( udid , containerType , options = { } ) {
645650 const cacheKey = `${ udid } :${ containerType } ` ;
646651 if ( appContainerPathCache . has ( cacheKey ) ) {
647652 return appContainerPathCache . get ( cacheKey ) ;
648653 }
649654
650- const result = run ( "xcrun" , [ "simctl" , "get_app_container" , udid , bundleId , containerType ] , {
651- timeout : simctlQueryTimeoutMs
652- } ) ;
655+ let result ;
656+ try {
657+ result = run ( "xcrun" , [ "simctl" , "get_app_container" , udid , bundleId , containerType ] , {
658+ timeout : options . timeout ?? simctlQueryTimeoutMs
659+ } ) ;
660+ } catch ( error ) {
661+ if ( ! options . quiet ) {
662+ console . warn ( `WARNING: Unable to resolve ${ containerType } container yet (${ error . message } ).` ) ;
663+ }
664+ return null ;
665+ }
666+
653667 if ( result . status !== 0 ) {
668+ if ( ! options . quiet ) {
669+ const stderr = ( result . stderr || "" ) . trim ( ) ;
670+ console . warn (
671+ `WARNING: Unable to resolve ${ containerType } container yet (simctl exited ${ result . status } ${ stderr ? `: ${ stderr } ` : "" } ).`
672+ ) ;
673+ }
654674 return null ;
655675 }
656676
@@ -661,16 +681,42 @@ function getAppContainerPath(udid, containerType) {
661681 return out || null ;
662682}
663683
664- function getAppDataContainerPath ( udid ) {
665- return getAppContainerPath ( udid , "data" ) ;
684+ function getAppDataContainerPath ( udid , options = { } ) {
685+ return getAppContainerPath ( udid , "data" , options ) ;
666686}
667687
668- function getInstalledAppPath ( udid ) {
669- return getAppContainerPath ( udid , "app" ) ;
688+ function getInstalledAppPath ( udid , options = { } ) {
689+ return getAppContainerPath ( udid , "app" , options ) ;
670690}
671691
672- function removeOldJunitFile ( udid ) {
673- const dataContainer = getAppDataContainerPath ( udid ) ;
692+ async function waitForAppContainerPath ( udid , containerType , timeoutMs = appContainerReadyTimeoutMs ) {
693+ const deadline = Date . now ( ) + timeoutMs ;
694+ let attempt = 0 ;
695+
696+ while ( Date . now ( ) < deadline ) {
697+ const remaining = Math . max ( 1 , deadline - Date . now ( ) ) ;
698+ const containerPath = getAppContainerPath ( udid , containerType , {
699+ quiet : attempt > 0 ,
700+ timeout : Math . min ( simctlQueryTimeoutMs , remaining )
701+ } ) ;
702+ if ( containerPath ) {
703+ return containerPath ;
704+ }
705+
706+ attempt += 1 ;
707+ await sleep ( Math . min ( 250 * attempt , 2000 ) ) ;
708+ }
709+
710+ console . warn ( `WARNING: ${ containerType } container was not available after ${ timeoutMs } ms.` ) ;
711+ return null ;
712+ }
713+
714+ function waitForAppDataContainerPath ( udid , timeoutMs ) {
715+ return waitForAppContainerPath ( udid , "data" , timeoutMs ) ;
716+ }
717+
718+ function removeOldJunitFile ( udid , options = { } ) {
719+ const dataContainer = getAppDataContainerPath ( udid , options ) ;
674720 if ( ! dataContainer ) {
675721 return ;
676722 }
@@ -1154,11 +1200,11 @@ async function main() {
11541200 const builtApp = buildTestRunnerApp ( destination , swiftVersion ) ;
11551201 const appPath = builtApp . appPath ;
11561202
1157- removeOldJunitFile ( udid ) ;
1203+ removeOldJunitFile ( udid , { quiet : true } ) ;
11581204
11591205 let shouldInstallApp = true ;
11601206 if ( builtApp . reusedBuild && process . env . IOS_TEST_FORCE_INSTALL !== "1" ) {
1161- const installedAppPath = getInstalledAppPath ( udid ) ;
1207+ const installedAppPath = getInstalledAppPath ( udid , { quiet : true } ) ;
11621208 if ( installedAppPath && fs . existsSync ( installedAppPath ) ) {
11631209 try {
11641210 syncAppResources ( path . join ( installedAppPath , "app" ) ) ;
@@ -1177,6 +1223,7 @@ async function main() {
11771223 }
11781224
11791225 // Ensure stale result does not get picked up if a previous run already created the container after install.
1226+ await waitForAppDataContainerPath ( udid ) ;
11801227 removeOldJunitFile ( udid ) ;
11811228
11821229 let launchProcess ;
0 commit comments