Skip to content

Commit f2d38a4

Browse files
authored
fix(app-management): surface app list failures instead of caching an empty list (#489)
1 parent 66eb1cc commit f2d38a4

2 files changed

Lines changed: 42 additions & 7 deletions

File tree

src/tests/tools/app-management/resolve-app-id.test.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ const rehydratedDriver = {rehydrated: true};
44

55
const mockResolveDriver = jest.fn<(sessionId?: string) => Promise<any>>();
66
const mockListAppsFromDevice = jest.fn<(...args: any[]) => Promise<{packageName: string; appName: string}[]>>();
7+
const mockGetPlatformName = jest.fn<() => string>();
8+
const mockIsXCUITestDriverSession = jest.fn<() => boolean>();
79

810
jest.unstable_mockModule('../../../session-store', () => ({
911
// The in-memory cache is empty after an MCP process recycle.
1012
getDriver: jest.fn(() => null),
1113
getSessionId: jest.fn(() => undefined),
12-
getPlatformName: jest.fn(() => 'Android'),
13-
isXCUITestDriverSession: jest.fn(() => false),
14+
getPlatformName: mockGetPlatformName,
15+
isXCUITestDriverSession: mockIsXCUITestDriverSession,
1416
PLATFORM: {ios: 'iOS', android: 'Android'},
1517
}));
1618

@@ -26,12 +28,14 @@ jest.unstable_mockModule('../../../tools/app-management/list-apps.js', () => ({
2628

2729
const {resolveAppId} = await import('../../../tools/app-management/resolve-app-id.js');
2830

29-
describe('resolveAppId on a persisted session', () => {
30-
beforeEach(() => {
31-
jest.clearAllMocks();
32-
mockListAppsFromDevice.mockResolvedValue([{packageName: 'com.example.calc', appName: 'Calculator'}]);
33-
});
31+
beforeEach(() => {
32+
jest.clearAllMocks();
33+
mockGetPlatformName.mockReturnValue('Android');
34+
mockIsXCUITestDriverSession.mockReturnValue(false);
35+
mockListAppsFromDevice.mockResolvedValue([{packageName: 'com.example.calc', appName: 'Calculator'}]);
36+
});
3437

38+
describe('resolveAppId on a persisted session', () => {
3539
test('rehydrates the session instead of failing on an empty driver cache', async () => {
3640
mockResolveDriver.mockResolvedValue({ok: true, driver: rehydratedDriver});
3741

@@ -48,3 +52,30 @@ describe('resolveAppId on a persisted session', () => {
4852
expect(mockListAppsFromDevice).not.toHaveBeenCalled();
4953
});
5054
});
55+
56+
describe('resolveAppId on a real iOS device', () => {
57+
const deviceDriver = {isSimulator: () => false};
58+
59+
beforeEach(() => {
60+
mockGetPlatformName.mockReturnValue('iOS');
61+
mockIsXCUITestDriverSession.mockReturnValue(true);
62+
mockResolveDriver.mockResolvedValue({ok: true, driver: deviceDriver});
63+
});
64+
65+
test('keeps the apps that were listed when only one app type fails', async () => {
66+
mockListAppsFromDevice
67+
.mockResolvedValueOnce([{packageName: 'com.example.calc', appName: 'Calculator'}])
68+
.mockRejectedValueOnce(new Error('System app list unavailable'));
69+
70+
await expect(resolveAppId('Calculator', 'device-1')).resolves.toBe('com.example.calc');
71+
});
72+
73+
test('reports the device failure instead of caching an empty app list', async () => {
74+
mockListAppsFromDevice.mockRejectedValue(new Error('Could not list applications on the device'));
75+
76+
await expect(resolveAppId('Calculator', 'device-2')).rejects.toThrow(/Could not list applications/);
77+
78+
mockListAppsFromDevice.mockResolvedValue([{packageName: 'com.example.calc', appName: 'Calculator'}]);
79+
await expect(resolveAppId('Calculator', 'device-2')).resolves.toBe('com.example.calc');
80+
});
81+
});

src/tools/app-management/resolve-app-id.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ async function getInstalledApps(sessionId?: string): Promise<{packageName: strin
107107
listAppsFromDevice(driver, 'User'),
108108
listAppsFromDevice(driver, 'System'),
109109
]);
110+
const failures = results.filter((result) => result.status === 'rejected');
111+
if (failures.length === results.length) {
112+
throw failures[0].reason;
113+
}
110114
const seen = new Set<string>();
111115
apps = [];
112116
for (const result of results) {

0 commit comments

Comments
 (0)