appium-mcp 1.60.2 → 1.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +2 -8
- package/dist/tools/app-management/permissions.d.ts.map +1 -1
- package/dist/tools/app-management/permissions.js +34 -26
- package/dist/tools/app-management/permissions.js.map +1 -1
- package/dist/tools/gestures/drag-and-drop.d.ts.map +1 -0
- package/dist/tools/gestures/drag-and-drop.js +140 -0
- package/dist/tools/gestures/drag-and-drop.js.map +1 -0
- package/dist/tools/gestures/gesture.d.ts +3 -0
- package/dist/tools/gestures/gesture.d.ts.map +1 -0
- package/dist/tools/gestures/gesture.js +44 -0
- package/dist/tools/gestures/gesture.js.map +1 -0
- package/dist/tools/gestures/handlers/pinch.d.ts +5 -0
- package/dist/tools/gestures/handlers/pinch.d.ts.map +1 -0
- package/dist/tools/gestures/handlers/pinch.js +97 -0
- package/dist/tools/gestures/handlers/pinch.js.map +1 -0
- package/dist/tools/gestures/handlers/scroll-to-element.d.ts +5 -0
- package/dist/tools/gestures/handlers/scroll-to-element.d.ts.map +1 -0
- package/dist/tools/gestures/handlers/scroll-to-element.js +65 -0
- package/dist/tools/gestures/handlers/scroll-to-element.js.map +1 -0
- package/dist/tools/gestures/handlers/swipe-scroll.d.ts +6 -0
- package/dist/tools/gestures/handlers/swipe-scroll.d.ts.map +1 -0
- package/dist/tools/gestures/handlers/swipe-scroll.js +166 -0
- package/dist/tools/gestures/handlers/swipe-scroll.js.map +1 -0
- package/dist/tools/gestures/handlers/tap.d.ts +7 -0
- package/dist/tools/gestures/handlers/tap.d.ts.map +1 -0
- package/dist/tools/gestures/handlers/tap.js +165 -0
- package/dist/tools/gestures/handlers/tap.js.map +1 -0
- package/dist/tools/gestures/perform-actions.d.ts +3 -0
- package/dist/tools/gestures/perform-actions.d.ts.map +1 -0
- package/dist/tools/gestures/perform-actions.js +94 -0
- package/dist/tools/gestures/perform-actions.js.map +1 -0
- package/dist/tools/gestures/schema.d.ts +51 -0
- package/dist/tools/gestures/schema.d.ts.map +1 -0
- package/dist/tools/gestures/schema.js +110 -0
- package/dist/tools/gestures/schema.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +9 -19
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/resources/submodules.zip +0 -0
- package/src/tools/app-management/permissions.ts +42 -29
- package/src/tools/gestures/drag-and-drop.ts +209 -0
- package/src/tools/gestures/gesture.ts +50 -0
- package/src/tools/gestures/handlers/pinch.ts +121 -0
- package/src/tools/gestures/handlers/scroll-to-element.ts +102 -0
- package/src/tools/gestures/handlers/swipe-scroll.ts +217 -0
- package/src/tools/gestures/handlers/tap.ts +217 -0
- package/src/tools/gestures/perform-actions.ts +121 -0
- package/src/tools/gestures/schema.ts +148 -0
- package/src/tools/index.ts +9 -19
- package/dist/tools/interactions/click.d.ts +0 -3
- package/dist/tools/interactions/click.d.ts.map +0 -1
- package/dist/tools/interactions/click.js +0 -76
- package/dist/tools/interactions/click.js.map +0 -1
- package/dist/tools/interactions/double-tap.d.ts +0 -3
- package/dist/tools/interactions/double-tap.d.ts.map +0 -1
- package/dist/tools/interactions/double-tap.js +0 -73
- package/dist/tools/interactions/double-tap.js.map +0 -1
- package/dist/tools/interactions/drag-and-drop.d.ts.map +0 -1
- package/dist/tools/interactions/drag-and-drop.js +0 -166
- package/dist/tools/interactions/drag-and-drop.js.map +0 -1
- package/dist/tools/interactions/long-press.d.ts +0 -3
- package/dist/tools/interactions/long-press.d.ts.map +0 -1
- package/dist/tools/interactions/long-press.js +0 -96
- package/dist/tools/interactions/long-press.js.map +0 -1
- package/dist/tools/interactions/pinch.d.ts +0 -3
- package/dist/tools/interactions/pinch.d.ts.map +0 -1
- package/dist/tools/interactions/pinch.js +0 -144
- package/dist/tools/interactions/pinch.js.map +0 -1
- package/dist/tools/interactions/tap.d.ts +0 -3
- package/dist/tools/interactions/tap.d.ts.map +0 -1
- package/dist/tools/interactions/tap.js +0 -51
- package/dist/tools/interactions/tap.js.map +0 -1
- package/dist/tools/navigations/scroll-to-element.d.ts +0 -2
- package/dist/tools/navigations/scroll-to-element.d.ts.map +0 -1
- package/dist/tools/navigations/scroll-to-element.js +0 -147
- package/dist/tools/navigations/scroll-to-element.js.map +0 -1
- package/dist/tools/navigations/scroll.d.ts +0 -2
- package/dist/tools/navigations/scroll.d.ts.map +0 -1
- package/dist/tools/navigations/scroll.js +0 -87
- package/dist/tools/navigations/scroll.js.map +0 -1
- package/dist/tools/navigations/swipe.d.ts +0 -2
- package/dist/tools/navigations/swipe.d.ts.map +0 -1
- package/dist/tools/navigations/swipe.js +0 -289
- package/dist/tools/navigations/swipe.js.map +0 -1
- package/src/tools/interactions/click.ts +0 -104
- package/src/tools/interactions/double-tap.ts +0 -90
- package/src/tools/interactions/drag-and-drop.ts +0 -233
- package/src/tools/interactions/long-press.ts +0 -115
- package/src/tools/interactions/pinch.ts +0 -173
- package/src/tools/interactions/tap.ts +0 -66
- package/src/tools/navigations/scroll-to-element.ts +0 -192
- package/src/tools/navigations/scroll.ts +0 -98
- package/src/tools/navigations/swipe.ts +0 -361
- /package/dist/tools/{interactions → gestures}/drag-and-drop.d.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [1.62.0](https://github.com/appium/appium-mcp/compare/v1.61.0...v1.62.0) (2026-04-22)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* **app-management:** export permissions to server and add name parameter ([#270](https://github.com/appium/appium-mcp/issues/270)) ([a6d8e1e](https://github.com/appium/appium-mcp/commit/a6d8e1e95b3374cb33237168dfb2b5d761563acc))
|
|
6
|
+
|
|
7
|
+
## [1.61.0](https://github.com/appium/appium-mcp/compare/v1.60.2...v1.61.0) (2026-04-21)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* consolidate gesture tools ([#278](https://github.com/appium/appium-mcp/issues/278)) ([17faac6](https://github.com/appium/appium-mcp/commit/17faac696cf4d096250fcc182d4d68af73ed1a92))
|
|
12
|
+
|
|
1
13
|
## [1.60.2](https://github.com/appium/appium-mcp/compare/v1.60.1...v1.60.2) (2026-04-21)
|
|
2
14
|
|
|
3
15
|
### Miscellaneous Chores
|
package/README.md
CHANGED
|
@@ -322,12 +322,9 @@ The default regex pattern allows any URL that starts with `http://` or `https://
|
|
|
322
322
|
| Tool | Description |
|
|
323
323
|
| --------------------- | -------------------------------------------------------------------------------------------- |
|
|
324
324
|
| `appium_find_element` | Find a specific element using traditional locator strategies (xpath, id, accessibility id, etc.) **OR** AI-powered natural language descriptions (e.g., "yellow search button at bottom"). Supports both traditional and AI modes. |
|
|
325
|
-
| `
|
|
326
|
-
| `appium_click` | Click on an element |
|
|
327
|
-
| `appium_double_tap` | Perform double tap on an element |
|
|
328
|
-
| `appium_long_press` | Perform a long press (press and hold) gesture on an element |
|
|
325
|
+
| `appium_gesture` | Perform a touch gesture. `action` = `tap`, `double_tap`, `long_press`, `scroll`, `swipe`, `pinch_zoom`, or `scroll_to_element`. Supports element UUIDs (including AI-found `ai-element:` UUIDs) and raw coordinates. For swipe, use `speed` = `slow` \| `normal` \| `fast` (fast for pull-to-refresh). |
|
|
329
326
|
| `appium_drag_and_drop` | Perform a drag and drop gesture from a source location to a target location (supports element-to-element, element-to-coordinates, coordinates-to-element, and coordinates-to-coordinates) |
|
|
330
|
-
| `
|
|
327
|
+
| `appium_perform_actions` | Execute raw W3C Actions API sequences for custom multi-touch gestures (rotate, three-finger swipe, edge swipes, precise timing). Prefer `appium_gesture` for standard gestures. |
|
|
331
328
|
| `appium_set_value` | Enter text into an input field |
|
|
332
329
|
| `appium_mobile_hide_keyboard` | Dismiss the on-screen keyboard (`mobile: hideKeyboard`) |
|
|
333
330
|
| `appium_mobile_is_keyboard_shown` | Whether the on-screen keyboard is visible (`mobile: isKeyboardShown`) |
|
|
@@ -342,9 +339,6 @@ The default regex pattern allows any URL that starts with `http://` or `https://
|
|
|
342
339
|
| -------------------------- | ------------------------------------------------------- |
|
|
343
340
|
| `appium_screenshot` | Take a screenshot and save as PNG. Optionally provide `elementUUID` to capture a specific element. |
|
|
344
341
|
| `appium_get_window_size` | Get the width and height of the device screen in pixels |
|
|
345
|
-
| `appium_scroll` | Scroll the screen vertically (up or down) |
|
|
346
|
-
| `appium_scroll_to_element` | Scroll until a specific element becomes visible |
|
|
347
|
-
| `appium_swipe` | Swipe the screen in a direction (left, right, up, down) or between custom coordinates |
|
|
348
342
|
| `appium_get_page_source` | Get the page source (XML) from the current screen |
|
|
349
343
|
| `appium_orientation` | Get or set device/screen orientation with `action` = `get` or `set` (requires `orientation` for set). |
|
|
350
344
|
| `appium_geolocation` | Get, set, or reset the device GPS coordinates with `action` = `get`, `set`, or `reset`. For `set`, provide `latitude` and `longitude` (and optional `altitude` on Android). Not supported on Android emulators for `reset`. |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../src/tools/app-management/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../src/tools/app-management/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;AActD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CA2L/D"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { getPlatformName, PLATFORM } from '../../session-store.js';
|
|
3
3
|
import { execute } from '../../command.js';
|
|
4
|
+
import { resolveAppId } from './resolve-app-id.js';
|
|
4
5
|
import { resolveDriver, textResult, errorResult, toolErrorMessage, } from '../tool-response.js';
|
|
5
6
|
const iosPermissionStateSchema = z.enum(['yes', 'no', 'unset', 'limited']);
|
|
6
7
|
export default function mobilePermissions(server) {
|
|
@@ -10,6 +11,16 @@ export default function mobilePermissions(server) {
|
|
|
10
11
|
.describe('get: list (Android) or read one privacy state (iOS Simulator). ' +
|
|
11
12
|
'update: grant/revoke (Android) or set privacy map (iOS Simulator). ' +
|
|
12
13
|
'reset: restore a privacy prompt for the app under test (iOS only).'),
|
|
14
|
+
id: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('App identifier (package name for Android, bundle ID for iOS). Takes precedence over name. ' +
|
|
18
|
+
'Optional for Android (defaults to the app under test). Required for iOS get and update.'),
|
|
19
|
+
name: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe('Human-readable app name (e.g. "Spotify"). Used to resolve the app id. ' +
|
|
23
|
+
'Optional for Android (defaults to the app under test). Required (as alternative to id) for iOS get and update.'),
|
|
13
24
|
sessionId: z
|
|
14
25
|
.string()
|
|
15
26
|
.optional()
|
|
@@ -18,14 +29,6 @@ export default function mobilePermissions(server) {
|
|
|
18
29
|
.enum(['denied', 'granted', 'requested'])
|
|
19
30
|
.optional()
|
|
20
31
|
.describe('Android get only: which bucket to return. Defaults to requested per UiAutomator2.'),
|
|
21
|
-
appPackage: z
|
|
22
|
-
.string()
|
|
23
|
-
.optional()
|
|
24
|
-
.describe('Android get/update: package to target. Defaults to the app under test.'),
|
|
25
|
-
bundleId: z
|
|
26
|
-
.string()
|
|
27
|
-
.optional()
|
|
28
|
-
.describe('iOS get/update: bundle id of the app. Required for iOS get and update.'),
|
|
29
32
|
service: z
|
|
30
33
|
.union([z.string(), z.number()])
|
|
31
34
|
.optional()
|
|
@@ -50,7 +53,7 @@ export default function mobilePermissions(server) {
|
|
|
50
53
|
});
|
|
51
54
|
server.addTool({
|
|
52
55
|
name: 'appium_mobile_permissions',
|
|
53
|
-
description: 'Manage mobile app permissions in one place. action=get: Android lists runtime permissions for a package; iOS Simulator reads one service state for
|
|
56
|
+
description: 'Manage mobile app permissions in one place. action=get: Android lists runtime permissions for a package; iOS Simulator reads one service state for an app (needs id or name + service). action=update: Android changes permissions (grant/revoke or AppOps); iOS Simulator sets privacy via access map (needs id or name + access). action=reset: iOS only — resets one privacy service for the AUT (needs service).',
|
|
54
57
|
parameters: schema,
|
|
55
58
|
annotations: {
|
|
56
59
|
readOnlyHint: false,
|
|
@@ -64,44 +67,48 @@ export default function mobilePermissions(server) {
|
|
|
64
67
|
const { driver } = resolved;
|
|
65
68
|
try {
|
|
66
69
|
const platform = getPlatformName(driver);
|
|
70
|
+
const appId = args.id ??
|
|
71
|
+
(args.name
|
|
72
|
+
? await resolveAppId(args.name, args.sessionId)
|
|
73
|
+
: undefined);
|
|
67
74
|
if (args.action === 'get') {
|
|
68
75
|
if (platform === PLATFORM.android) {
|
|
69
76
|
const params = {};
|
|
70
77
|
if (args.permissionFilter != null) {
|
|
71
78
|
params.type = args.permissionFilter;
|
|
72
79
|
}
|
|
73
|
-
if (
|
|
74
|
-
params.appPackage =
|
|
80
|
+
if (appId != null) {
|
|
81
|
+
params.appPackage = appId;
|
|
75
82
|
}
|
|
76
83
|
const raw = await execute(driver, 'mobile: getPermissions', params);
|
|
77
84
|
return textResult(JSON.stringify(raw, null, 2));
|
|
78
85
|
}
|
|
79
86
|
if (platform === PLATFORM.ios) {
|
|
80
|
-
if (!
|
|
81
|
-
|
|
87
|
+
if (!appId) {
|
|
88
|
+
return errorResult('iOS get requires id or name and service (string).');
|
|
82
89
|
}
|
|
83
90
|
if (args.service === undefined ||
|
|
84
91
|
typeof args.service === 'number') {
|
|
85
|
-
|
|
92
|
+
return errorResult('iOS get requires service as a string name (e.g. camera, photos).');
|
|
86
93
|
}
|
|
87
94
|
const raw = await execute(driver, 'mobile: getPermission', {
|
|
88
|
-
bundleId:
|
|
95
|
+
bundleId: appId,
|
|
89
96
|
service: args.service,
|
|
90
97
|
});
|
|
91
98
|
return textResult(String(raw));
|
|
92
99
|
}
|
|
93
|
-
|
|
100
|
+
return errorResult(`Unsupported platform: ${platform}. Only Android and iOS are supported.`);
|
|
94
101
|
}
|
|
95
102
|
if (args.action === 'update') {
|
|
96
103
|
if (platform === PLATFORM.android) {
|
|
97
104
|
if (args.permissions === undefined) {
|
|
98
|
-
|
|
105
|
+
return errorResult('Android update requires permissions.');
|
|
99
106
|
}
|
|
100
107
|
const params = {
|
|
101
108
|
permissions: args.permissions,
|
|
102
109
|
};
|
|
103
|
-
if (
|
|
104
|
-
params.appPackage =
|
|
110
|
+
if (appId != null) {
|
|
111
|
+
params.appPackage = appId;
|
|
105
112
|
}
|
|
106
113
|
if (args.permissionChangeAction != null) {
|
|
107
114
|
params.action = args.permissionChangeAction;
|
|
@@ -113,22 +120,23 @@ export default function mobilePermissions(server) {
|
|
|
113
120
|
return textResult('Permissions updated successfully.');
|
|
114
121
|
}
|
|
115
122
|
if (platform === PLATFORM.ios) {
|
|
116
|
-
if (!
|
|
117
|
-
|
|
123
|
+
if (!appId || !args.access) {
|
|
124
|
+
return errorResult('iOS update requires id or name and access map.');
|
|
118
125
|
}
|
|
119
126
|
await execute(driver, 'mobile: setPermission', {
|
|
120
|
-
bundleId:
|
|
127
|
+
bundleId: appId,
|
|
121
128
|
access: args.access,
|
|
122
129
|
});
|
|
123
130
|
return textResult('Permission settings updated successfully.');
|
|
124
131
|
}
|
|
125
|
-
|
|
132
|
+
return errorResult(`Unsupported platform: ${platform}. Only Android and iOS are supported.`);
|
|
126
133
|
}
|
|
134
|
+
// action === 'reset'
|
|
127
135
|
if (platform !== PLATFORM.ios) {
|
|
128
|
-
|
|
136
|
+
return errorResult('action=reset is only supported on iOS (mobile: resetPermission for the AUT).');
|
|
129
137
|
}
|
|
130
138
|
if (args.service === undefined) {
|
|
131
|
-
|
|
139
|
+
return errorResult('iOS reset requires service (name or numeric id).');
|
|
132
140
|
}
|
|
133
141
|
await execute(driver, 'mobile: resetPermission', {
|
|
134
142
|
service: args.service,
|
|
@@ -136,7 +144,7 @@ export default function mobilePermissions(server) {
|
|
|
136
144
|
return textResult('Permission reset successfully.');
|
|
137
145
|
}
|
|
138
146
|
catch (err) {
|
|
139
|
-
return errorResult(`Failed permissions action ${args.action}
|
|
147
|
+
return errorResult(`Failed permissions action ${args.action}: ${toolErrorMessage(err)}`);
|
|
140
148
|
}
|
|
141
149
|
},
|
|
142
150
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../../src/tools/app-management/permissions.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EACL,aAAa,EACb,UAAU,EACV,WAAW,EACX,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAE7B,MAAM,wBAAwB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;AAE3E,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,MAAe;IACvD,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;QACtB,MAAM,EAAE,CAAC;aACN,IAAI,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;aAChC,QAAQ,CACP,iEAAiE;YAC/D,qEAAqE;YACrE,oEAAoE,CACvE;QACH,
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../../src/tools/app-management/permissions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drag-and-drop.d.ts","sourceRoot":"","sources":["../../../src/tools/gestures/drag-and-drop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;AAwGtD,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAwGzD"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { elementUUIDScheme } from '../../schema.js';
|
|
3
|
+
import { getElementRect, getWindowRect, performActions, } from '../../command.js';
|
|
4
|
+
import { errorResult, resolveDriver, textResult, toolErrorMessage, } from '../tool-response.js';
|
|
5
|
+
const DROP_PAUSE_DURATION_MS = 150;
|
|
6
|
+
const dragAndDropSchema = z.object({
|
|
7
|
+
sourceElementUUID: elementUUIDScheme
|
|
8
|
+
.optional()
|
|
9
|
+
.describe('UUID of source element to drag from. Either sourceElementUUID or sourceX+sourceY must be provided.'),
|
|
10
|
+
sourceX: z
|
|
11
|
+
.number()
|
|
12
|
+
.int()
|
|
13
|
+
.min(0)
|
|
14
|
+
.optional()
|
|
15
|
+
.describe('Source X coordinate. Required if sourceElementUUID is not provided.'),
|
|
16
|
+
sourceY: z
|
|
17
|
+
.number()
|
|
18
|
+
.int()
|
|
19
|
+
.min(0)
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('Source Y coordinate. Required if sourceElementUUID is not provided.'),
|
|
22
|
+
targetElementUUID: elementUUIDScheme
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('UUID of target element to drop on. Either targetElementUUID or targetX+targetY must be provided.'),
|
|
25
|
+
targetX: z
|
|
26
|
+
.number()
|
|
27
|
+
.int()
|
|
28
|
+
.min(0)
|
|
29
|
+
.optional()
|
|
30
|
+
.describe('Target X coordinate. Required if targetElementUUID is not provided.'),
|
|
31
|
+
targetY: z
|
|
32
|
+
.number()
|
|
33
|
+
.int()
|
|
34
|
+
.min(0)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('Target Y coordinate. Required if targetElementUUID is not provided.'),
|
|
37
|
+
duration: z
|
|
38
|
+
.number()
|
|
39
|
+
.int()
|
|
40
|
+
.min(100)
|
|
41
|
+
.max(5000)
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('Duration of the drag movement in milliseconds. Default 1200.'),
|
|
44
|
+
longPressDuration: z
|
|
45
|
+
.number()
|
|
46
|
+
.int()
|
|
47
|
+
.min(400)
|
|
48
|
+
.max(2000)
|
|
49
|
+
.optional()
|
|
50
|
+
.describe('Duration of the long press before dragging in milliseconds. Default 600.'),
|
|
51
|
+
sessionId: z
|
|
52
|
+
.string()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe('Session ID to target. If omitted, uses the active session.'),
|
|
55
|
+
});
|
|
56
|
+
async function resolvePoint(driver, uuid, x, y) {
|
|
57
|
+
if (uuid) {
|
|
58
|
+
const rect = await getElementRect(driver, uuid);
|
|
59
|
+
return {
|
|
60
|
+
x: Math.floor(rect.x + rect.width / 2),
|
|
61
|
+
y: Math.floor(rect.y + rect.height / 2),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (x === undefined || y === undefined) {
|
|
65
|
+
return { error: 'missing element UUID or coordinates' };
|
|
66
|
+
}
|
|
67
|
+
return { x, y };
|
|
68
|
+
}
|
|
69
|
+
export default function dragAndDrop(server) {
|
|
70
|
+
server.addTool({
|
|
71
|
+
name: 'appium_drag_and_drop',
|
|
72
|
+
description: 'Perform a drag-and-drop gesture from a source location to a target location. ' +
|
|
73
|
+
'The gesture: long press the source (default 600ms), drag to the target (default 1200ms), then release. ' +
|
|
74
|
+
'Source and target can each be specified as either an element UUID or coordinates. ' +
|
|
75
|
+
'Useful for reordering lists, moving items, drag-to-delete.',
|
|
76
|
+
parameters: dragAndDropSchema,
|
|
77
|
+
annotations: {
|
|
78
|
+
readOnlyHint: false,
|
|
79
|
+
openWorldHint: false,
|
|
80
|
+
},
|
|
81
|
+
execute: async (args, _context) => {
|
|
82
|
+
const resolved = resolveDriver(args.sessionId);
|
|
83
|
+
if (!resolved.ok) {
|
|
84
|
+
return resolved.result;
|
|
85
|
+
}
|
|
86
|
+
const { driver } = resolved;
|
|
87
|
+
try {
|
|
88
|
+
const source = await resolvePoint(driver, args.sourceElementUUID, args.sourceX, args.sourceY);
|
|
89
|
+
if ('error' in source) {
|
|
90
|
+
return errorResult('drag_and_drop requires either sourceElementUUID, or both sourceX and sourceY.');
|
|
91
|
+
}
|
|
92
|
+
const target = await resolvePoint(driver, args.targetElementUUID, args.targetX, args.targetY);
|
|
93
|
+
if ('error' in target) {
|
|
94
|
+
return errorResult('drag_and_drop requires either targetElementUUID, or both targetX and targetY.');
|
|
95
|
+
}
|
|
96
|
+
const { width, height } = await getWindowRect(driver);
|
|
97
|
+
if (source.x < 0 ||
|
|
98
|
+
source.x >= width ||
|
|
99
|
+
source.y < 0 ||
|
|
100
|
+
source.y >= height) {
|
|
101
|
+
return errorResult(`Source coordinates (${source.x}, ${source.y}) are out of screen bounds (${width}x${height}).`);
|
|
102
|
+
}
|
|
103
|
+
if (target.x < 0 ||
|
|
104
|
+
target.x >= width ||
|
|
105
|
+
target.y < 0 ||
|
|
106
|
+
target.y >= height) {
|
|
107
|
+
return errorResult(`Target coordinates (${target.x}, ${target.y}) are out of screen bounds (${width}x${height}).`);
|
|
108
|
+
}
|
|
109
|
+
const duration = args.duration ?? 1200;
|
|
110
|
+
const longPressDuration = args.longPressDuration ?? 600;
|
|
111
|
+
await performActions(driver, [
|
|
112
|
+
{
|
|
113
|
+
type: 'pointer',
|
|
114
|
+
id: 'finger1',
|
|
115
|
+
parameters: { pointerType: 'touch' },
|
|
116
|
+
actions: [
|
|
117
|
+
{ type: 'pointerMove', duration: 0, x: source.x, y: source.y },
|
|
118
|
+
{ type: 'pointerDown', button: 0 },
|
|
119
|
+
{ type: 'pause', duration: longPressDuration },
|
|
120
|
+
{ type: 'pointerMove', duration, x: target.x, y: target.y },
|
|
121
|
+
{ type: 'pause', duration: DROP_PAUSE_DURATION_MS },
|
|
122
|
+
{ type: 'pointerUp', button: 0 },
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
]);
|
|
126
|
+
const sourceDesc = args.sourceElementUUID
|
|
127
|
+
? `element ${args.sourceElementUUID}`
|
|
128
|
+
: `(${source.x}, ${source.y})`;
|
|
129
|
+
const targetDesc = args.targetElementUUID
|
|
130
|
+
? `element ${args.targetElementUUID}`
|
|
131
|
+
: `(${target.x}, ${target.y})`;
|
|
132
|
+
return textResult(`Successfully dragged from ${sourceDesc} to ${targetDesc}.`);
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
return errorResult(`Failed to perform drag_and_drop. ${toolErrorMessage(err)}`);
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=drag-and-drop.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drag-and-drop.js","sourceRoot":"","sources":["../../../src/tools/gestures/drag-and-drop.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gesture.d.ts","sourceRoot":"","sources":["../../../src/tools/gestures/gesture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,OAAO,EAAE,MAAM,SAAS,CAAC;AAQtD,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAyCrD"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { resolveDriver } from '../tool-response.js';
|
|
2
|
+
import { GESTURE_ACTIONS, gestureSchema } from './schema.js';
|
|
3
|
+
import { handleTap, handleDoubleTap, handleLongPress } from './handlers/tap.js';
|
|
4
|
+
import { handleScroll, handleSwipe } from './handlers/swipe-scroll.js';
|
|
5
|
+
import { handlePinchZoom } from './handlers/pinch.js';
|
|
6
|
+
import { handleScrollToElement } from './handlers/scroll-to-element.js';
|
|
7
|
+
export default function gesture(server) {
|
|
8
|
+
server.addTool({
|
|
9
|
+
name: 'appium_gesture',
|
|
10
|
+
description: `Perform a touch gesture. Use 'action' to choose: ${GESTURE_ACTIONS.join(', ')}. ` +
|
|
11
|
+
`Choose scroll vs swipe by intent: scroll to browse content in a list or feed; ` +
|
|
12
|
+
`swipe to dismiss, switch screens, navigate carousels, or pull-to-refresh (speed=fast). ` +
|
|
13
|
+
`For drag-and-drop use appium_drag_and_drop. For custom multi-touch use appium_perform_actions.`,
|
|
14
|
+
parameters: gestureSchema,
|
|
15
|
+
annotations: {
|
|
16
|
+
readOnlyHint: false,
|
|
17
|
+
openWorldHint: false,
|
|
18
|
+
},
|
|
19
|
+
execute: async (args, _context) => {
|
|
20
|
+
const resolved = resolveDriver(args.sessionId);
|
|
21
|
+
if (!resolved.ok) {
|
|
22
|
+
return resolved.result;
|
|
23
|
+
}
|
|
24
|
+
const { driver } = resolved;
|
|
25
|
+
switch (args.action) {
|
|
26
|
+
case 'tap':
|
|
27
|
+
return handleTap(driver, args);
|
|
28
|
+
case 'double_tap':
|
|
29
|
+
return handleDoubleTap(driver, args);
|
|
30
|
+
case 'long_press':
|
|
31
|
+
return handleLongPress(driver, args);
|
|
32
|
+
case 'scroll':
|
|
33
|
+
return handleScroll(driver, args);
|
|
34
|
+
case 'swipe':
|
|
35
|
+
return handleSwipe(driver, args);
|
|
36
|
+
case 'pinch_zoom':
|
|
37
|
+
return handlePinchZoom(driver, args);
|
|
38
|
+
case 'scroll_to_element':
|
|
39
|
+
return handleScrollToElement(driver, args);
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=gesture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gesture.js","sourceRoot":"","sources":["../../../src/tools/gestures/gesture.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAoB,MAAM,aAAa,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AAExE,MAAM,CAAC,OAAO,UAAU,OAAO,CAAC,MAAe;IAC7C,MAAM,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,gBAAgB;QACtB,WAAW,EACT,oDAAoD,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;YAClF,gFAAgF;YAChF,yFAAyF;YACzF,gGAAgG;QAClG,UAAU,EAAE,aAAa;QACzB,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,aAAa,EAAE,KAAK;SACrB;QACD,OAAO,EAAE,KAAK,EACZ,IAAiB,EACjB,QAA6C,EACrB,EAAE;YAC1B,MAAM,QAAQ,GAAG,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC/C,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,OAAO,QAAQ,CAAC,MAAM,CAAC;YACzB,CAAC;YACD,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;YAE5B,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC;gBACpB,KAAK,KAAK;oBACR,OAAO,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACjC,KAAK,YAAY;oBACf,OAAO,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACvC,KAAK,YAAY;oBACf,OAAO,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACvC,KAAK,QAAQ;oBACX,OAAO,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACpC,KAAK,OAAO;oBACV,OAAO,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACnC,KAAK,YAAY;oBACf,OAAO,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACvC,KAAK,mBAAmB;oBACtB,OAAO,qBAAqB,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ContentResult } from 'fastmcp';
|
|
2
|
+
import type { DriverInstance } from '../../../session-store.js';
|
|
3
|
+
import type { GestureArgs } from '../schema.js';
|
|
4
|
+
export declare function handlePinchZoom(driver: DriverInstance, args: GestureArgs): Promise<ContentResult>;
|
|
5
|
+
//# sourceMappingURL=pinch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pinch.d.ts","sourceRoot":"","sources":["../../../../src/tools/gestures/handlers/pinch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAahE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD,wBAAsB,eAAe,CACnC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,aAAa,CAAC,CAmGxB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { getPlatformName, PLATFORM } from '../../../session-store.js';
|
|
2
|
+
import { execute, getElementRect, getWindowRect, performActions, } from '../../../command.js';
|
|
3
|
+
import { errorResult, textResult, toolErrorMessage, } from '../../tool-response.js';
|
|
4
|
+
const DEFAULT_VELOCITY = 2.2;
|
|
5
|
+
export async function handlePinchZoom(driver, args) {
|
|
6
|
+
if (args.scale === undefined) {
|
|
7
|
+
return errorResult('pinch_zoom requires a scale value (e.g. 0.5 to zoom out, 2.0 to zoom in).');
|
|
8
|
+
}
|
|
9
|
+
const scale = args.scale;
|
|
10
|
+
const velocity = args.velocity ?? DEFAULT_VELOCITY;
|
|
11
|
+
try {
|
|
12
|
+
const platform = getPlatformName(driver);
|
|
13
|
+
let cx;
|
|
14
|
+
let cy;
|
|
15
|
+
let spread;
|
|
16
|
+
let windowRect = null;
|
|
17
|
+
if (args.elementUUID) {
|
|
18
|
+
const rect = await getElementRect(driver, args.elementUUID);
|
|
19
|
+
cx = Math.floor(rect.x + rect.width / 2);
|
|
20
|
+
cy = Math.floor(rect.y + rect.height / 2);
|
|
21
|
+
spread = Math.floor(Math.min(rect.width, rect.height) * 0.3);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
windowRect = await getWindowRect(driver);
|
|
25
|
+
cx = Math.floor(windowRect.width / 2);
|
|
26
|
+
cy = Math.floor(windowRect.height / 2);
|
|
27
|
+
spread = Math.floor(Math.min(windowRect.width, windowRect.height) * 0.3);
|
|
28
|
+
}
|
|
29
|
+
if (scale < 1) {
|
|
30
|
+
// Zoom out: two fingers move from spread apart to close together. W3C Actions on both platforms.
|
|
31
|
+
const startSpread = spread;
|
|
32
|
+
const endSpread = Math.max(1, Math.floor(spread * scale));
|
|
33
|
+
const duration = Math.floor((1 / Math.abs(velocity)) * 1000);
|
|
34
|
+
await performActions(driver, [
|
|
35
|
+
{
|
|
36
|
+
type: 'pointer',
|
|
37
|
+
id: 'finger1',
|
|
38
|
+
parameters: { pointerType: 'touch' },
|
|
39
|
+
actions: [
|
|
40
|
+
{ type: 'pointerMove', duration: 0, x: cx - startSpread, y: cy },
|
|
41
|
+
{ type: 'pointerDown', button: 0 },
|
|
42
|
+
{ type: 'pointerMove', duration, x: cx - endSpread, y: cy },
|
|
43
|
+
{ type: 'pointerUp', button: 0 },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'pointer',
|
|
48
|
+
id: 'finger2',
|
|
49
|
+
parameters: { pointerType: 'touch' },
|
|
50
|
+
actions: [
|
|
51
|
+
{ type: 'pointerMove', duration: 0, x: cx + startSpread, y: cy },
|
|
52
|
+
{ type: 'pointerDown', button: 0 },
|
|
53
|
+
{ type: 'pointerMove', duration, x: cx + endSpread, y: cy },
|
|
54
|
+
{ type: 'pointerUp', button: 0 },
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
else if (platform === PLATFORM.ios) {
|
|
60
|
+
const params = {
|
|
61
|
+
scale,
|
|
62
|
+
velocity: Math.abs(velocity),
|
|
63
|
+
};
|
|
64
|
+
if (args.elementUUID) {
|
|
65
|
+
params.elementId = args.elementUUID;
|
|
66
|
+
}
|
|
67
|
+
await execute(driver, 'mobile: pinch', params);
|
|
68
|
+
}
|
|
69
|
+
else if (platform === PLATFORM.android) {
|
|
70
|
+
// Convert scale factor to percent (0–1) for pinchOpenGesture.
|
|
71
|
+
// scale=2 → 0.5, scale=4 → 0.75, scale=10 → 0.9. Capped at 0.99.
|
|
72
|
+
const percent = Math.min(0.99, 1 - 1 / scale);
|
|
73
|
+
const params = { percent };
|
|
74
|
+
if (args.elementUUID) {
|
|
75
|
+
params.elementId = args.elementUUID;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const rect = windowRect;
|
|
79
|
+
params.left = rect.x ?? 0;
|
|
80
|
+
params.top = rect.y ?? 0;
|
|
81
|
+
params.width = rect.width;
|
|
82
|
+
params.height = rect.height;
|
|
83
|
+
}
|
|
84
|
+
await execute(driver, 'mobile: pinchOpenGesture', params);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
return errorResult(`pinch_zoom is not supported on platform '${platform}'. Supported: iOS, Android.`);
|
|
88
|
+
}
|
|
89
|
+
const direction = scale < 1 ? 'out' : 'in';
|
|
90
|
+
const target = args.elementUUID ? `element ${args.elementUUID}` : 'screen';
|
|
91
|
+
return textResult(`Successfully pinched ${direction} (scale=${scale}) on ${target}.`);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
return errorResult(`Failed to perform pinch_zoom. ${toolErrorMessage(err)}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=pinch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pinch.js","sourceRoot":"","sources":["../../../../src/tools/gestures/handlers/pinch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ContentResult } from 'fastmcp';
|
|
2
|
+
import type { DriverInstance } from '../../../session-store.js';
|
|
3
|
+
import type { GestureArgs } from '../schema.js';
|
|
4
|
+
export declare function handleScrollToElement(driver: DriverInstance, args: GestureArgs): Promise<ContentResult>;
|
|
5
|
+
//# sourceMappingURL=scroll-to-element.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-to-element.d.ts","sourceRoot":"","sources":["../../../../src/tools/gestures/handlers/scroll-to-element.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAQhE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAwDhD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,WAAW,GAChB,OAAO,CAAC,aAAa,CAAC,CAiCxB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getPlatformName, PLATFORM } from '../../../session-store.js';
|
|
2
|
+
import { execute, getWindowRect, performActions } from '../../../command.js';
|
|
3
|
+
import { errorResult, textResult, toolErrorMessage, } from '../../tool-response.js';
|
|
4
|
+
const MAX_SCROLL_ATTEMPTS = 10;
|
|
5
|
+
async function tryFindElement(driver, strategy, selector) {
|
|
6
|
+
try {
|
|
7
|
+
await driver.findElement(strategy, selector);
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async function performW3CScroll(driver, direction) {
|
|
15
|
+
const { width, height } = await getWindowRect(driver);
|
|
16
|
+
const startX = Math.floor(width / 2);
|
|
17
|
+
const startY = direction === 'down' ? Math.floor(height * 0.8) : Math.floor(height * 0.2);
|
|
18
|
+
const endY = direction === 'down' ? Math.floor(height * 0.2) : Math.floor(height * 0.8);
|
|
19
|
+
await performActions(driver, [
|
|
20
|
+
{
|
|
21
|
+
type: 'pointer',
|
|
22
|
+
id: 'finger1',
|
|
23
|
+
parameters: { pointerType: 'touch' },
|
|
24
|
+
actions: [
|
|
25
|
+
{ type: 'pointerMove', duration: 0, x: startX, y: startY },
|
|
26
|
+
{ type: 'pointerDown', button: 0 },
|
|
27
|
+
{ type: 'pause', duration: 250 },
|
|
28
|
+
{ type: 'pointerMove', duration: 600, x: startX, y: endY },
|
|
29
|
+
{ type: 'pointerUp', button: 0 },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
async function scrollOnce(driver, direction) {
|
|
35
|
+
const platform = getPlatformName(driver);
|
|
36
|
+
if (platform === PLATFORM.ios) {
|
|
37
|
+
await execute(driver, 'mobile: scroll', { direction });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
await performW3CScroll(driver, direction);
|
|
41
|
+
}
|
|
42
|
+
export async function handleScrollToElement(driver, args) {
|
|
43
|
+
if (!args.strategy || !args.selector) {
|
|
44
|
+
return errorResult('scroll_to_element requires both strategy and selector.');
|
|
45
|
+
}
|
|
46
|
+
const direction = args.direction === 'up' || args.direction === 'down'
|
|
47
|
+
? args.direction
|
|
48
|
+
: 'down';
|
|
49
|
+
try {
|
|
50
|
+
if (await tryFindElement(driver, args.strategy, args.selector)) {
|
|
51
|
+
return textResult(`Element ${args.selector} is already visible on screen.`);
|
|
52
|
+
}
|
|
53
|
+
for (let attempt = 1; attempt <= MAX_SCROLL_ATTEMPTS; attempt++) {
|
|
54
|
+
await scrollOnce(driver, direction);
|
|
55
|
+
if (await tryFindElement(driver, args.strategy, args.selector)) {
|
|
56
|
+
return textResult(`Successfully scrolled to element ${args.selector} after ${attempt} scroll(s).`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return errorResult(`Element ${args.selector} not found after ${MAX_SCROLL_ATTEMPTS} scrolls in direction '${direction}'.`);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
return errorResult(`Failed to scroll_to_element. ${toolErrorMessage(err)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=scroll-to-element.js.map
|