appium-mcp 1.1.17 → 1.3.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 +8 -0
- package/dist/session-store.d.ts +4 -0
- package/dist/session-store.js +6 -2
- package/dist/session-store.js.map +1 -1
- package/dist/tools/context/get-contexts.d.ts +2 -0
- package/dist/tools/context/get-contexts.js +55 -0
- package/dist/tools/context/get-contexts.js.map +1 -0
- package/dist/tools/context/switch-context.d.ts +2 -0
- package/dist/tools/context/switch-context.js +85 -0
- package/dist/tools/context/switch-context.js.map +1 -0
- package/dist/tools/index.js +7 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/navigations/swipe.d.ts +1 -0
- package/dist/tools/navigations/swipe.js +295 -0
- package/dist/tools/navigations/swipe.js.map +1 -0
- package/package.json +1 -1
- package/src/session-store.ts +7 -2
- package/src/tools/README.md +1 -0
- package/src/tools/context/get-contexts.ts +59 -0
- package/src/tools/context/switch-context.ts +95 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/navigations/swipe.ts +357 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## [1.3.0](https://github.com/appium/appium-mcp/compare/v1.2.0...v1.3.0) (2025-12-05)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* add new tools to get active contexts and switch context ([#51](https://github.com/appium/appium-mcp/issues/51)) ([e6ffad0](https://github.com/appium/appium-mcp/commit/e6ffad0a73522f0df2d6a8550fd0a751f797cc40))
|
|
6
|
+
|
|
7
|
+
## [1.2.0](https://github.com/appium/appium-mcp/compare/v1.1.17...v1.2.0) (2025-12-01)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* **navigation:** add swipe tool for horizontal and vertical swiping ([#49](https://github.com/appium/appium-mcp/issues/49)) ([5c163b7](https://github.com/appium/appium-mcp/commit/5c163b75896999a33c0db098ca6fb3973a390313))
|
|
12
|
+
|
|
1
13
|
## [1.1.17](https://github.com/appium/appium-mcp/compare/v1.1.16...v1.1.17) (2025-11-21)
|
|
2
14
|
|
|
3
15
|
### Miscellaneous Chores
|
package/README.md
CHANGED
|
@@ -178,6 +178,13 @@ MCP Appium provides a comprehensive set of tools organized into the following ca
|
|
|
178
178
|
| `create_session` | Create a new mobile automation session for Android or iOS |
|
|
179
179
|
| `delete_session` | Delete the current mobile session and clean up resources |
|
|
180
180
|
|
|
181
|
+
### Context Management
|
|
182
|
+
|
|
183
|
+
| Tool | Description |
|
|
184
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| `appium_get_contexts` | Get all available contexts in the current Appium session. Returns a list of context names including NATIVE_APP and any webview contexts (e.g., WEBVIEW_<id> or WEBVIEW_<package>). |
|
|
186
|
+
| `appium_switch_context` | Switch to a specific context in the Appium session. Use this to switch between native app context (NATIVE_APP) and webview contexts (WEBVIEW_<id> or WEBVIEW_<package>). Use appium_get_contexts to see available contexts first. |
|
|
187
|
+
|
|
181
188
|
### Element Discovery & Interaction
|
|
182
189
|
|
|
183
190
|
| Tool | Description |
|
|
@@ -195,6 +202,7 @@ MCP Appium provides a comprehensive set of tools organized into the following ca
|
|
|
195
202
|
| `appium_screenshot` | Take a screenshot of the current screen and save as PNG |
|
|
196
203
|
| `appium_scroll` | Scroll the screen vertically (up or down) |
|
|
197
204
|
| `appium_scroll_to_element` | Scroll until a specific element becomes visible |
|
|
205
|
+
| `appium_swipe` | Swipe the screen in a direction (left, right, up, down) or between custom coordinates |
|
|
198
206
|
| `appium_get_page_source` | Get the page source (XML) from the current screen |
|
|
199
207
|
|
|
200
208
|
### App Management
|
package/dist/session-store.d.ts
CHANGED
package/dist/session-store.js
CHANGED
|
@@ -4,6 +4,10 @@ import log from './logger.js';
|
|
|
4
4
|
let driver = null;
|
|
5
5
|
let sessionId = null;
|
|
6
6
|
let isDeletingSession = false; // Lock to prevent concurrent deletion
|
|
7
|
+
export const PLATFORM = {
|
|
8
|
+
android: 'Android',
|
|
9
|
+
ios: 'iOS',
|
|
10
|
+
};
|
|
7
11
|
export function setSession(d, id) {
|
|
8
12
|
driver = d;
|
|
9
13
|
sessionId = id;
|
|
@@ -57,9 +61,9 @@ export async function safeDeleteSession() {
|
|
|
57
61
|
}
|
|
58
62
|
export const getPlatformName = (driver) => {
|
|
59
63
|
if (driver instanceof AndroidUiautomator2Driver)
|
|
60
|
-
return
|
|
64
|
+
return PLATFORM.android;
|
|
61
65
|
if (driver instanceof XCUITestDriver)
|
|
62
|
-
return
|
|
66
|
+
return PLATFORM.ios;
|
|
63
67
|
throw new Error('Unknown driver type');
|
|
64
68
|
};
|
|
65
69
|
//# sourceMappingURL=session-store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,GAAG,MAAM,aAAa,CAAC;AAE9B,IAAI,MAAM,GAAQ,IAAI,CAAC;AACvB,IAAI,SAAS,GAAkB,IAAI,CAAC;AACpC,IAAI,iBAAiB,GAAG,KAAK,CAAC,CAAC,sCAAsC;AAErE,MAAM,UAAU,UAAU,CAAC,CAAM,EAAE,EAAiB;IAClD,MAAM,GAAG,CAAC,CAAC;IACX,SAAS,GAAG,EAAE,CAAC;IACf,iDAAiD;IACjD,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACZ,iBAAiB,GAAG,KAAK,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,MAAM,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI,IAAI,CAAC,iBAAiB,CAAC;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,wCAAwC;IACxC,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QAC1B,GAAG,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2CAA2C;IAC3C,IAAI,iBAAiB,EAAE,CAAC;QACtB,GAAG,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;QAC9D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,WAAW;IACX,iBAAiB,GAAG,IAAI,CAAC;IAEzB,IAAI,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACrC,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC;QAE7B,+BAA+B;QAC/B,MAAM,GAAG,IAAI,CAAC;QACd,SAAS,GAAG,IAAI,CAAC;QAEjB,GAAG,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,sBAAsB;QACtB,iBAAiB,GAAG,KAAK,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAW,EAAU,EAAE;IACrD,IAAI,MAAM,YAAY,yBAAyB;QAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../src/session-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,GAAG,MAAM,aAAa,CAAC;AAE9B,IAAI,MAAM,GAAQ,IAAI,CAAC;AACvB,IAAI,SAAS,GAAkB,IAAI,CAAC;AACpC,IAAI,iBAAiB,GAAG,KAAK,CAAC,CAAC,sCAAsC;AAErE,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,OAAO,EAAE,SAAS;IAClB,GAAG,EAAE,KAAK;CACX,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,CAAM,EAAE,EAAiB;IAClD,MAAM,GAAG,CAAC,CAAC;IACX,SAAS,GAAG,EAAE,CAAC;IACf,iDAAiD;IACjD,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACZ,iBAAiB,GAAG,KAAK,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS;IACvB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,2BAA2B;IACzC,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,MAAM,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI,IAAI,CAAC,iBAAiB,CAAC;AACrE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,wCAAwC;IACxC,IAAI,CAAC,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QAC1B,GAAG,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;QACzC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2CAA2C;IAC3C,IAAI,iBAAiB,EAAE,CAAC;QACtB,GAAG,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC;QAC9D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,WAAW;IACX,iBAAiB,GAAG,IAAI,CAAC;IAEzB,IAAI,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACrC,MAAM,MAAM,CAAC,aAAa,EAAE,CAAC;QAE7B,+BAA+B;QAC/B,MAAM,GAAG,IAAI,CAAC;QACd,SAAS,GAAG,IAAI,CAAC;QAEjB,GAAG,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;QAC1C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,sBAAsB;QACtB,iBAAiB,GAAG,KAAK,CAAC;IAC5B,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAW,EAAU,EAAE;IACrD,IAAI,MAAM,YAAY,yBAAyB;QAAE,OAAO,QAAQ,CAAC,OAAO,CAAC;IACzE,IAAI,MAAM,YAAY,cAAc;QAAE,OAAO,QAAQ,CAAC,GAAG,CAAC;IAC1D,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;AACzC,CAAC,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getDriver } from '../../session-store.js';
|
|
3
|
+
export default function getContexts(server) {
|
|
4
|
+
server.addTool({
|
|
5
|
+
name: 'appium_get_contexts',
|
|
6
|
+
description: 'Get all available contexts in the current Appium session. Returns a list of context names including NATIVE_APP and any webview contexts (e.g., WEBVIEW_<id> or WEBVIEW_<package>).',
|
|
7
|
+
parameters: z.object({}),
|
|
8
|
+
annotations: {
|
|
9
|
+
readOnlyHint: true,
|
|
10
|
+
openWorldHint: false,
|
|
11
|
+
},
|
|
12
|
+
execute: async (args, context) => {
|
|
13
|
+
const driver = getDriver();
|
|
14
|
+
if (!driver) {
|
|
15
|
+
throw new Error('No driver found. Please create a session first.');
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const [currentContext, contexts] = await Promise.all([
|
|
19
|
+
driver.getCurrentContext().catch(() => null),
|
|
20
|
+
driver.getContexts().catch(() => []),
|
|
21
|
+
]);
|
|
22
|
+
if (!contexts || contexts.length === 0) {
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: 'text',
|
|
27
|
+
text: 'No contexts available.',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
type: 'text',
|
|
36
|
+
text: `Available contexts: ${JSON.stringify(contexts, null, 2)}\nCurrent context: ${currentContext || 'N/A'}`,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return {
|
|
43
|
+
content: [
|
|
44
|
+
{
|
|
45
|
+
type: 'text',
|
|
46
|
+
text: `Failed to get contexts. Error: ${err.toString()}`,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
isError: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=get-contexts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-contexts.js","sourceRoot":"","sources":["../../../src/tools/context/get-contexts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,MAAe;IACjD,MAAM,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,qBAAqB;QAC3B,WAAW,EACT,oLAAoL;QACtL,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACxB,WAAW,EAAE;YACX,YAAY,EAAE,IAAI;YAClB,aAAa,EAAE,KAAK;SACrB;QACD,OAAO,EAAE,KAAK,EAAE,IAAS,EAAE,OAAY,EAAgB,EAAE;YACvD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACrE,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,CAAC,cAAc,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;oBACnD,MAAM,CAAC,iBAAiB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;oBAC5C,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;iBACrC,CAAC,CAAC;gBAEH,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACvC,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,wBAAwB;6BAC/B;yBACF;qBACF,CAAC;gBACJ,CAAC;gBAED,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,uBAAuB,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,sBAAsB,cAAc,IAAI,KAAK,EAAE;yBAC9G;qBACF;iBACF,CAAC;YACJ,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAClB,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,kCAAkC,GAAG,CAAC,QAAQ,EAAE,EAAE;yBACzD;qBACF;oBACD,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getDriver } from '../../session-store.js';
|
|
3
|
+
export default function switchContext(server) {
|
|
4
|
+
const schema = z.object({
|
|
5
|
+
context: z
|
|
6
|
+
.string()
|
|
7
|
+
.describe('The name of the context to switch to. Common values: "NATIVE_APP" for native context, or "WEBVIEW_<id>" / "WEBVIEW_<package>" for webview contexts.'),
|
|
8
|
+
});
|
|
9
|
+
server.addTool({
|
|
10
|
+
name: 'appium_switch_context',
|
|
11
|
+
description: 'Switch to a specific context in the Appium session. Use this to switch between native app context (NATIVE_APP) and webview contexts (WEBVIEW_<id> or WEBVIEW_<package>). Use appium_get_contexts to see available contexts first.',
|
|
12
|
+
parameters: schema,
|
|
13
|
+
annotations: {
|
|
14
|
+
readOnlyHint: false,
|
|
15
|
+
openWorldHint: false,
|
|
16
|
+
},
|
|
17
|
+
execute: async (args, context) => {
|
|
18
|
+
const driver = getDriver();
|
|
19
|
+
if (!driver) {
|
|
20
|
+
throw new Error('No driver found. Please create a session first.');
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const [currentContext, availableContexts] = await Promise.all([
|
|
24
|
+
driver.getCurrentContext().catch(() => null),
|
|
25
|
+
driver.getContexts().catch(() => []),
|
|
26
|
+
]);
|
|
27
|
+
if (currentContext === args.context) {
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: 'text',
|
|
32
|
+
text: `Already on context "${args.context}".`,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (!availableContexts || availableContexts.length === 0) {
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: 'text',
|
|
42
|
+
text: 'No contexts available. Cannot switch context.',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
isError: true,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (!availableContexts.includes(args.context)) {
|
|
49
|
+
return {
|
|
50
|
+
content: [
|
|
51
|
+
{
|
|
52
|
+
type: 'text',
|
|
53
|
+
text: `Context "${args.context}" not found. Available contexts: ${JSON.stringify(availableContexts, null, 2)}`,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
isError: true,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
await driver.switchContext(args.context);
|
|
60
|
+
// Verify the switch was successful
|
|
61
|
+
const newContext = await driver.getCurrentContext();
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: `Successfully switched context from "${currentContext || 'N/A'}" to "${newContext}".`,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: `Failed to switch context. Error: ${err.toString()}`,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
isError: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=switch-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"switch-context.js","sourceRoot":"","sources":["../../../src/tools/context/switch-context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,SAAS,EAA6B,MAAM,wBAAwB,CAAC;AAE9E,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,MAAe;IACnD,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;QACtB,OAAO,EAAE,CAAC;aACP,MAAM,EAAE;aACR,QAAQ,CACP,qJAAqJ,CACtJ;KACJ,CAAC,CAAC;IAEH,MAAM,CAAC,OAAO,CAAC;QACb,IAAI,EAAE,uBAAuB;QAC7B,WAAW,EACT,mOAAmO;QACrO,UAAU,EAAE,MAAM;QAClB,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,aAAa,EAAE,KAAK;SACrB;QACD,OAAO,EAAE,KAAK,EAAE,IAAS,EAAE,OAAY,EAAgB,EAAE;YACvD,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACrE,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;oBAC5D,MAAM,CAAC,iBAAiB,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;oBAC5C,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;iBACrC,CAAC,CAAC;gBAEH,IAAI,cAAc,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;oBACpC,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,uBAAuB,IAAI,CAAC,OAAO,IAAI;6BAC9C;yBACF;qBACF,CAAC;gBACJ,CAAC;gBAED,IAAI,CAAC,iBAAiB,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBACzD,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,+CAA+C;6BACtD;yBACF;wBACD,OAAO,EAAE,IAAI;qBACd,CAAC;gBACJ,CAAC;gBAED,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC9C,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,YAAY,IAAI,CAAC,OAAO,oCAAoC,IAAI,CAAC,SAAS,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;6BAC/G;yBACF;wBACD,OAAO,EAAE,IAAI;qBACd,CAAC;gBACJ,CAAC;gBACD,MAAM,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBAEzC,mCAAmC;gBACnC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,iBAAiB,EAAE,CAAC;gBAEpD,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,uCAAuC,cAAc,IAAI,KAAK,SAAS,UAAU,IAAI;yBAC5F;qBACF;iBACF,CAAC;YACJ,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAClB,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,oCAAoC,GAAG,CAAC,QAAQ,EAAE,EAAE;yBAC3D;qBACF;oBACD,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;QACH,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
package/dist/tools/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import installWDA from './ios/install-wda.js';
|
|
|
11
11
|
import generateTest from './test-generation/generate-tests.js';
|
|
12
12
|
import scroll from './navigations/scroll.js';
|
|
13
13
|
import scrollToElement from './navigations/scroll-to-element.js';
|
|
14
|
+
import swipe from './navigations/swipe.js';
|
|
14
15
|
import findElement from './interactions/find.js';
|
|
15
16
|
import clickElement from './interactions/click.js';
|
|
16
17
|
import doubleTap from './interactions/double-tap.js';
|
|
@@ -23,6 +24,8 @@ import installApp from './app-management/install-app.js';
|
|
|
23
24
|
import uninstallApp from './app-management/uninstall-app.js';
|
|
24
25
|
import terminateApp from './app-management/terminate-app.js';
|
|
25
26
|
import listApps from './app-management/list-apps.js';
|
|
27
|
+
import getContexts from './context/get-contexts.js';
|
|
28
|
+
import switchContext from './context/switch-context.js';
|
|
26
29
|
export default function registerTools(server) {
|
|
27
30
|
// Wrap addTool to inject logging around tool execution
|
|
28
31
|
const originalAddTool = server.addTool.bind(server);
|
|
@@ -97,6 +100,7 @@ export default function registerTools(server) {
|
|
|
97
100
|
// Navigation
|
|
98
101
|
scroll(server);
|
|
99
102
|
scrollToElement(server);
|
|
103
|
+
swipe(server);
|
|
100
104
|
// Element Interactions
|
|
101
105
|
findElement(server);
|
|
102
106
|
clickElement(server);
|
|
@@ -111,6 +115,9 @@ export default function registerTools(server) {
|
|
|
111
115
|
uninstallApp(server);
|
|
112
116
|
terminateApp(server);
|
|
113
117
|
listApps(server);
|
|
118
|
+
// Context Management
|
|
119
|
+
getContexts(server);
|
|
120
|
+
switchContext(server);
|
|
114
121
|
// Test Generation
|
|
115
122
|
generateLocators(server);
|
|
116
123
|
generateTest(server);
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAeA,OAAO,GAAG,MAAM,cAAc,CAAC;AAC/B,OAAO,YAAY,MAAM,kCAAkC,CAAC;AAC5D,OAAO,aAAa,MAAM,6BAA6B,CAAC;AACxD,OAAO,aAAa,MAAM,6BAA6B,CAAC;AACxD,OAAO,gBAAgB,MAAM,+BAA+B,CAAC;AAC7D,OAAO,cAAc,MAAM,8BAA8B,CAAC;AAC1D,OAAO,YAAY,MAAM,4BAA4B,CAAC;AACtD,OAAO,aAAa,MAAM,yBAAyB,CAAC;AACpD,OAAO,QAAQ,MAAM,oBAAoB,CAAC;AAC1C,OAAO,UAAU,MAAM,sBAAsB,CAAC;AAC9C,OAAO,YAAY,MAAM,qCAAqC,CAAC;AAC/D,OAAO,MAAM,MAAM,yBAAyB,CAAC;AAC7C,OAAO,eAAe,MAAM,oCAAoC,CAAC;AACjE,OAAO,WAAW,MAAM,wBAAwB,CAAC;AACjD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AACnD,OAAO,SAAS,MAAM,8BAA8B,CAAC;AACrD,OAAO,QAAQ,MAAM,6BAA6B,CAAC;AACnD,OAAO,OAAO,MAAM,4BAA4B,CAAC;AACjD,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,UAAU,MAAM,8BAA8B,CAAC;AACtD,OAAO,WAAW,MAAM,kCAAkC,CAAC;AAC3D,OAAO,UAAU,MAAM,iCAAiC,CAAC;AACzD,OAAO,YAAY,MAAM,mCAAmC,CAAC;AAC7D,OAAO,YAAY,MAAM,mCAAmC,CAAC;AAC7D,OAAO,QAAQ,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function swipe(server: any): void;
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getDriver, getPlatformName } from '../../session-store.js';
|
|
3
|
+
import log from '../../logger.js';
|
|
4
|
+
import { elementUUIDScheme } from '../../schema.js';
|
|
5
|
+
function calculateSwipeCoordinates(direction, width, height) {
|
|
6
|
+
const centerX = Math.floor(width / 2);
|
|
7
|
+
const centerY = Math.floor(height / 2);
|
|
8
|
+
switch (direction) {
|
|
9
|
+
case 'left':
|
|
10
|
+
return {
|
|
11
|
+
startX: Math.floor(width * 0.8),
|
|
12
|
+
startY: centerY,
|
|
13
|
+
endX: Math.floor(width * 0.2),
|
|
14
|
+
endY: centerY,
|
|
15
|
+
};
|
|
16
|
+
case 'right':
|
|
17
|
+
return {
|
|
18
|
+
startX: Math.floor(width * 0.2),
|
|
19
|
+
startY: centerY,
|
|
20
|
+
endX: Math.floor(width * 0.8),
|
|
21
|
+
endY: centerY,
|
|
22
|
+
};
|
|
23
|
+
case 'up':
|
|
24
|
+
return {
|
|
25
|
+
startX: centerX,
|
|
26
|
+
startY: Math.floor(height * 0.8),
|
|
27
|
+
endX: centerX,
|
|
28
|
+
endY: Math.floor(height * 0.2),
|
|
29
|
+
};
|
|
30
|
+
case 'down':
|
|
31
|
+
return {
|
|
32
|
+
startX: centerX,
|
|
33
|
+
startY: Math.floor(height * 0.2),
|
|
34
|
+
endX: centerX,
|
|
35
|
+
endY: Math.floor(height * 0.8),
|
|
36
|
+
};
|
|
37
|
+
default:
|
|
38
|
+
throw new Error(`Invalid direction: ${direction}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function performAndroidSwipe(driver, startX, startY, endX, endY, duration) {
|
|
42
|
+
await driver.performActions([
|
|
43
|
+
{
|
|
44
|
+
type: 'pointer',
|
|
45
|
+
id: 'finger1',
|
|
46
|
+
parameters: { pointerType: 'touch' },
|
|
47
|
+
actions: [
|
|
48
|
+
{ type: 'pointerMove', duration: 0, x: startX, y: startY },
|
|
49
|
+
{ type: 'pointerDown', button: 0 },
|
|
50
|
+
{ type: 'pause', duration: 250 },
|
|
51
|
+
{ type: 'pointerMove', duration: duration, x: endX, y: endY },
|
|
52
|
+
{ type: 'pointerUp', button: 0 },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
}
|
|
57
|
+
async function performiOSSwipe(driver, startX, startY, endX, endY, duration) {
|
|
58
|
+
try {
|
|
59
|
+
await driver.execute('mobile: dragFromToForDuration', {
|
|
60
|
+
fromX: startX,
|
|
61
|
+
fromY: startY,
|
|
62
|
+
toX: endX,
|
|
63
|
+
toY: endY,
|
|
64
|
+
duration: duration / 1000,
|
|
65
|
+
});
|
|
66
|
+
log.info('iOS swipe completed using mobile: dragFromToForDuration');
|
|
67
|
+
}
|
|
68
|
+
catch (dragError) {
|
|
69
|
+
log.info('mobile: dragFromToForDuration failed, trying performActions');
|
|
70
|
+
await driver.performActions([
|
|
71
|
+
{
|
|
72
|
+
type: 'pointer',
|
|
73
|
+
id: 'finger1',
|
|
74
|
+
parameters: { pointerType: 'touch' },
|
|
75
|
+
actions: [
|
|
76
|
+
{ type: 'pointerMove', duration: 0, x: startX, y: startY },
|
|
77
|
+
{ type: 'pointerDown', button: 0 },
|
|
78
|
+
{ type: 'pause', duration: 200 },
|
|
79
|
+
{ type: 'pointerMove', duration: duration, x: endX, y: endY },
|
|
80
|
+
{ type: 'pause', duration: 50 },
|
|
81
|
+
{ type: 'pointerUp', button: 0 },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
log.info('iOS swipe completed using performActions');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export default function swipe(server) {
|
|
89
|
+
server.addTool({
|
|
90
|
+
name: 'appium_swipe',
|
|
91
|
+
description: `Swipe on the current screen in a specified direction or between custom coordinates.
|
|
92
|
+
Supports four directions: left, right, up, down.
|
|
93
|
+
Can also perform custom coordinate-based swipes for precise control.
|
|
94
|
+
This is useful for navigating carousels, switching tabs, dismissing elements, or navigating between screens.`,
|
|
95
|
+
parameters: z.object({
|
|
96
|
+
direction: z
|
|
97
|
+
.enum(['left', 'right', 'up', 'down'])
|
|
98
|
+
.optional()
|
|
99
|
+
.describe('Direction to swipe. If provided, coordinates will be calculated automatically based on screen size or, when elementUUID is set, relative to that element. Either direction OR custom coordinates must be provided.'),
|
|
100
|
+
elementUUID: elementUUIDScheme
|
|
101
|
+
.optional()
|
|
102
|
+
.describe('Optional element to base the swipe on. When provided with direction, the swipe is calculated relative to this element instead of the whole screen.'),
|
|
103
|
+
startX: z
|
|
104
|
+
.number()
|
|
105
|
+
.int()
|
|
106
|
+
.min(0)
|
|
107
|
+
.optional()
|
|
108
|
+
.describe('Starting X coordinate for custom swipe. Required if direction is not provided.'),
|
|
109
|
+
startY: z
|
|
110
|
+
.number()
|
|
111
|
+
.int()
|
|
112
|
+
.min(0)
|
|
113
|
+
.optional()
|
|
114
|
+
.describe('Starting Y coordinate for custom swipe. Required if direction is not provided.'),
|
|
115
|
+
endX: z
|
|
116
|
+
.number()
|
|
117
|
+
.int()
|
|
118
|
+
.min(0)
|
|
119
|
+
.optional()
|
|
120
|
+
.describe('Ending X coordinate for custom swipe. Required if direction is not provided.'),
|
|
121
|
+
endY: z
|
|
122
|
+
.number()
|
|
123
|
+
.int()
|
|
124
|
+
.min(0)
|
|
125
|
+
.optional()
|
|
126
|
+
.describe('Ending Y coordinate for custom swipe. Required if direction is not provided.'),
|
|
127
|
+
duration: z
|
|
128
|
+
.number()
|
|
129
|
+
.int()
|
|
130
|
+
.min(0)
|
|
131
|
+
.max(5000)
|
|
132
|
+
.default(600)
|
|
133
|
+
.optional()
|
|
134
|
+
.describe('Duration of the swipe gesture in milliseconds. Default is 600ms. Higher values create slower swipes.'),
|
|
135
|
+
}),
|
|
136
|
+
annotations: {
|
|
137
|
+
readOnlyHint: false,
|
|
138
|
+
openWorldHint: false,
|
|
139
|
+
},
|
|
140
|
+
execute: async (args, context) => {
|
|
141
|
+
const driver = getDriver();
|
|
142
|
+
if (!driver) {
|
|
143
|
+
throw new Error('No active driver session. Please create a session first.');
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const platform = getPlatformName(driver);
|
|
147
|
+
let startX, startY, endX, endY;
|
|
148
|
+
if (args.direction) {
|
|
149
|
+
if (args.elementUUID) {
|
|
150
|
+
const rect = await driver.getElementRect(args.elementUUID);
|
|
151
|
+
const elementCenterX = Math.floor(rect.x + rect.width / 2);
|
|
152
|
+
const elementCenterY = Math.floor(rect.y + rect.height / 2);
|
|
153
|
+
switch (args.direction) {
|
|
154
|
+
case 'left':
|
|
155
|
+
startX = Math.floor(rect.x + rect.width * 0.8);
|
|
156
|
+
startY = elementCenterY;
|
|
157
|
+
endX = Math.floor(rect.x + rect.width * 0.2);
|
|
158
|
+
endY = elementCenterY;
|
|
159
|
+
break;
|
|
160
|
+
case 'right':
|
|
161
|
+
startX = Math.floor(rect.x + rect.width * 0.2);
|
|
162
|
+
startY = elementCenterY;
|
|
163
|
+
endX = Math.floor(rect.x + rect.width * 0.8);
|
|
164
|
+
endY = elementCenterY;
|
|
165
|
+
break;
|
|
166
|
+
case 'up':
|
|
167
|
+
startX = elementCenterX;
|
|
168
|
+
startY = Math.floor(rect.y + rect.height * 0.8);
|
|
169
|
+
endX = elementCenterX;
|
|
170
|
+
endY = Math.floor(rect.y + rect.height * 0.2);
|
|
171
|
+
break;
|
|
172
|
+
case 'down':
|
|
173
|
+
startX = elementCenterX;
|
|
174
|
+
startY = Math.floor(rect.y + rect.height * 0.2);
|
|
175
|
+
endX = elementCenterX;
|
|
176
|
+
endY = Math.floor(rect.y + rect.height * 0.8);
|
|
177
|
+
break;
|
|
178
|
+
default:
|
|
179
|
+
throw new Error(`Invalid direction: ${args.direction}`);
|
|
180
|
+
}
|
|
181
|
+
log.info('Calculated element-based swipe coordinates:', {
|
|
182
|
+
elementUUID: args.elementUUID,
|
|
183
|
+
startX,
|
|
184
|
+
startY,
|
|
185
|
+
endX,
|
|
186
|
+
endY,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const { width, height } = await driver.getWindowSize();
|
|
191
|
+
log.info('Device screen size:', { width, height });
|
|
192
|
+
const coords = calculateSwipeCoordinates(args.direction, width, height);
|
|
193
|
+
startX = coords.startX;
|
|
194
|
+
startY = coords.startY;
|
|
195
|
+
endX = coords.endX;
|
|
196
|
+
endY = coords.endY;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else if (args.startX !== undefined &&
|
|
200
|
+
args.startY !== undefined &&
|
|
201
|
+
args.endX !== undefined &&
|
|
202
|
+
args.endY !== undefined) {
|
|
203
|
+
startX = args.startX;
|
|
204
|
+
startY = args.startY;
|
|
205
|
+
endX = args.endX;
|
|
206
|
+
endY = args.endY;
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
throw new Error('Either direction or all custom coordinates (startX, startY, endX, endY) must be provided.');
|
|
210
|
+
}
|
|
211
|
+
const duration = args.duration || 600;
|
|
212
|
+
log.info('Swipe coordinates:', {
|
|
213
|
+
startX,
|
|
214
|
+
startY,
|
|
215
|
+
endX,
|
|
216
|
+
endY,
|
|
217
|
+
duration,
|
|
218
|
+
});
|
|
219
|
+
if (platform === 'Android') {
|
|
220
|
+
if (startX !== endX && Math.abs(startY - endY) < 50) {
|
|
221
|
+
const swipeDuration = Math.min(duration, 400);
|
|
222
|
+
await driver.performActions([
|
|
223
|
+
{
|
|
224
|
+
type: 'pointer',
|
|
225
|
+
id: 'finger1',
|
|
226
|
+
parameters: { pointerType: 'touch' },
|
|
227
|
+
actions: [
|
|
228
|
+
{ type: 'pointerMove', duration: 0, x: startX, y: startY },
|
|
229
|
+
{ type: 'pointerDown', button: 0 },
|
|
230
|
+
{ type: 'pause', duration: 200 },
|
|
231
|
+
{
|
|
232
|
+
type: 'pointerMove',
|
|
233
|
+
duration: swipeDuration,
|
|
234
|
+
x: endX,
|
|
235
|
+
y: endY,
|
|
236
|
+
},
|
|
237
|
+
{ type: 'pause', duration: 50 },
|
|
238
|
+
{ type: 'pointerUp', button: 0 },
|
|
239
|
+
],
|
|
240
|
+
},
|
|
241
|
+
]);
|
|
242
|
+
log.info('Android horizontal swipe completed');
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
await performAndroidSwipe(driver, startX, startY, endX, endY, duration);
|
|
246
|
+
}
|
|
247
|
+
log.info('Android swipe action completed successfully.');
|
|
248
|
+
}
|
|
249
|
+
else if (platform === 'iOS') {
|
|
250
|
+
if (args.direction) {
|
|
251
|
+
try {
|
|
252
|
+
await driver.execute('mobile: swipe', {
|
|
253
|
+
direction: args.direction,
|
|
254
|
+
});
|
|
255
|
+
log.info(`iOS swipe completed using mobile: swipe (${args.direction})`);
|
|
256
|
+
}
|
|
257
|
+
catch (swipeError) {
|
|
258
|
+
log.info('mobile: swipe failed, trying dragFromToForDuration');
|
|
259
|
+
await performiOSSwipe(driver, startX, startY, endX, endY, duration);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
await performiOSSwipe(driver, startX, startY, endX, endY, duration);
|
|
264
|
+
}
|
|
265
|
+
log.info('iOS swipe action completed successfully.');
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
throw new Error(`Unsupported platform: ${platform}. Only Android and iOS are supported.`);
|
|
269
|
+
}
|
|
270
|
+
const directionText = args.direction
|
|
271
|
+
? ` ${args.direction}`
|
|
272
|
+
: ` from (${startX}, ${startY}) to (${endX}, ${endY})`;
|
|
273
|
+
return {
|
|
274
|
+
content: [
|
|
275
|
+
{
|
|
276
|
+
type: 'text',
|
|
277
|
+
text: `Swiped${directionText} successfully.`,
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
catch (err) {
|
|
283
|
+
return {
|
|
284
|
+
content: [
|
|
285
|
+
{
|
|
286
|
+
type: 'text',
|
|
287
|
+
text: `Failed to perform swipe. Error: ${err.toString()}`,
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
//# sourceMappingURL=swipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swipe.js","sourceRoot":"","sources":["../../../src/tools/navigations/swipe.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/session-store.ts
CHANGED
|
@@ -6,6 +6,11 @@ let driver: any = null;
|
|
|
6
6
|
let sessionId: string | null = null;
|
|
7
7
|
let isDeletingSession = false; // Lock to prevent concurrent deletion
|
|
8
8
|
|
|
9
|
+
export const PLATFORM = {
|
|
10
|
+
android: 'Android',
|
|
11
|
+
ios: 'iOS',
|
|
12
|
+
};
|
|
13
|
+
|
|
9
14
|
export function setSession(d: any, id: string | null) {
|
|
10
15
|
driver = d;
|
|
11
16
|
sessionId = id;
|
|
@@ -67,7 +72,7 @@ export async function safeDeleteSession(): Promise<boolean> {
|
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
export const getPlatformName = (driver: any): string => {
|
|
70
|
-
if (driver instanceof AndroidUiautomator2Driver) return
|
|
71
|
-
if (driver instanceof XCUITestDriver) return
|
|
75
|
+
if (driver instanceof AndroidUiautomator2Driver) return PLATFORM.android;
|
|
76
|
+
if (driver instanceof XCUITestDriver) return PLATFORM.ios;
|
|
72
77
|
throw new Error('Unknown driver type');
|
|
73
78
|
};
|
package/src/tools/README.md
CHANGED
|
@@ -21,6 +21,7 @@ This directory contains all MCP tools available in MCP Appium.
|
|
|
21
21
|
|
|
22
22
|
- `scroll.ts` - Scroll screens
|
|
23
23
|
- `scroll-to-element.ts` - Scroll until element found
|
|
24
|
+
- `swipe.ts` - Swipe screens in any direction or between custom coordinates
|
|
24
25
|
|
|
25
26
|
### Element Interactions (`interactions/`)
|
|
26
27
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { FastMCP } from 'fastmcp/dist/FastMCP.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriver } from '../../session-store.js';
|
|
4
|
+
|
|
5
|
+
export default function getContexts(server: FastMCP): void {
|
|
6
|
+
server.addTool({
|
|
7
|
+
name: 'appium_get_contexts',
|
|
8
|
+
description:
|
|
9
|
+
'Get all available contexts in the current Appium session. Returns a list of context names including NATIVE_APP and any webview contexts (e.g., WEBVIEW_<id> or WEBVIEW_<package>).',
|
|
10
|
+
parameters: z.object({}),
|
|
11
|
+
annotations: {
|
|
12
|
+
readOnlyHint: true,
|
|
13
|
+
openWorldHint: false,
|
|
14
|
+
},
|
|
15
|
+
execute: async (args: any, context: any): Promise<any> => {
|
|
16
|
+
const driver = getDriver();
|
|
17
|
+
if (!driver) {
|
|
18
|
+
throw new Error('No driver found. Please create a session first.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const [currentContext, contexts] = await Promise.all([
|
|
23
|
+
driver.getCurrentContext().catch(() => null),
|
|
24
|
+
driver.getContexts().catch(() => []),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
if (!contexts || contexts.length === 0) {
|
|
28
|
+
return {
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: 'text',
|
|
32
|
+
text: 'No contexts available.',
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
content: [
|
|
40
|
+
{
|
|
41
|
+
type: 'text',
|
|
42
|
+
text: `Available contexts: ${JSON.stringify(contexts, null, 2)}\nCurrent context: ${currentContext || 'N/A'}`,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
};
|
|
46
|
+
} catch (err: any) {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: `Failed to get contexts. Error: ${err.toString()}`,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { FastMCP } from 'fastmcp/dist/FastMCP.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getDriver, getPlatformName, PLATFORM } from '../../session-store.js';
|
|
4
|
+
|
|
5
|
+
export default function switchContext(server: FastMCP): void {
|
|
6
|
+
const schema = z.object({
|
|
7
|
+
context: z
|
|
8
|
+
.string()
|
|
9
|
+
.describe(
|
|
10
|
+
'The name of the context to switch to. Common values: "NATIVE_APP" for native context, or "WEBVIEW_<id>" / "WEBVIEW_<package>" for webview contexts.'
|
|
11
|
+
),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
server.addTool({
|
|
15
|
+
name: 'appium_switch_context',
|
|
16
|
+
description:
|
|
17
|
+
'Switch to a specific context in the Appium session. Use this to switch between native app context (NATIVE_APP) and webview contexts (WEBVIEW_<id> or WEBVIEW_<package>). Use appium_get_contexts to see available contexts first.',
|
|
18
|
+
parameters: schema,
|
|
19
|
+
annotations: {
|
|
20
|
+
readOnlyHint: false,
|
|
21
|
+
openWorldHint: false,
|
|
22
|
+
},
|
|
23
|
+
execute: async (args: any, context: any): Promise<any> => {
|
|
24
|
+
const driver = getDriver();
|
|
25
|
+
if (!driver) {
|
|
26
|
+
throw new Error('No driver found. Please create a session first.');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const [currentContext, availableContexts] = await Promise.all([
|
|
31
|
+
driver.getCurrentContext().catch(() => null),
|
|
32
|
+
driver.getContexts().catch(() => []),
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
if (currentContext === args.context) {
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: 'text',
|
|
40
|
+
text: `Already on context "${args.context}".`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!availableContexts || availableContexts.length === 0) {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: 'text',
|
|
51
|
+
text: 'No contexts available. Cannot switch context.',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!availableContexts.includes(args.context)) {
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: 'text',
|
|
63
|
+
text: `Context "${args.context}" not found. Available contexts: ${JSON.stringify(availableContexts, null, 2)}`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
isError: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
await driver.switchContext(args.context);
|
|
70
|
+
|
|
71
|
+
// Verify the switch was successful
|
|
72
|
+
const newContext = await driver.getCurrentContext();
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: 'text',
|
|
78
|
+
text: `Successfully switched context from "${currentContext || 'N/A'}" to "${newContext}".`,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
} catch (err: any) {
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: 'text',
|
|
87
|
+
text: `Failed to switch context. Error: ${err.toString()}`,
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
isError: true,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
package/src/tools/index.ts
CHANGED
|
@@ -26,6 +26,7 @@ import installWDA from './ios/install-wda.js';
|
|
|
26
26
|
import generateTest from './test-generation/generate-tests.js';
|
|
27
27
|
import scroll from './navigations/scroll.js';
|
|
28
28
|
import scrollToElement from './navigations/scroll-to-element.js';
|
|
29
|
+
import swipe from './navigations/swipe.js';
|
|
29
30
|
import findElement from './interactions/find.js';
|
|
30
31
|
import clickElement from './interactions/click.js';
|
|
31
32
|
import doubleTap from './interactions/double-tap.js';
|
|
@@ -38,6 +39,8 @@ import installApp from './app-management/install-app.js';
|
|
|
38
39
|
import uninstallApp from './app-management/uninstall-app.js';
|
|
39
40
|
import terminateApp from './app-management/terminate-app.js';
|
|
40
41
|
import listApps from './app-management/list-apps.js';
|
|
42
|
+
import getContexts from './context/get-contexts.js';
|
|
43
|
+
import switchContext from './context/switch-context.js';
|
|
41
44
|
|
|
42
45
|
export default function registerTools(server: FastMCP): void {
|
|
43
46
|
// Wrap addTool to inject logging around tool execution
|
|
@@ -120,6 +123,7 @@ export default function registerTools(server: FastMCP): void {
|
|
|
120
123
|
// Navigation
|
|
121
124
|
scroll(server);
|
|
122
125
|
scrollToElement(server);
|
|
126
|
+
swipe(server);
|
|
123
127
|
|
|
124
128
|
// Element Interactions
|
|
125
129
|
findElement(server);
|
|
@@ -137,6 +141,10 @@ export default function registerTools(server: FastMCP): void {
|
|
|
137
141
|
terminateApp(server);
|
|
138
142
|
listApps(server);
|
|
139
143
|
|
|
144
|
+
// Context Management
|
|
145
|
+
getContexts(server);
|
|
146
|
+
switchContext(server);
|
|
147
|
+
|
|
140
148
|
// Test Generation
|
|
141
149
|
generateLocators(server);
|
|
142
150
|
generateTest(server);
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getDriver, getPlatformName } from '../../session-store.js';
|
|
3
|
+
import log from '../../logger.js';
|
|
4
|
+
import { elementUUIDScheme } from '../../schema.js';
|
|
5
|
+
|
|
6
|
+
function calculateSwipeCoordinates(
|
|
7
|
+
direction: 'left' | 'right' | 'up' | 'down',
|
|
8
|
+
width: number,
|
|
9
|
+
height: number
|
|
10
|
+
): { startX: number; startY: number; endX: number; endY: number } {
|
|
11
|
+
const centerX = Math.floor(width / 2);
|
|
12
|
+
const centerY = Math.floor(height / 2);
|
|
13
|
+
|
|
14
|
+
switch (direction) {
|
|
15
|
+
case 'left':
|
|
16
|
+
return {
|
|
17
|
+
startX: Math.floor(width * 0.8),
|
|
18
|
+
startY: centerY,
|
|
19
|
+
endX: Math.floor(width * 0.2),
|
|
20
|
+
endY: centerY,
|
|
21
|
+
};
|
|
22
|
+
case 'right':
|
|
23
|
+
return {
|
|
24
|
+
startX: Math.floor(width * 0.2),
|
|
25
|
+
startY: centerY,
|
|
26
|
+
endX: Math.floor(width * 0.8),
|
|
27
|
+
endY: centerY,
|
|
28
|
+
};
|
|
29
|
+
case 'up':
|
|
30
|
+
return {
|
|
31
|
+
startX: centerX,
|
|
32
|
+
startY: Math.floor(height * 0.8),
|
|
33
|
+
endX: centerX,
|
|
34
|
+
endY: Math.floor(height * 0.2),
|
|
35
|
+
};
|
|
36
|
+
case 'down':
|
|
37
|
+
return {
|
|
38
|
+
startX: centerX,
|
|
39
|
+
startY: Math.floor(height * 0.2),
|
|
40
|
+
endX: centerX,
|
|
41
|
+
endY: Math.floor(height * 0.8),
|
|
42
|
+
};
|
|
43
|
+
default:
|
|
44
|
+
throw new Error(`Invalid direction: ${direction}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function performAndroidSwipe(
|
|
49
|
+
driver: any,
|
|
50
|
+
startX: number,
|
|
51
|
+
startY: number,
|
|
52
|
+
endX: number,
|
|
53
|
+
endY: number,
|
|
54
|
+
duration: number
|
|
55
|
+
): Promise<void> {
|
|
56
|
+
await driver.performActions([
|
|
57
|
+
{
|
|
58
|
+
type: 'pointer',
|
|
59
|
+
id: 'finger1',
|
|
60
|
+
parameters: { pointerType: 'touch' },
|
|
61
|
+
actions: [
|
|
62
|
+
{ type: 'pointerMove', duration: 0, x: startX, y: startY },
|
|
63
|
+
{ type: 'pointerDown', button: 0 },
|
|
64
|
+
{ type: 'pause', duration: 250 },
|
|
65
|
+
{ type: 'pointerMove', duration: duration, x: endX, y: endY },
|
|
66
|
+
{ type: 'pointerUp', button: 0 },
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function performiOSSwipe(
|
|
73
|
+
driver: any,
|
|
74
|
+
startX: number,
|
|
75
|
+
startY: number,
|
|
76
|
+
endX: number,
|
|
77
|
+
endY: number,
|
|
78
|
+
duration: number
|
|
79
|
+
): Promise<void> {
|
|
80
|
+
try {
|
|
81
|
+
await driver.execute('mobile: dragFromToForDuration', {
|
|
82
|
+
fromX: startX,
|
|
83
|
+
fromY: startY,
|
|
84
|
+
toX: endX,
|
|
85
|
+
toY: endY,
|
|
86
|
+
duration: duration / 1000,
|
|
87
|
+
});
|
|
88
|
+
log.info('iOS swipe completed using mobile: dragFromToForDuration');
|
|
89
|
+
} catch (dragError) {
|
|
90
|
+
log.info('mobile: dragFromToForDuration failed, trying performActions');
|
|
91
|
+
await driver.performActions([
|
|
92
|
+
{
|
|
93
|
+
type: 'pointer',
|
|
94
|
+
id: 'finger1',
|
|
95
|
+
parameters: { pointerType: 'touch' },
|
|
96
|
+
actions: [
|
|
97
|
+
{ type: 'pointerMove', duration: 0, x: startX, y: startY },
|
|
98
|
+
{ type: 'pointerDown', button: 0 },
|
|
99
|
+
{ type: 'pause', duration: 200 },
|
|
100
|
+
{ type: 'pointerMove', duration: duration, x: endX, y: endY },
|
|
101
|
+
{ type: 'pause', duration: 50 },
|
|
102
|
+
{ type: 'pointerUp', button: 0 },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
]);
|
|
106
|
+
log.info('iOS swipe completed using performActions');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default function swipe(server: any): void {
|
|
111
|
+
server.addTool({
|
|
112
|
+
name: 'appium_swipe',
|
|
113
|
+
description: `Swipe on the current screen in a specified direction or between custom coordinates.
|
|
114
|
+
Supports four directions: left, right, up, down.
|
|
115
|
+
Can also perform custom coordinate-based swipes for precise control.
|
|
116
|
+
This is useful for navigating carousels, switching tabs, dismissing elements, or navigating between screens.`,
|
|
117
|
+
parameters: z.object({
|
|
118
|
+
direction: z
|
|
119
|
+
.enum(['left', 'right', 'up', 'down'])
|
|
120
|
+
.optional()
|
|
121
|
+
.describe(
|
|
122
|
+
'Direction to swipe. If provided, coordinates will be calculated automatically based on screen size or, when elementUUID is set, relative to that element. Either direction OR custom coordinates must be provided.'
|
|
123
|
+
),
|
|
124
|
+
elementUUID: elementUUIDScheme
|
|
125
|
+
.optional()
|
|
126
|
+
.describe(
|
|
127
|
+
'Optional element to base the swipe on. When provided with direction, the swipe is calculated relative to this element instead of the whole screen.'
|
|
128
|
+
),
|
|
129
|
+
startX: z
|
|
130
|
+
.number()
|
|
131
|
+
.int()
|
|
132
|
+
.min(0)
|
|
133
|
+
.optional()
|
|
134
|
+
.describe(
|
|
135
|
+
'Starting X coordinate for custom swipe. Required if direction is not provided.'
|
|
136
|
+
),
|
|
137
|
+
startY: z
|
|
138
|
+
.number()
|
|
139
|
+
.int()
|
|
140
|
+
.min(0)
|
|
141
|
+
.optional()
|
|
142
|
+
.describe(
|
|
143
|
+
'Starting Y coordinate for custom swipe. Required if direction is not provided.'
|
|
144
|
+
),
|
|
145
|
+
endX: z
|
|
146
|
+
.number()
|
|
147
|
+
.int()
|
|
148
|
+
.min(0)
|
|
149
|
+
.optional()
|
|
150
|
+
.describe(
|
|
151
|
+
'Ending X coordinate for custom swipe. Required if direction is not provided.'
|
|
152
|
+
),
|
|
153
|
+
endY: z
|
|
154
|
+
.number()
|
|
155
|
+
.int()
|
|
156
|
+
.min(0)
|
|
157
|
+
.optional()
|
|
158
|
+
.describe(
|
|
159
|
+
'Ending Y coordinate for custom swipe. Required if direction is not provided.'
|
|
160
|
+
),
|
|
161
|
+
duration: z
|
|
162
|
+
.number()
|
|
163
|
+
.int()
|
|
164
|
+
.min(0)
|
|
165
|
+
.max(5000)
|
|
166
|
+
.default(600)
|
|
167
|
+
.optional()
|
|
168
|
+
.describe(
|
|
169
|
+
'Duration of the swipe gesture in milliseconds. Default is 600ms. Higher values create slower swipes.'
|
|
170
|
+
),
|
|
171
|
+
}),
|
|
172
|
+
annotations: {
|
|
173
|
+
readOnlyHint: false,
|
|
174
|
+
openWorldHint: false,
|
|
175
|
+
},
|
|
176
|
+
execute: async (args: any, context: any): Promise<any> => {
|
|
177
|
+
const driver = getDriver();
|
|
178
|
+
if (!driver) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
'No active driver session. Please create a session first.'
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const platform = getPlatformName(driver);
|
|
186
|
+
let startX: number, startY: number, endX: number, endY: number;
|
|
187
|
+
|
|
188
|
+
if (args.direction) {
|
|
189
|
+
if (args.elementUUID) {
|
|
190
|
+
const rect = await (driver as any).getElementRect(args.elementUUID);
|
|
191
|
+
const elementCenterX = Math.floor(rect.x + rect.width / 2);
|
|
192
|
+
const elementCenterY = Math.floor(rect.y + rect.height / 2);
|
|
193
|
+
|
|
194
|
+
switch (args.direction) {
|
|
195
|
+
case 'left':
|
|
196
|
+
startX = Math.floor(rect.x + rect.width * 0.8);
|
|
197
|
+
startY = elementCenterY;
|
|
198
|
+
endX = Math.floor(rect.x + rect.width * 0.2);
|
|
199
|
+
endY = elementCenterY;
|
|
200
|
+
break;
|
|
201
|
+
case 'right':
|
|
202
|
+
startX = Math.floor(rect.x + rect.width * 0.2);
|
|
203
|
+
startY = elementCenterY;
|
|
204
|
+
endX = Math.floor(rect.x + rect.width * 0.8);
|
|
205
|
+
endY = elementCenterY;
|
|
206
|
+
break;
|
|
207
|
+
case 'up':
|
|
208
|
+
startX = elementCenterX;
|
|
209
|
+
startY = Math.floor(rect.y + rect.height * 0.8);
|
|
210
|
+
endX = elementCenterX;
|
|
211
|
+
endY = Math.floor(rect.y + rect.height * 0.2);
|
|
212
|
+
break;
|
|
213
|
+
case 'down':
|
|
214
|
+
startX = elementCenterX;
|
|
215
|
+
startY = Math.floor(rect.y + rect.height * 0.2);
|
|
216
|
+
endX = elementCenterX;
|
|
217
|
+
endY = Math.floor(rect.y + rect.height * 0.8);
|
|
218
|
+
break;
|
|
219
|
+
default:
|
|
220
|
+
throw new Error(`Invalid direction: ${args.direction}`);
|
|
221
|
+
}
|
|
222
|
+
log.info('Calculated element-based swipe coordinates:', {
|
|
223
|
+
elementUUID: args.elementUUID,
|
|
224
|
+
startX,
|
|
225
|
+
startY,
|
|
226
|
+
endX,
|
|
227
|
+
endY,
|
|
228
|
+
});
|
|
229
|
+
} else {
|
|
230
|
+
const { width, height } = await driver.getWindowSize();
|
|
231
|
+
log.info('Device screen size:', { width, height });
|
|
232
|
+
const coords = calculateSwipeCoordinates(
|
|
233
|
+
args.direction,
|
|
234
|
+
width,
|
|
235
|
+
height
|
|
236
|
+
);
|
|
237
|
+
startX = coords.startX;
|
|
238
|
+
startY = coords.startY;
|
|
239
|
+
endX = coords.endX;
|
|
240
|
+
endY = coords.endY;
|
|
241
|
+
}
|
|
242
|
+
} else if (
|
|
243
|
+
args.startX !== undefined &&
|
|
244
|
+
args.startY !== undefined &&
|
|
245
|
+
args.endX !== undefined &&
|
|
246
|
+
args.endY !== undefined
|
|
247
|
+
) {
|
|
248
|
+
startX = args.startX;
|
|
249
|
+
startY = args.startY;
|
|
250
|
+
endX = args.endX;
|
|
251
|
+
endY = args.endY;
|
|
252
|
+
} else {
|
|
253
|
+
throw new Error(
|
|
254
|
+
'Either direction or all custom coordinates (startX, startY, endX, endY) must be provided.'
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const duration = args.duration || 600;
|
|
259
|
+
|
|
260
|
+
log.info('Swipe coordinates:', {
|
|
261
|
+
startX,
|
|
262
|
+
startY,
|
|
263
|
+
endX,
|
|
264
|
+
endY,
|
|
265
|
+
duration,
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
if (platform === 'Android') {
|
|
269
|
+
if (startX !== endX && Math.abs(startY - endY) < 50) {
|
|
270
|
+
const swipeDuration = Math.min(duration, 400);
|
|
271
|
+
await driver.performActions([
|
|
272
|
+
{
|
|
273
|
+
type: 'pointer',
|
|
274
|
+
id: 'finger1',
|
|
275
|
+
parameters: { pointerType: 'touch' },
|
|
276
|
+
actions: [
|
|
277
|
+
{ type: 'pointerMove', duration: 0, x: startX, y: startY },
|
|
278
|
+
{ type: 'pointerDown', button: 0 },
|
|
279
|
+
{ type: 'pause', duration: 200 },
|
|
280
|
+
{
|
|
281
|
+
type: 'pointerMove',
|
|
282
|
+
duration: swipeDuration,
|
|
283
|
+
x: endX,
|
|
284
|
+
y: endY,
|
|
285
|
+
},
|
|
286
|
+
{ type: 'pause', duration: 50 },
|
|
287
|
+
{ type: 'pointerUp', button: 0 },
|
|
288
|
+
],
|
|
289
|
+
},
|
|
290
|
+
]);
|
|
291
|
+
log.info('Android horizontal swipe completed');
|
|
292
|
+
} else {
|
|
293
|
+
await performAndroidSwipe(
|
|
294
|
+
driver,
|
|
295
|
+
startX,
|
|
296
|
+
startY,
|
|
297
|
+
endX,
|
|
298
|
+
endY,
|
|
299
|
+
duration
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
log.info('Android swipe action completed successfully.');
|
|
303
|
+
} else if (platform === 'iOS') {
|
|
304
|
+
if (args.direction) {
|
|
305
|
+
try {
|
|
306
|
+
await driver.execute('mobile: swipe', {
|
|
307
|
+
direction: args.direction,
|
|
308
|
+
});
|
|
309
|
+
log.info(
|
|
310
|
+
`iOS swipe completed using mobile: swipe (${args.direction})`
|
|
311
|
+
);
|
|
312
|
+
} catch (swipeError) {
|
|
313
|
+
log.info('mobile: swipe failed, trying dragFromToForDuration');
|
|
314
|
+
await performiOSSwipe(
|
|
315
|
+
driver,
|
|
316
|
+
startX,
|
|
317
|
+
startY,
|
|
318
|
+
endX,
|
|
319
|
+
endY,
|
|
320
|
+
duration
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
await performiOSSwipe(driver, startX, startY, endX, endY, duration);
|
|
325
|
+
}
|
|
326
|
+
log.info('iOS swipe action completed successfully.');
|
|
327
|
+
} else {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`Unsupported platform: ${platform}. Only Android and iOS are supported.`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const directionText = args.direction
|
|
334
|
+
? ` ${args.direction}`
|
|
335
|
+
: ` from (${startX}, ${startY}) to (${endX}, ${endY})`;
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
content: [
|
|
339
|
+
{
|
|
340
|
+
type: 'text',
|
|
341
|
+
text: `Swiped${directionText} successfully.`,
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
};
|
|
345
|
+
} catch (err: any) {
|
|
346
|
+
return {
|
|
347
|
+
content: [
|
|
348
|
+
{
|
|
349
|
+
type: 'text',
|
|
350
|
+
text: `Failed to perform swipe. Error: ${err.toString()}`,
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
}
|