Skip to content

Commit 5c4c71f

Browse files
committed
fix: disable mobile crash monitors when detection is off
1 parent b32ab51 commit 5c4c71f

5 files changed

Lines changed: 149 additions & 12 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
__default__: patch
3+
---
4+
5+
Mobile runners now fully disable native crash monitoring when `detectNativeCrashes` is set to `false`, including iOS simulators and Android emulators and physical devices. This keeps crash-monitor setup aligned with the runtime setting while preserving the existing default behavior of enabling native crash detection when the option is omitted.

packages/platform-android/src/__tests__/instance.test.ts

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import { HarnessAppPathError, HarnessEmulatorConfigError } from '../errors.js';
1818
const harnessConfig = {
1919
metroPort: DEFAULT_METRO_PORT,
2020
} as HarnessConfig;
21+
const harnessConfigWithoutNativeCrashDetection = {
22+
metroPort: DEFAULT_METRO_PORT,
23+
detectNativeCrashes: false,
24+
} as HarnessConfig;
2125
const init = {
2226
signal: new AbortController().signal,
2327
};
@@ -504,7 +508,53 @@ describe('Android platform instance', () => {
504508
).rejects.toBeInstanceOf(HarnessEmulatorConfigError);
505509
});
506510

507-
it('keeps physical device behavior unchanged', async () => {
511+
it('returns a noop emulator app monitor when native crash detection is disabled', async () => {
512+
vi.spyOn(
513+
await import('../environment.js'),
514+
'ensureAndroidEmulatorEnvironment'
515+
).mockResolvedValue('/tmp/android-sdk');
516+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
517+
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
518+
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
519+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
520+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
521+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
522+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
523+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
524+
undefined
525+
);
526+
527+
const instance = await getAndroidEmulatorPlatformInstance(
528+
{
529+
name: 'android',
530+
device: {
531+
type: 'emulator',
532+
name: 'Pixel_8_API_35',
533+
avd: {
534+
apiLevel: 35,
535+
profile: 'pixel_8',
536+
diskSize: '1G',
537+
heapSize: '1G',
538+
},
539+
},
540+
bundleId: 'com.harnessplayground',
541+
activityName: '.MainActivity',
542+
},
543+
harnessConfigWithoutNativeCrashDetection,
544+
init
545+
);
546+
547+
const listener = vi.fn();
548+
const appMonitor = instance.createAppMonitor();
549+
550+
await expect(appMonitor.start()).resolves.toBeUndefined();
551+
await expect(appMonitor.stop()).resolves.toBeUndefined();
552+
await expect(appMonitor.dispose()).resolves.toBeUndefined();
553+
expect(appMonitor.addListener(listener)).toBeUndefined();
554+
expect(appMonitor.removeListener(listener)).toBeUndefined();
555+
});
556+
557+
it('returns a noop physical device app monitor when native crash detection is disabled', async () => {
508558
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['012345']);
509559
vi.spyOn(adb, 'getDeviceInfo').mockResolvedValue({
510560
manufacturer: 'motorola',
@@ -530,8 +580,31 @@ describe('Android platform instance', () => {
530580
bundleId: 'com.harnessplayground',
531581
activityName: '.MainActivity',
532582
},
533-
harnessConfig
583+
harnessConfigWithoutNativeCrashDetection
534584
)
535585
).resolves.toBeDefined();
586+
587+
const instance = await getAndroidPhysicalDevicePlatformInstance(
588+
{
589+
name: 'android-device',
590+
device: {
591+
type: 'physical',
592+
manufacturer: 'motorola',
593+
model: 'moto g72',
594+
},
595+
bundleId: 'com.harnessplayground',
596+
activityName: '.MainActivity',
597+
},
598+
harnessConfigWithoutNativeCrashDetection
599+
);
600+
601+
const listener = vi.fn();
602+
const appMonitor = instance.createAppMonitor();
603+
604+
await expect(appMonitor.start()).resolves.toBeUndefined();
605+
await expect(appMonitor.stop()).resolves.toBeUndefined();
606+
await expect(appMonitor.dispose()).resolves.toBeUndefined();
607+
expect(appMonitor.addListener(listener)).toBeUndefined();
608+
expect(appMonitor.removeListener(listener)).toBeUndefined();
536609
});
537610
});

packages/platform-android/src/instance.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,18 @@ import {
3232
} from './environment.js';
3333
import { isInteractive } from '@react-native-harness/tools';
3434
import fs from 'node:fs';
35+
import type { AppMonitor } from '@react-native-harness/platforms';
3536

3637
const androidInstanceLogger = logger.child('android-instance');
3738

