Skip to content

Commit 6c50275

Browse files
committed
fix: clarify element and device selection guidance
1 parent c066375 commit 6c50275

3 files changed

Lines changed: 68 additions & 11 deletions

File tree

src/tests/tools/llm-wording.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,30 @@ jest.unstable_mockModule('../../command', () => ({
2222
findElement: jest.fn(),
2323
}));
2424

25+
jest.unstable_mockModule('../../devicemanager/adb-manager', () => ({
26+
ADBManager: jest.fn(),
27+
}));
28+
29+
jest.unstable_mockModule('../../devicemanager/ios-manager', () => ({
30+
IOSManager: jest.fn(),
31+
}));
32+
33+
jest.unstable_mockModule('../../logger', () => ({
34+
default: {
35+
debug: jest.fn(),
36+
error: jest.fn(),
37+
info: jest.fn(),
38+
warn: jest.fn(),
39+
},
40+
}));
41+
42+
jest.unstable_mockModule('../../ui/mcp-ui-utils', () => ({
43+
addUIResourceToResponse: jest.fn(),
44+
createAppListUI: jest.fn(),
45+
createDevicePickerUI: jest.fn(),
46+
createUIResource: jest.fn(),
47+
}));
48+
2549
jest.unstable_mockModule('../../tools/session/attach-session', () => ({
2650
attachSessionAction: jest.fn(),
2751
}));
@@ -97,7 +121,9 @@ describe('LLM-facing MCP tool wording', () => {
97121
/Android.*prefer.*android uiautomator/i
98122
);
99123
expect(strategyDescription).toMatch(/xpath last/i);
100-
expect(selectorDescription).toMatch(/not natural language/i);
124+
expect(selectorDescription).toMatch(
125+
/do not pass natural-language descriptions/i
126+
);
101127
});
102128

103129
test('appium_session_management explains local vs remote session creation', async () => {
@@ -141,6 +167,35 @@ describe('LLM-facing MCP tool wording', () => {
141167
expect(tool.annotations?.destructiveHint).toBe(true);
142168
});
143169

170+
test('select_device preserves the local and remote workflow', async () => {
171+
const tool = await registerTool('../../tools/session/select-device.js');
172+
const description = normalizeText(tool.description);
173+
174+
expect(tool.name).toBe('select_device');
175+
expect(description).toMatch(/LOCAL servers ONLY/i);
176+
expect(description).toMatch(/ASK THE USER.*Android.*iOS.*do not assume/i);
177+
expect(description).toMatch(/without deviceUdid.*list/i);
178+
expect(description).toMatch(/one result.*auto-select/i);
179+
expect(description).toMatch(
180+
/multiple results.*ask the user.*chosen deviceUdid/i
181+
);
182+
expect(description).toMatch(/NEVER use.*REMOTE.*remoteServerUrl/i);
183+
expect(description).toMatch(
184+
/prepare_ios_simulator.*appium_session_management.*create/i
185+
);
186+
expect(description).toMatch(/Remote devices.*session capabilities/i);
187+
188+
expect(normalizeText(paramDescription(tool, 'platform'))).toMatch(
189+
/user-selected.*never assume/i
190+
);
191+
expect(normalizeText(paramDescription(tool, 'iosDeviceType'))).toMatch(
192+
/Required.*iOS.*simulator.*real/i
193+
);
194+
expect(normalizeText(paramDescription(tool, 'deviceUdid'))).toMatch(
195+
/omit.*list.*ask.*multiple/i
196+
);
197+
});
198+
144199
test('appium_mobile_permissions retains action-specific requirements', async () => {
145200
const tool = await registerTool(
146201
'../../tools/app-management/permissions.js'

src/tools/interactions/find.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ export const findElementSchema = z.object({
2424
'css selector',
2525
])
2626
.describe(
27-
'Prefer accessibility id (cross-platform, fastest, stable), then id. ' +
28-
'iOS: prefer -ios predicate/class chain; Android: prefer -android uiautomator. ' +
29-
'Use xpath last. CSS is webview-only; appium_ai handles natural language/vision.'
27+
'Priority: (1) accessibility id (cross-platform, fastest, stable), (2) id, ' +
28+
'(3) iOS: prefer -ios predicate/class chain; Android: prefer -android uiautomator, (4) xpath LAST RESORT (slow/brittle). ' +
29+
'name/class are fallbacks; CSS is webview-only. Use appium_ai action=find_element for natural language/vision.'
3030
),
3131
selector: z
3232
.string()
3333
.describe(
34-
'Selector for strategy; not natural language (use appium_ai for that).'
34+
'Selector string for the chosen strategy. Do not pass natural-language descriptions; use appium_ai action=find_element.'
3535
),
3636
sessionId: z
3737
.string()
@@ -44,7 +44,8 @@ export default function findElement(server: FastMCP): void {
4444
name: 'appium_find_element',
4545
description:
4646
'Find one element by strategy and selector; return its interaction UUID. ' +
47-
'Prefer accessibility id over id before xpath, which is a last resort. Use appium_gesture scroll_to_element off-screen and appium_ai find_element for vision/natural language.',
47+
'Prefer accessibility id over id before xpath, which is a last resort. ' +
48+
'This tool does not scroll: use appium_gesture scroll_to_element for off-screen targets. Use appium_ai find_element for vision/natural-language targets.',
4849
parameters: findElementSchema,
4950
annotations: {
5051
readOnlyHint: true,

src/tools/session/select-device.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,22 +81,23 @@ export default function selectDevice(server: any): void {
8181
server.addTool({
8282
name: 'select_device',
8383
description:
84-
'LOCAL servers only: ask the user for platform, list devices, then select deviceUdid; one result auto-selects. ' +
85-
'For iOS also pass iosDeviceType and prepare simulators before session create. ' +
86-
'Skip for REMOTE servers; pass device capabilities to appium_session_management.',
84+
'LOCAL servers ONLY; NEVER use for REMOTE/remoteServerUrl. ASK THE USER Android or iOS—do not assume. ' +
85+
'Call without deviceUdid to list: one result auto-selects; for multiple results ask the user, then call again with the chosen deviceUdid. ' +
86+
'For iOS require iosDeviceType and run prepare_ios_simulator before appium_session_management create. ' +
87+
'Remote devices are selected through session capabilities.',
8788
parameters: z
8889
.object({
8990
platform: z
9091
.enum(['ios', 'android'])
91-
.describe('Platform selected by the user.'),
92+
.describe('User-selected platform; never assume.'),
9293
iosDeviceType: z
9394
.enum(['simulator', 'real'])
9495
.optional()
9596
.describe('Required for iOS: simulator or real.'),
9697
deviceUdid: z
9798
.string()
9899
.optional()
99-
.describe('Chosen UDID; omit to list devices.'),
100+
.describe('Chosen UDID; omit to list, then ask if multiple.'),
100101
})
101102
.refine(
102103
(data) => data.platform !== 'ios' || data.iosDeviceType !== undefined,

0 commit comments

Comments
 (0)