@wdio/mcp 3.4.4 → 3.5.1

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 CHANGED
@@ -797,6 +797,44 @@ The generated script reconstructs the full session — including capabilities, n
797
797
  standalone `import { remote } from 'webdriverio'` file. For BrowserStack sessions it includes the full try/catch/finally
798
798
  with automatic session result marking.
799
799
 
800
+ ### Trace Recording
801
+
802
+ Passing `trace: true` to `start_session` produces a Playwright-compatible `.trace` zip in the `.trace/` directory when
803
+ the session closes. The zip is playable at [player.vibium.dev](https://player.vibium.dev) and shows a filmstrip of
804
+ screenshots alongside the action timeline.
805
+
806
+ **How screenshots are timed**
807
+
808
+ Appium's `takeScreenshot` round-trip takes 700–1300 ms on a local emulator, which is long enough for the previous
809
+ action's animations to settle. We exploit this: each screenshot is captured **before** the next action fires, so what
810
+ the Appium server returns is already the settled result of the prior action.
811
+
812
+ The tricky part is making the trace player show that screenshot under the right action. The player associates a
813
+ `screencast-frame` event with whichever action's time window contains the frame's `timestamp` field. If the timestamp
814
+ is set to "now" (capture time), it falls before the current action's `startTime` and the player labels it as the
815
+ *before* state of the next action — one action out of sync.
816
+
817
+ The fix: stamp each `screencast-frame` with `lastAfterEndTime` — the `endTime` of the action that just completed. That
818
+ places the frame inside the previous action's window, so the player shows it as the result of that action, not the
819
+ precursor to the next one.
820
+
821
+ ```
822
+ Timeline (monotonic ms):
823
+
824
+ prev.endTime ← frame timestamp stamped here
825
+
826
+ │ [screenshot captured here — shows settled state after prev action]
827
+
828
+ curr.startTime
829
+
830
+ │ [action executes]
831
+
832
+ curr.endTime ← next frame will be stamped here
833
+ ```
834
+
835
+ The final screenshot at session close is stamped with the last action's `endTime`, so it renders under that action
836
+ rather than appearing as an orphaned frame after the timeline ends.
837
+
800
838
  ## Troubleshooting
801
839
 
802
840
  **Browser automation not working?**
package/lib/server.js CHANGED
@@ -46,7 +46,7 @@ var package_default = {
46
46
  type: "git",
47
47
  url: "git://github.com/webdriverio/mcp.git"
48
48
  },
49
- version: "3.4.3",
49
+ version: "3.5.0",
50
50
  description: "MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)",
51
51
  main: "./lib/server.js",
52
52
  module: "./lib/server.js",
@@ -59,10 +59,15 @@ var package_default = {
59
59
  "./snapshot": {
60
60
  import: "./lib/snapshot.js",
61
61
  types: "./lib/snapshot.d.ts"
62
+ },
63
+ "./trace": {
64
+ import: "./lib/trace.js",
65
+ types: "./lib/trace.d.ts"
62
66
  }
63
67
  },
64
68
  bin: {
65
- "wdio-mcp": "lib/server.js"
69
+ "wdio-mcp": "lib/server.js",
70
+ "wdio-show-trace": "lib/show-trace.js"
66
71
  },
67
72
  license: "MIT",
68
73
  publishConfig: {
@@ -95,11 +100,14 @@ var package_default = {
95
100
  sharp: "^0.34.5",
96
101
  webdriverio: "^9.27.0",
97
102
  xpath: "^0.0.34",
103
+ yazl: "^3.3.1",
98
104
  zod: "^4.3.6"
99
105
  },
100
106
  devDependencies: {
101
107
  "@release-it/conventional-changelog": "^10.0.6",
102
108
  "@types/node": "^20.19.37",
109
+ "@types/yauzl": "^2.10.3",
110
+ "@types/yazl": "^3.3.1",
103
111
  "@wdio/eslint": "^0.1.3",
104
112
  "@wdio/types": "^9.27.0",
105
113
  eslint: "^9.39.4",
@@ -111,7 +119,8 @@ var package_default = {
111
119
  tsup: "^8.5.1",
112
120
  tsx: "^4.21.0",
113
121
  typescript: "~5.9.3",
114
- vitest: "^4.1.2"
122
+ vitest: "^4.1.2",
123
+ yauzl: "^3.3.0"
115
124
  },
116
125
  packageManager: "pnpm@10.32.1",
117
126
  pnpm: {
@@ -119,7 +128,7 @@ var package_default = {
119
128
  "release-it>undici": "^6.24.0",
120
129
  "basic-ftp": "^5.3.1",
121
130
  lodash: "^4.18.1",
122
- hono: "^4.12.17",
131
+ hono: "^4.12.18",
123
132
  "@hono/node-server": "^1.19.14",
124
133
  "fast-xml-parser": "^5.7.3",
125
134
  defu: "^6.1.7",
@@ -2116,6 +2125,228 @@ function withRecording(toolName, callback) {
2116
2125
  };
2117
2126
  }
2118
2127
 
2128
+ // src/trace/recorder.ts
2129
+ init_state();
2130
+ import sharp from "sharp";
2131
+
2132
+ // src/trace/state.ts
2133
+ var libraryVersion = package_default.version;
2134
+ var traceSessions = /* @__PURE__ */ new Map();
2135
+ function createTraceSession(sessionId, browserName, viewport, title, sessionType = "browser") {
2136
+ const prefix = sessionId.slice(0, 8);
2137
+ const session = {
2138
+ sessionId,
2139
+ startWallTime: Date.now(),
2140
+ startHrTime: process.hrtime.bigint(),
2141
+ pageId: `page@${prefix}`,
2142
+ contextId: `context@${prefix}`,
2143
+ callCounter: 0,
2144
+ events: [],
2145
+ screenshots: [],
2146
+ browserName,
2147
+ viewport,
2148
+ sessionType,
2149
+ lastAfterEndTime: 0,
2150
+ screenshotChain: Promise.resolve()
2151
+ };
2152
+ session.events.push({
2153
+ version: 8,
2154
+ type: "context-options",
2155
+ origin: "library",
2156
+ libraryName: "@wdio/mcp",
2157
+ libraryVersion,
2158
+ browserName,
2159
+ platform: process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "windows" : "linux",
2160
+ wallTime: session.startWallTime,
2161
+ monotonicTime: 0,
2162
+ sdkLanguage: "javascript",
2163
+ title,
2164
+ contextId: session.contextId,
2165
+ options: { viewport }
2166
+ });
2167
+ traceSessions.set(sessionId, session);
2168
+ return session;
2169
+ }
2170
+ function getTraceSession(sessionId) {
2171
+ return traceSessions.get(sessionId);
2172
+ }
2173
+ function deleteTraceSession(sessionId) {
2174
+ traceSessions.delete(sessionId);
2175
+ }
2176
+ function getMonotonicMs(session) {
2177
+ return Number((process.hrtime.bigint() - session.startHrTime) / 1000000n);
2178
+ }
2179
+
2180
+ // src/trace/tool-mapping.ts
2181
+ var TOOL_MAP = {
2182
+ navigate: { class: "Page", method: "navigate" },
2183
+ click_element: { class: "Element", method: "click" },
2184
+ set_value: { class: "Element", method: "fill" },
2185
+ scroll: { class: "Page", method: "scroll" },
2186
+ tap_element: { class: "Element", method: "tap" },
2187
+ swipe: { class: "Page", method: "swipe" },
2188
+ drag_and_drop: { class: "Element", method: "dragTo" },
2189
+ execute_script: { class: "Page", method: "evaluate" },
2190
+ launch_chrome: { class: "Browser", method: "launch" }
2191
+ };
2192
+ function mapToolToTraceAction(toolName) {
2193
+ return TOOL_MAP[toolName] ?? null;
2194
+ }
2195
+ function extractSelectorLabel(selector) {
2196
+ const uiautomator = selector.match(/\.(?:text|description|textContains)\("([^"]+)"\)/);
2197
+ if (uiautomator) return uiautomator[1];
2198
+ if (selector.startsWith("~")) return selector.slice(1);
2199
+ const predicate = selector.match(/(?:label|name|value)\s*==\s*"([^"]+)"/);
2200
+ if (predicate) return predicate[1];
2201
+ const xpath2 = selector.match(/@(?:text|label|name|content-desc)="([^"]+)"/);
2202
+ if (xpath2) return xpath2[1];
2203
+ return selector;
2204
+ }
2205
+ function formatActionTitle(action, params) {
2206
+ const { class: cls, method } = action;
2207
+ const firstKey = Object.keys(params)[0];
2208
+ const firstVal = Object.values(params)[0];
2209
+ if (firstVal === void 0) return `${cls}.${method}()`;
2210
+ const raw = String(firstVal);
2211
+ const label = firstKey === "selector" ? extractSelectorLabel(raw) : raw;
2212
+ return `${cls}.${method}("${label.slice(0, 80)}")`;
2213
+ }
2214
+
2215
+ // src/trace/recorder.ts
2216
+ function startTrace(sessionId, capabilities, sessionType = "browser", browserViewport) {
2217
+ let browserName;
2218
+ let viewport;
2219
+ let title;
2220
+ if (sessionType === "browser") {
2221
+ browserName = String(capabilities.browserName ?? "chromium");
2222
+ viewport = browserViewport ?? { width: 1920, height: 1080 };
2223
+ title = String(capabilities.browserName ?? browserName);
2224
+ } else {
2225
+ browserName = "chromium";
2226
+ const deviceName = String(capabilities["appium:deviceName"] ?? capabilities.deviceName ?? "device");
2227
+ const platformVersion = capabilities["appium:platformVersion"] ?? capabilities.platformVersion ?? "";
2228
+ title = `${sessionType} - ${deviceName}${platformVersion ? ` (${platformVersion})` : ""}`;
2229
+ viewport = sessionType === "ios" ? { width: 390, height: 844 } : { width: 412, height: 915 };
2230
+ }
2231
+ createTraceSession(sessionId, browserName, viewport, title, sessionType);
2232
+ }
2233
+ function endTrace(_sessionId) {
2234
+ }
2235
+ async function recordInitialNavigation(sessionId, url) {
2236
+ const traceSession = getTraceSession(sessionId);
2237
+ if (!traceSession) return;
2238
+ const callId = `call@${++traceSession.callCounter}`;
2239
+ const startTime = getMonotonicMs(traceSession);
2240
+ traceSession.events.push({
2241
+ type: "before",
2242
+ callId,
2243
+ startTime,
2244
+ class: "Page",
2245
+ method: "navigate",
2246
+ pageId: traceSession.pageId,
2247
+ params: { url },
2248
+ title: `Page.navigate("${url.slice(0, 80)}")`
2249
+ });
2250
+ await captureScreenshot(traceSession);
2251
+ const navEndTime = getMonotonicMs(traceSession);
2252
+ traceSession.events.push({
2253
+ type: "after",
2254
+ callId,
2255
+ endTime: navEndTime
2256
+ });
2257
+ traceSession.lastAfterEndTime = navEndTime;
2258
+ }
2259
+ function withTrace(toolName, callback) {
2260
+ return async (params, extra) => {
2261
+ const state2 = getState();
2262
+ const sessionId = state2.currentSession;
2263
+ if (!sessionId) return callback(params, extra);
2264
+ const metadata = state2.sessionMetadata.get(sessionId);
2265
+ if (!metadata?.trace) return callback(params, extra);
2266
+ const traceSession = getTraceSession(sessionId);
2267
+ if (!traceSession) return callback(params, extra);
2268
+ const action = mapToolToTraceAction(toolName);
2269
+ if (!action) return callback(params, extra);
2270
+ await captureScreenshot(traceSession);
2271
+ if (traceSession.sessionType !== "browser") {
2272
+ await new Promise((r) => setTimeout(r, 50));
2273
+ }
2274
+ const callId = `call@${++traceSession.callCounter}`;
2275
+ const startTime = getMonotonicMs(traceSession);
2276
+ traceSession.events.push({
2277
+ type: "before",
2278
+ callId,
2279
+ startTime,
2280
+ class: action.class,
2281
+ method: action.method,
2282
+ pageId: traceSession.pageId,
2283
+ params,
2284
+ title: formatActionTitle(action, params)
2285
+ });
2286
+ let result;
2287
+ let actionError;
2288
+ try {
2289
+ result = await callback(params, extra);
2290
+ if (result.isError) {
2291
+ const text = result.content?.find((c) => c.type === "text")?.text;
2292
+ actionError = text ? String(text) : "unknown error";
2293
+ }
2294
+ } catch (e) {
2295
+ actionError = String(e);
2296
+ const errorEndTime = getMonotonicMs(traceSession);
2297
+ traceSession.events.push({
2298
+ type: "after",
2299
+ callId,
2300
+ endTime: errorEndTime,
2301
+ error: { message: actionError }
2302
+ });
2303
+ traceSession.lastAfterEndTime = errorEndTime;
2304
+ throw e;
2305
+ }
2306
+ const endTime = getMonotonicMs(traceSession);
2307
+ traceSession.events.push({
2308
+ type: "after",
2309
+ callId,
2310
+ endTime,
2311
+ ...actionError ? { error: { message: actionError } } : {}
2312
+ });
2313
+ traceSession.lastAfterEndTime = endTime;
2314
+ return result;
2315
+ };
2316
+ }
2317
+ function captureTraceScreenshot(sessionId, browser) {
2318
+ const traceSession = getTraceSession(sessionId);
2319
+ if (!traceSession) return;
2320
+ const p = captureScreenshot(traceSession, browser);
2321
+ traceSession.screenshotChain = traceSession.screenshotChain.then(() => p);
2322
+ }
2323
+ async function captureScreenshot(traceSession, browser) {
2324
+ try {
2325
+ const b = browser ?? getBrowser();
2326
+ const base64 = await b.takeScreenshot();
2327
+ const inputBuffer = Buffer.from(base64, "base64");
2328
+ const image = sharp(inputBuffer);
2329
+ const metadata = await image.metadata();
2330
+ const width = metadata.width ?? 1280;
2331
+ const height = metadata.height ?? 720;
2332
+ const jpegBuffer = await image.jpeg({ quality: 60 }).toBuffer();
2333
+ const wallTimestamp = traceSession.startWallTime + getMonotonicMs(traceSession);
2334
+ const resourceName = `${traceSession.pageId}-${wallTimestamp}.jpeg`;
2335
+ traceSession.screenshots.push({ resourceName, data: jpegBuffer, width, height });
2336
+ traceSession.events.push({
2337
+ type: "screencast-frame",
2338
+ pageId: traceSession.pageId,
2339
+ sha1: resourceName,
2340
+ width,
2341
+ height,
2342
+ // Stamp at the previous action's endTime so the player shows this frame
2343
+ // as the result of that action, not as the "before" state of the next one.
2344
+ timestamp: traceSession.lastAfterEndTime
2345
+ });
2346
+ } catch {
2347
+ }
2348
+ }
2349
+
2119
2350
  // src/resources/browserstack-local.resource.ts
2120
2351
  function getLocalBinaryInfo() {
2121
2352
  const platform2 = process.platform;
@@ -2833,12 +3064,12 @@ var accessibilityResource = {
2833
3064
 
2834
3065
  // src/resources/screenshot.resource.ts
2835
3066
  init_state();
2836
- import sharp from "sharp";
3067
+ import sharp2 from "sharp";
2837
3068
  var MAX_DIMENSION = 2e3;
2838
3069
  var MAX_FILE_SIZE_BYTES = 1024 * 1024;
2839
3070
  async function processScreenshot(screenshotBase64) {
2840
3071
  const inputBuffer = Buffer.from(screenshotBase64, "base64");
2841
- let image = sharp(inputBuffer);
3072
+ let image = sharp2(inputBuffer);
2842
3073
  const metadata = await image.metadata();
2843
3074
  const width = metadata.width ?? 0;
2844
3075
  const height = metadata.height ?? 0;
@@ -3044,6 +3275,8 @@ import { z as z14 } from "zod";
3044
3275
 
3045
3276
  // src/session/lifecycle.ts
3046
3277
  init_state();
3278
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
3279
+ import { join as join3 } from "path";
3047
3280
 
3048
3281
  // src/providers/local-browser.provider.ts
3049
3282
  var LocalBrowserProvider = class {
@@ -3410,7 +3643,47 @@ function getProvider(providerName, platform2) {
3410
3643
  return platform2 === "browser" ? localBrowserProvider : localAppiumProvider;
3411
3644
  }
3412
3645
 
3646
+ // src/trace/zip-writer.ts
3647
+ import yazl from "yazl";
3648
+ function buildTraceZip(session) {
3649
+ return new Promise((resolve, reject) => {
3650
+ const zipFile = new yazl.ZipFile();
3651
+ const traceNdjson = session.events.map((e) => JSON.stringify(e)).join("\n");
3652
+ const traceBuffer = Buffer.from(traceNdjson, "utf8");
3653
+ zipFile.addBuffer(traceBuffer, "trace.trace");
3654
+ zipFile.addBuffer(Buffer.alloc(0), "trace.network");
3655
+ for (const screenshot of session.screenshots) {
3656
+ zipFile.addBuffer(screenshot.data, `resources/${screenshot.resourceName}`);
3657
+ }
3658
+ zipFile.end();
3659
+ const chunks = [];
3660
+ zipFile.outputStream.on("data", (chunk) => chunks.push(chunk));
3661
+ zipFile.outputStream.on("end", () => resolve(Buffer.concat(chunks)));
3662
+ zipFile.outputStream.on("error", reject);
3663
+ });
3664
+ }
3665
+
3413
3666
  // src/session/lifecycle.ts
3667
+ async function finalizeTrace(sessionId, browser) {
3668
+ endTrace(sessionId);
3669
+ captureTraceScreenshot(sessionId, browser);
3670
+ const traceSession = getTraceSession(sessionId);
3671
+ if (!traceSession) return;
3672
+ try {
3673
+ await traceSession.screenshotChain;
3674
+ const traceDir = join3(process.cwd(), ".trace");
3675
+ mkdirSync2(traceDir, { recursive: true });
3676
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3677
+ const outPath = join3(traceDir, `${timestamp}-${sessionId.slice(0, 8)}.zip`);
3678
+ const zipBuffer = await buildTraceZip(traceSession);
3679
+ writeFileSync2(outPath, zipBuffer);
3680
+ console.error(`[TRACE] Saved to ${outPath}`);
3681
+ } catch (e) {
3682
+ console.error("[TRACE] Failed to save trace:", e);
3683
+ } finally {
3684
+ deleteTraceSession(sessionId);
3685
+ }
3686
+ }
3414
3687
  function getSessionResult(history) {
3415
3688
  const errorStep = history?.steps.find((s) => s.status === "error");
3416
3689
  return errorStep ? { status: "failed", reason: errorStep.error } : { status: "passed" };
@@ -3447,6 +3720,9 @@ function registerSession(sessionId, browser, metadata, historyEntry) {
3447
3720
  const oldMetadata = state2.sessionMetadata.get(oldSessionId);
3448
3721
  if (oldBrowser) {
3449
3722
  void (async () => {
3723
+ if (oldMetadata?.trace) {
3724
+ await finalizeTrace(oldSessionId, oldBrowser);
3725
+ }
3450
3726
  if (oldMetadata?.provider) {
3451
3727
  const oldHistory = state2.sessionHistory.get(oldSessionId);
3452
3728
  const provider = getProvider(oldMetadata.provider, oldMetadata.type);
@@ -3470,6 +3746,9 @@ async function closeSession(sessionId, detach, isAttached, force) {
3470
3746
  history.endedAt = (/* @__PURE__ */ new Date()).toISOString();
3471
3747
  }
3472
3748
  const metadata = state2.sessionMetadata.get(sessionId);
3749
+ if (metadata?.trace) {
3750
+ await finalizeTrace(sessionId, browser);
3751
+ }
3473
3752
  if (force || !detach && !isAttached) {
3474
3753
  if (metadata?.provider) {
3475
3754
  try {
@@ -3524,6 +3803,7 @@ var startSessionToolDefinition = {
3524
3803
  noReset: coerceBoolean.optional().describe("Preserve app data between sessions"),
3525
3804
  fullReset: coerceBoolean.optional().describe("Uninstall app before/after session"),
3526
3805
  newCommandTimeout: z14.number().min(0).optional().default(300).describe("Appium command timeout in seconds"),
3806
+ trace: coerceBoolean.optional().default(false).describe("Enable trace recording \u2014 produces a Playwright-compatible zip saved to .trace/ on close_session, playable at player.vibium.dev."),
3527
3807
  attach: coerceBoolean.optional().default(false).describe("Attach to existing Chrome instead of launching"),
3528
3808
  attachConfig: z14.object({
3529
3809
  port: z14.number().optional().default(9222),
@@ -3630,7 +3910,8 @@ async function startBrowserSession(args) {
3630
3910
  capabilities: mergedCapabilities,
3631
3911
  isAttached: false,
3632
3912
  provider: args.provider ?? "local",
3633
- tunnelHandle
3913
+ tunnelHandle,
3914
+ trace: args.trace ?? false
3634
3915
  };
3635
3916
  registerSession(sessionId, wdioBrowser, sessionMetadata, {
3636
3917
  sessionId,
@@ -3639,6 +3920,9 @@ async function startBrowserSession(args) {
3639
3920
  capabilities: mergedCapabilities,
3640
3921
  steps: []
3641
3922
  });
3923
+ if (args.trace) {
3924
+ startTrace(sessionId, mergedCapabilities, "browser", { width: windowWidth, height: windowHeight });
3925
+ }
3642
3926
  let sizeNote = "";
3643
3927
  try {
3644
3928
  await wdioBrowser.setWindowSize(windowWidth, windowHeight);
@@ -3648,6 +3932,9 @@ Note: Unable to set window size (${windowWidth}x${windowHeight}). ${e}`;
3648
3932
  }
3649
3933
  if (navigationUrl) {
3650
3934
  await wdioBrowser.url(navigationUrl);
3935
+ if (args.trace) {
3936
+ await recordInitialNavigation(sessionId, navigationUrl);
3937
+ }
3651
3938
  }
3652
3939
  const modeText = effectiveHeadless ? "headless" : "headed";
3653
3940
  const urlText = navigationUrl ? ` and navigated to ${navigationUrl}` : "";
@@ -3682,7 +3969,8 @@ async function startMobileSession(args) {
3682
3969
  capabilities: mergedCapabilities,
3683
3970
  isAttached: shouldAutoDetach,
3684
3971
  provider: args.provider ?? "local",
3685
- tunnelHandle
3972
+ tunnelHandle,
3973
+ trace: args.trace ?? false
3686
3974
  };
3687
3975
  registerSession(sessionId, browser, metadata, {
3688
3976
  sessionId,
@@ -3692,6 +3980,9 @@ async function startMobileSession(args) {
3692
3980
  appiumConfig: { hostname: serverConfig.hostname, port: serverConfig.port, path: serverConfig.path },
3693
3981
  steps: []
3694
3982
  });
3983
+ if (args.trace) {
3984
+ startTrace(sessionId, mergedCapabilities, sessionType);
3985
+ }
3695
3986
  const appInfo = appPath ? `
3696
3987
  App: ${appPath}` : "\nApp: (connected to running app)";
3697
3988
  const detachNote = shouldAutoDetach ? "\n\n(Auto-detach enabled: session will be preserved on close. Use close_session({ detach: false }) to force terminate.)" : "";
@@ -3729,7 +4020,8 @@ async function attachBrowserSession(args) {
3729
4020
  type: "browser",
3730
4021
  capabilities,
3731
4022
  isAttached: true,
3732
- provider: "local"
4023
+ provider: "local",
4024
+ trace: args.trace ?? false
3733
4025
  };
3734
4026
  registerSession(sessionId, browser, sessionMetadata, {
3735
4027
  sessionId,
@@ -3738,10 +4030,19 @@ async function attachBrowserSession(args) {
3738
4030
  capabilities,
3739
4031
  steps: []
3740
4032
  });
4033
+ if (args.trace) {
4034
+ startTrace(sessionId, capabilities, "browser", { width: 1920, height: 1080 });
4035
+ }
3741
4036
  if (navigationUrl) {
3742
4037
  await browser.url(navigationUrl);
4038
+ if (args.trace) {
4039
+ await recordInitialNavigation(sessionId, navigationUrl);
4040
+ }
3743
4041
  } else if (activeTabUrl) {
3744
4042
  await restoreAndSwitchToActiveTab(browser, activeTabUrl, allTabUrls);
4043
+ if (args.trace) {
4044
+ await recordInitialNavigation(sessionId, activeTabUrl);
4045
+ }
3745
4046
  }
3746
4047
  const title = await browser.getTitle();
3747
4048
  const url = await browser.getUrl();
@@ -4094,26 +4395,27 @@ function createServer() {
4094
4395
  );
4095
4396
  }
4096
4397
  };
4398
+ const instrument = (name, cb) => withTrace(name, withRecording(name, cb));
4097
4399
  registerTool(startSessionToolDefinition, withRecording("start_session", startSessionTool));
4098
4400
  registerTool(closeSessionToolDefinition, closeSessionTool);
4099
- registerTool(launchChromeToolDefinition, withRecording("launch_chrome", launchChromeTool));
4401
+ registerTool(launchChromeToolDefinition, instrument("launch_chrome", launchChromeTool));
4100
4402
  registerTool(emulateDeviceToolDefinition, emulateDeviceTool);
4101
- registerTool(navigateToolDefinition, withRecording("navigate", navigateTool));
4403
+ registerTool(navigateToolDefinition, instrument("navigate", navigateTool));
4102
4404
  registerTool(switchTabToolDefinition, switchTabTool);
4103
4405
  registerTool(switchFrameToolDefinition, switchFrameTool);
4104
- registerTool(scrollToolDefinition, withRecording("scroll", scrollTool));
4105
- registerTool(clickToolDefinition, withRecording("click_element", clickTool));
4106
- registerTool(setValueToolDefinition, withRecording("set_value", setValueTool));
4406
+ registerTool(scrollToolDefinition, instrument("scroll", scrollTool));
4407
+ registerTool(clickToolDefinition, instrument("click_element", clickTool));
4408
+ registerTool(setValueToolDefinition, instrument("set_value", setValueTool));
4107
4409
  registerTool(setCookieToolDefinition, setCookieTool);
4108
4410
  registerTool(deleteCookiesToolDefinition, deleteCookiesTool);
4109
- registerTool(tapElementToolDefinition, withRecording("tap_element", tapElementTool));
4110
- registerTool(swipeToolDefinition, withRecording("swipe", swipeTool));
4111
- registerTool(dragAndDropToolDefinition, withRecording("drag_and_drop", dragAndDropTool));
4411
+ registerTool(tapElementToolDefinition, instrument("tap_element", tapElementTool));
4412
+ registerTool(swipeToolDefinition, instrument("swipe", swipeTool));
4413
+ registerTool(dragAndDropToolDefinition, instrument("drag_and_drop", dragAndDropTool));
4112
4414
  registerTool(switchContextToolDefinition, switchContextTool);
4113
4415
  registerTool(rotateDeviceToolDefinition, rotateDeviceTool);
4114
4416
  registerTool(hideKeyboardToolDefinition, hideKeyboardTool);
4115
4417
  registerTool(setGeolocationToolDefinition, setGeolocationTool);
4116
- registerTool(executeScriptToolDefinition, withRecording("execute_script", executeScriptTool));
4418
+ registerTool(executeScriptToolDefinition, instrument("execute_script", executeScriptTool));
4117
4419
  registerTool(getElementsToolDefinition, getElementsTool);
4118
4420
  registerTool(listAppsToolDefinition, listAppsTool);
4119
4421
  registerTool(uploadAppToolDefinition, uploadAppTool);