Skip to content

Commit ef6092e

Browse files
authored
feat(interactions): add long press tool for press and hold gestures (#53)
1 parent 67b0d80 commit ef6092e

File tree

4 files changed

+112
-0
lines changed

4 files changed

+112
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ MCP Appium provides a comprehensive set of tools organized into the following ca
192192
| `appium_find_element` | Find a specific element using various locator strategies (xpath, id, accessibility id, etc.) |
193193
| `appium_click` | Click on an element |
194194
| `appium_double_tap` | Perform double tap on an element |
195+
| `appium_long_press` | Perform a long press (press and hold) gesture on an element |
195196
| `appium_set_value` | Enter text into an input field |
196197
| `appium_get_text` | Get text content from an element |
197198

src/tools/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ This directory contains all MCP tools available in MCP Appium.
2828
- `find.ts` - Find elements
2929
- `click.ts` - Click elements
3030
- `double-tap.ts` - Double tap elements
31+
- `long-press.ts` - Long press (press and hold) elements
3132
- `set-value.ts` - Enter text
3233
- `get-text.ts` - Get element text
3334
- `get-page-source.ts` - Get page source (XML) from current screen

src/tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import swipe from './navigations/swipe.js';
3030
import findElement from './interactions/find.js';
3131
import clickElement from './interactions/click.js';
3232
import doubleTap from './interactions/double-tap.js';
33+
import longPress from './interactions/long-press.js';
3334
import setValue from './interactions/set-value.js';
3435
import getText from './interactions/get-text.js';
3536
import getPageSource from './interactions/get-page-source.js';
@@ -129,6 +130,7 @@ export default function registerTools(server: FastMCP): void {
129130
findElement(server);
130131
clickElement(server);
131132
doubleTap(server);
133+
longPress(server);
132134
setValue(server);
133135
getText(server);
134136
getPageSource(server);
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { FastMCP } from 'fastmcp/dist/FastMCP.js';
2+
import { z } from 'zod';
3+
import { getDriver, getPlatformName } from '../../session-store.js';
4+
import { elementUUIDScheme } from '../../schema.js';
5+
6+
export default function longPress(server: FastMCP): void {
7+
const longPressSchema = z.object({
8+
elementUUID: elementUUIDScheme,
9+
duration: z
10+
.number()
11+
.int()
12+
.min(500)
13+
.max(10000)
14+
.default(2000)
15+
.optional()
16+
.describe(
17+
'Duration of the long press in milliseconds. Default is 2000ms.'
18+
),
19+
});
20+
21+
server.addTool({
22+
name: 'appium_long_press',
23+
description: 'Perform a long press (press and hold) gesture on an element',
24+
parameters: longPressSchema,
25+
annotations: {
26+
readOnlyHint: false,
27+
openWorldHint: false,
28+
},
29+
execute: async (args: any, context: any): Promise<any> => {
30+
const driver = getDriver();
31+
if (!driver) {
32+
throw new Error('No driver found');
33+
}
34+
35+
try {
36+
const platform = getPlatformName(driver);
37+
const duration = args.duration || 2000;
38+
39+
if (platform === 'Android') {
40+
const rect = await driver.getElementRect(args.elementUUID);
41+
const x = Math.floor(rect.x + rect.width / 2);
42+
const y = Math.floor(rect.y + rect.height / 2);
43+
44+
await driver.performActions([
45+
{
46+
type: 'pointer',
47+
id: 'finger1',
48+
parameters: { pointerType: 'touch' },
49+
actions: [
50+
{ type: 'pointerMove', duration: 0, x, y },
51+
{ type: 'pointerDown', button: 0 },
52+
{ type: 'pause', duration: duration },
53+
{ type: 'pointerUp', button: 0 },
54+
],
55+
},
56+
]);
57+
} else if (platform === 'iOS') {
58+
try {
59+
await driver.execute('mobile: touchAndHold', {
60+
elementId: args.elementUUID,
61+
duration: duration / 1000,
62+
});
63+
} catch (touchAndHoldError) {
64+
const rect = await driver.getElementRect(args.elementUUID);
65+
const x = Math.floor(rect.x + rect.width / 2);
66+
const y = Math.floor(rect.y + rect.height / 2);
67+
68+
await driver.performActions([
69+
{
70+
type: 'pointer',
71+
id: 'finger1',
72+
parameters: { pointerType: 'touch' },
73+
actions: [
74+
{ type: 'pointerMove', duration: 0, x, y },
75+
{ type: 'pointerDown', button: 0 },
76+
{ type: 'pause', duration: duration },
77+
{ type: 'pointerUp', button: 0 },
78+
],
79+
},
80+
]);
81+
}
82+
} else {
83+
throw new Error(
84+
`Unsupported platform: ${platform}. Only Android and iOS are supported.`
85+
);
86+
}
87+
88+
return {
89+
content: [
90+
{
91+
type: 'text',
92+
text: `Successfully performed long press on element ${args.elementUUID}`,
93+
},
94+
],
95+
};
96+
} catch (err: any) {
97+
return {
98+
content: [
99+
{
100+
type: 'text',
101+
text: `Failed to perform long press on element ${args.elementUUID}. err: ${err.toString()}`,
102+
},
103+
],
104+
};
105+
}
106+
},
107+
});
108+
}

0 commit comments

Comments
 (0)