@wdio/mcp 2.4.1 → 2.5.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 +11 -0
- package/lib/server.js +130 -136
- package/lib/server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -85,6 +85,7 @@ appium
|
|
|
85
85
|
- **Scrolling**: Smooth scrolling with configurable distances
|
|
86
86
|
- **Attach to running Chrome**: Connect to an existing Chrome window via `--remote-debugging-port` — ideal for testing authenticated or pre-configured sessions
|
|
87
87
|
- **Device emulation**: Apply mobile/tablet presets (iPhone 15, Pixel 7, etc.) to simulate responsive layouts without a physical device
|
|
88
|
+
- **Session Recording**: All tool calls are automatically recorded and exportable as runnable WebdriverIO JS
|
|
88
89
|
|
|
89
90
|
### Mobile App Automation (iOS/Android)
|
|
90
91
|
|
|
@@ -458,6 +459,16 @@ This eliminates the need to manually handle permission popups during automated t
|
|
|
458
459
|
- **Data Format:** TOON (Token-Oriented Object Notation) for efficient LLM communication
|
|
459
460
|
- **Element Detection:** XML-based page source parsing with intelligent filtering and multi-strategy locator generation
|
|
460
461
|
|
|
462
|
+
### Session Recording & Code Export
|
|
463
|
+
|
|
464
|
+
Every tool call is automatically recorded to a session history. You can inspect sessions and export runnable code via MCP resources — no extra tool calls needed:
|
|
465
|
+
|
|
466
|
+
- `wdio://sessions` — lists all recorded sessions with type, timestamps, and step count
|
|
467
|
+
- `wdio://session/current/steps` — step log for the active session, plus a generated WebdriverIO JS script ready to run with `webdriverio`
|
|
468
|
+
- `wdio://session/{sessionId}/steps` — same for any past session by ID
|
|
469
|
+
|
|
470
|
+
The generated script reconstructs the full session — including capabilities, navigation, clicks, and inputs — as a standalone `import { remote } from 'webdriverio'` file.
|
|
471
|
+
|
|
461
472
|
## Troubleshooting
|
|
462
473
|
|
|
463
474
|
**Browser automation not working?**
|
package/lib/server.js
CHANGED
|
@@ -157,14 +157,7 @@ var startBrowserTool = async ({
|
|
|
157
157
|
type: "browser",
|
|
158
158
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
159
159
|
capabilities: wdioBrowser.capabilities,
|
|
160
|
-
steps: [
|
|
161
|
-
index: 1,
|
|
162
|
-
tool: "start_browser",
|
|
163
|
-
params: { browser, headless, windowWidth, windowHeight, ...navigationUrl && { navigationUrl }, ...Object.keys(userCapabilities).length > 0 && { capabilities: userCapabilities } },
|
|
164
|
-
status: "ok",
|
|
165
|
-
durationMs: 0,
|
|
166
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
167
|
-
}]
|
|
160
|
+
steps: []
|
|
168
161
|
});
|
|
169
162
|
state.currentSession = sessionId;
|
|
170
163
|
let sizeNote = "";
|
|
@@ -211,6 +204,7 @@ var closeSessionTool = async (args = {}) => {
|
|
|
211
204
|
};
|
|
212
205
|
} catch (e) {
|
|
213
206
|
return {
|
|
207
|
+
isError: true,
|
|
214
208
|
content: [{ type: "text", text: `Error closing session: ${e}` }]
|
|
215
209
|
};
|
|
216
210
|
}
|
|
@@ -234,6 +228,7 @@ var navigateTool = async ({ url }) => {
|
|
|
234
228
|
};
|
|
235
229
|
} catch (e) {
|
|
236
230
|
return {
|
|
231
|
+
isError: true,
|
|
237
232
|
content: [{ type: "text", text: `Error navigating: ${e}` }]
|
|
238
233
|
};
|
|
239
234
|
}
|
|
@@ -264,6 +259,7 @@ var clickAction = async (selector, timeout, scrollToView = true) => {
|
|
|
264
259
|
};
|
|
265
260
|
} catch (e) {
|
|
266
261
|
return {
|
|
262
|
+
isError: true,
|
|
267
263
|
content: [{ type: "text", text: `Error clicking element: ${e}` }]
|
|
268
264
|
};
|
|
269
265
|
}
|
|
@@ -297,6 +293,7 @@ var setValueTool = async ({ selector, value, scrollToView = true, timeout = defa
|
|
|
297
293
|
};
|
|
298
294
|
} catch (e) {
|
|
299
295
|
return {
|
|
296
|
+
isError: true,
|
|
300
297
|
content: [{ type: "text", text: `Error entering text: ${e}` }]
|
|
301
298
|
};
|
|
302
299
|
}
|
|
@@ -343,7 +340,7 @@ function buildIOSCapabilities(appPath, options) {
|
|
|
343
340
|
capabilities["appium:autoAcceptAlerts"] = void 0;
|
|
344
341
|
}
|
|
345
342
|
for (const [key, value] of Object.entries(options)) {
|
|
346
|
-
if (!["deviceName", "platformVersion", "automationName", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset", "newCommandTimeout"].includes(
|
|
343
|
+
if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset", "newCommandTimeout"].includes(
|
|
347
344
|
key
|
|
348
345
|
)) {
|
|
349
346
|
capabilities[`appium:${key}`] = value;
|
|
@@ -380,7 +377,7 @@ function buildAndroidCapabilities(appPath, options) {
|
|
|
380
377
|
capabilities["appium:appWaitActivity"] = options.appWaitActivity;
|
|
381
378
|
}
|
|
382
379
|
for (const [key, value] of Object.entries(options)) {
|
|
383
|
-
if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "appWaitActivity", "noReset", "fullReset", "newCommandTimeout"].includes(
|
|
380
|
+
if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "autoAcceptAlerts", "autoDismissAlerts", "appWaitActivity", "noReset", "fullReset", "newCommandTimeout"].includes(
|
|
384
381
|
key
|
|
385
382
|
)) {
|
|
386
383
|
capabilities[`appium:${key}`] = value;
|
|
@@ -521,32 +518,8 @@ var startAppTool = async (args) => {
|
|
|
521
518
|
type: platform.toLowerCase(),
|
|
522
519
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
523
520
|
capabilities: mergedCapabilities,
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
tool: "start_app_session",
|
|
527
|
-
params: {
|
|
528
|
-
platform,
|
|
529
|
-
deviceName,
|
|
530
|
-
...platformVersion !== void 0 && { platformVersion },
|
|
531
|
-
...automationName !== void 0 && { automationName },
|
|
532
|
-
...appPath !== void 0 && { appPath },
|
|
533
|
-
...udid !== void 0 && { udid },
|
|
534
|
-
...noReset !== void 0 && { noReset },
|
|
535
|
-
...fullReset !== void 0 && { fullReset },
|
|
536
|
-
...autoGrantPermissions !== void 0 && { autoGrantPermissions },
|
|
537
|
-
...autoAcceptAlerts !== void 0 && { autoAcceptAlerts },
|
|
538
|
-
...autoDismissAlerts !== void 0 && { autoDismissAlerts },
|
|
539
|
-
...appWaitActivity !== void 0 && { appWaitActivity },
|
|
540
|
-
...newCommandTimeout !== void 0 && { newCommandTimeout },
|
|
541
|
-
...appiumHost !== void 0 && { appiumHost },
|
|
542
|
-
...appiumPort !== void 0 && { appiumPort },
|
|
543
|
-
...appiumPath !== void 0 && { appiumPath },
|
|
544
|
-
...Object.keys(userCapabilities).length > 0 && { capabilities: userCapabilities }
|
|
545
|
-
},
|
|
546
|
-
status: "ok",
|
|
547
|
-
durationMs: 0,
|
|
548
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
549
|
-
}]
|
|
521
|
+
appiumConfig: { hostname: serverConfig.hostname, port: serverConfig.port, path: serverConfig.path },
|
|
522
|
+
steps: []
|
|
550
523
|
});
|
|
551
524
|
state2.currentSession = sessionId;
|
|
552
525
|
const appInfo = appPath ? `
|
|
@@ -564,6 +537,7 @@ Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}
|
|
|
564
537
|
};
|
|
565
538
|
} catch (e) {
|
|
566
539
|
return {
|
|
540
|
+
isError: true,
|
|
567
541
|
content: [{ type: "text", text: `Error starting app session: ${e}` }]
|
|
568
542
|
};
|
|
569
543
|
}
|
|
@@ -597,6 +571,7 @@ var scrollTool = async ({ direction, pixels = 500 }) => {
|
|
|
597
571
|
};
|
|
598
572
|
} catch (e) {
|
|
599
573
|
return {
|
|
574
|
+
isError: true,
|
|
600
575
|
content: [{ type: "text", text: `Error scrolling: ${e}` }]
|
|
601
576
|
};
|
|
602
577
|
}
|
|
@@ -1769,6 +1744,7 @@ var getVisibleElementsTool = async (args) => {
|
|
|
1769
1744
|
};
|
|
1770
1745
|
} catch (e) {
|
|
1771
1746
|
return {
|
|
1747
|
+
isError: true,
|
|
1772
1748
|
content: [{ type: "text", text: `Error getting visible elements: ${e}` }]
|
|
1773
1749
|
};
|
|
1774
1750
|
}
|
|
@@ -2088,6 +2064,7 @@ var getAccessibilityTreeTool = async (args) => {
|
|
|
2088
2064
|
};
|
|
2089
2065
|
} catch (e) {
|
|
2090
2066
|
return {
|
|
2067
|
+
isError: true,
|
|
2091
2068
|
content: [{ type: "text", text: `Error getting accessibility tree: ${e}` }]
|
|
2092
2069
|
};
|
|
2093
2070
|
}
|
|
@@ -2148,6 +2125,7 @@ var takeScreenshotTool = async ({ outputPath }) => {
|
|
|
2148
2125
|
};
|
|
2149
2126
|
} catch (e) {
|
|
2150
2127
|
return {
|
|
2128
|
+
isError: true,
|
|
2151
2129
|
content: [{ type: "text", text: `Error taking screenshot: ${e.message}` }]
|
|
2152
2130
|
};
|
|
2153
2131
|
}
|
|
@@ -2187,6 +2165,7 @@ var getCookiesTool = async ({ name }) => {
|
|
|
2187
2165
|
};
|
|
2188
2166
|
} catch (e) {
|
|
2189
2167
|
return {
|
|
2168
|
+
isError: true,
|
|
2190
2169
|
content: [{ type: "text", text: `Error getting cookies: ${e}` }]
|
|
2191
2170
|
};
|
|
2192
2171
|
}
|
|
@@ -2224,6 +2203,7 @@ var setCookieTool = async ({
|
|
|
2224
2203
|
};
|
|
2225
2204
|
} catch (e) {
|
|
2226
2205
|
return {
|
|
2206
|
+
isError: true,
|
|
2227
2207
|
content: [{ type: "text", text: `Error setting cookie: ${e}` }]
|
|
2228
2208
|
};
|
|
2229
2209
|
}
|
|
@@ -2250,6 +2230,7 @@ var deleteCookiesTool = async ({ name }) => {
|
|
|
2250
2230
|
};
|
|
2251
2231
|
} catch (e) {
|
|
2252
2232
|
return {
|
|
2233
|
+
isError: true,
|
|
2253
2234
|
content: [{ type: "text", text: `Error deleting cookies: ${e}` }]
|
|
2254
2235
|
};
|
|
2255
2236
|
}
|
|
@@ -2283,10 +2264,12 @@ var tapElementTool = async (args) => {
|
|
|
2283
2264
|
};
|
|
2284
2265
|
}
|
|
2285
2266
|
return {
|
|
2267
|
+
isError: true,
|
|
2286
2268
|
content: [{ type: "text", text: "Error: Must provide either selector or x,y coordinates" }]
|
|
2287
2269
|
};
|
|
2288
2270
|
} catch (e) {
|
|
2289
2271
|
return {
|
|
2272
|
+
isError: true,
|
|
2290
2273
|
content: [{ type: "text", text: `Error tapping: ${e}` }]
|
|
2291
2274
|
};
|
|
2292
2275
|
}
|
|
@@ -2321,6 +2304,7 @@ var swipeTool = async (args) => {
|
|
|
2321
2304
|
};
|
|
2322
2305
|
} catch (e) {
|
|
2323
2306
|
return {
|
|
2307
|
+
isError: true,
|
|
2324
2308
|
content: [{ type: "text", text: `Error swiping: ${e}` }]
|
|
2325
2309
|
};
|
|
2326
2310
|
}
|
|
@@ -2354,10 +2338,12 @@ var dragAndDropTool = async (args) => {
|
|
|
2354
2338
|
};
|
|
2355
2339
|
}
|
|
2356
2340
|
return {
|
|
2341
|
+
isError: true,
|
|
2357
2342
|
content: [{ type: "text", text: "Error: Must provide either targetSelector or x,y coordinates" }]
|
|
2358
2343
|
};
|
|
2359
2344
|
} catch (e) {
|
|
2360
2345
|
return {
|
|
2346
|
+
isError: true,
|
|
2361
2347
|
content: [{ type: "text", text: `Error dragging: ${e}` }]
|
|
2362
2348
|
};
|
|
2363
2349
|
}
|
|
@@ -2395,6 +2381,7 @@ var getAppStateTool = async (args) => {
|
|
|
2395
2381
|
};
|
|
2396
2382
|
} catch (e) {
|
|
2397
2383
|
return {
|
|
2384
|
+
isError: true,
|
|
2398
2385
|
content: [{ type: "text", text: `Error getting app state: ${e}` }]
|
|
2399
2386
|
};
|
|
2400
2387
|
}
|
|
@@ -2436,6 +2423,7 @@ ${contexts.map((ctx, idx) => `${idx + 1}. ${ctx}`).join("\n")}`
|
|
|
2436
2423
|
};
|
|
2437
2424
|
} catch (e) {
|
|
2438
2425
|
return {
|
|
2426
|
+
isError: true,
|
|
2439
2427
|
content: [{ type: "text", text: `Error getting contexts: ${e}` }]
|
|
2440
2428
|
};
|
|
2441
2429
|
}
|
|
@@ -2449,6 +2437,7 @@ var getCurrentContextTool = async () => {
|
|
|
2449
2437
|
};
|
|
2450
2438
|
} catch (e) {
|
|
2451
2439
|
return {
|
|
2440
|
+
isError: true,
|
|
2452
2441
|
content: [{ type: "text", text: `Error getting current context: ${e}` }]
|
|
2453
2442
|
};
|
|
2454
2443
|
}
|
|
@@ -2464,14 +2453,7 @@ var switchContextTool = async (args) => {
|
|
|
2464
2453
|
if (index >= 0 && index < contexts.length) {
|
|
2465
2454
|
targetContext = contexts[index];
|
|
2466
2455
|
} else {
|
|
2467
|
-
|
|
2468
|
-
content: [
|
|
2469
|
-
{
|
|
2470
|
-
type: "text",
|
|
2471
|
-
text: `Error: Invalid context index ${context}. Available contexts: ${contexts.length}`
|
|
2472
|
-
}
|
|
2473
|
-
]
|
|
2474
|
-
};
|
|
2456
|
+
throw new Error(`Error: Invalid context index ${context}. Available contexts: ${contexts.length}`);
|
|
2475
2457
|
}
|
|
2476
2458
|
}
|
|
2477
2459
|
await browser.switchContext(targetContext);
|
|
@@ -2480,6 +2462,7 @@ var switchContextTool = async (args) => {
|
|
|
2480
2462
|
};
|
|
2481
2463
|
} catch (e) {
|
|
2482
2464
|
return {
|
|
2465
|
+
isError: true,
|
|
2483
2466
|
content: [{ type: "text", text: `Error switching context: ${e}` }]
|
|
2484
2467
|
};
|
|
2485
2468
|
}
|
|
@@ -2523,6 +2506,7 @@ var rotateDeviceTool = async (args) => {
|
|
|
2523
2506
|
};
|
|
2524
2507
|
} catch (e) {
|
|
2525
2508
|
return {
|
|
2509
|
+
isError: true,
|
|
2526
2510
|
content: [{ type: "text", text: `Error rotating device: ${e}` }]
|
|
2527
2511
|
};
|
|
2528
2512
|
}
|
|
@@ -2536,6 +2520,7 @@ var hideKeyboardTool = async () => {
|
|
|
2536
2520
|
};
|
|
2537
2521
|
} catch (e) {
|
|
2538
2522
|
return {
|
|
2523
|
+
isError: true,
|
|
2539
2524
|
content: [{ type: "text", text: `Error hiding keyboard: ${e}` }]
|
|
2540
2525
|
};
|
|
2541
2526
|
}
|
|
@@ -2557,6 +2542,7 @@ var getGeolocationTool = async () => {
|
|
|
2557
2542
|
};
|
|
2558
2543
|
} catch (e) {
|
|
2559
2544
|
return {
|
|
2545
|
+
isError: true,
|
|
2560
2546
|
content: [{ type: "text", text: `Error getting geolocation: ${e}` }]
|
|
2561
2547
|
};
|
|
2562
2548
|
}
|
|
@@ -2579,6 +2565,7 @@ var setGeolocationTool = async (args) => {
|
|
|
2579
2565
|
};
|
|
2580
2566
|
} catch (e) {
|
|
2581
2567
|
return {
|
|
2568
|
+
isError: true,
|
|
2582
2569
|
content: [{ type: "text", text: `Error setting geolocation: ${e}` }]
|
|
2583
2570
|
};
|
|
2584
2571
|
}
|
|
@@ -2644,6 +2631,7 @@ var executeScriptTool = async (args) => {
|
|
|
2644
2631
|
};
|
|
2645
2632
|
} catch (e) {
|
|
2646
2633
|
return {
|
|
2634
|
+
isError: true,
|
|
2647
2635
|
content: [{ type: "text", text: `Error executing script: ${e}` }]
|
|
2648
2636
|
};
|
|
2649
2637
|
}
|
|
@@ -2718,6 +2706,19 @@ var attachBrowserTool = async ({
|
|
|
2718
2706
|
capabilities: browser.capabilities,
|
|
2719
2707
|
isAttached: true
|
|
2720
2708
|
});
|
|
2709
|
+
state2.sessionHistory.set(sessionId, {
|
|
2710
|
+
sessionId,
|
|
2711
|
+
type: "browser",
|
|
2712
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2713
|
+
capabilities: {
|
|
2714
|
+
browserName: "chrome",
|
|
2715
|
+
"goog:chromeOptions": {
|
|
2716
|
+
debuggerAddress: `${host}:${port}`,
|
|
2717
|
+
args: [`--user-data-dir=${userDataDir}`]
|
|
2718
|
+
}
|
|
2719
|
+
},
|
|
2720
|
+
steps: []
|
|
2721
|
+
});
|
|
2721
2722
|
if (activeUrl) {
|
|
2722
2723
|
await browser.url(activeUrl);
|
|
2723
2724
|
}
|
|
@@ -2733,6 +2734,7 @@ Current page: "${title}" (${url})`
|
|
|
2733
2734
|
};
|
|
2734
2735
|
} catch (e) {
|
|
2735
2736
|
return {
|
|
2737
|
+
isError: true,
|
|
2736
2738
|
content: [{ type: "text", text: `Error attaching to browser: ${e}` }]
|
|
2737
2739
|
};
|
|
2738
2740
|
}
|
|
@@ -2767,11 +2769,13 @@ var emulateDeviceTool = async ({
|
|
|
2767
2769
|
const metadata = state2.sessionMetadata.get(sessionId);
|
|
2768
2770
|
if (metadata?.type === "ios" || metadata?.type === "android") {
|
|
2769
2771
|
return {
|
|
2772
|
+
isError: true,
|
|
2770
2773
|
content: [{ type: "text", text: "Error: emulate_device is only supported for web browser sessions, not iOS/Android." }]
|
|
2771
2774
|
};
|
|
2772
2775
|
}
|
|
2773
2776
|
if (!browser.isBidi) {
|
|
2774
2777
|
return {
|
|
2778
|
+
isError: true,
|
|
2775
2779
|
content: [{
|
|
2776
2780
|
type: "text",
|
|
2777
2781
|
text: "Error: emulate_device requires a BiDi-enabled session.\nRestart the browser with: start_browser({ capabilities: { webSocketUrl: true } })"
|
|
@@ -2791,7 +2795,7 @@ var emulateDeviceTool = async ({
|
|
|
2791
2795
|
${names.join("\n")}` }]
|
|
2792
2796
|
};
|
|
2793
2797
|
}
|
|
2794
|
-
return { content: [{ type: "text", text: `Error listing devices: ${e}` }] };
|
|
2798
|
+
return { isError: true, content: [{ type: "text", text: `Error listing devices: ${e}` }] };
|
|
2795
2799
|
}
|
|
2796
2800
|
return { content: [{ type: "text", text: "Could not retrieve device list." }] };
|
|
2797
2801
|
}
|
|
@@ -2820,10 +2824,11 @@ ${names.join("\n")}` }]
|
|
|
2820
2824
|
}]
|
|
2821
2825
|
};
|
|
2822
2826
|
}
|
|
2823
|
-
return { content: [{ type: "text", text: `Error: ${e}` }] };
|
|
2827
|
+
return { isError: true, content: [{ type: "text", text: `Error: ${e}` }] };
|
|
2824
2828
|
}
|
|
2825
2829
|
} catch (e) {
|
|
2826
2830
|
return {
|
|
2831
|
+
isError: true,
|
|
2827
2832
|
content: [{ type: "text", text: `Error: ${e}` }]
|
|
2828
2833
|
};
|
|
2829
2834
|
}
|
|
@@ -2837,7 +2842,7 @@ var package_default = {
|
|
|
2837
2842
|
type: "git",
|
|
2838
2843
|
url: "git://github.com/webdriverio/mcp.git"
|
|
2839
2844
|
},
|
|
2840
|
-
version: "2.4.
|
|
2845
|
+
version: "2.4.1",
|
|
2841
2846
|
description: "MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)",
|
|
2842
2847
|
main: "./lib/server.js",
|
|
2843
2848
|
module: "./lib/server.js",
|
|
@@ -2940,9 +2945,7 @@ function withRecording(toolName, callback) {
|
|
|
2940
2945
|
return async (params, extra) => {
|
|
2941
2946
|
const start = Date.now();
|
|
2942
2947
|
const result = await callback(params, extra);
|
|
2943
|
-
const isError = result.
|
|
2944
|
-
(c) => c.type === "text" && typeof c.text === "string" && c.text.startsWith("Error")
|
|
2945
|
-
);
|
|
2948
|
+
const isError = result.isError === true;
|
|
2946
2949
|
appendStep(
|
|
2947
2950
|
toolName,
|
|
2948
2951
|
params,
|
|
@@ -2964,7 +2967,7 @@ function formatParams(params) {
|
|
|
2964
2967
|
function indentJson(value) {
|
|
2965
2968
|
return JSON.stringify(value, null, 2).split("\n").map((line, i) => i > 0 ? ` ${line}` : line).join("\n");
|
|
2966
2969
|
}
|
|
2967
|
-
function generateStep(step) {
|
|
2970
|
+
function generateStep(step, history) {
|
|
2968
2971
|
if (step.tool === "__session_transition__") {
|
|
2969
2972
|
const newId = step.params.newSessionId ?? "unknown";
|
|
2970
2973
|
return `// --- new session: ${newId} started at ${step.timestamp} ---`;
|
|
@@ -2975,52 +2978,29 @@ function generateStep(step) {
|
|
|
2975
2978
|
const p = step.params;
|
|
2976
2979
|
switch (step.tool) {
|
|
2977
2980
|
case "start_browser": {
|
|
2978
|
-
const browserName = p.browser === "edge" ? "msedge" : String(p.browser ?? "chrome");
|
|
2979
|
-
const headless = p.headless !== false;
|
|
2980
|
-
const width = p.windowWidth ?? 1920;
|
|
2981
|
-
const height = p.windowHeight ?? 1080;
|
|
2982
|
-
const args = [`--window-size=${width},${height}`];
|
|
2983
|
-
if (headless && browserName !== "safari") {
|
|
2984
|
-
args.push("--headless=new", "--disable-gpu", "--disable-dev-shm-usage");
|
|
2985
|
-
}
|
|
2986
|
-
const caps = { browserName };
|
|
2987
|
-
if (browserName === "chrome") caps["goog:chromeOptions"] = { args };
|
|
2988
|
-
else if (browserName === "msedge") caps["ms:edgeOptions"] = { args };
|
|
2989
|
-
else if (browserName === "firefox" && headless) caps["moz:firefoxOptions"] = { args: ["-headless"] };
|
|
2990
|
-
const extra = p.capabilities;
|
|
2991
|
-
const merged = extra ? { ...caps, ...extra } : caps;
|
|
2992
2981
|
const nav = p.navigationUrl ? `
|
|
2993
2982
|
await browser.url('${escapeStr(p.navigationUrl)}');` : "";
|
|
2994
2983
|
return `const browser = await remote({
|
|
2995
|
-
capabilities: ${indentJson(
|
|
2984
|
+
capabilities: ${indentJson(history.capabilities)}
|
|
2996
2985
|
});${nav}`;
|
|
2997
2986
|
}
|
|
2998
2987
|
case "start_app_session": {
|
|
2999
|
-
const caps = {
|
|
3000
|
-
platformName: p.platform,
|
|
3001
|
-
"appium:deviceName": p.deviceName,
|
|
3002
|
-
...p.platformVersion !== void 0 && { "appium:platformVersion": p.platformVersion },
|
|
3003
|
-
...p.automationName !== void 0 && { "appium:automationName": p.automationName },
|
|
3004
|
-
...p.appPath !== void 0 && { "appium:app": p.appPath },
|
|
3005
|
-
...p.udid !== void 0 && { "appium:udid": p.udid },
|
|
3006
|
-
...p.noReset !== void 0 && { "appium:noReset": p.noReset },
|
|
3007
|
-
...p.fullReset !== void 0 && { "appium:fullReset": p.fullReset },
|
|
3008
|
-
...p.autoGrantPermissions !== void 0 && { "appium:autoGrantPermissions": p.autoGrantPermissions },
|
|
3009
|
-
...p.autoAcceptAlerts !== void 0 && { "appium:autoAcceptAlerts": p.autoAcceptAlerts },
|
|
3010
|
-
...p.autoDismissAlerts !== void 0 && { "appium:autoDismissAlerts": p.autoDismissAlerts },
|
|
3011
|
-
...p.appWaitActivity !== void 0 && { "appium:appWaitActivity": p.appWaitActivity },
|
|
3012
|
-
...p.newCommandTimeout !== void 0 && { "appium:newCommandTimeout": p.newCommandTimeout },
|
|
3013
|
-
...p.capabilities ?? {}
|
|
3014
|
-
};
|
|
3015
2988
|
const config = {
|
|
3016
2989
|
protocol: "http",
|
|
3017
|
-
hostname:
|
|
3018
|
-
port:
|
|
3019
|
-
path:
|
|
3020
|
-
capabilities:
|
|
2990
|
+
hostname: history.appiumConfig?.hostname ?? "localhost",
|
|
2991
|
+
port: history.appiumConfig?.port ?? 4723,
|
|
2992
|
+
path: history.appiumConfig?.path ?? "/",
|
|
2993
|
+
capabilities: history.capabilities
|
|
3021
2994
|
};
|
|
3022
2995
|
return `const browser = await remote(${indentJson(config)});`;
|
|
3023
2996
|
}
|
|
2997
|
+
case "attach_browser": {
|
|
2998
|
+
const nav = p.navigationUrl ? `
|
|
2999
|
+
await browser.url('${escapeStr(p.navigationUrl)}');` : "";
|
|
3000
|
+
return `const browser = await remote({
|
|
3001
|
+
capabilities: ${indentJson(history.capabilities)}
|
|
3002
|
+
});${nav}`;
|
|
3003
|
+
}
|
|
3024
3004
|
case "navigate":
|
|
3025
3005
|
return `await browser.url('${escapeStr(p.url)}');`;
|
|
3026
3006
|
case "click_element":
|
|
@@ -3048,7 +3028,7 @@ await browser.url('${escapeStr(p.navigationUrl)}');` : "";
|
|
|
3048
3028
|
}
|
|
3049
3029
|
}
|
|
3050
3030
|
function generateCode(history) {
|
|
3051
|
-
const steps = history.steps.map(generateStep).join("\n");
|
|
3031
|
+
const steps = history.steps.map((step) => generateStep(step, history)).join("\n");
|
|
3052
3032
|
return `import { remote } from 'webdriverio';
|
|
3053
3033
|
|
|
3054
3034
|
${steps}
|
|
@@ -3062,47 +3042,37 @@ function getCurrentSessionId() {
|
|
|
3062
3042
|
}
|
|
3063
3043
|
function buildSessionsIndex() {
|
|
3064
3044
|
const histories = getSessionHistory();
|
|
3065
|
-
if (histories.size === 0) return "No sessions recorded.";
|
|
3066
3045
|
const currentId = getCurrentSessionId();
|
|
3067
|
-
const
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3046
|
+
const sessions = Array.from(histories.values()).map((h) => ({
|
|
3047
|
+
sessionId: h.sessionId,
|
|
3048
|
+
type: h.type,
|
|
3049
|
+
startedAt: h.startedAt,
|
|
3050
|
+
...h.endedAt ? { endedAt: h.endedAt } : {},
|
|
3051
|
+
stepCount: h.steps.length,
|
|
3052
|
+
isCurrent: h.sessionId === currentId
|
|
3053
|
+
}));
|
|
3054
|
+
return JSON.stringify({ sessions });
|
|
3075
3055
|
}
|
|
3076
3056
|
function buildCurrentSessionSteps() {
|
|
3077
3057
|
const currentId = getCurrentSessionId();
|
|
3078
|
-
if (!currentId) return
|
|
3058
|
+
if (!currentId) return null;
|
|
3079
3059
|
return buildSessionStepsById(currentId);
|
|
3080
3060
|
}
|
|
3081
3061
|
function buildSessionStepsById(sessionId) {
|
|
3082
3062
|
const history = getSessionHistory().get(sessionId);
|
|
3083
|
-
if (!history) return
|
|
3084
|
-
return
|
|
3085
|
-
}
|
|
3086
|
-
function
|
|
3087
|
-
const
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
const params = Object.entries(step.params).map(([k, v]) => `${k}="${v}"`).join(" ");
|
|
3095
|
-
const errorSuffix = step.error ? ` \u2014 ${step.error}` : "";
|
|
3096
|
-
return `${step.index}. ${statusLabel} ${step.tool.padEnd(24)} ${params}${errorSuffix} ${step.durationMs}ms`;
|
|
3063
|
+
if (!history) return null;
|
|
3064
|
+
return buildSessionPayload(history);
|
|
3065
|
+
}
|
|
3066
|
+
function buildSessionPayload(history) {
|
|
3067
|
+
const stepsJson = JSON.stringify({
|
|
3068
|
+
sessionId: history.sessionId,
|
|
3069
|
+
type: history.type,
|
|
3070
|
+
startedAt: history.startedAt,
|
|
3071
|
+
...history.endedAt ? { endedAt: history.endedAt } : {},
|
|
3072
|
+
stepCount: history.steps.length,
|
|
3073
|
+
steps: history.steps
|
|
3097
3074
|
});
|
|
3098
|
-
|
|
3099
|
-
const jsCode = generateCode(history);
|
|
3100
|
-
return `${header}
|
|
3101
|
-
Steps:
|
|
3102
|
-
${stepsText}
|
|
3103
|
-
|
|
3104
|
-
--- Generated WebdriverIO JS ---
|
|
3105
|
-
${jsCode}`;
|
|
3075
|
+
return { stepsJson, generatedJs: generateCode(history) };
|
|
3106
3076
|
}
|
|
3107
3077
|
|
|
3108
3078
|
// src/server.ts
|
|
@@ -3127,10 +3097,10 @@ var registerTool = (definition, callback) => server.registerTool(definition.name
|
|
|
3127
3097
|
description: definition.description,
|
|
3128
3098
|
inputSchema: definition.inputSchema
|
|
3129
3099
|
}, callback);
|
|
3130
|
-
registerTool(startBrowserToolDefinition, startBrowserTool);
|
|
3131
|
-
registerTool(startAppToolDefinition, startAppTool);
|
|
3100
|
+
registerTool(startBrowserToolDefinition, withRecording("start_browser", startBrowserTool));
|
|
3101
|
+
registerTool(startAppToolDefinition, withRecording("start_app_session", startAppTool));
|
|
3132
3102
|
registerTool(closeSessionToolDefinition, closeSessionTool);
|
|
3133
|
-
registerTool(attachBrowserToolDefinition, attachBrowserTool);
|
|
3103
|
+
registerTool(attachBrowserToolDefinition, withRecording("attach_browser", attachBrowserTool));
|
|
3134
3104
|
registerTool(emulateDeviceToolDefinition, emulateDeviceTool);
|
|
3135
3105
|
registerTool(navigateToolDefinition, withRecording("navigate", navigateTool));
|
|
3136
3106
|
registerTool(getVisibleElementsToolDefinition, getVisibleElementsTool);
|
|
@@ -3157,30 +3127,54 @@ registerTool(executeScriptToolDefinition, executeScriptTool);
|
|
|
3157
3127
|
server.registerResource(
|
|
3158
3128
|
"sessions",
|
|
3159
3129
|
"wdio://sessions",
|
|
3160
|
-
{ description: "
|
|
3130
|
+
{ description: "JSON index of all browser and app sessions with metadata and step counts" },
|
|
3161
3131
|
async () => ({
|
|
3162
|
-
contents: [{ uri: "wdio://sessions", mimeType: "
|
|
3132
|
+
contents: [{ uri: "wdio://sessions", mimeType: "application/json", text: buildSessionsIndex() }]
|
|
3163
3133
|
})
|
|
3164
3134
|
);
|
|
3165
3135
|
server.registerResource(
|
|
3166
3136
|
"session-current-steps",
|
|
3167
3137
|
"wdio://session/current/steps",
|
|
3168
|
-
{ description: "
|
|
3169
|
-
async () =>
|
|
3170
|
-
|
|
3171
|
-
|
|
3138
|
+
{ description: "JSON step log for the currently active session" },
|
|
3139
|
+
async () => {
|
|
3140
|
+
const payload = buildCurrentSessionSteps();
|
|
3141
|
+
return {
|
|
3142
|
+
contents: [{ uri: "wdio://session/current/steps", mimeType: "application/json", text: payload?.stepsJson ?? '{"error":"No active session"}' }]
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
);
|
|
3146
|
+
server.registerResource(
|
|
3147
|
+
"session-current-code",
|
|
3148
|
+
"wdio://session/current/code",
|
|
3149
|
+
{ description: "Generated WebdriverIO JS code for the currently active session" },
|
|
3150
|
+
async () => {
|
|
3151
|
+
const payload = buildCurrentSessionSteps();
|
|
3152
|
+
return {
|
|
3153
|
+
contents: [{ uri: "wdio://session/current/code", mimeType: "text/plain", text: payload?.generatedJs ?? "// No active session" }]
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3172
3156
|
);
|
|
3173
3157
|
server.registerResource(
|
|
3174
3158
|
"session-steps",
|
|
3175
3159
|
new ResourceTemplate("wdio://session/{sessionId}/steps", { list: void 0 }),
|
|
3176
|
-
{ description: "
|
|
3177
|
-
async (uri, { sessionId }) =>
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
mimeType: "
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3160
|
+
{ description: "JSON step log for a specific session by ID" },
|
|
3161
|
+
async (uri, { sessionId }) => {
|
|
3162
|
+
const payload = buildSessionStepsById(sessionId);
|
|
3163
|
+
return {
|
|
3164
|
+
contents: [{ uri: uri.href, mimeType: "application/json", text: payload?.stepsJson ?? `{"error":"Session not found: ${sessionId}"}` }]
|
|
3165
|
+
};
|
|
3166
|
+
}
|
|
3167
|
+
);
|
|
3168
|
+
server.registerResource(
|
|
3169
|
+
"session-code",
|
|
3170
|
+
new ResourceTemplate("wdio://session/{sessionId}/code", { list: void 0 }),
|
|
3171
|
+
{ description: "Generated WebdriverIO JS code for a specific session by ID" },
|
|
3172
|
+
async (uri, { sessionId }) => {
|
|
3173
|
+
const payload = buildSessionStepsById(sessionId);
|
|
3174
|
+
return {
|
|
3175
|
+
contents: [{ uri: uri.href, mimeType: "text/plain", text: payload?.generatedJs ?? `// Session not found: ${sessionId}` }]
|
|
3176
|
+
};
|
|
3177
|
+
}
|
|
3184
3178
|
);
|
|
3185
3179
|
async function main() {
|
|
3186
3180
|
const transport = new StdioServerTransport();
|