@@ -531,6 +531,89 @@ function adbShell(serial, command, timeoutMs = 15000) {
531531 } ) . trim ( ) ;
532532}
533533
534+ // After ANR / system_server death, boot_completed can still be 1 while package /
535+ // activity binders are gone — Detox then fails pm install with "Can't find service".
536+ function androidCoreServicesPresent ( serial ) {
537+ try {
538+ const list = adbShell ( serial , 'service list' , 15000 ) ;
539+ return {
540+ hasPackage : / \b p a c k a g e : / . test ( list ) ,
541+ hasActivity : / \b a c t i v i t y : / . test ( list ) ,
542+ list,
543+ } ;
544+ } catch ( err ) {
545+ return {
546+ hasPackage : false ,
547+ hasActivity : false ,
548+ list : '' ,
549+ error : err ?. message || String ( err ) ,
550+ } ;
551+ }
552+ }
553+
554+ // promisify-child-process leaves "Can't find service: …" on err.stderr while
555+ // err.message is only "Process exited with code N". Fold so cold-boot matching
556+ // and retry logs see the device-side fault without reading Detox SPAWN_STDERR alone.
557+ function foldDetoxChildProcessOutputIntoError ( err ) {
558+ if ( ! err || typeof err !== 'object' ) {
559+ return err ;
560+ }
561+ const extras = [ err . stderr , err . stdout ]
562+ . filter ( part => part != null && String ( part ) . trim ( ) . length > 0 )
563+ . map ( part => String ( part ) . trim ( ) ) ;
564+ if ( extras . length === 0 ) {
565+ return err ;
566+ }
567+ const folded = extras . join ( '\n' ) ;
568+ const current = String ( err . message || '' ) ;
569+ if ( current . includes ( folded ) || folded . split ( '\n' ) . every ( line => current . includes ( line ) ) ) {
570+ return err ;
571+ }
572+ err . message = current ? `${ current } \n${ folded } ` : folded ;
573+ return err ;
574+ }
575+
576+ // launchApp timeouts / Jet desync often leave ANR only in Detox console + logcat,
577+ // or leave boot_completed=1 while package/activity binders are already gone.
578+ // Best-effort append so shouldColdBootAndroidOnLaunchRetry sees device-side faults.
579+ function enrichAndroidLaunchFailureWithGuestSignals ( err ) {
580+ if ( ! err || typeof err !== 'object' ) {
581+ return err ;
582+ }
583+ if ( shouldColdBootAndroidOnLaunchRetry ( err ) ) {
584+ return err ;
585+ }
586+ try {
587+ const serial = resolveAndroidSerial ( ) ;
588+ const services = androidCoreServicesPresent ( serial ) ;
589+ if ( ! services . hasPackage || ! services . hasActivity ) {
590+ const missing = [
591+ ! services . hasPackage ? "Can't find service: package" : null ,
592+ ! services . hasActivity ? "Can't find service: activity" : null ,
593+ ]
594+ . filter ( Boolean )
595+ . join ( '\n' ) ;
596+ err . message = `${ err . message || '' } \n${ missing } ` . trim ( ) ;
597+ console . warn (
598+ `[rnfb-e2e] android guest-signal: core services missing package=${ services . hasPackage } activity=${ services . hasActivity } ` ,
599+ ) ;
600+ return err ;
601+ }
602+ const adb = resolveAdbPath ( ) ;
603+ const logTail = execSync ( `${ adb } -s ${ serial } logcat -d -t 400` , {
604+ encoding : 'utf8' ,
605+ timeout : 15000 ,
606+ } ) ;
607+ if ( / A N R E r r o r | A p p l i c a t i o n N o t R e s p o n d i n g | A N R i n / i. test ( logTail ) ) {
608+ err . message = `${ err . message || '' } \nANR detected in recent logcat` . trim ( ) ;
609+ console . warn ( '[rnfb-e2e] android guest-signal: ANR found in recent logcat' ) ;
610+ }
611+ } catch ( probeErr ) {
612+ console . warn ( `[rnfb-e2e] android guest-signal probe failed: ${ probeErr ?. message || probeErr } ` ) ;
613+ }
614+ return err ;
615+ }
616+
534617function parseGuestLoad1Min ( loadavgLine ) {
535618 const first = loadavgLine . trim ( ) . split ( / \s + / ) [ 0 ] ;
536619 const load = parseFloat ( first ) ;
@@ -827,6 +910,21 @@ async function waitForAndroidEmulatorReady() {
827910 continue ;
828911 }
829912
913+ // Gate before wait-for-handler / pm install: binders must be listed.
914+ const coreServices = androidCoreServicesPresent ( serial ) ;
915+ if ( ! coreServices . hasPackage || ! coreServices . hasActivity ) {
916+ stableLoadPolls = 0 ;
917+ packageHandlerDone = false ;
918+ console . log (
919+ `[rnfb-e2e] android-ready probe core-services package=${ coreServices . hasPackage } ` +
920+ `activity=${ coreServices . hasActivity } ` +
921+ ( coreServices . error ? ` error=${ coreServices . error } ` : '' ) +
922+ ' (waiting for service list)' ,
923+ ) ;
924+ await sleep ( ANDROID_READY_POLL_MS ) ;
925+ continue ;
926+ }
927+
830928 if ( ! packageHandlerDone ) {
831929 console . log ( '[rnfb-e2e] android-ready probe waiting for package handler queue...' ) ;
832930 try {
@@ -1395,6 +1493,7 @@ async function killJetForLaunchRetry(jetProcess, reason = 'launch-retry') {
13951493async function launchAppWithRetry ( launchArgs , { testsDir, onBeforeRelaunch } = { } ) {
13961494 const liveMetro = usesLiveMetro ( ) ;
13971495 let lastLaunchFailure ;
1496+ let deleteAppAfterColdBoot = false ;
13981497
13991498 for ( let launchAttempt = 1 ; launchAttempt <= LAUNCH_APP_MAX_ATTEMPTS ; launchAttempt ++ ) {
14001499 try {
@@ -1411,6 +1510,8 @@ async function launchAppWithRetry(launchArgs, { testsDir, onBeforeRelaunch } = {
14111510 ) ;
14121511 await coldBootAndroidEmulator ( ) ;
14131512 await waitForAndroidEmulatorReady ( ) ;
1513+ // Reinstall after guest death — prior APK/instrumentation may be gone or corrupt.
1514+ deleteAppAfterColdBoot = true ;
14141515 }
14151516 if ( onBeforeRelaunch ) {
14161517 await onBeforeRelaunch ( ) ;
@@ -1428,11 +1529,17 @@ async function launchAppWithRetry(launchArgs, { testsDir, onBeforeRelaunch } = {
14281529 await new Promise ( resolve => setTimeout ( resolve , 2000 ) ) ;
14291530 }
14301531
1532+ const deleteApp = launchAttempt === 1 || deleteAppAfterColdBoot ;
1533+ deleteAppAfterColdBoot = false ;
14311534 await launchAppWithTimeout ( launchArgs , {
1432- deleteApp : launchAttempt === 1 ,
1535+ deleteApp,
14331536 } ) ;
14341537 return ;
14351538 } catch ( err ) {
1539+ foldDetoxChildProcessOutputIntoError ( err ) ;
1540+ if ( detoxPlatformKey ( ) === 'android' ) {
1541+ enrichAndroidLaunchFailureWithGuestSignals ( err ) ;
1542+ }
14361543 lastLaunchFailure = err ;
14371544 console . warn ( `[rnfb-e2e] launchApp failure reason=${ err ?. message || err } ` ) ;
14381545 logLaunchInstallState ( `after-launch-failure attempt=${ launchAttempt } ` ) ;
@@ -1691,6 +1798,8 @@ describe('Jet Tests', function () {
16911798 if ( platform === 'ios' && process . platform === 'darwin' ) {
16921799 await rebootIosSimulator ( testsDir ) ;
16931800 } else if ( platform === 'android' ) {
1801+ foldDetoxChildProcessOutputIntoError ( lastFailure ) ;
1802+ enrichAndroidLaunchFailureWithGuestSignals ( lastFailure ) ;
16941803 if ( shouldColdBootAndroidOnLaunchRetry ( lastFailure ) ) {
16951804 console . warn ( '[rnfb-e2e] Android device-side launch failure — cold booting emulator' ) ;
16961805 await coldBootAndroidEmulator ( ) ;
@@ -1717,6 +1826,10 @@ describe('Jet Tests', function () {
17171826 await runJetE2eAttempt ( attempt ) ;
17181827 break ;
17191828 } catch ( err ) {
1829+ foldDetoxChildProcessOutputIntoError ( err ) ;
1830+ if ( platform === 'android' ) {
1831+ enrichAndroidLaunchFailureWithGuestSignals ( err ) ;
1832+ }
17201833 lastFailure = err ;
17211834 logRetryEligibility ( err , attempt ) ;
17221835 if ( isRetryableCloudQuotaFailure ( err ?. jetOutput || '' ) ) {
0 commit comments