39+
const createNoopAppMonitor = (): AppMonitor => ({
40+
start: async () => {},
41+
stop: async () => {},
42+
dispose: async () => {},
43+
addListener: () => {},
44+
removeListener: () => {},
45+
});
46+
3847
const getHarnessAppPath = (): string => {
3948
const appPath = process.env.HARNESS_APP_PATH;
4049

@@ -168,6 +177,7 @@ export const getAndroidEmulatorPlatformInstance = async (
168177
init: HarnessPlatformInitOptions
169178
): Promise<HarnessPlatformRunner> => {
170179
assertAndroidDeviceEmulator(config.device);
180+
const detectNativeCrashes = harnessConfig.detectNativeCrashes ?? true;
171181
const emulatorConfig = config.device;
172182
const emulatorName = emulatorConfig.name;
173183
const avdConfig = emulatorConfig.avd;
@@ -284,13 +294,18 @@ export const getAndroidEmulatorPlatformInstance = async (
284294
isAppRunning: async () => {
285295
return await adb.isAppRunning(adbId, config.bundleId);
286296
},
287-
createAppMonitor: (options?: CreateAppMonitorOptions) =>
288-
createAndroidAppMonitor({
297+
createAppMonitor: (options?: CreateAppMonitorOptions) => {
298+
if (!detectNativeCrashes) {
299+
return createNoopAppMonitor();
300+
}
301+
302+
return createAndroidAppMonitor({
289303
adbId,
290304
bundleId: config.bundleId,
291305
appUid,
292306
crashArtifactWriter: options?.crashArtifactWriter,
293-
}),
307+
});
308+
},
294309
};
295310
};
296311

@@ -299,6 +314,7 @@ export const getAndroidPhysicalDevicePlatformInstance = async (
299314
harnessConfig: HarnessConfig
300315
): Promise<HarnessPlatformRunner> => {
301316
assertAndroidDevicePhysical(config.device);
317+
const detectNativeCrashes = harnessConfig.detectNativeCrashes ?? true;
302318

303319
const adbId = await getAdbId(config.device);
304320

@@ -348,12 +364,17 @@ export const getAndroidPhysicalDevicePlatformInstance = async (
348364
isAppRunning: async () => {
349365
return await adb.isAppRunning(adbId, config.bundleId);
350366
},
351-
createAppMonitor: (options?: CreateAppMonitorOptions) =>
352-
createAndroidAppMonitor({
367+
createAppMonitor: (options?: CreateAppMonitorOptions) => {
368+
if (!detectNativeCrashes) {
369+
return createNoopAppMonitor();
370+
}
371+
372+
return createAndroidAppMonitor({
353373
adbId,
354374
bundleId: config.bundleId,
355375
appUid,
356376
crashArtifactWriter: options?.crashArtifactWriter,
357-
}),
377+
});
378+
},
358379
};
359380
};

packages/platform-ios/src/__tests__/instance.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,38 @@ describe('iOS platform instance dependency validation', () => {
159159
expect(assertInstalled).not.toHaveBeenCalled();
160160
});
161161

162+
it('returns a noop simulator app monitor when native crash detection is disabled', async () => {
163+
vi.spyOn(simctl, 'getSimulatorId').mockResolvedValue('sim-udid');
164+
vi.spyOn(simctl, 'isAppInstalled').mockResolvedValue(true);
165+
vi.spyOn(simctl, 'getSimulatorStatus').mockResolvedValue('Booted');
166+
vi.spyOn(simctl, 'applyHarnessJsLocationOverride').mockResolvedValue(
167+
undefined
168+
);
169+
170+
const instance = await getAppleSimulatorPlatformInstance(
171+
{
172+
name: 'ios',
173+
device: {
174+
type: 'simulator',
175+
name: 'iPhone 16 Pro',
176+
systemVersion: '18.0',
177+
},
178+
bundleId: 'com.harnessplayground',
179+
},
180+
harnessConfigWithoutNativeCrashDetection,
181+
init
182+
);
183+
184+
const listener = vi.fn();
185+
const appMonitor = instance.createAppMonitor();
186+
187+
await expect(appMonitor.start()).resolves.toBeUndefined();
188+
await expect(appMonitor.stop()).resolves.toBeUndefined();
189+
await expect(appMonitor.dispose()).resolves.toBeUndefined();
190+
expect(appMonitor.addListener(listener)).toBeUndefined();
191+
expect(appMonitor.removeListener(listener)).toBeUndefined();
192+
});
193+
162194
it('reuses a booted simulator and does not shut it down on dispose', async () => {
163195
vi.spyOn(simctl, 'getSimulatorId').mockResolvedValue('sim-udid');
164196
vi.spyOn(simctl, 'getSimulatorStatus').mockResolvedValue('Booted');

packages/platform-ios/src/instance.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const getAppleSimulatorPlatformInstance = async (
5757
init: HarnessPlatformInitOptions
5858
): Promise<HarnessPlatformRunner> => {
5959
assertAppleDeviceSimulator(config.device);
60+
const detectNativeCrashes = harnessConfig.detectNativeCrashes ?? true;
6061

6162
const udid = await simctl.getSimulatorId(
6263
config.device.name,
@@ -153,12 +154,17 @@ export const getAppleSimulatorPlatformInstance = async (
153154
isAppRunning: async () => {
154155
return await simctl.isAppRunning(udid, config.bundleId);
155156
},
156-
createAppMonitor: (options?: CreateAppMonitorOptions) =>
157-
createIosSimulatorAppMonitor({
157+
createAppMonitor: (options?: CreateAppMonitorOptions) => {
158+
if (!detectNativeCrashes) {
159+
return createNoopAppMonitor();
160+
}
161+
162+
return createIosSimulatorAppMonitor({
158163
udid,
159164
bundleId: config.bundleId,
160165
crashArtifactWriter: options?.crashArtifactWriter,
161-
}),
166+
});
167+
},
162168
};
163169
};
164170

@@ -167,7 +173,7 @@ export const getApplePhysicalDevicePlatformInstance = async (
167173
harnessConfig: HarnessConfig
168174
): Promise<HarnessPlatformRunner> => {
169175
assertAppleDevicePhysical(config.device);
170-
const detectNativeCrashes = harnessConfig.detectNativeCrashes;
176+
const detectNativeCrashes = harnessConfig.detectNativeCrashes ?? true;
171177

172178
if (detectNativeCrashes) {
173179
await assertLibimobiledeviceInstalled();

0 commit comments

Comments
 (0)