Skip to content

Commit 128776f

Browse files
quanruclaude
andauthored
fix(ios): use WebDriver Clear API for dynamic input fields (#1403) (#1568)
* fix(ios): use WebDriver Clear API for dynamic input fields ## Problem The previous clearInput implementation used tripleTap on element coordinates, which failed with dynamic input fields (e.g., eBay search box that shows a new input overlay when clicked). The stale coordinates caused the tap to hit the wrong element, breaking focus on the dynamic input field. ## Solution Replaced tripleTap-based clearing with WebDriver's standard Clear API: - Get the currently focused element using /element/active endpoint - Clear it using /element/{id}/clear endpoint - Works reliably regardless of element position changes - Doesn't trigger unwanted web page events ## Changes - Added getActiveElement(), clearElement(), clearActiveElement() methods to IOSWebDriverClient - Updated clearInput() in IOSDevice to use WebDriver Clear API - Updated ebay.test.ts to reflect the fix 🤖 Generated with [Claude Code](https://claude.com/claude-code) * test(ebay): unify search action descriptions across platforms Standardize the search action description across all platforms from "hit Enter" to "click search button" for consistency. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f353300 commit 128776f

File tree

6 files changed

+93
-22
lines changed

6 files changed

+93
-22
lines changed

packages/android/tests/ai/ebay.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ describe('Test todo list', () => {
2727
'search headphones',
2828
async () => {
2929
// 👀 type keywords, perform a search
30-
await agent.aiAction('type "Headphones" in search box, hit Enter');
30+
await agent.aiAction(
31+
'type "Headphones" in search box, click search button',
32+
);
3133

3234
// 👀 wait for the loading
3335
await agent.aiWaitFor('there is at least one headphone item on page');

packages/cli/tests/multi_yaml_android_scripts/search-headphone-on-ebay.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ tasks:
88
- name: search headphones
99
flow:
1010
- aiAction: open browser and navigate to ebay.com
11-
- aiAction: type 'Headphones' in ebay search box, hit Enter
11+
- aiAction: type 'Headphones' in ebay search box, click the search button
1212
- sleep: 5000
1313
- aiAction: scroll down the page for 800px
1414

packages/cli/tests/multi_yaml_ios_scripts/search-headphone-on-ebay.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ tasks:
77
- name: search headphones
88
flow:
99
- aiAction: open browser and navigate to ebay.com
10-
- aiAction: type 'Headphones' in ebay search box, hit Enter
10+
- aiAction: type 'Headphones' in ebay search box, click the search button
1111
- sleep: 5000
1212
- aiAction: scroll down the page for 800px
1313

packages/ios/src/device.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -402,24 +402,17 @@ ScreenSize: ${size.width}x${size.height} (DPR: ${size.scale})
402402
await this.tap(element.center[0], element.center[1]);
403403
await sleep(100);
404404

405-
// For iOS, we need to use different methods to clear text
406-
try {
407-
// Method 1: Triple tap to select all, then delete
408-
await this.tripleTap(element.center[0], element.center[1]);
409-
await sleep(200); // Wait for selection to appear
410-
411-
// Type empty string to replace selected text
412-
await this.wdaBackend.typeText(BackspaceChar);
413-
} catch (error2) {
414-
debugDevice(`Method 1 failed, trying method 2: ${error2}`);
415-
try {
416-
// Method 2: Send multiple backspace characters
417-
const backspaces = Array(100).fill(BackspaceChar).join(''); // Unicode backspace
418-
await this.wdaBackend.typeText(backspaces);
419-
} catch (error3) {
420-
debugDevice(`All clear methods failed: ${error3}`);
421-
// Continue anyway, maybe there was no text to clear
422-
}
405+
// For iOS, use WebDriver's standard clear API
406+
// This gets the currently focused element and clears it using the /element/{id}/clear endpoint
407+
// Works reliably with dynamic input fields and doesn't trigger unwanted events
408+
debugDevice('Attempting to clear input with WebDriver Clear API');
409+
const cleared = await this.wdaBackend.clearActiveElement();
410+
if (cleared) {
411+
debugDevice('Successfully cleared input with WebDriver Clear API');
412+
} else {
413+
debugDevice(
414+
'WebDriver Clear API returned false (no active element or clear failed)',
415+
);
423416
}
424417
}
425418

packages/ios/src/ios-webdriver-client.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,82 @@ export class IOSWebDriverClient extends WebDriverClient {
199199
throw new Error(`Key "${key}" is not supported on iOS platform`);
200200
}
201201

202+
/**
203+
* Get the currently focused element's WebDriver ID
204+
* @returns WebDriver element ID or null if no element is focused
205+
*/
206+
async getActiveElement(): Promise<string | null> {
207+
this.ensureSession();
208+
debugIOS('Getting active element');
209+
210+
try {
211+
const response = await this.makeRequest(
212+
'GET',
213+
`/session/${this.sessionId}/element/active`,
214+
);
215+
216+
// WebDriver can return element ID in two formats:
217+
// - Legacy format: response.ELEMENT or response.value.ELEMENT
218+
// - W3C format: response['element-6066-11e4-a52e-4f735466cecf']
219+
const elementId =
220+
response.value?.ELEMENT ||
221+
response.value?.['element-6066-11e4-a52e-4f735466cecf'] ||
222+
response.ELEMENT ||
223+
response['element-6066-11e4-a52e-4f735466cecf'];
224+
225+
if (elementId) {
226+
debugIOS(`Got active element ID: ${elementId}`);
227+
return elementId;
228+
}
229+
230+
debugIOS('No active element found');
231+
return null;
232+
} catch (error) {
233+
debugIOS(`Failed to get active element: ${error}`);
234+
return null;
235+
}
236+
}
237+
238+
/**
239+
* Clear an element using WebDriver's clear endpoint
240+
* @param elementId WebDriver element ID
241+
*/
242+
async clearElement(elementId: string): Promise<void> {
243+
this.ensureSession();
244+
debugIOS(`Clearing element: ${elementId}`);
245+
246+
try {
247+
await this.makeRequest(
248+
'POST',
249+
`/session/${this.sessionId}/element/${elementId}/clear`,
250+
);
251+
debugIOS('Element cleared successfully');
252+
} catch (error) {
253+
debugIOS(`Failed to clear element: ${error}`);
254+
throw new Error(`Failed to clear element: ${error}`);
255+
}
256+
}
257+
258+
/**
259+
* Clear the currently focused input field using WebDriver Clear API
260+
* @returns true if successful, false otherwise
261+
*/
262+
async clearActiveElement(): Promise<boolean> {
263+
try {
264+
const elementId = await this.getActiveElement();
265+
if (!elementId) {
266+
debugIOS('No active element to clear');
267+
return false;
268+
}
269+
270+
await this.clearElement(elementId);
271+
return true;
272+
} catch (error) {
273+
debugIOS(`Failed to clear active element: ${error}`);
274+
return false;
275+
}
276+
}
277+
202278
private normalizeKeyName(key: string): string {
203279
// Convert to proper case for mapping (first letter uppercase, rest lowercase)
204280
return key.charAt(0).toUpperCase() + key.slice(1).toLowerCase();

packages/ios/tests/ai/ebay.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ describe('Test eBay search', () => {
4242

4343
// 👀 type keywords, perform a search
4444
await agent.aiAction(
45-
'type "Headphones" in search box, tap search button',
45+
'type "Headphones" in search box, click search button',
4646
);
4747

4848
// 👀 wait for the loading

0 commit comments

Comments
 (0)