@wdio/mcp 1.6.0 → 2.0.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/README.md +10 -26
- package/lib/server.js +407 -775
- package/lib/server.js.map +1 -1
- package/package.json +2 -1
package/lib/server.js
CHANGED
|
@@ -12,8 +12,9 @@ var startBrowserToolDefinition = {
|
|
|
12
12
|
description: "starts a browser session and sets it to the current state",
|
|
13
13
|
inputSchema: {
|
|
14
14
|
headless: z.boolean().optional(),
|
|
15
|
-
windowWidth: z.number().min(400).max(3840).optional(),
|
|
16
|
-
windowHeight: z.number().min(400).max(2160).optional()
|
|
15
|
+
windowWidth: z.number().min(400).max(3840).optional().default(1920),
|
|
16
|
+
windowHeight: z.number().min(400).max(2160).optional().default(1080),
|
|
17
|
+
navigationUrl: z.string().optional().describe("URL to navigate to after starting the browser")
|
|
17
18
|
}
|
|
18
19
|
};
|
|
19
20
|
var closeSessionToolDefinition = {
|
|
@@ -36,7 +37,12 @@ var getBrowser = () => {
|
|
|
36
37
|
return browser;
|
|
37
38
|
};
|
|
38
39
|
getBrowser.__state = state;
|
|
39
|
-
var startBrowserTool = async ({
|
|
40
|
+
var startBrowserTool = async ({
|
|
41
|
+
headless = false,
|
|
42
|
+
windowWidth = 1920,
|
|
43
|
+
windowHeight = 1080,
|
|
44
|
+
navigationUrl
|
|
45
|
+
}) => {
|
|
40
46
|
const chromeArgs = [
|
|
41
47
|
`--window-size=${windowWidth},${windowHeight}`,
|
|
42
48
|
"--no-sandbox",
|
|
@@ -70,11 +76,15 @@ var startBrowserTool = async ({ headless = false, windowWidth = 1280, windowHeig
|
|
|
70
76
|
capabilities: browser.capabilities,
|
|
71
77
|
isAttached: false
|
|
72
78
|
});
|
|
79
|
+
if (navigationUrl) {
|
|
80
|
+
await browser.url(navigationUrl);
|
|
81
|
+
}
|
|
73
82
|
const modeText = headless ? "headless" : "headed";
|
|
83
|
+
const urlText = navigationUrl ? ` and navigated to ${navigationUrl}` : "";
|
|
74
84
|
return {
|
|
75
85
|
content: [{
|
|
76
86
|
type: "text",
|
|
77
|
-
text: `Browser started in ${modeText} mode with sessionId: ${sessionId} (${windowWidth}x${windowHeight})`
|
|
87
|
+
text: `Browser started in ${modeText} mode with sessionId: ${sessionId} (${windowWidth}x${windowHeight})${urlText}`
|
|
78
88
|
}]
|
|
79
89
|
};
|
|
80
90
|
};
|
|
@@ -136,15 +146,6 @@ var clickToolDefinition = {
|
|
|
136
146
|
timeout: z3.number().optional().describe("Maximum time to wait for element in milliseconds")
|
|
137
147
|
}
|
|
138
148
|
};
|
|
139
|
-
var clickViaTextToolDefinition = {
|
|
140
|
-
name: "click_via_text",
|
|
141
|
-
description: "clicks an element",
|
|
142
|
-
inputSchema: {
|
|
143
|
-
selector: z3.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']" or "button=Exact text with spaces" or "a*=Link containing text")`),
|
|
144
|
-
scrollToView: z3.boolean().optional().describe("Whether to scroll the element into view before clicking").default(true),
|
|
145
|
-
timeout: z3.number().optional().describe("Maximum time to wait for element in milliseconds")
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
149
|
var clickAction = async (selector, timeout, scrollToView = true) => {
|
|
149
150
|
try {
|
|
150
151
|
const browser = getBrowser();
|
|
@@ -163,7 +164,6 @@ var clickAction = async (selector, timeout, scrollToView = true) => {
|
|
|
163
164
|
}
|
|
164
165
|
};
|
|
165
166
|
var clickTool = async ({ selector, scrollToView, timeout = defaultTimeout }) => clickAction(selector, timeout, scrollToView);
|
|
166
|
-
var clickToolViaText = async ({ text, scrollToView, timeout = defaultTimeout }) => clickAction(`//a[contains(text(), '${text}')]`, timeout, scrollToView);
|
|
167
167
|
|
|
168
168
|
// src/tools/set-value.tool.ts
|
|
169
169
|
import { z as z4 } from "zod";
|
|
@@ -197,137 +197,250 @@ var setValueTool = async ({ selector, value, scrollToView = true, timeout = defa
|
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
199
|
|
|
200
|
-
// src/tools/
|
|
200
|
+
// src/tools/app-session.tool.ts
|
|
201
|
+
import { remote as remote2 } from "webdriverio";
|
|
201
202
|
import { z as z5 } from "zod";
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
203
|
+
|
|
204
|
+
// src/config/appium.config.ts
|
|
205
|
+
function getAppiumServerConfig(overrides) {
|
|
206
|
+
return {
|
|
207
|
+
hostname: overrides?.hostname || process.env.APPIUM_URL || "127.0.0.1",
|
|
208
|
+
port: overrides?.port || Number(process.env.APPIUM_URL_PORT) || 4723,
|
|
209
|
+
path: overrides?.path || process.env.APPIUM_PATH || "/"
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function buildIOSCapabilities(appPath, options) {
|
|
213
|
+
const capabilities = {
|
|
214
|
+
platformName: "iOS",
|
|
215
|
+
"appium:platformVersion": options.platformVersion,
|
|
216
|
+
"appium:deviceName": options.deviceName,
|
|
217
|
+
"appium:automationName": options.automationName || "XCUITest"
|
|
218
|
+
};
|
|
219
|
+
if (appPath) {
|
|
220
|
+
capabilities["appium:app"] = appPath;
|
|
209
221
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
const browser = getBrowser();
|
|
214
|
-
await browser.waitUntil(browser.$(selector).isExisting, { timeout });
|
|
215
|
-
return {
|
|
216
|
-
content: [{ type: "text", text: "Element found" }]
|
|
217
|
-
};
|
|
218
|
-
} catch (e) {
|
|
219
|
-
return {
|
|
220
|
-
content: [{ type: "text", text: `Error finding element: ${e}` }]
|
|
221
|
-
};
|
|
222
|
+
if (options.udid) {
|
|
223
|
+
capabilities["appium:udid"] = options.udid;
|
|
222
224
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// src/tools/get-element-text.tool.ts
|
|
226
|
-
import { z as z6 } from "zod";
|
|
227
|
-
var defaultTimeout4 = 3e3;
|
|
228
|
-
var getElementTextToolDefinition = {
|
|
229
|
-
name: "get_element_text",
|
|
230
|
-
description: "gets the text content of an element",
|
|
231
|
-
inputSchema: {
|
|
232
|
-
selector: z6.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']")`),
|
|
233
|
-
timeout: z6.number().optional().describe("Maximum time to wait for element in milliseconds")
|
|
225
|
+
if (options.noReset !== void 0) {
|
|
226
|
+
capabilities["appium:noReset"] = options.noReset;
|
|
234
227
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
try {
|
|
238
|
-
const browser = getBrowser();
|
|
239
|
-
await browser.waitUntil(browser.$(selector).isExisting, { timeout });
|
|
240
|
-
const text = await browser.$(selector).getText();
|
|
241
|
-
return {
|
|
242
|
-
content: [{ type: "text", text: `Text from element "${selector}": ${text}` }]
|
|
243
|
-
};
|
|
244
|
-
} catch (e) {
|
|
245
|
-
return {
|
|
246
|
-
content: [{ type: "text", text: `Error getting element text: ${e}` }]
|
|
247
|
-
};
|
|
228
|
+
if (options.fullReset !== void 0) {
|
|
229
|
+
capabilities["appium:fullReset"] = options.fullReset;
|
|
248
230
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
// src/tools/is-displayed.tool.ts
|
|
252
|
-
import { z as z7 } from "zod";
|
|
253
|
-
var defaultTimeout5 = 3e3;
|
|
254
|
-
var isDisplayedToolDefinition = {
|
|
255
|
-
name: "is_displayed",
|
|
256
|
-
description: "checks if an element is displayed",
|
|
257
|
-
inputSchema: {
|
|
258
|
-
selector: z7.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']")`),
|
|
259
|
-
timeout: z7.number().optional().describe("Maximum time to wait for element in milliseconds")
|
|
231
|
+
if (options.newCommandTimeout !== void 0) {
|
|
232
|
+
capabilities["appium:newCommandTimeout"] = options.newCommandTimeout;
|
|
260
233
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
const displayed = await browser.$(selector).isDisplayed();
|
|
267
|
-
return {
|
|
268
|
-
content: [{
|
|
269
|
-
type: "text",
|
|
270
|
-
text: `Element with selector "${selector}" is ${displayed ? "displayed" : "not displayed"}`
|
|
271
|
-
}]
|
|
272
|
-
};
|
|
273
|
-
} catch (e) {
|
|
274
|
-
return {
|
|
275
|
-
content: [{ type: "text", text: `Error checking if element is displayed: ${e}` }]
|
|
276
|
-
};
|
|
234
|
+
capabilities["appium:autoGrantPermissions"] = options.autoGrantPermissions ?? true;
|
|
235
|
+
capabilities["appium:autoAcceptAlerts"] = options.autoAcceptAlerts ?? true;
|
|
236
|
+
if (options.autoDismissAlerts !== void 0) {
|
|
237
|
+
capabilities["appium:autoDismissAlerts"] = options.autoDismissAlerts;
|
|
238
|
+
capabilities["appium:autoAcceptAlerts"] = void 0;
|
|
277
239
|
}
|
|
278
|
-
|
|
240
|
+
for (const [key, value] of Object.entries(options)) {
|
|
241
|
+
if (!["deviceName", "platformVersion", "automationName", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset", "newCommandTimeout"].includes(
|
|
242
|
+
key
|
|
243
|
+
)) {
|
|
244
|
+
capabilities[`appium:${key}`] = value;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return capabilities;
|
|
248
|
+
}
|
|
249
|
+
function buildAndroidCapabilities(appPath, options) {
|
|
250
|
+
const capabilities = {
|
|
251
|
+
platformName: "Android",
|
|
252
|
+
"appium:platformVersion": options.platformVersion,
|
|
253
|
+
"appium:deviceName": options.deviceName,
|
|
254
|
+
"appium:automationName": options.automationName || "UiAutomator2"
|
|
255
|
+
};
|
|
256
|
+
if (appPath) {
|
|
257
|
+
capabilities["appium:app"] = appPath;
|
|
258
|
+
}
|
|
259
|
+
if (options.noReset !== void 0) {
|
|
260
|
+
capabilities["appium:noReset"] = options.noReset;
|
|
261
|
+
}
|
|
262
|
+
if (options.fullReset !== void 0) {
|
|
263
|
+
capabilities["appium:fullReset"] = options.fullReset;
|
|
264
|
+
}
|
|
265
|
+
if (options.newCommandTimeout !== void 0) {
|
|
266
|
+
capabilities["appium:newCommandTimeout"] = options.newCommandTimeout;
|
|
267
|
+
}
|
|
268
|
+
capabilities["appium:autoGrantPermissions"] = options.autoGrantPermissions ?? true;
|
|
269
|
+
capabilities["appium:autoAcceptAlerts"] = options.autoAcceptAlerts ?? true;
|
|
270
|
+
if (options.autoDismissAlerts !== void 0) {
|
|
271
|
+
capabilities["appium:autoDismissAlerts"] = options.autoDismissAlerts;
|
|
272
|
+
capabilities["appium:autoAcceptAlerts"] = void 0;
|
|
273
|
+
}
|
|
274
|
+
if (options.appWaitActivity) {
|
|
275
|
+
capabilities["appium:appWaitActivity"] = options.appWaitActivity;
|
|
276
|
+
}
|
|
277
|
+
for (const [key, value] of Object.entries(options)) {
|
|
278
|
+
if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "appWaitActivity", "noReset", "fullReset", "newCommandTimeout"].includes(
|
|
279
|
+
key
|
|
280
|
+
)) {
|
|
281
|
+
capabilities[`appium:${key}`] = value;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return capabilities;
|
|
285
|
+
}
|
|
279
286
|
|
|
280
|
-
// src/tools/
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
description: "scrolls the page down by specified pixels",
|
|
287
|
+
// src/tools/app-session.tool.ts
|
|
288
|
+
var startAppToolDefinition = {
|
|
289
|
+
name: "start_app_session",
|
|
290
|
+
description: "starts a mobile app session (iOS/Android) via Appium",
|
|
285
291
|
inputSchema: {
|
|
286
|
-
|
|
292
|
+
platform: z5.enum(["iOS", "Android"]).describe("Mobile platform"),
|
|
293
|
+
appPath: z5.string().optional().describe("Path to the app file (.app/.apk/.ipa). Required unless noReset=true (connecting to already-running app)"),
|
|
294
|
+
deviceName: z5.string().describe("Device/emulator/simulator name"),
|
|
295
|
+
platformVersion: z5.string().optional().describe('OS version (e.g., "17.0", "14")'),
|
|
296
|
+
automationName: z5.enum(["XCUITest", "UiAutomator2", "Espresso"]).optional().describe("Automation driver name"),
|
|
297
|
+
appiumHost: z5.string().optional().describe("Appium server hostname (overrides APPIUM_URL env var)"),
|
|
298
|
+
appiumPort: z5.number().optional().describe("Appium server port (overrides APPIUM_URL_PORT env var)"),
|
|
299
|
+
appiumPath: z5.string().optional().describe("Appium server path (overrides APPIUM_PATH env var)"),
|
|
300
|
+
autoGrantPermissions: z5.boolean().optional().describe("Auto-grant app permissions (default: true)"),
|
|
301
|
+
autoAcceptAlerts: z5.boolean().optional().describe("Auto-accept alerts (default: true)"),
|
|
302
|
+
autoDismissAlerts: z5.boolean().optional().describe('Auto-dismiss alerts (default: false, will override "autoAcceptAlerts" to undefined if set)'),
|
|
303
|
+
appWaitActivity: z5.string().optional().describe("Activity to wait for on launch (Android only)"),
|
|
304
|
+
udid: z5.string().optional().describe('Unique Device Identifier for iOS real device testing (e.g., "00008030-001234567890002E")'),
|
|
305
|
+
noReset: z5.boolean().optional().describe("Do not reset app state before session (preserves app data). Default: false"),
|
|
306
|
+
fullReset: z5.boolean().optional().describe("Uninstall app before/after session. Default: true. Set to false with noReset=true to preserve app state completely"),
|
|
307
|
+
newCommandTimeout: z5.number().min(0).optional().describe("How long (in seconds) Appium will wait for a new command before assuming the client has quit and ending the session. Default: 60. Set to 300 for 5 minutes, etc.")
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
var getState = () => {
|
|
311
|
+
const sharedState = getBrowser.__state;
|
|
312
|
+
if (!sharedState) {
|
|
313
|
+
throw new Error("Browser state not initialized");
|
|
287
314
|
}
|
|
315
|
+
return sharedState;
|
|
288
316
|
};
|
|
289
|
-
var
|
|
317
|
+
var startAppTool = async (args) => {
|
|
290
318
|
try {
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
319
|
+
const {
|
|
320
|
+
platform,
|
|
321
|
+
appPath,
|
|
322
|
+
deviceName,
|
|
323
|
+
platformVersion,
|
|
324
|
+
automationName,
|
|
325
|
+
appiumHost,
|
|
326
|
+
appiumPort,
|
|
327
|
+
appiumPath,
|
|
328
|
+
autoGrantPermissions = true,
|
|
329
|
+
autoAcceptAlerts,
|
|
330
|
+
autoDismissAlerts,
|
|
331
|
+
appWaitActivity,
|
|
332
|
+
udid,
|
|
333
|
+
noReset,
|
|
334
|
+
fullReset,
|
|
335
|
+
newCommandTimeout
|
|
336
|
+
} = args;
|
|
337
|
+
if (!appPath && noReset !== true) {
|
|
338
|
+
return {
|
|
339
|
+
content: [{
|
|
340
|
+
type: "text",
|
|
341
|
+
text: 'Error: Either "appPath" must be provided to install an app, or "noReset: true" must be set to connect to an already-running app.'
|
|
342
|
+
}]
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
const serverConfig = getAppiumServerConfig({
|
|
346
|
+
hostname: appiumHost,
|
|
347
|
+
port: appiumPort,
|
|
348
|
+
path: appiumPath
|
|
349
|
+
});
|
|
350
|
+
const capabilities = platform === "iOS" ? buildIOSCapabilities(appPath, {
|
|
351
|
+
deviceName,
|
|
352
|
+
platformVersion,
|
|
353
|
+
automationName: automationName || "XCUITest",
|
|
354
|
+
autoGrantPermissions,
|
|
355
|
+
autoAcceptAlerts,
|
|
356
|
+
autoDismissAlerts,
|
|
357
|
+
udid,
|
|
358
|
+
noReset,
|
|
359
|
+
fullReset,
|
|
360
|
+
newCommandTimeout
|
|
361
|
+
}) : buildAndroidCapabilities(appPath, {
|
|
362
|
+
deviceName,
|
|
363
|
+
platformVersion,
|
|
364
|
+
automationName: automationName || "UiAutomator2",
|
|
365
|
+
autoGrantPermissions,
|
|
366
|
+
autoAcceptAlerts,
|
|
367
|
+
autoDismissAlerts,
|
|
368
|
+
appWaitActivity,
|
|
369
|
+
noReset,
|
|
370
|
+
fullReset,
|
|
371
|
+
newCommandTimeout
|
|
372
|
+
});
|
|
373
|
+
const browser = await remote2({
|
|
374
|
+
protocol: "http",
|
|
375
|
+
hostname: serverConfig.hostname,
|
|
376
|
+
port: serverConfig.port,
|
|
377
|
+
path: serverConfig.path,
|
|
378
|
+
capabilities
|
|
379
|
+
});
|
|
380
|
+
const { sessionId } = browser;
|
|
381
|
+
const shouldAutoDetach = noReset === true || !appPath;
|
|
382
|
+
const state2 = getState();
|
|
383
|
+
state2.browsers.set(sessionId, browser);
|
|
384
|
+
state2.currentSession = sessionId;
|
|
385
|
+
state2.sessionMetadata.set(sessionId, {
|
|
386
|
+
type: platform.toLowerCase(),
|
|
387
|
+
capabilities,
|
|
388
|
+
isAttached: shouldAutoDetach
|
|
389
|
+
});
|
|
390
|
+
const appInfo = appPath ? `
|
|
391
|
+
App: ${appPath}` : "\nApp: (connected to running app)";
|
|
392
|
+
const detachNote = shouldAutoDetach ? "\n\n(Auto-detach enabled: session will be preserved on close. Use close_session({ detach: false }) to force terminate.)" : "";
|
|
295
393
|
return {
|
|
296
|
-
content: [
|
|
394
|
+
content: [
|
|
395
|
+
{
|
|
396
|
+
type: "text",
|
|
397
|
+
text: `${platform} app session started with sessionId: ${sessionId}
|
|
398
|
+
Device: ${deviceName}${appInfo}
|
|
399
|
+
Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}${detachNote}`
|
|
400
|
+
}
|
|
401
|
+
]
|
|
297
402
|
};
|
|
298
403
|
} catch (e) {
|
|
299
404
|
return {
|
|
300
|
-
content: [{ type: "text", text: `Error
|
|
405
|
+
content: [{ type: "text", text: `Error starting app session: ${e}` }]
|
|
301
406
|
};
|
|
302
407
|
}
|
|
303
408
|
};
|
|
304
409
|
|
|
305
|
-
// src/tools/scroll
|
|
306
|
-
import { z as
|
|
307
|
-
var
|
|
308
|
-
name: "
|
|
309
|
-
description: "scrolls the page
|
|
410
|
+
// src/tools/scroll.tool.ts
|
|
411
|
+
import { z as z6 } from "zod";
|
|
412
|
+
var scrollToolDefinition = {
|
|
413
|
+
name: "scroll",
|
|
414
|
+
description: "scrolls the page by specified pixels (browser only). For mobile, use the swipe tool.",
|
|
310
415
|
inputSchema: {
|
|
311
|
-
|
|
416
|
+
direction: z6.enum(["up", "down"]).describe("Scroll direction"),
|
|
417
|
+
pixels: z6.number().optional().default(500).describe("Number of pixels to scroll")
|
|
312
418
|
}
|
|
313
419
|
};
|
|
314
|
-
var
|
|
420
|
+
var scrollTool = async ({ direction, pixels = 500 }) => {
|
|
315
421
|
try {
|
|
316
422
|
const browser = getBrowser();
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
423
|
+
const state2 = getState();
|
|
424
|
+
const metadata = state2.sessionMetadata.get(state2.currentSession);
|
|
425
|
+
const sessionType = metadata?.type;
|
|
426
|
+
if (sessionType !== "browser") {
|
|
427
|
+
throw new Error("scroll only works in browser sessions. For mobile, use the swipe tool.");
|
|
428
|
+
}
|
|
429
|
+
const scrollAmount = direction === "down" ? pixels : -pixels;
|
|
430
|
+
await browser.execute((amount) => {
|
|
431
|
+
window.scrollBy(0, amount);
|
|
432
|
+
}, scrollAmount);
|
|
320
433
|
return {
|
|
321
|
-
content: [{ type: "text", text: `Scrolled
|
|
434
|
+
content: [{ type: "text", text: `Scrolled ${direction} ${pixels} pixels` }]
|
|
322
435
|
};
|
|
323
436
|
} catch (e) {
|
|
324
437
|
return {
|
|
325
|
-
content: [{ type: "text", text: `Error scrolling
|
|
438
|
+
content: [{ type: "text", text: `Error scrolling: ${e}` }]
|
|
326
439
|
};
|
|
327
440
|
}
|
|
328
441
|
};
|
|
329
442
|
|
|
330
|
-
// src/scripts/get-interactable-elements.ts
|
|
443
|
+
// src/scripts/get-interactable-browser-elements.ts
|
|
331
444
|
var elementsScript = (elementType = "interactable") => (function() {
|
|
332
445
|
const interactableSelectors = [
|
|
333
446
|
"a[href]",
|
|
@@ -494,7 +607,7 @@ var elementsScript = (elementType = "interactable") => (function() {
|
|
|
494
607
|
}
|
|
495
608
|
return getElements();
|
|
496
609
|
})();
|
|
497
|
-
var
|
|
610
|
+
var get_interactable_browser_elements_default = elementsScript;
|
|
498
611
|
|
|
499
612
|
// src/locators/source-parsing.ts
|
|
500
613
|
import { DOMParser } from "@xmldom/xmldom";
|
|
@@ -1097,7 +1210,7 @@ function generateAllElementLocators(sourceXML, options) {
|
|
|
1097
1210
|
return ctx.results;
|
|
1098
1211
|
}
|
|
1099
1212
|
|
|
1100
|
-
// src/
|
|
1213
|
+
// src/scripts/get-visible-mobile-elements.ts
|
|
1101
1214
|
var LOCATOR_PRIORITY = [
|
|
1102
1215
|
"accessibility-id",
|
|
1103
1216
|
// Most stable, cross-platform
|
|
@@ -1185,7 +1298,7 @@ async function getMobileVisibleElements(browser, platform, options = {}) {
|
|
|
1185
1298
|
|
|
1186
1299
|
// src/tools/get-visible-elements.tool.ts
|
|
1187
1300
|
import { encode } from "@toon-format/toon";
|
|
1188
|
-
import { z as
|
|
1301
|
+
import { z as z7 } from "zod";
|
|
1189
1302
|
|
|
1190
1303
|
// src/utils/strip-undefined.ts
|
|
1191
1304
|
function stripUndefined(obj) {
|
|
@@ -1202,20 +1315,20 @@ var getVisibleElementsToolDefinition = {
|
|
|
1202
1315
|
name: "get_visible_elements",
|
|
1203
1316
|
description: 'get a list of visible (in viewport & displayed) interactable elements on the page (buttons, links, inputs). Use elementType="visual" for images/SVGs. Must prefer this to take_screenshot for interactions',
|
|
1204
1317
|
inputSchema: {
|
|
1205
|
-
inViewportOnly:
|
|
1318
|
+
inViewportOnly: z7.boolean().optional().describe(
|
|
1206
1319
|
"Only return elements within the visible viewport. Default: true. Set to false to get ALL elements on the page."
|
|
1207
1320
|
),
|
|
1208
|
-
includeContainers:
|
|
1321
|
+
includeContainers: z7.boolean().optional().describe(
|
|
1209
1322
|
"Include layout containers (ViewGroup, FrameLayout, ScrollView, etc). Default: false. Set to true to see all elements including layouts."
|
|
1210
1323
|
),
|
|
1211
|
-
includeBounds:
|
|
1324
|
+
includeBounds: z7.boolean().optional().describe(
|
|
1212
1325
|
"Include element bounds/coordinates (x, y, width, height). Default: false. Set to true for coordinate-based interactions or layout debugging."
|
|
1213
1326
|
),
|
|
1214
|
-
elementType:
|
|
1327
|
+
elementType: z7.enum(["interactable", "visual", "all"]).optional().describe(
|
|
1215
1328
|
'Type of elements to return: "interactable" (default) for buttons/links/inputs, "visual" for images/SVGs, "all" for both.'
|
|
1216
1329
|
),
|
|
1217
|
-
limit:
|
|
1218
|
-
offset:
|
|
1330
|
+
limit: z7.number().optional().describe("Maximum number of elements to return. Default: 0 (unlimited)."),
|
|
1331
|
+
offset: z7.number().optional().describe("Number of elements to skip (for pagination). Default: 0.")
|
|
1219
1332
|
}
|
|
1220
1333
|
};
|
|
1221
1334
|
var getVisibleElementsTool = async (args) => {
|
|
@@ -1234,7 +1347,7 @@ var getVisibleElementsTool = async (args) => {
|
|
|
1234
1347
|
const platform = browser.isAndroid ? "android" : "ios";
|
|
1235
1348
|
elements = await getMobileVisibleElements(browser, platform, { includeContainers, includeBounds });
|
|
1236
1349
|
} else {
|
|
1237
|
-
const raw = await browser.execute(
|
|
1350
|
+
const raw = await browser.execute(get_interactable_browser_elements_default, elementType);
|
|
1238
1351
|
elements = stripUndefinedFromArray(raw);
|
|
1239
1352
|
}
|
|
1240
1353
|
if (inViewportOnly) {
|
|
@@ -1264,29 +1377,56 @@ var getVisibleElementsTool = async (args) => {
|
|
|
1264
1377
|
};
|
|
1265
1378
|
|
|
1266
1379
|
// src/tools/take-screenshot.tool.ts
|
|
1267
|
-
import { z as
|
|
1380
|
+
import { z as z8 } from "zod";
|
|
1381
|
+
import sharp from "sharp";
|
|
1382
|
+
var MAX_DIMENSION = 2e3;
|
|
1383
|
+
var MAX_FILE_SIZE_BYTES = 1024 * 1024;
|
|
1268
1384
|
var takeScreenshotToolDefinition = {
|
|
1269
1385
|
name: "take_screenshot",
|
|
1270
1386
|
description: "captures a screenshot of the current page",
|
|
1271
1387
|
inputSchema: {
|
|
1272
|
-
outputPath:
|
|
1388
|
+
outputPath: z8.string().optional().describe("Optional path where to save the screenshot. If not provided, returns base64 data.")
|
|
1273
1389
|
}
|
|
1274
1390
|
};
|
|
1391
|
+
async function processScreenshot(screenshotBase64) {
|
|
1392
|
+
const inputBuffer = Buffer.from(screenshotBase64, "base64");
|
|
1393
|
+
let image = sharp(inputBuffer);
|
|
1394
|
+
const metadata = await image.metadata();
|
|
1395
|
+
const width = metadata.width ?? 0;
|
|
1396
|
+
const height = metadata.height ?? 0;
|
|
1397
|
+
if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
|
|
1398
|
+
const resizeOptions = width > height ? { width: MAX_DIMENSION } : { height: MAX_DIMENSION };
|
|
1399
|
+
image = image.resize(resizeOptions);
|
|
1400
|
+
}
|
|
1401
|
+
let outputBuffer = await image.png({ compressionLevel: 9 }).toBuffer();
|
|
1402
|
+
if (outputBuffer.length > MAX_FILE_SIZE_BYTES) {
|
|
1403
|
+
let quality = 90;
|
|
1404
|
+
while (quality >= 10 && outputBuffer.length > MAX_FILE_SIZE_BYTES) {
|
|
1405
|
+
outputBuffer = await image.jpeg({ quality, mozjpeg: true }).toBuffer();
|
|
1406
|
+
quality -= 10;
|
|
1407
|
+
}
|
|
1408
|
+
return { data: outputBuffer, mimeType: "image/jpeg" };
|
|
1409
|
+
}
|
|
1410
|
+
return { data: outputBuffer, mimeType: "image/png" };
|
|
1411
|
+
}
|
|
1275
1412
|
var takeScreenshotTool = async ({ outputPath }) => {
|
|
1276
1413
|
try {
|
|
1277
1414
|
const browser = getBrowser();
|
|
1278
1415
|
const screenshot = await browser.takeScreenshot();
|
|
1416
|
+
const { data, mimeType } = await processScreenshot(screenshot);
|
|
1279
1417
|
if (outputPath) {
|
|
1280
1418
|
const fs = await import("fs");
|
|
1281
|
-
await fs.promises.writeFile(outputPath,
|
|
1419
|
+
await fs.promises.writeFile(outputPath, data);
|
|
1420
|
+
const sizeKB2 = (data.length / 1024).toFixed(1);
|
|
1282
1421
|
return {
|
|
1283
|
-
content: [{ type: "text", text: `Screenshot saved to ${outputPath}` }]
|
|
1422
|
+
content: [{ type: "text", text: `Screenshot saved to ${outputPath} (${sizeKB2}KB, ${mimeType})` }]
|
|
1284
1423
|
};
|
|
1285
1424
|
}
|
|
1425
|
+
const sizeKB = (data.length / 1024).toFixed(1);
|
|
1286
1426
|
return {
|
|
1287
1427
|
content: [
|
|
1288
|
-
{ type: "text", text:
|
|
1289
|
-
{ type: "image", data:
|
|
1428
|
+
{ type: "text", text: `Screenshot captured (${sizeKB}KB, ${mimeType}):` },
|
|
1429
|
+
{ type: "image", data: data.toString("base64"), mimeType }
|
|
1290
1430
|
]
|
|
1291
1431
|
};
|
|
1292
1432
|
} catch (e) {
|
|
@@ -1297,12 +1437,12 @@ var takeScreenshotTool = async ({ outputPath }) => {
|
|
|
1297
1437
|
};
|
|
1298
1438
|
|
|
1299
1439
|
// src/tools/cookies.tool.ts
|
|
1300
|
-
import { z as
|
|
1440
|
+
import { z as z9 } from "zod";
|
|
1301
1441
|
var getCookiesToolDefinition = {
|
|
1302
1442
|
name: "get_cookies",
|
|
1303
1443
|
description: "gets all cookies or a specific cookie by name",
|
|
1304
1444
|
inputSchema: {
|
|
1305
|
-
name:
|
|
1445
|
+
name: z9.string().optional().describe("Optional cookie name to retrieve a specific cookie. If not provided, returns all cookies")
|
|
1306
1446
|
}
|
|
1307
1447
|
};
|
|
1308
1448
|
var getCookiesTool = async ({ name }) => {
|
|
@@ -1338,14 +1478,14 @@ var setCookieToolDefinition = {
|
|
|
1338
1478
|
name: "set_cookie",
|
|
1339
1479
|
description: "sets a cookie with specified name, value, and optional attributes",
|
|
1340
1480
|
inputSchema: {
|
|
1341
|
-
name:
|
|
1342
|
-
value:
|
|
1343
|
-
domain:
|
|
1344
|
-
path:
|
|
1345
|
-
|
|
1346
|
-
httpOnly:
|
|
1347
|
-
secure:
|
|
1348
|
-
sameSite:
|
|
1481
|
+
name: z9.string().describe("Cookie name"),
|
|
1482
|
+
value: z9.string().describe("Cookie value"),
|
|
1483
|
+
domain: z9.string().optional().describe("Cookie domain (defaults to current domain)"),
|
|
1484
|
+
path: z9.string().optional().describe('Cookie path (defaults to "/")'),
|
|
1485
|
+
expiry: z9.number().optional().describe("Expiry date as Unix timestamp in seconds"),
|
|
1486
|
+
httpOnly: z9.boolean().optional().describe("HttpOnly flag"),
|
|
1487
|
+
secure: z9.boolean().optional().describe("Secure flag"),
|
|
1488
|
+
sameSite: z9.enum(["strict", "lax", "none"]).optional().describe("SameSite attribute")
|
|
1349
1489
|
}
|
|
1350
1490
|
};
|
|
1351
1491
|
var setCookieTool = async ({
|
|
@@ -1353,23 +1493,14 @@ var setCookieTool = async ({
|
|
|
1353
1493
|
value,
|
|
1354
1494
|
domain,
|
|
1355
1495
|
path = "/",
|
|
1356
|
-
|
|
1496
|
+
expiry,
|
|
1357
1497
|
httpOnly,
|
|
1358
1498
|
secure,
|
|
1359
1499
|
sameSite
|
|
1360
1500
|
}) => {
|
|
1361
1501
|
try {
|
|
1362
1502
|
const browser = getBrowser();
|
|
1363
|
-
const cookie = {
|
|
1364
|
-
name,
|
|
1365
|
-
value,
|
|
1366
|
-
path,
|
|
1367
|
-
domain,
|
|
1368
|
-
expiry: expires,
|
|
1369
|
-
httpOnly,
|
|
1370
|
-
secure,
|
|
1371
|
-
sameSite
|
|
1372
|
-
};
|
|
1503
|
+
const cookie = { name, value, path, domain, expiry, httpOnly, secure, sameSite };
|
|
1373
1504
|
await browser.setCookies(cookie);
|
|
1374
1505
|
return {
|
|
1375
1506
|
content: [{ type: "text", text: `Cookie "${name}" set successfully` }]
|
|
@@ -1384,7 +1515,7 @@ var deleteCookiesToolDefinition = {
|
|
|
1384
1515
|
name: "delete_cookies",
|
|
1385
1516
|
description: "deletes all cookies or a specific cookie by name",
|
|
1386
1517
|
inputSchema: {
|
|
1387
|
-
name:
|
|
1518
|
+
name: z9.string().optional().describe("Optional cookie name to delete a specific cookie. If not provided, deletes all cookies")
|
|
1388
1519
|
}
|
|
1389
1520
|
};
|
|
1390
1521
|
var deleteCookiesTool = async ({ name }) => {
|
|
@@ -1409,15 +1540,15 @@ var deleteCookiesTool = async ({ name }) => {
|
|
|
1409
1540
|
|
|
1410
1541
|
// src/tools/get-accessibility-tree.tool.ts
|
|
1411
1542
|
import { encode as encode2 } from "@toon-format/toon";
|
|
1412
|
-
import { z as
|
|
1543
|
+
import { z as z10 } from "zod";
|
|
1413
1544
|
var getAccessibilityToolDefinition = {
|
|
1414
1545
|
name: "get_accessibility",
|
|
1415
1546
|
description: "gets accessibility tree snapshot with semantic information about page elements (roles, names, states). Browser-only - use when get_visible_elements does not return expected elements.",
|
|
1416
1547
|
inputSchema: {
|
|
1417
|
-
limit:
|
|
1418
|
-
offset:
|
|
1419
|
-
roles:
|
|
1420
|
-
namedOnly:
|
|
1548
|
+
limit: z10.number().optional().describe("Maximum number of nodes to return. Default: 100. Use 0 for unlimited."),
|
|
1549
|
+
offset: z10.number().optional().describe("Number of nodes to skip (for pagination). Default: 0."),
|
|
1550
|
+
roles: z10.array(z10.string()).optional().describe('Filter to specific roles (e.g., ["button", "link", "textbox"]). Default: all roles.'),
|
|
1551
|
+
namedOnly: z10.boolean().optional().describe("Only return nodes with a name/label. Default: true. Filters out anonymous containers.")
|
|
1421
1552
|
}
|
|
1422
1553
|
};
|
|
1423
1554
|
function flattenAccessibilityTree(node, result = []) {
|
|
@@ -1518,215 +1649,15 @@ var getAccessibilityTreeTool = async (args) => {
|
|
|
1518
1649
|
}
|
|
1519
1650
|
};
|
|
1520
1651
|
|
|
1521
|
-
// src/tools/
|
|
1522
|
-
import {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
path: overrides?.path || process.env.APPIUM_PATH || "/"
|
|
1531
|
-
};
|
|
1532
|
-
}
|
|
1533
|
-
function buildIOSCapabilities(appPath, options) {
|
|
1534
|
-
const capabilities = {
|
|
1535
|
-
platformName: "iOS",
|
|
1536
|
-
"appium:platformVersion": options.platformVersion,
|
|
1537
|
-
"appium:deviceName": options.deviceName,
|
|
1538
|
-
"appium:automationName": options.automationName || "XCUITest"
|
|
1539
|
-
};
|
|
1540
|
-
if (appPath) {
|
|
1541
|
-
capabilities["appium:app"] = appPath;
|
|
1542
|
-
}
|
|
1543
|
-
if (options.udid) {
|
|
1544
|
-
capabilities["appium:udid"] = options.udid;
|
|
1545
|
-
}
|
|
1546
|
-
if (options.noReset !== void 0) {
|
|
1547
|
-
capabilities["appium:noReset"] = options.noReset;
|
|
1548
|
-
}
|
|
1549
|
-
if (options.fullReset !== void 0) {
|
|
1550
|
-
capabilities["appium:fullReset"] = options.fullReset;
|
|
1551
|
-
}
|
|
1552
|
-
capabilities["appium:autoGrantPermissions"] = options.autoGrantPermissions ?? true;
|
|
1553
|
-
capabilities["appium:autoAcceptAlerts"] = options.autoAcceptAlerts ?? true;
|
|
1554
|
-
if (options.autoDismissAlerts !== void 0) {
|
|
1555
|
-
capabilities["appium:autoDismissAlerts"] = options.autoDismissAlerts;
|
|
1556
|
-
capabilities["appium:autoAcceptAlerts"] = void 0;
|
|
1557
|
-
}
|
|
1558
|
-
for (const [key, value] of Object.entries(options)) {
|
|
1559
|
-
if (!["deviceName", "platformVersion", "automationName", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset"].includes(
|
|
1560
|
-
key
|
|
1561
|
-
)) {
|
|
1562
|
-
capabilities[`appium:${key}`] = value;
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
return capabilities;
|
|
1566
|
-
}
|
|
1567
|
-
function buildAndroidCapabilities(appPath, options) {
|
|
1568
|
-
const capabilities = {
|
|
1569
|
-
platformName: "Android",
|
|
1570
|
-
"appium:platformVersion": options.platformVersion,
|
|
1571
|
-
"appium:deviceName": options.deviceName,
|
|
1572
|
-
"appium:automationName": options.automationName || "UiAutomator2"
|
|
1573
|
-
};
|
|
1574
|
-
if (appPath) {
|
|
1575
|
-
capabilities["appium:app"] = appPath;
|
|
1576
|
-
}
|
|
1577
|
-
if (options.noReset !== void 0) {
|
|
1578
|
-
capabilities["appium:noReset"] = options.noReset;
|
|
1579
|
-
}
|
|
1580
|
-
if (options.fullReset !== void 0) {
|
|
1581
|
-
capabilities["appium:fullReset"] = options.fullReset;
|
|
1582
|
-
}
|
|
1583
|
-
capabilities["appium:autoGrantPermissions"] = options.autoGrantPermissions ?? true;
|
|
1584
|
-
capabilities["appium:autoAcceptAlerts"] = options.autoAcceptAlerts ?? true;
|
|
1585
|
-
if (options.autoDismissAlerts !== void 0) {
|
|
1586
|
-
capabilities["appium:autoDismissAlerts"] = options.autoDismissAlerts;
|
|
1587
|
-
capabilities["appium:autoAcceptAlerts"] = void 0;
|
|
1588
|
-
}
|
|
1589
|
-
if (options.appWaitActivity) {
|
|
1590
|
-
capabilities["appium:appWaitActivity"] = options.appWaitActivity;
|
|
1591
|
-
}
|
|
1592
|
-
for (const [key, value] of Object.entries(options)) {
|
|
1593
|
-
if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "appWaitActivity", "noReset", "fullReset"].includes(
|
|
1594
|
-
key
|
|
1595
|
-
)) {
|
|
1596
|
-
capabilities[`appium:${key}`] = value;
|
|
1597
|
-
}
|
|
1598
|
-
}
|
|
1599
|
-
return capabilities;
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
// src/tools/app-session.tool.ts
|
|
1603
|
-
var startAppToolDefinition = {
|
|
1604
|
-
name: "start_app_session",
|
|
1605
|
-
description: "starts a mobile app session (iOS/Android) via Appium",
|
|
1606
|
-
inputSchema: {
|
|
1607
|
-
platform: z14.enum(["iOS", "Android"]).describe("Mobile platform"),
|
|
1608
|
-
appPath: z14.string().optional().describe("Path to the app file (.app/.apk/.ipa). Required unless noReset=true (connecting to already-running app)"),
|
|
1609
|
-
deviceName: z14.string().describe("Device/emulator/simulator name"),
|
|
1610
|
-
platformVersion: z14.string().optional().describe('OS version (e.g., "17.0", "14")'),
|
|
1611
|
-
automationName: z14.enum(["XCUITest", "UiAutomator2", "Espresso"]).optional().describe("Automation driver name"),
|
|
1612
|
-
appiumHost: z14.string().optional().describe("Appium server hostname (overrides APPIUM_URL env var)"),
|
|
1613
|
-
appiumPort: z14.number().optional().describe("Appium server port (overrides APPIUM_URL_PORT env var)"),
|
|
1614
|
-
appiumPath: z14.string().optional().describe("Appium server path (overrides APPIUM_PATH env var)"),
|
|
1615
|
-
autoGrantPermissions: z14.boolean().optional().describe("Auto-grant app permissions (default: true)"),
|
|
1616
|
-
autoAcceptAlerts: z14.boolean().optional().describe("Auto-accept alerts (default: true)"),
|
|
1617
|
-
autoDismissAlerts: z14.boolean().optional().describe('Auto-dismiss alerts (default: false, will override "autoAcceptAlerts" to undefined if set)'),
|
|
1618
|
-
appWaitActivity: z14.string().optional().describe("Activity to wait for on launch (Android only)"),
|
|
1619
|
-
udid: z14.string().optional().describe('Unique Device Identifier for iOS real device testing (e.g., "00008030-001234567890002E")'),
|
|
1620
|
-
noReset: z14.boolean().optional().describe("Do not reset app state before session (preserves app data). Default: false"),
|
|
1621
|
-
fullReset: z14.boolean().optional().describe("Uninstall app before/after session. Default: true. Set to false with noReset=true to preserve app state completely")
|
|
1622
|
-
}
|
|
1623
|
-
};
|
|
1624
|
-
var getState = () => {
|
|
1625
|
-
const sharedState = getBrowser.__state;
|
|
1626
|
-
if (!sharedState) {
|
|
1627
|
-
throw new Error("Browser state not initialized");
|
|
1628
|
-
}
|
|
1629
|
-
return sharedState;
|
|
1630
|
-
};
|
|
1631
|
-
var startAppTool = async (args) => {
|
|
1632
|
-
try {
|
|
1633
|
-
const {
|
|
1634
|
-
platform,
|
|
1635
|
-
appPath,
|
|
1636
|
-
deviceName,
|
|
1637
|
-
platformVersion,
|
|
1638
|
-
automationName,
|
|
1639
|
-
appiumHost,
|
|
1640
|
-
appiumPort,
|
|
1641
|
-
appiumPath,
|
|
1642
|
-
autoGrantPermissions = true,
|
|
1643
|
-
autoAcceptAlerts,
|
|
1644
|
-
autoDismissAlerts,
|
|
1645
|
-
appWaitActivity,
|
|
1646
|
-
udid,
|
|
1647
|
-
noReset,
|
|
1648
|
-
fullReset
|
|
1649
|
-
} = args;
|
|
1650
|
-
if (!appPath && noReset !== true) {
|
|
1651
|
-
return {
|
|
1652
|
-
content: [{
|
|
1653
|
-
type: "text",
|
|
1654
|
-
text: 'Error: Either "appPath" must be provided to install an app, or "noReset: true" must be set to connect to an already-running app.'
|
|
1655
|
-
}]
|
|
1656
|
-
};
|
|
1657
|
-
}
|
|
1658
|
-
const serverConfig = getAppiumServerConfig({
|
|
1659
|
-
hostname: appiumHost,
|
|
1660
|
-
port: appiumPort,
|
|
1661
|
-
path: appiumPath
|
|
1662
|
-
});
|
|
1663
|
-
const capabilities = platform === "iOS" ? buildIOSCapabilities(appPath, {
|
|
1664
|
-
deviceName,
|
|
1665
|
-
platformVersion,
|
|
1666
|
-
automationName: automationName || "XCUITest",
|
|
1667
|
-
autoGrantPermissions,
|
|
1668
|
-
autoAcceptAlerts,
|
|
1669
|
-
autoDismissAlerts,
|
|
1670
|
-
udid,
|
|
1671
|
-
noReset,
|
|
1672
|
-
fullReset
|
|
1673
|
-
}) : buildAndroidCapabilities(appPath, {
|
|
1674
|
-
deviceName,
|
|
1675
|
-
platformVersion,
|
|
1676
|
-
automationName: automationName || "UiAutomator2",
|
|
1677
|
-
autoGrantPermissions,
|
|
1678
|
-
autoAcceptAlerts,
|
|
1679
|
-
autoDismissAlerts,
|
|
1680
|
-
appWaitActivity,
|
|
1681
|
-
noReset,
|
|
1682
|
-
fullReset
|
|
1683
|
-
});
|
|
1684
|
-
const browser = await remote2({
|
|
1685
|
-
protocol: "http",
|
|
1686
|
-
hostname: serverConfig.hostname,
|
|
1687
|
-
port: serverConfig.port,
|
|
1688
|
-
path: serverConfig.path,
|
|
1689
|
-
capabilities
|
|
1690
|
-
});
|
|
1691
|
-
const { sessionId } = browser;
|
|
1692
|
-
const shouldAutoDetach = noReset === true || !appPath;
|
|
1693
|
-
const state2 = getState();
|
|
1694
|
-
state2.browsers.set(sessionId, browser);
|
|
1695
|
-
state2.currentSession = sessionId;
|
|
1696
|
-
state2.sessionMetadata.set(sessionId, {
|
|
1697
|
-
type: platform.toLowerCase(),
|
|
1698
|
-
capabilities,
|
|
1699
|
-
isAttached: shouldAutoDetach
|
|
1700
|
-
});
|
|
1701
|
-
const appInfo = appPath ? `
|
|
1702
|
-
App: ${appPath}` : "\nApp: (connected to running app)";
|
|
1703
|
-
const detachNote = shouldAutoDetach ? "\n\n(Auto-detach enabled: session will be preserved on close. Use close_session({ detach: false }) to force terminate.)" : "";
|
|
1704
|
-
return {
|
|
1705
|
-
content: [
|
|
1706
|
-
{
|
|
1707
|
-
type: "text",
|
|
1708
|
-
text: `${platform} app session started with sessionId: ${sessionId}
|
|
1709
|
-
Device: ${deviceName}${appInfo}
|
|
1710
|
-
Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}${detachNote}`
|
|
1711
|
-
}
|
|
1712
|
-
]
|
|
1713
|
-
};
|
|
1714
|
-
} catch (e) {
|
|
1715
|
-
return {
|
|
1716
|
-
content: [{ type: "text", text: `Error starting app session: ${e}` }]
|
|
1717
|
-
};
|
|
1718
|
-
}
|
|
1719
|
-
};
|
|
1720
|
-
|
|
1721
|
-
// src/tools/gestures.tool.ts
|
|
1722
|
-
import { z as z15 } from "zod";
|
|
1723
|
-
var tapElementToolDefinition = {
|
|
1724
|
-
name: "tap_element",
|
|
1725
|
-
description: "taps an element by selector or coordinates (mobile)",
|
|
1726
|
-
inputSchema: {
|
|
1727
|
-
selector: z15.string().optional().describe("Element selector (CSS, XPath, accessibility ID, or UiAutomator)"),
|
|
1728
|
-
x: z15.number().optional().describe("X coordinate for tap (if no selector provided)"),
|
|
1729
|
-
y: z15.number().optional().describe("Y coordinate for tap (if no selector provided)")
|
|
1652
|
+
// src/tools/gestures.tool.ts
|
|
1653
|
+
import { z as z11 } from "zod";
|
|
1654
|
+
var tapElementToolDefinition = {
|
|
1655
|
+
name: "tap_element",
|
|
1656
|
+
description: "taps an element by selector or screen coordinates (mobile)",
|
|
1657
|
+
inputSchema: {
|
|
1658
|
+
selector: z11.string().optional().describe("Element selector (CSS, XPath, accessibility ID, or UiAutomator)"),
|
|
1659
|
+
x: z11.number().optional().describe("X coordinate for screen tap (if no selector provided)"),
|
|
1660
|
+
y: z11.number().optional().describe("Y coordinate for screen tap (if no selector provided)")
|
|
1730
1661
|
}
|
|
1731
1662
|
};
|
|
1732
1663
|
var tapElementTool = async (args) => {
|
|
@@ -1740,11 +1671,7 @@ var tapElementTool = async (args) => {
|
|
|
1740
1671
|
content: [{ type: "text", text: `Tapped element: ${selector}` }]
|
|
1741
1672
|
};
|
|
1742
1673
|
} else if (x !== void 0 && y !== void 0) {
|
|
1743
|
-
await browser.
|
|
1744
|
-
action: "tap",
|
|
1745
|
-
x,
|
|
1746
|
-
y
|
|
1747
|
-
});
|
|
1674
|
+
await browser.tap({ x, y });
|
|
1748
1675
|
return {
|
|
1749
1676
|
content: [{ type: "text", text: `Tapped at coordinates: (${x}, ${y})` }]
|
|
1750
1677
|
};
|
|
@@ -1754,7 +1681,7 @@ var tapElementTool = async (args) => {
|
|
|
1754
1681
|
};
|
|
1755
1682
|
} catch (e) {
|
|
1756
1683
|
return {
|
|
1757
|
-
content: [{ type: "text", text: `Error tapping
|
|
1684
|
+
content: [{ type: "text", text: `Error tapping: ${e}` }]
|
|
1758
1685
|
};
|
|
1759
1686
|
}
|
|
1760
1687
|
};
|
|
@@ -1762,52 +1689,29 @@ var swipeToolDefinition = {
|
|
|
1762
1689
|
name: "swipe",
|
|
1763
1690
|
description: "performs a swipe gesture in specified direction (mobile)",
|
|
1764
1691
|
inputSchema: {
|
|
1765
|
-
direction:
|
|
1766
|
-
duration:
|
|
1767
|
-
|
|
1768
|
-
startY: z15.number().optional().describe("Start Y coordinate (optional, uses screen center)"),
|
|
1769
|
-
distance: z15.number().optional().describe("Swipe distance in pixels (optional, uses percentage of screen)")
|
|
1692
|
+
direction: z11.enum(["up", "down", "left", "right"]).describe("Swipe direction"),
|
|
1693
|
+
duration: z11.number().min(100).max(5e3).optional().describe("Swipe duration in milliseconds (default: 500)"),
|
|
1694
|
+
percent: z11.number().min(0).max(1).optional().describe("Percentage of screen to swipe (0-1, default: 0.5 for up/down, 0.95 for left/right)")
|
|
1770
1695
|
}
|
|
1771
1696
|
};
|
|
1697
|
+
var contentToFingerDirection = {
|
|
1698
|
+
up: "down",
|
|
1699
|
+
down: "up",
|
|
1700
|
+
left: "right",
|
|
1701
|
+
right: "left"
|
|
1702
|
+
};
|
|
1772
1703
|
var swipeTool = async (args) => {
|
|
1773
1704
|
try {
|
|
1774
1705
|
const browser = getBrowser();
|
|
1775
|
-
const { direction, duration
|
|
1776
|
-
const
|
|
1777
|
-
const
|
|
1778
|
-
const
|
|
1779
|
-
const
|
|
1780
|
-
const
|
|
1781
|
-
|
|
1782
|
-
let endX = centerX;
|
|
1783
|
-
let endY = centerY;
|
|
1784
|
-
switch (direction) {
|
|
1785
|
-
case "up":
|
|
1786
|
-
endY = centerY - swipeDistance;
|
|
1787
|
-
break;
|
|
1788
|
-
case "down":
|
|
1789
|
-
endY = centerY + swipeDistance;
|
|
1790
|
-
break;
|
|
1791
|
-
case "left":
|
|
1792
|
-
endX = centerX - swipeDistance;
|
|
1793
|
-
break;
|
|
1794
|
-
case "right":
|
|
1795
|
-
endX = centerX + swipeDistance;
|
|
1796
|
-
break;
|
|
1797
|
-
}
|
|
1798
|
-
await browser.touchPerform([
|
|
1799
|
-
{ action: "press", options: { x: centerX, y: centerY } },
|
|
1800
|
-
{ action: "wait", options: { ms: duration } },
|
|
1801
|
-
{ action: "moveTo", options: { x: endX, y: endY } },
|
|
1802
|
-
{ action: "release", options: {} }
|
|
1803
|
-
]);
|
|
1706
|
+
const { direction, duration, percent } = args;
|
|
1707
|
+
const isVertical = direction === "up" || direction === "down";
|
|
1708
|
+
const defaultPercent = isVertical ? 0.5 : 0.95;
|
|
1709
|
+
const effectivePercent = percent ?? defaultPercent;
|
|
1710
|
+
const effectiveDuration = duration ?? 500;
|
|
1711
|
+
const fingerDirection = contentToFingerDirection[direction];
|
|
1712
|
+
await browser.swipe({ direction: fingerDirection, duration: effectiveDuration, percent: effectivePercent });
|
|
1804
1713
|
return {
|
|
1805
|
-
content: [
|
|
1806
|
-
{
|
|
1807
|
-
type: "text",
|
|
1808
|
-
text: `Swiped ${direction} from (${Math.round(centerX)}, ${Math.round(centerY)}) to (${Math.round(endX)}, ${Math.round(endY)}) over ${duration}ms`
|
|
1809
|
-
}
|
|
1810
|
-
]
|
|
1714
|
+
content: [{ type: "text", text: `Swiped ${direction}` }]
|
|
1811
1715
|
};
|
|
1812
1716
|
} catch (e) {
|
|
1813
1717
|
return {
|
|
@@ -1815,126 +1719,51 @@ var swipeTool = async (args) => {
|
|
|
1815
1719
|
};
|
|
1816
1720
|
}
|
|
1817
1721
|
};
|
|
1818
|
-
var longPressToolDefinition = {
|
|
1819
|
-
name: "long_press",
|
|
1820
|
-
description: "performs a long press on element or coordinates (mobile)",
|
|
1821
|
-
inputSchema: {
|
|
1822
|
-
selector: z15.string().optional().describe("Element selector (CSS, XPath, accessibility ID, or UiAutomator)"),
|
|
1823
|
-
x: z15.number().optional().describe("X coordinate for long press (if no selector provided)"),
|
|
1824
|
-
y: z15.number().optional().describe("Y coordinate for long press (if no selector provided)"),
|
|
1825
|
-
duration: z15.number().min(500).max(1e4).optional().describe("Long press duration in milliseconds (default: 1000)")
|
|
1826
|
-
}
|
|
1827
|
-
};
|
|
1828
|
-
var longPressTool = async (args) => {
|
|
1829
|
-
try {
|
|
1830
|
-
const browser = getBrowser();
|
|
1831
|
-
const { selector, x, y, duration = 1e3 } = args;
|
|
1832
|
-
if (selector) {
|
|
1833
|
-
const element = await browser.$(selector);
|
|
1834
|
-
await element.touchAction([
|
|
1835
|
-
{ action: "longPress" },
|
|
1836
|
-
{ action: "wait", ms: duration },
|
|
1837
|
-
{ action: "release" }
|
|
1838
|
-
]);
|
|
1839
|
-
return {
|
|
1840
|
-
content: [{ type: "text", text: `Long pressed element: ${selector} for ${duration}ms` }]
|
|
1841
|
-
};
|
|
1842
|
-
} else if (x !== void 0 && y !== void 0) {
|
|
1843
|
-
await browser.touchPerform([
|
|
1844
|
-
{ action: "press", options: { x, y } },
|
|
1845
|
-
{ action: "wait", options: { ms: duration } },
|
|
1846
|
-
{ action: "release", options: {} }
|
|
1847
|
-
]);
|
|
1848
|
-
return {
|
|
1849
|
-
content: [{ type: "text", text: `Long pressed at coordinates: (${x}, ${y}) for ${duration}ms` }]
|
|
1850
|
-
};
|
|
1851
|
-
}
|
|
1852
|
-
return {
|
|
1853
|
-
content: [{ type: "text", text: "Error: Must provide either selector or x,y coordinates" }]
|
|
1854
|
-
};
|
|
1855
|
-
} catch (e) {
|
|
1856
|
-
return {
|
|
1857
|
-
content: [{ type: "text", text: `Error long pressing: ${e}` }]
|
|
1858
|
-
};
|
|
1859
|
-
}
|
|
1860
|
-
};
|
|
1861
1722
|
var dragAndDropToolDefinition = {
|
|
1862
1723
|
name: "drag_and_drop",
|
|
1863
|
-
description: "drags
|
|
1724
|
+
description: "drags an element to another element or coordinates (mobile)",
|
|
1864
1725
|
inputSchema: {
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
toY: z15.number().optional().describe("Target Y coordinate"),
|
|
1871
|
-
duration: z15.number().min(100).max(5e3).optional().describe("Drag duration in milliseconds (default: 500)")
|
|
1726
|
+
sourceSelector: z11.string().describe("Source element selector to drag"),
|
|
1727
|
+
targetSelector: z11.string().optional().describe("Target element selector to drop onto"),
|
|
1728
|
+
x: z11.number().optional().describe("Target X offset (if no targetSelector)"),
|
|
1729
|
+
y: z11.number().optional().describe("Target Y offset (if no targetSelector)"),
|
|
1730
|
+
duration: z11.number().min(100).max(5e3).optional().describe("Drag duration in milliseconds")
|
|
1872
1731
|
}
|
|
1873
1732
|
};
|
|
1874
1733
|
var dragAndDropTool = async (args) => {
|
|
1875
1734
|
try {
|
|
1876
1735
|
const browser = getBrowser();
|
|
1877
|
-
const {
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
if (fromSelector) {
|
|
1883
|
-
const element = await browser.$(fromSelector);
|
|
1884
|
-
const location = await element.getLocation();
|
|
1885
|
-
const size = await element.getSize();
|
|
1886
|
-
startX = location.x + size.width / 2;
|
|
1887
|
-
startY = location.y + size.height / 2;
|
|
1888
|
-
} else if (fromX !== void 0 && fromY !== void 0) {
|
|
1889
|
-
startX = fromX;
|
|
1890
|
-
startY = fromY;
|
|
1891
|
-
} else {
|
|
1736
|
+
const { sourceSelector, targetSelector, x, y, duration } = args;
|
|
1737
|
+
const sourceElement = await browser.$(sourceSelector);
|
|
1738
|
+
if (targetSelector) {
|
|
1739
|
+
const targetElement = await browser.$(targetSelector);
|
|
1740
|
+
await sourceElement.dragAndDrop(targetElement, { duration });
|
|
1892
1741
|
return {
|
|
1893
|
-
content: [{ type: "text", text:
|
|
1742
|
+
content: [{ type: "text", text: `Dragged ${sourceSelector} to ${targetSelector}` }]
|
|
1894
1743
|
};
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
const element = await browser.$(toSelector);
|
|
1898
|
-
const location = await element.getLocation();
|
|
1899
|
-
const size = await element.getSize();
|
|
1900
|
-
endX = location.x + size.width / 2;
|
|
1901
|
-
endY = location.y + size.height / 2;
|
|
1902
|
-
} else if (toX !== void 0 && toY !== void 0) {
|
|
1903
|
-
endX = toX;
|
|
1904
|
-
endY = toY;
|
|
1905
|
-
} else {
|
|
1744
|
+
} else if (x !== void 0 && y !== void 0) {
|
|
1745
|
+
await sourceElement.dragAndDrop({ x, y }, { duration });
|
|
1906
1746
|
return {
|
|
1907
|
-
content: [{ type: "text", text:
|
|
1747
|
+
content: [{ type: "text", text: `Dragged ${sourceSelector} by (${x}, ${y})` }]
|
|
1908
1748
|
};
|
|
1909
1749
|
}
|
|
1910
|
-
await browser.touchPerform([
|
|
1911
|
-
{ action: "press", options: { x: startX, y: startY } },
|
|
1912
|
-
{ action: "wait", options: { ms: duration } },
|
|
1913
|
-
{ action: "moveTo", options: { x: endX, y: endY } },
|
|
1914
|
-
{ action: "release", options: {} }
|
|
1915
|
-
]);
|
|
1916
1750
|
return {
|
|
1917
|
-
content: [
|
|
1918
|
-
{
|
|
1919
|
-
type: "text",
|
|
1920
|
-
text: `Dragged from (${Math.round(startX)}, ${Math.round(startY)}) to (${Math.round(endX)}, ${Math.round(endY)}) over ${duration}ms`
|
|
1921
|
-
}
|
|
1922
|
-
]
|
|
1751
|
+
content: [{ type: "text", text: "Error: Must provide either targetSelector or x,y coordinates" }]
|
|
1923
1752
|
};
|
|
1924
1753
|
} catch (e) {
|
|
1925
1754
|
return {
|
|
1926
|
-
content: [{ type: "text", text: `Error dragging
|
|
1755
|
+
content: [{ type: "text", text: `Error dragging: ${e}` }]
|
|
1927
1756
|
};
|
|
1928
1757
|
}
|
|
1929
1758
|
};
|
|
1930
1759
|
|
|
1931
1760
|
// src/tools/app-actions.tool.ts
|
|
1932
|
-
import { z as
|
|
1761
|
+
import { z as z12 } from "zod";
|
|
1933
1762
|
var getAppStateToolDefinition = {
|
|
1934
1763
|
name: "get_app_state",
|
|
1935
1764
|
description: "gets the state of an app (not installed, not running, background, foreground)",
|
|
1936
1765
|
inputSchema: {
|
|
1937
|
-
bundleId:
|
|
1766
|
+
bundleId: z12.string().describe("App bundle ID (e.g., com.example.app)")
|
|
1938
1767
|
}
|
|
1939
1768
|
};
|
|
1940
1769
|
var getAppStateTool = async (args) => {
|
|
@@ -1964,53 +1793,9 @@ var getAppStateTool = async (args) => {
|
|
|
1964
1793
|
};
|
|
1965
1794
|
}
|
|
1966
1795
|
};
|
|
1967
|
-
var activateAppToolDefinition = {
|
|
1968
|
-
name: "activate_app",
|
|
1969
|
-
description: "activates/brings an app to foreground",
|
|
1970
|
-
inputSchema: {
|
|
1971
|
-
bundleId: z16.string().describe("App bundle ID to activate (e.g., com.example.app)")
|
|
1972
|
-
}
|
|
1973
|
-
};
|
|
1974
|
-
var activateAppTool = async (args) => {
|
|
1975
|
-
try {
|
|
1976
|
-
const browser = getBrowser();
|
|
1977
|
-
const { bundleId } = args;
|
|
1978
|
-
const appIdentifier = browser.isAndroid ? { appId: bundleId } : { bundleId };
|
|
1979
|
-
await browser.execute("mobile: activateApp", appIdentifier);
|
|
1980
|
-
return {
|
|
1981
|
-
content: [{ type: "text", text: `Activated app: ${bundleId}` }]
|
|
1982
|
-
};
|
|
1983
|
-
} catch (e) {
|
|
1984
|
-
return {
|
|
1985
|
-
content: [{ type: "text", text: `Error activating app: ${e}` }]
|
|
1986
|
-
};
|
|
1987
|
-
}
|
|
1988
|
-
};
|
|
1989
|
-
var terminateAppToolDefinition = {
|
|
1990
|
-
name: "terminate_app",
|
|
1991
|
-
description: "terminates a running app",
|
|
1992
|
-
inputSchema: {
|
|
1993
|
-
bundleId: z16.string().describe("App bundle ID to terminate (e.g., com.example.app)")
|
|
1994
|
-
}
|
|
1995
|
-
};
|
|
1996
|
-
var terminateAppTool = async (args) => {
|
|
1997
|
-
try {
|
|
1998
|
-
const browser = getBrowser();
|
|
1999
|
-
const { bundleId } = args;
|
|
2000
|
-
const appIdentifier = browser.isAndroid ? { appId: bundleId } : { bundleId };
|
|
2001
|
-
await browser.execute("mobile: terminateApp", appIdentifier);
|
|
2002
|
-
return {
|
|
2003
|
-
content: [{ type: "text", text: `Terminated app: ${bundleId}` }]
|
|
2004
|
-
};
|
|
2005
|
-
} catch (e) {
|
|
2006
|
-
return {
|
|
2007
|
-
content: [{ type: "text", text: `Error terminating app: ${e}` }]
|
|
2008
|
-
};
|
|
2009
|
-
}
|
|
2010
|
-
};
|
|
2011
1796
|
|
|
2012
1797
|
// src/tools/context.tool.ts
|
|
2013
|
-
import { z as
|
|
1798
|
+
import { z as z13 } from "zod";
|
|
2014
1799
|
var getContextsToolDefinition = {
|
|
2015
1800
|
name: "get_contexts",
|
|
2016
1801
|
description: "lists available contexts (NATIVE_APP, WEBVIEW)",
|
|
@@ -2025,7 +1810,7 @@ var switchContextToolDefinition = {
|
|
|
2025
1810
|
name: "switch_context",
|
|
2026
1811
|
description: "switches between native and webview contexts",
|
|
2027
1812
|
inputSchema: {
|
|
2028
|
-
context:
|
|
1813
|
+
context: z13.string().describe(
|
|
2029
1814
|
'Context name to switch to (e.g., "NATIVE_APP", "WEBVIEW_com.example.app", or use index from get_contexts)'
|
|
2030
1815
|
)
|
|
2031
1816
|
}
|
|
@@ -2095,52 +1880,12 @@ var switchContextTool = async (args) => {
|
|
|
2095
1880
|
};
|
|
2096
1881
|
|
|
2097
1882
|
// src/tools/device.tool.ts
|
|
2098
|
-
import { z as
|
|
2099
|
-
var getDeviceInfoToolDefinition = {
|
|
2100
|
-
name: "get_device_info",
|
|
2101
|
-
description: "gets device information (platform, version, screen size)",
|
|
2102
|
-
inputSchema: {}
|
|
2103
|
-
};
|
|
2104
|
-
var getOrientationToolDefinition = {
|
|
2105
|
-
name: "get_orientation",
|
|
2106
|
-
description: "gets current device orientation",
|
|
2107
|
-
inputSchema: {}
|
|
2108
|
-
};
|
|
2109
|
-
var lockDeviceToolDefinition = {
|
|
2110
|
-
name: "lock_device",
|
|
2111
|
-
description: "locks the device screen",
|
|
2112
|
-
inputSchema: {}
|
|
2113
|
-
};
|
|
2114
|
-
var unlockDeviceToolDefinition = {
|
|
2115
|
-
name: "unlock_device",
|
|
2116
|
-
description: "unlocks the device screen",
|
|
2117
|
-
inputSchema: {}
|
|
2118
|
-
};
|
|
2119
|
-
var isDeviceLockedToolDefinition = {
|
|
2120
|
-
name: "is_device_locked",
|
|
2121
|
-
description: "checks if device is locked",
|
|
2122
|
-
inputSchema: {}
|
|
2123
|
-
};
|
|
2124
|
-
var shakeDeviceToolDefinition = {
|
|
2125
|
-
name: "shake_device",
|
|
2126
|
-
description: "shakes the device (iOS only)",
|
|
2127
|
-
inputSchema: {}
|
|
2128
|
-
};
|
|
1883
|
+
import { z as z14 } from "zod";
|
|
2129
1884
|
var hideKeyboardToolDefinition = {
|
|
2130
1885
|
name: "hide_keyboard",
|
|
2131
1886
|
description: "hides the on-screen keyboard",
|
|
2132
1887
|
inputSchema: {}
|
|
2133
1888
|
};
|
|
2134
|
-
var isKeyboardShownToolDefinition = {
|
|
2135
|
-
name: "is_keyboard_shown",
|
|
2136
|
-
description: "checks if keyboard is visible",
|
|
2137
|
-
inputSchema: {}
|
|
2138
|
-
};
|
|
2139
|
-
var openNotificationsToolDefinition = {
|
|
2140
|
-
name: "open_notifications",
|
|
2141
|
-
description: "opens the notifications panel (Android only)",
|
|
2142
|
-
inputSchema: {}
|
|
2143
|
-
};
|
|
2144
1889
|
var getGeolocationToolDefinition = {
|
|
2145
1890
|
name: "get_geolocation",
|
|
2146
1891
|
description: "gets current device geolocation",
|
|
@@ -2150,57 +1895,16 @@ var rotateDeviceToolDefinition = {
|
|
|
2150
1895
|
name: "rotate_device",
|
|
2151
1896
|
description: "rotates device to portrait or landscape orientation",
|
|
2152
1897
|
inputSchema: {
|
|
2153
|
-
orientation:
|
|
2154
|
-
}
|
|
2155
|
-
};
|
|
2156
|
-
var sendKeysToolDefinition = {
|
|
2157
|
-
name: "send_keys",
|
|
2158
|
-
description: "sends keys to the app (Android only)",
|
|
2159
|
-
inputSchema: {
|
|
2160
|
-
keys: z18.array(z18.string()).describe('Array of keys to send (e.g., ["h", "e", "l", "l", "o"])')
|
|
2161
|
-
}
|
|
2162
|
-
};
|
|
2163
|
-
var pressKeyCodeToolDefinition = {
|
|
2164
|
-
name: "press_key_code",
|
|
2165
|
-
description: "presses an Android key code (Android only)",
|
|
2166
|
-
inputSchema: {
|
|
2167
|
-
keyCode: z18.number().describe("Android key code (e.g., 4 for BACK, 3 for HOME)")
|
|
1898
|
+
orientation: z14.enum(["PORTRAIT", "LANDSCAPE"]).describe("Device orientation")
|
|
2168
1899
|
}
|
|
2169
1900
|
};
|
|
2170
1901
|
var setGeolocationToolDefinition = {
|
|
2171
1902
|
name: "set_geolocation",
|
|
2172
1903
|
description: "sets device geolocation (latitude, longitude, altitude)",
|
|
2173
1904
|
inputSchema: {
|
|
2174
|
-
latitude:
|
|
2175
|
-
longitude:
|
|
2176
|
-
altitude:
|
|
2177
|
-
}
|
|
2178
|
-
};
|
|
2179
|
-
var getDeviceInfoTool = async () => {
|
|
2180
|
-
try {
|
|
2181
|
-
const browser = getBrowser();
|
|
2182
|
-
const capabilities = browser.capabilities;
|
|
2183
|
-
const windowSize = await browser.getWindowSize();
|
|
2184
|
-
const info = {
|
|
2185
|
-
platformName: capabilities.platformName,
|
|
2186
|
-
platformVersion: capabilities["appium:platformVersion"],
|
|
2187
|
-
deviceName: capabilities["appium:deviceName"],
|
|
2188
|
-
automationName: capabilities["appium:automationName"],
|
|
2189
|
-
screenSize: `${windowSize.width}x${windowSize.height}`
|
|
2190
|
-
};
|
|
2191
|
-
return {
|
|
2192
|
-
content: [
|
|
2193
|
-
{
|
|
2194
|
-
type: "text",
|
|
2195
|
-
text: `Device Info:
|
|
2196
|
-
${Object.entries(info).map(([key, value]) => ` ${key}: ${value}`).join("\n")}`
|
|
2197
|
-
}
|
|
2198
|
-
]
|
|
2199
|
-
};
|
|
2200
|
-
} catch (e) {
|
|
2201
|
-
return {
|
|
2202
|
-
content: [{ type: "text", text: `Error getting device info: ${e}` }]
|
|
2203
|
-
};
|
|
1905
|
+
latitude: z14.number().min(-90).max(90).describe("Latitude coordinate"),
|
|
1906
|
+
longitude: z14.number().min(-180).max(180).describe("Longitude coordinate"),
|
|
1907
|
+
altitude: z14.number().optional().describe("Altitude in meters (optional)")
|
|
2204
1908
|
}
|
|
2205
1909
|
};
|
|
2206
1910
|
var rotateDeviceTool = async (args) => {
|
|
@@ -2217,99 +1921,6 @@ var rotateDeviceTool = async (args) => {
|
|
|
2217
1921
|
};
|
|
2218
1922
|
}
|
|
2219
1923
|
};
|
|
2220
|
-
var getOrientationTool = async () => {
|
|
2221
|
-
try {
|
|
2222
|
-
const browser = getBrowser();
|
|
2223
|
-
const orientation = await browser.getOrientation();
|
|
2224
|
-
return {
|
|
2225
|
-
content: [{ type: "text", text: `Current orientation: ${orientation}` }]
|
|
2226
|
-
};
|
|
2227
|
-
} catch (e) {
|
|
2228
|
-
return {
|
|
2229
|
-
content: [{ type: "text", text: `Error getting orientation: ${e}` }]
|
|
2230
|
-
};
|
|
2231
|
-
}
|
|
2232
|
-
};
|
|
2233
|
-
var lockDeviceTool = async () => {
|
|
2234
|
-
try {
|
|
2235
|
-
const browser = getBrowser();
|
|
2236
|
-
await browser.lock();
|
|
2237
|
-
return {
|
|
2238
|
-
content: [{ type: "text", text: "Device locked" }]
|
|
2239
|
-
};
|
|
2240
|
-
} catch (e) {
|
|
2241
|
-
return {
|
|
2242
|
-
content: [{ type: "text", text: `Error locking device: ${e}` }]
|
|
2243
|
-
};
|
|
2244
|
-
}
|
|
2245
|
-
};
|
|
2246
|
-
var unlockDeviceTool = async () => {
|
|
2247
|
-
try {
|
|
2248
|
-
const browser = getBrowser();
|
|
2249
|
-
await browser.unlock();
|
|
2250
|
-
return {
|
|
2251
|
-
content: [{ type: "text", text: "Device unlocked" }]
|
|
2252
|
-
};
|
|
2253
|
-
} catch (e) {
|
|
2254
|
-
return {
|
|
2255
|
-
content: [{ type: "text", text: `Error unlocking device: ${e}` }]
|
|
2256
|
-
};
|
|
2257
|
-
}
|
|
2258
|
-
};
|
|
2259
|
-
var isDeviceLockedTool = async () => {
|
|
2260
|
-
try {
|
|
2261
|
-
const browser = getBrowser();
|
|
2262
|
-
const isLocked = await browser.isLocked();
|
|
2263
|
-
return {
|
|
2264
|
-
content: [{ type: "text", text: `Device is ${isLocked ? "locked" : "unlocked"}` }]
|
|
2265
|
-
};
|
|
2266
|
-
} catch (e) {
|
|
2267
|
-
return {
|
|
2268
|
-
content: [{ type: "text", text: `Error checking lock status: ${e}` }]
|
|
2269
|
-
};
|
|
2270
|
-
}
|
|
2271
|
-
};
|
|
2272
|
-
var shakeDeviceTool = async () => {
|
|
2273
|
-
try {
|
|
2274
|
-
const browser = getBrowser();
|
|
2275
|
-
await browser.shake();
|
|
2276
|
-
return {
|
|
2277
|
-
content: [{ type: "text", text: "Device shaken" }]
|
|
2278
|
-
};
|
|
2279
|
-
} catch (e) {
|
|
2280
|
-
return {
|
|
2281
|
-
content: [{ type: "text", text: `Error shaking device: ${e}` }]
|
|
2282
|
-
};
|
|
2283
|
-
}
|
|
2284
|
-
};
|
|
2285
|
-
var sendKeysTool = async (args) => {
|
|
2286
|
-
try {
|
|
2287
|
-
const browser = getBrowser();
|
|
2288
|
-
const { keys } = args;
|
|
2289
|
-
await browser.sendKeys(keys);
|
|
2290
|
-
return {
|
|
2291
|
-
content: [{ type: "text", text: `Sent keys: ${keys.join("")}` }]
|
|
2292
|
-
};
|
|
2293
|
-
} catch (e) {
|
|
2294
|
-
return {
|
|
2295
|
-
content: [{ type: "text", text: `Error sending keys: ${e}` }]
|
|
2296
|
-
};
|
|
2297
|
-
}
|
|
2298
|
-
};
|
|
2299
|
-
var pressKeyCodeTool = async (args) => {
|
|
2300
|
-
try {
|
|
2301
|
-
const browser = getBrowser();
|
|
2302
|
-
const { keyCode } = args;
|
|
2303
|
-
await browser.pressKeyCode(keyCode);
|
|
2304
|
-
return {
|
|
2305
|
-
content: [{ type: "text", text: `Pressed key code: ${keyCode}` }]
|
|
2306
|
-
};
|
|
2307
|
-
} catch (e) {
|
|
2308
|
-
return {
|
|
2309
|
-
content: [{ type: "text", text: `Error pressing key code: ${e}` }]
|
|
2310
|
-
};
|
|
2311
|
-
}
|
|
2312
|
-
};
|
|
2313
1924
|
var hideKeyboardTool = async () => {
|
|
2314
1925
|
try {
|
|
2315
1926
|
const browser = getBrowser();
|
|
@@ -2323,32 +1934,6 @@ var hideKeyboardTool = async () => {
|
|
|
2323
1934
|
};
|
|
2324
1935
|
}
|
|
2325
1936
|
};
|
|
2326
|
-
var isKeyboardShownTool = async () => {
|
|
2327
|
-
try {
|
|
2328
|
-
const browser = getBrowser();
|
|
2329
|
-
const isShown = await browser.isKeyboardShown();
|
|
2330
|
-
return {
|
|
2331
|
-
content: [{ type: "text", text: `Keyboard is ${isShown ? "shown" : "hidden"}` }]
|
|
2332
|
-
};
|
|
2333
|
-
} catch (e) {
|
|
2334
|
-
return {
|
|
2335
|
-
content: [{ type: "text", text: `Error checking keyboard status: ${e}` }]
|
|
2336
|
-
};
|
|
2337
|
-
}
|
|
2338
|
-
};
|
|
2339
|
-
var openNotificationsTool = async () => {
|
|
2340
|
-
try {
|
|
2341
|
-
const browser = getBrowser();
|
|
2342
|
-
await browser.openNotifications();
|
|
2343
|
-
return {
|
|
2344
|
-
content: [{ type: "text", text: "Opened notifications panel" }]
|
|
2345
|
-
};
|
|
2346
|
-
} catch (e) {
|
|
2347
|
-
return {
|
|
2348
|
-
content: [{ type: "text", text: `Error opening notifications: ${e}` }]
|
|
2349
|
-
};
|
|
2350
|
-
}
|
|
2351
|
-
};
|
|
2352
1937
|
var getGeolocationTool = async () => {
|
|
2353
1938
|
try {
|
|
2354
1939
|
const browser = getBrowser();
|
|
@@ -2393,6 +1978,69 @@ var setGeolocationTool = async (args) => {
|
|
|
2393
1978
|
}
|
|
2394
1979
|
};
|
|
2395
1980
|
|
|
1981
|
+
// src/tools/execute-script.tool.ts
|
|
1982
|
+
import { z as z15 } from "zod";
|
|
1983
|
+
var executeScriptToolDefinition = {
|
|
1984
|
+
name: "execute_script",
|
|
1985
|
+
description: `Executes JavaScript in browser or mobile commands via Appium.
|
|
1986
|
+
|
|
1987
|
+
**Browser:** Runs JavaScript in page context. Use 'return' to get values back.
|
|
1988
|
+
- Example: execute_script({ script: "return document.title" })
|
|
1989
|
+
- Example: execute_script({ script: "return window.scrollY" })
|
|
1990
|
+
- Example: execute_script({ script: "arguments[0].click()", args: ["#myButton"] })
|
|
1991
|
+
|
|
1992
|
+
**Mobile (Appium):** Executes mobile-specific commands using 'mobile: <command>' syntax.
|
|
1993
|
+
- Press key (Android): execute_script({ script: "mobile: pressKey", args: [{ keycode: 4 }] }) // BACK=4, HOME=3
|
|
1994
|
+
- Activate app: execute_script({ script: "mobile: activateApp", args: [{ appId: "com.example" }] })
|
|
1995
|
+
- Terminate app: execute_script({ script: "mobile: terminateApp", args: [{ appId: "com.example" }] })
|
|
1996
|
+
- Deep link: execute_script({ script: "mobile: deepLink", args: [{ url: "myapp://screen", package: "com.example" }] })
|
|
1997
|
+
- Shell command (Android): execute_script({ script: "mobile: shell", args: [{ command: "dumpsys", args: ["battery"] }] })`,
|
|
1998
|
+
inputSchema: {
|
|
1999
|
+
script: z15.string().describe('JavaScript code (browser) or mobile command string like "mobile: pressKey" (Appium)'),
|
|
2000
|
+
args: z15.array(z15.any()).optional().describe("Arguments to pass to the script. For browser: element selectors or values. For mobile commands: command-specific parameters as objects.")
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
2003
|
+
var executeScriptTool = async (args) => {
|
|
2004
|
+
try {
|
|
2005
|
+
const browser = getBrowser();
|
|
2006
|
+
const { script, args: scriptArgs = [] } = args;
|
|
2007
|
+
const resolvedArgs = await Promise.all(
|
|
2008
|
+
scriptArgs.map(async (arg) => {
|
|
2009
|
+
if (typeof arg === "string" && !script.startsWith("mobile:")) {
|
|
2010
|
+
try {
|
|
2011
|
+
const element = await browser.$(arg);
|
|
2012
|
+
if (await element.isExisting()) {
|
|
2013
|
+
return element;
|
|
2014
|
+
}
|
|
2015
|
+
} catch {
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
return arg;
|
|
2019
|
+
})
|
|
2020
|
+
);
|
|
2021
|
+
const result = await browser.execute(script, ...resolvedArgs);
|
|
2022
|
+
let resultText;
|
|
2023
|
+
if (result === void 0 || result === null) {
|
|
2024
|
+
resultText = "Script executed successfully (no return value)";
|
|
2025
|
+
} else if (typeof result === "object") {
|
|
2026
|
+
try {
|
|
2027
|
+
resultText = `Result: ${JSON.stringify(result, null, 2)}`;
|
|
2028
|
+
} catch {
|
|
2029
|
+
resultText = `Result: ${String(result)}`;
|
|
2030
|
+
}
|
|
2031
|
+
} else {
|
|
2032
|
+
resultText = `Result: ${result}`;
|
|
2033
|
+
}
|
|
2034
|
+
return {
|
|
2035
|
+
content: [{ type: "text", text: resultText }]
|
|
2036
|
+
};
|
|
2037
|
+
} catch (e) {
|
|
2038
|
+
return {
|
|
2039
|
+
content: [{ type: "text", text: `Error executing script: ${e}` }]
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
|
|
2396
2044
|
// package.json
|
|
2397
2045
|
var package_default = {
|
|
2398
2046
|
name: "@wdio/mcp",
|
|
@@ -2401,7 +2049,7 @@ var package_default = {
|
|
|
2401
2049
|
type: "git",
|
|
2402
2050
|
url: "git://github.com/webdriverio/mcp.git"
|
|
2403
2051
|
},
|
|
2404
|
-
version: "1.
|
|
2052
|
+
version: "1.6.1",
|
|
2405
2053
|
description: "MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)",
|
|
2406
2054
|
main: "./lib/server.js",
|
|
2407
2055
|
module: "./lib/server.js",
|
|
@@ -2433,6 +2081,7 @@ var package_default = {
|
|
|
2433
2081
|
"@wdio/protocols": "^9.16.2",
|
|
2434
2082
|
"@xmldom/xmldom": "^0.8.11",
|
|
2435
2083
|
"puppeteer-core": "^24.35.0",
|
|
2084
|
+
sharp: "^0.34.5",
|
|
2436
2085
|
webdriverio: "9.23",
|
|
2437
2086
|
zod: "^4.3.5"
|
|
2438
2087
|
},
|
|
@@ -2480,42 +2129,25 @@ registerTool(closeSessionToolDefinition, closeSessionTool);
|
|
|
2480
2129
|
registerTool(navigateToolDefinition, navigateTool);
|
|
2481
2130
|
registerTool(getVisibleElementsToolDefinition, getVisibleElementsTool);
|
|
2482
2131
|
registerTool(getAccessibilityToolDefinition, getAccessibilityTreeTool);
|
|
2483
|
-
registerTool(
|
|
2484
|
-
registerTool(scrollUpToolDefinition, scrollUpTool);
|
|
2485
|
-
registerTool(findElementToolDefinition, findElementTool);
|
|
2132
|
+
registerTool(scrollToolDefinition, scrollTool);
|
|
2486
2133
|
registerTool(clickToolDefinition, clickTool);
|
|
2487
|
-
registerTool(clickViaTextToolDefinition, clickToolViaText);
|
|
2488
2134
|
registerTool(setValueToolDefinition, setValueTool);
|
|
2489
|
-
registerTool(getElementTextToolDefinition, getElementTextTool);
|
|
2490
|
-
registerTool(isDisplayedToolDefinition, isDisplayedTool);
|
|
2491
2135
|
registerTool(takeScreenshotToolDefinition, takeScreenshotTool);
|
|
2492
2136
|
registerTool(getCookiesToolDefinition, getCookiesTool);
|
|
2493
2137
|
registerTool(setCookieToolDefinition, setCookieTool);
|
|
2494
2138
|
registerTool(deleteCookiesToolDefinition, deleteCookiesTool);
|
|
2495
2139
|
registerTool(tapElementToolDefinition, tapElementTool);
|
|
2496
2140
|
registerTool(swipeToolDefinition, swipeTool);
|
|
2497
|
-
registerTool(longPressToolDefinition, longPressTool);
|
|
2498
2141
|
registerTool(dragAndDropToolDefinition, dragAndDropTool);
|
|
2499
2142
|
registerTool(getAppStateToolDefinition, getAppStateTool);
|
|
2500
|
-
registerTool(activateAppToolDefinition, activateAppTool);
|
|
2501
|
-
registerTool(terminateAppToolDefinition, terminateAppTool);
|
|
2502
2143
|
registerTool(getContextsToolDefinition, getContextsTool);
|
|
2503
2144
|
registerTool(getCurrentContextToolDefinition, getCurrentContextTool);
|
|
2504
2145
|
registerTool(switchContextToolDefinition, switchContextTool);
|
|
2505
|
-
registerTool(getDeviceInfoToolDefinition, getDeviceInfoTool);
|
|
2506
2146
|
registerTool(rotateDeviceToolDefinition, rotateDeviceTool);
|
|
2507
|
-
registerTool(getOrientationToolDefinition, getOrientationTool);
|
|
2508
|
-
registerTool(lockDeviceToolDefinition, lockDeviceTool);
|
|
2509
|
-
registerTool(unlockDeviceToolDefinition, unlockDeviceTool);
|
|
2510
|
-
registerTool(isDeviceLockedToolDefinition, isDeviceLockedTool);
|
|
2511
|
-
registerTool(shakeDeviceToolDefinition, shakeDeviceTool);
|
|
2512
|
-
registerTool(sendKeysToolDefinition, sendKeysTool);
|
|
2513
|
-
registerTool(pressKeyCodeToolDefinition, pressKeyCodeTool);
|
|
2514
2147
|
registerTool(hideKeyboardToolDefinition, hideKeyboardTool);
|
|
2515
|
-
registerTool(isKeyboardShownToolDefinition, isKeyboardShownTool);
|
|
2516
|
-
registerTool(openNotificationsToolDefinition, openNotificationsTool);
|
|
2517
2148
|
registerTool(getGeolocationToolDefinition, getGeolocationTool);
|
|
2518
2149
|
registerTool(setGeolocationToolDefinition, setGeolocationTool);
|
|
2150
|
+
registerTool(executeScriptToolDefinition, executeScriptTool);
|
|
2519
2151
|
async function main() {
|
|
2520
2152
|
const transport = new StdioServerTransport();
|
|
2521
2153
|
await server.connect(transport);
|