Skip to content

Commit a38828e

Browse files
committed
fix(platform-android): reinstall apps on cached emulators
1 parent e2b2c87 commit a38828e

4 files changed

Lines changed: 97 additions & 2 deletions

File tree

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
hasAvd,
1313
installApp,
1414
startEmulator,
15+
uninstallApp,
1516
waitForBoot,
1617
waitForEmulatorDisconnect,
1718
} from '../adb.js';
@@ -146,6 +147,21 @@ describe('getStartAppArgs', () => {
146147
]);
147148
});
148149

150+
it('uninstalls the app via adb', async () => {
151+
const spawnSpy = vi
152+
.spyOn(tools, 'spawn')
153+
.mockResolvedValueOnce({} as Awaited<ReturnType<typeof tools.spawn>>);
154+
155+
await uninstallApp('emulator-5554', 'com.example.app');
156+
157+
expect(spawnSpy).toHaveBeenCalledWith(expect.stringMatching(/adb$/), [
158+
'-s',
159+
'emulator-5554',
160+
'uninstall',
161+
'com.example.app',
162+
]);
163+
});
164+
149165
it('creates an AVD and appends config overrides', async () => {
150166
vi.spyOn(avdConfig, 'readAvdConfig').mockResolvedValue({});
151167
const spawnSpy = vi

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

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,60 @@ describe('Android platform instance', () => {
429429
fs.rmSync(appPath, { force: true });
430430
});
431431

432+
it('reinstalls the app from HARNESS_APP_PATH when already installed', async () => {
433+
const appPath = path.join(os.tmpdir(), 'HarnessPlayground-installed.apk');
434+
fs.writeFileSync(appPath, 'apk');
435+
vi.stubEnv('HARNESS_APP_PATH', appPath);
436+
vi.spyOn(
437+
await import('../environment.js'),
438+
'ensureAndroidEmulatorEnvironment',
439+
).mockResolvedValue('/tmp/android-sdk');
440+
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
441+
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');
442+
vi.spyOn(adb, 'waitForBoot').mockResolvedValue('emulator-5554');
443+
vi.spyOn(adb, 'isAppInstalled').mockResolvedValue(true);
444+
const uninstallApp = vi
445+
.spyOn(adb, 'uninstallApp')
446+
.mockResolvedValue(undefined);
447+
const installApp = vi.spyOn(adb, 'installApp').mockResolvedValue(undefined);
448+
vi.spyOn(adb, 'reversePort').mockResolvedValue(undefined);
449+
vi.spyOn(adb, 'setHideErrorDialogs').mockResolvedValue(undefined);
450+
vi.spyOn(adb, 'getAppUid').mockResolvedValue(10234);
451+
vi.spyOn(sharedPrefs, 'applyHarnessDebugHttpHost').mockResolvedValue(
452+
undefined,
453+
);
454+
455+
await expect(
456+
getAndroidEmulatorPlatformInstance(
457+
{
458+
name: 'android',
459+
device: {
460+
type: 'emulator',
461+
name: 'Pixel_8_API_35',
462+
avd: {
463+
apiLevel: 35,
464+
profile: 'pixel_8',
465+
diskSize: '1G',
466+
heapSize: '1G',
467+
},
468+
},
469+
bundleId: 'com.harnessplayground',
470+
activityName: '.MainActivity',
471+
},
472+
harnessConfig,
473+
init,
474+
),
475+
).resolves.toBeDefined();
476+
477+
expect(uninstallApp).toHaveBeenCalledWith(
478+
'emulator-5554',
479+
'com.harnessplayground',
480+
);
481+
expect(installApp).toHaveBeenCalledWith('emulator-5554', appPath);
482+
483+
fs.rmSync(appPath, { force: true });
484+
});
485+
432486
it('throws a HarnessAppPathError when HARNESS_APP_PATH is missing', async () => {
433487
vi.spyOn(adb, 'getDeviceIds').mockResolvedValue(['emulator-5554']);
434488
vi.spyOn(adb, 'getEmulatorName').mockResolvedValue('Pixel_8_API_35');

packages/platform-android/src/adb.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,13 @@ export const installApp = async (
476476
await spawn(getAdbBinaryPath(), ['-s', adbId, 'install', '-r', appPath]);
477477
};
478478

479+
export const uninstallApp = async (
480+
adbId: string,
481+
bundleId: string,
482+
): Promise<void> => {
483+
await spawn(getAdbBinaryPath(), ['-s', adbId, 'uninstall', bundleId]);
484+
};
485+
479486
export const hasAvd = async (name: string): Promise<boolean> => {
480487
const avds = await getAvds();
481488
return avds.includes(name);

packages/platform-android/src/instance.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@ const getHarnessAppPath = (): string => {
5959
return appPath;
6060
};
6161

62+
const getOptionalHarnessAppPath = (): string | undefined => {
63+
const appPath = process.env.HARNESS_APP_PATH;
64+
65+
if (!appPath) {
66+
return undefined;
67+
}
68+
69+
if (!fs.existsSync(appPath)) {
70+
throw new HarnessAppPathError('invalid', appPath);
71+
}
72+
73+
return appPath;
74+
};
75+
6276
const configureAndroidRuntime = async (
6377
adbId: string,
6478
config: AndroidPlatformConfig,
@@ -251,10 +265,14 @@ export const getAndroidEmulatorPlatformInstance = async (
251265
);
252266

253267
const isInstalled = await adb.isAppInstalled(adbId, config.bundleId);
268+
const appPath = getOptionalHarnessAppPath();
254269

255-
if (!isInstalled) {
256-
const appPath = getHarnessAppPath();
270+
if (isInstalled && appPath) {
271+
await adb.uninstallApp(adbId, config.bundleId);
257272
await adb.installApp(adbId, appPath);
273+
} else if (!isInstalled) {
274+
const installPath = appPath ?? getHarnessAppPath();
275+
await adb.installApp(adbId, installPath);
258276
}
259277

260278
const appUid = await configureAndroidRuntime(adbId, config, harnessConfig);

0 commit comments

Comments
 (0)