From 9935a384ad47dd69d46ef6a7e6c36c713cca02d3 Mon Sep 17 00:00:00 2001 From: Mohamed Mostafa Date: Sat, 6 Dec 2025 15:26:53 +0200 Subject: [PATCH] feat(interactions): add long press tool for press and hold gestures --- README.md | 1 + src/tools/README.md | 1 + src/tools/index.ts | 2 + src/tools/interactions/long-press.ts | 108 +++++++++++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 src/tools/interactions/long-press.ts diff --git a/README.md b/README.md index bf3db93..151a41a 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,7 @@ MCP Appium provides a comprehensive set of tools organized into the following ca | `appium_find_element` | Find a specific element using various locator strategies (xpath, id, accessibility id, etc.) | | `appium_click` | Click on an element | | `appium_double_tap` | Perform double tap on an element | +| `appium_long_press` | Perform a long press (press and hold) gesture on an element | | `appium_set_value` | Enter text into an input field | | `appium_get_text` | Get text content from an element | diff --git a/src/tools/README.md b/src/tools/README.md index 8e80d7e..b10cdaa 100644 --- a/src/tools/README.md +++ b/src/tools/README.md @@ -28,6 +28,7 @@ This directory contains all MCP tools available in MCP Appium. - `find.ts` - Find elements - `click.ts` - Click elements - `double-tap.ts` - Double tap elements +- `long-press.ts` - Long press (press and hold) elements - `set-value.ts` - Enter text - `get-text.ts` - Get element text - `get-page-source.ts` - Get page source (XML) from current screen diff --git a/src/tools/index.ts b/src/tools/index.ts index a859805..a21f0ce 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -30,6 +30,7 @@ import swipe from './navigations/swipe.js'; import findElement from './interactions/find.js'; import clickElement from './interactions/click.js'; import doubleTap from './interactions/double-tap.js'; +import longPress from './interactions/long-press.js'; import setValue from './interactions/set-value.js'; import getText from './interactions/get-text.js'; import getPageSource from './interactions/get-page-source.js'; @@ -129,6 +130,7 @@ export default function registerTools(server: FastMCP): void { findElement(server); clickElement(server); doubleTap(server); + longPress(server); setValue(server); getText(server); getPageSource(server); diff --git a/src/tools/interactions/long-press.ts b/src/tools/interactions/long-press.ts new file mode 100644 index 0000000..937a42b --- /dev/null +++ b/src/tools/interactions/long-press.ts @@ -0,0 +1,108 @@ +import { FastMCP } from 'fastmcp/dist/FastMCP.js'; +import { z } from 'zod'; +import { getDriver, getPlatformName } from '../../session-store.js'; +import { elementUUIDScheme } from '../../schema.js'; + +export default function longPress(server: FastMCP): void { + const longPressSchema = z.object({ + elementUUID: elementUUIDScheme, + duration: z + .number() + .int() + .min(500) + .max(10000) + .default(2000) + .optional() + .describe( + 'Duration of the long press in milliseconds. Default is 2000ms.' + ), + }); + + server.addTool({ + name: 'appium_long_press', + description: 'Perform a long press (press and hold) gesture on an element', + parameters: longPressSchema, + annotations: { + readOnlyHint: false, + openWorldHint: false, + }, + execute: async (args: any, context: any): Promise => { + const driver = getDriver(); + if (!driver) { + throw new Error('No driver found'); + } + + try { + const platform = getPlatformName(driver); + const duration = args.duration || 2000; + + if (platform === 'Android') { + const rect = await driver.getElementRect(args.elementUUID); + const x = Math.floor(rect.x + rect.width / 2); + const y = Math.floor(rect.y + rect.height / 2); + + await driver.performActions([ + { + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerMove', duration: 0, x, y }, + { type: 'pointerDown', button: 0 }, + { type: 'pause', duration: duration }, + { type: 'pointerUp', button: 0 }, + ], + }, + ]); + } else if (platform === 'iOS') { + try { + await driver.execute('mobile: touchAndHold', { + elementId: args.elementUUID, + duration: duration / 1000, + }); + } catch (touchAndHoldError) { + const rect = await driver.getElementRect(args.elementUUID); + const x = Math.floor(rect.x + rect.width / 2); + const y = Math.floor(rect.y + rect.height / 2); + + await driver.performActions([ + { + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerMove', duration: 0, x, y }, + { type: 'pointerDown', button: 0 }, + { type: 'pause', duration: duration }, + { type: 'pointerUp', button: 0 }, + ], + }, + ]); + } + } else { + throw new Error( + `Unsupported platform: ${platform}. Only Android and iOS are supported.` + ); + } + + return { + content: [ + { + type: 'text', + text: `Successfully performed long press on element ${args.elementUUID}`, + }, + ], + }; + } catch (err: any) { + return { + content: [ + { + type: 'text', + text: `Failed to perform long press on element ${args.elementUUID}. err: ${err.toString()}`, + }, + ], + }; + } + }, + }); +}