@wdio/mcp 3.4.3 → 3.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 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.2",
49
+ version: "3.4.4",
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,230 @@ 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
+ import { createRequire } from "module";
2134
+ var require2 = createRequire(import.meta.url);
2135
+ var { version: LIBRARY_VERSION } = require2("../../package.json");
2136
+ var traceSessions = /* @__PURE__ */ new Map();
2137
+ function createTraceSession(sessionId, browserName, viewport, title, sessionType = "browser") {
2138
+ const prefix = sessionId.slice(0, 8);
2139
+ const session = {
2140
+ sessionId,
2141
+ startWallTime: Date.now(),
2142
+ startHrTime: process.hrtime.bigint(),
2143
+ pageId: `page@${prefix}`,
2144
+ contextId: `context@${prefix}`,
2145
+ callCounter: 0,
2146
+ events: [],
2147
+ screenshots: [],
2148
+ browserName,
2149
+ viewport,
2150
+ sessionType,
2151
+ lastAfterEndTime: 0,
2152
+ screenshotChain: Promise.resolve()
2153
+ };
2154
+ session.events.push({
2155
+ version: 8,
2156
+ type: "context-options",
2157
+ origin: "library",
2158
+ libraryName: "@wdio/mcp",
2159
+ libraryVersion: LIBRARY_VERSION,
2160
+ browserName,
2161
+ platform: process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "windows" : "linux",
2162
+ wallTime: session.startWallTime,
2163
+ monotonicTime: 0,
2164
+ sdkLanguage: "javascript",
2165
+ title,
2166
+ contextId: session.contextId,
2167
+ options: { viewport }
2168
+ });
2169
+ traceSessions.set(sessionId, session);
2170
+ return session;
2171
+ }
2172
+ function getTraceSession(sessionId) {
2173
+ return traceSessions.get(sessionId);
2174
+ }
2175
+ function deleteTraceSession(sessionId) {
2176
+ traceSessions.delete(sessionId);
2177
+ }
2178
+ function getMonotonicMs(session) {
2179
+ return Number((process.hrtime.bigint() - session.startHrTime) / 1000000n);
2180
+ }
2181
+
2182
+ // src/trace/tool-mapping.ts
2183
+ var TOOL_MAP = {
2184
+ navigate: { class: "Page", method: "navigate" },
2185
+ click_element: { class: "Element", method: "click" },
2186
+ set_value: { class: "Element", method: "fill" },
2187
+ scroll: { class: "Page", method: "scroll" },
2188
+ tap_element: { class: "Element", method: "tap" },
2189
+ swipe: { class: "Page", method: "swipe" },
2190
+ drag_and_drop: { class: "Element", method: "dragTo" },
2191
+ execute_script: { class: "Page", method: "evaluate" },
2192
+ launch_chrome: { class: "Browser", method: "launch" }
2193
+ };
2194
+ function mapToolToTraceAction(toolName) {
2195
+ return TOOL_MAP[toolName] ?? null;
2196
+ }
2197
+ function extractSelectorLabel(selector) {
2198
+ const uiautomator = selector.match(/\.(?:text|description|textContains)\("([^"]+)"\)/);
2199
+ if (uiautomator) return uiautomator[1];
2200
+ if (selector.startsWith("~")) return selector.slice(1);
2201
+ const predicate = selector.match(/(?:label|name|value)\s*==\s*"([^"]+)"/);
2202
+ if (predicate) return predicate[1];
2203
+ const xpath2 = selector.match(/@(?:text|label|name|content-desc)="([^"]+)"/);
2204
+ if (xpath2) return xpath2[1];
2205
+ return selector;
2206
+ }
2207
+ function formatActionTitle(action, params) {
2208
+ const { class: cls, method } = action;
2209
+ const firstKey = Object.keys(params)[0];
2210
+ const firstVal = Object.values(params)[0];
2211
+ if (firstVal === void 0) return `${cls}.${method}()`;
2212
+ const raw = String(firstVal);
2213
+ const label = firstKey === "selector" ? extractSelectorLabel(raw) : raw;
2214
+ return `${cls}.${method}("${label.slice(0, 80)}")`;
2215
+ }
2216
+
2217
+ // src/trace/recorder.ts
2218
+ function startTrace(sessionId, capabilities, sessionType = "browser", browserViewport) {
2219
+ let browserName;
2220
+ let viewport;
2221
+ let title;
2222
+ if (sessionType === "browser") {
2223
+ browserName = String(capabilities.browserName ?? "chromium");
2224
+ viewport = browserViewport ?? { width: 1920, height: 1080 };
2225
+ title = String(capabilities.browserName ?? browserName);
2226
+ } else {
2227
+ browserName = "chromium";
2228
+ const deviceName = String(capabilities["appium:deviceName"] ?? capabilities.deviceName ?? "device");
2229
+ const platformVersion = capabilities["appium:platformVersion"] ?? capabilities.platformVersion ?? "";
2230
+ title = `${sessionType} - ${deviceName}${platformVersion ? ` (${platformVersion})` : ""}`;
2231
+ viewport = sessionType === "ios" ? { width: 390, height: 844 } : { width: 412, height: 915 };
2232
+ }
2233
+ createTraceSession(sessionId, browserName, viewport, title, sessionType);
2234
+ }
2235
+ function endTrace(_sessionId) {
2236
+ }
2237
+ async function recordInitialNavigation(sessionId, url) {
2238
+ const traceSession = getTraceSession(sessionId);
2239
+ if (!traceSession) return;
2240
+ const callId = `call@${++traceSession.callCounter}`;
2241
+ const startTime = getMonotonicMs(traceSession);
2242
+ traceSession.events.push({
2243
+ type: "before",
2244
+ callId,
2245
+ startTime,
2246
+ class: "Page",
2247
+ method: "navigate",
2248
+ pageId: traceSession.pageId,
2249
+ params: { url },
2250
+ title: `Page.navigate("${url.slice(0, 80)}")`
2251
+ });
2252
+ await captureScreenshot(traceSession);
2253
+ const navEndTime = getMonotonicMs(traceSession);
2254
+ traceSession.events.push({
2255
+ type: "after",
2256
+ callId,
2257
+ endTime: navEndTime
2258
+ });
2259
+ traceSession.lastAfterEndTime = navEndTime;
2260
+ }
2261
+ function withTrace(toolName, callback) {
2262
+ return async (params, extra) => {
2263
+ const state2 = getState();
2264
+ const sessionId = state2.currentSession;
2265
+ if (!sessionId) return callback(params, extra);
2266
+ const metadata = state2.sessionMetadata.get(sessionId);
2267
+ if (!metadata?.trace) return callback(params, extra);
2268
+ const traceSession = getTraceSession(sessionId);
2269
+ if (!traceSession) return callback(params, extra);
2270
+ const action = mapToolToTraceAction(toolName);
2271
+ if (!action) return callback(params, extra);
2272
+ await captureScreenshot(traceSession);
2273
+ if (traceSession.sessionType !== "browser") {
2274
+ await new Promise((r) => setTimeout(r, 50));
2275
+ }
2276
+ const callId = `call@${++traceSession.callCounter}`;
2277
+ const startTime = getMonotonicMs(traceSession);
2278
+ traceSession.events.push({
2279
+ type: "before",
2280
+ callId,
2281
+ startTime,
2282
+ class: action.class,
2283
+ method: action.method,
2284
+ pageId: traceSession.pageId,
2285
+ params,
2286
+ title: formatActionTitle(action, params)
2287
+ });
2288
+ let result;
2289
+ let actionError;
2290
+ try {
2291
+ result = await callback(params, extra);
2292
+ if (result.isError) {
2293
+ const text = result.content?.find((c) => c.type === "text")?.text;
2294
+ actionError = text ? String(text) : "unknown error";
2295
+ }
2296
+ } catch (e) {
2297
+ actionError = String(e);
2298
+ const errorEndTime = getMonotonicMs(traceSession);
2299
+ traceSession.events.push({
2300
+ type: "after",
2301
+ callId,
2302
+ endTime: errorEndTime,
2303
+ error: { message: actionError }
2304
+ });
2305
+ traceSession.lastAfterEndTime = errorEndTime;
2306
+ throw e;
2307
+ }
2308
+ const endTime = getMonotonicMs(traceSession);
2309
+ traceSession.events.push({
2310
+ type: "after",
2311
+ callId,
2312
+ endTime,
2313
+ ...actionError ? { error: { message: actionError } } : {}
2314
+ });
2315
+ traceSession.lastAfterEndTime = endTime;
2316
+ return result;
2317
+ };
2318
+ }
2319
+ function captureTraceScreenshot(sessionId, browser) {
2320
+ const traceSession = getTraceSession(sessionId);
2321
+ if (!traceSession) return;
2322
+ const p = captureScreenshot(traceSession, browser);
2323
+ traceSession.screenshotChain = traceSession.screenshotChain.then(() => p);
2324
+ }
2325
+ async function captureScreenshot(traceSession, browser) {
2326
+ try {
2327
+ const b = browser ?? getBrowser();
2328
+ const base64 = await b.takeScreenshot();
2329
+ const inputBuffer = Buffer.from(base64, "base64");
2330
+ const image = sharp(inputBuffer);
2331
+ const metadata = await image.metadata();
2332
+ const width = metadata.width ?? 1280;
2333
+ const height = metadata.height ?? 720;
2334
+ const jpegBuffer = await image.jpeg({ quality: 60 }).toBuffer();
2335
+ const wallTimestamp = traceSession.startWallTime + getMonotonicMs(traceSession);
2336
+ const resourceName = `${traceSession.pageId}-${wallTimestamp}.jpeg`;
2337
+ traceSession.screenshots.push({ resourceName, data: jpegBuffer, width, height });
2338
+ traceSession.events.push({
2339
+ type: "screencast-frame",
2340
+ pageId: traceSession.pageId,
2341
+ sha1: resourceName,
2342
+ width,
2343
+ height,
2344
+ // Stamp at the previous action's endTime so the player shows this frame
2345
+ // as the result of that action, not as the "before" state of the next one.
2346
+ timestamp: traceSession.lastAfterEndTime
2347
+ });
2348
+ } catch {
2349
+ }
2350
+ }
2351
+
2119
2352
  // src/resources/browserstack-local.resource.ts
2120
2353
  function getLocalBinaryInfo() {
2121
2354
  const platform2 = process.platform;
@@ -2833,12 +3066,12 @@ var accessibilityResource = {
2833
3066
 
2834
3067
  // src/resources/screenshot.resource.ts
2835
3068
  init_state();
2836
- import sharp from "sharp";
3069
+ import sharp2 from "sharp";
2837
3070
  var MAX_DIMENSION = 2e3;
2838
3071
  var MAX_FILE_SIZE_BYTES = 1024 * 1024;
2839
3072
  async function processScreenshot(screenshotBase64) {
2840
3073
  const inputBuffer = Buffer.from(screenshotBase64, "base64");
2841
- let image = sharp(inputBuffer);
3074
+ let image = sharp2(inputBuffer);
2842
3075
  const metadata = await image.metadata();
2843
3076
  const width = metadata.width ?? 0;
2844
3077
  const height = metadata.height ?? 0;
@@ -3044,6 +3277,8 @@ import { z as z14 } from "zod";
3044
3277
 
3045
3278
  // src/session/lifecycle.ts
3046
3279
  init_state();
3280
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
3281
+ import { join as join3 } from "path";
3047
3282
 
3048
3283
  // src/providers/local-browser.provider.ts
3049
3284
  var LocalBrowserProvider = class {
@@ -3410,7 +3645,47 @@ function getProvider(providerName, platform2) {
3410
3645
  return platform2 === "browser" ? localBrowserProvider : localAppiumProvider;
3411
3646
  }
3412
3647
 
3648
+ // src/trace/zip-writer.ts
3649
+ import yazl from "yazl";
3650
+ function buildTraceZip(session) {
3651
+ return new Promise((resolve, reject) => {
3652
+ const zipFile = new yazl.ZipFile();
3653
+ const traceNdjson = session.events.map((e) => JSON.stringify(e)).join("\n");
3654
+ const traceBuffer = Buffer.from(traceNdjson, "utf8");
3655
+ zipFile.addBuffer(traceBuffer, "trace.trace");
3656
+ zipFile.addBuffer(Buffer.alloc(0), "trace.network");
3657
+ for (const screenshot of session.screenshots) {
3658
+ zipFile.addBuffer(screenshot.data, `resources/${screenshot.resourceName}`);
3659
+ }
3660
+ zipFile.end();
3661
+ const chunks = [];
3662
+ zipFile.outputStream.on("data", (chunk) => chunks.push(chunk));
3663
+ zipFile.outputStream.on("end", () => resolve(Buffer.concat(chunks)));
3664
+ zipFile.outputStream.on("error", reject);
3665
+ });
3666
+ }
3667
+
3413
3668
  // src/session/lifecycle.ts
3669
+ async function finalizeTrace(sessionId, browser) {
3670
+ endTrace(sessionId);
3671
+ captureTraceScreenshot(sessionId, browser);
3672
+ const traceSession = getTraceSession(sessionId);
3673
+ if (!traceSession) return;
3674
+ try {
3675
+ await traceSession.screenshotChain;
3676
+ const traceDir = join3(process.cwd(), ".trace");
3677
+ mkdirSync2(traceDir, { recursive: true });
3678
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
3679
+ const outPath = join3(traceDir, `${timestamp}-${sessionId.slice(0, 8)}.zip`);
3680
+ const zipBuffer = await buildTraceZip(traceSession);
3681
+ writeFileSync2(outPath, zipBuffer);
3682
+ console.error(`[TRACE] Saved to ${outPath}`);
3683
+ } catch (e) {
3684
+ console.error("[TRACE] Failed to save trace:", e);
3685
+ } finally {
3686
+ deleteTraceSession(sessionId);
3687
+ }
3688
+ }
3414
3689
  function getSessionResult(history) {
3415
3690
  const errorStep = history?.steps.find((s) => s.status === "error");
3416
3691
  return errorStep ? { status: "failed", reason: errorStep.error } : { status: "passed" };
@@ -3447,6 +3722,9 @@ function registerSession(sessionId, browser, metadata, historyEntry) {
3447
3722
  const oldMetadata = state2.sessionMetadata.get(oldSessionId);
3448
3723
  if (oldBrowser) {
3449
3724
  void (async () => {
3725
+ if (oldMetadata?.trace) {
3726
+ await finalizeTrace(oldSessionId, oldBrowser);
3727
+ }
3450
3728
  if (oldMetadata?.provider) {
3451
3729
  const oldHistory = state2.sessionHistory.get(oldSessionId);
3452
3730
  const provider = getProvider(oldMetadata.provider, oldMetadata.type);
@@ -3470,6 +3748,9 @@ async function closeSession(sessionId, detach, isAttached, force) {
3470
3748
  history.endedAt = (/* @__PURE__ */ new Date()).toISOString();
3471
3749
  }
3472
3750
  const metadata = state2.sessionMetadata.get(sessionId);
3751
+ if (metadata?.trace) {
3752
+ await finalizeTrace(sessionId, browser);
3753
+ }
3473
3754
  if (force || !detach && !isAttached) {
3474
3755
  if (metadata?.provider) {
3475
3756
  try {
@@ -3524,6 +3805,7 @@ var startSessionToolDefinition = {
3524
3805
  noReset: coerceBoolean.optional().describe("Preserve app data between sessions"),
3525
3806
  fullReset: coerceBoolean.optional().describe("Uninstall app before/after session"),
3526
3807
  newCommandTimeout: z14.number().min(0).optional().default(300).describe("Appium command timeout in seconds"),
3808
+ 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
3809
  attach: coerceBoolean.optional().default(false).describe("Attach to existing Chrome instead of launching"),
3528
3810
  attachConfig: z14.object({
3529
3811
  port: z14.number().optional().default(9222),
@@ -3630,7 +3912,8 @@ async function startBrowserSession(args) {
3630
3912
  capabilities: mergedCapabilities,
3631
3913
  isAttached: false,
3632
3914
  provider: args.provider ?? "local",
3633
- tunnelHandle
3915
+ tunnelHandle,
3916
+ trace: args.trace ?? false
3634
3917
  };
3635
3918
  registerSession(sessionId, wdioBrowser, sessionMetadata, {
3636
3919
  sessionId,
@@ -3639,6 +3922,9 @@ async function startBrowserSession(args) {
3639
3922
  capabilities: mergedCapabilities,
3640
3923
  steps: []
3641
3924
  });
3925
+ if (args.trace) {
3926
+ startTrace(sessionId, mergedCapabilities, "browser", { width: windowWidth, height: windowHeight });
3927
+ }
3642
3928
  let sizeNote = "";
3643
3929
  try {
3644
3930
  await wdioBrowser.setWindowSize(windowWidth, windowHeight);
@@ -3648,6 +3934,9 @@ Note: Unable to set window size (${windowWidth}x${windowHeight}). ${e}`;
3648
3934
  }
3649
3935
  if (navigationUrl) {
3650
3936
  await wdioBrowser.url(navigationUrl);
3937
+ if (args.trace) {
3938
+ await recordInitialNavigation(sessionId, navigationUrl);
3939
+ }
3651
3940
  }
3652
3941
  const modeText = effectiveHeadless ? "headless" : "headed";
3653
3942
  const urlText = navigationUrl ? ` and navigated to ${navigationUrl}` : "";
@@ -3682,7 +3971,8 @@ async function startMobileSession(args) {
3682
3971
  capabilities: mergedCapabilities,
3683
3972
  isAttached: shouldAutoDetach,
3684
3973
  provider: args.provider ?? "local",
3685
- tunnelHandle
3974
+ tunnelHandle,
3975
+ trace: args.trace ?? false
3686
3976
  };
3687
3977
  registerSession(sessionId, browser, metadata, {
3688
3978
  sessionId,
@@ -3692,6 +3982,9 @@ async function startMobileSession(args) {
3692
3982
  appiumConfig: { hostname: serverConfig.hostname, port: serverConfig.port, path: serverConfig.path },
3693
3983
  steps: []
3694
3984
  });
3985
+ if (args.trace) {
3986
+ startTrace(sessionId, mergedCapabilities, sessionType);
3987
+ }
3695
3988
  const appInfo = appPath ? `
3696
3989
  App: ${appPath}` : "\nApp: (connected to running app)";
3697
3990
  const detachNote = shouldAutoDetach ? "\n\n(Auto-detach enabled: session will be preserved on close. Use close_session({ detach: false }) to force terminate.)" : "";
@@ -3729,7 +4022,8 @@ async function attachBrowserSession(args) {
3729
4022
  type: "browser",
3730
4023
  capabilities,
3731
4024
  isAttached: true,
3732
- provider: "local"
4025
+ provider: "local",
4026
+ trace: args.trace ?? false
3733
4027
  };
3734
4028
  registerSession(sessionId, browser, sessionMetadata, {
3735
4029
  sessionId,
@@ -3738,10 +4032,19 @@ async function attachBrowserSession(args) {
3738
4032
  capabilities,
3739
4033
  steps: []
3740
4034
  });
4035
+ if (args.trace) {
4036
+ startTrace(sessionId, capabilities, "browser", { width: 1920, height: 1080 });
4037
+ }
3741
4038
  if (navigationUrl) {
3742
4039
  await browser.url(navigationUrl);
4040
+ if (args.trace) {
4041
+ await recordInitialNavigation(sessionId, navigationUrl);
4042
+ }
3743
4043
  } else if (activeTabUrl) {
3744
4044
  await restoreAndSwitchToActiveTab(browser, activeTabUrl, allTabUrls);
4045
+ if (args.trace) {
4046
+ await recordInitialNavigation(sessionId, activeTabUrl);
4047
+ }
3745
4048
  }
3746
4049
  const title = await browser.getTitle();
3747
4050
  const url = await browser.getUrl();
@@ -4094,26 +4397,27 @@ function createServer() {
4094
4397
  );
4095
4398
  }
4096
4399
  };
4400
+ const instrument = (name, cb) => withTrace(name, withRecording(name, cb));
4097
4401
  registerTool(startSessionToolDefinition, withRecording("start_session", startSessionTool));
4098
4402
  registerTool(closeSessionToolDefinition, closeSessionTool);
4099
- registerTool(launchChromeToolDefinition, withRecording("launch_chrome", launchChromeTool));
4403
+ registerTool(launchChromeToolDefinition, instrument("launch_chrome", launchChromeTool));
4100
4404
  registerTool(emulateDeviceToolDefinition, emulateDeviceTool);
4101
- registerTool(navigateToolDefinition, withRecording("navigate", navigateTool));
4405
+ registerTool(navigateToolDefinition, instrument("navigate", navigateTool));
4102
4406
  registerTool(switchTabToolDefinition, switchTabTool);
4103
4407
  registerTool(switchFrameToolDefinition, switchFrameTool);
4104
- registerTool(scrollToolDefinition, withRecording("scroll", scrollTool));
4105
- registerTool(clickToolDefinition, withRecording("click_element", clickTool));
4106
- registerTool(setValueToolDefinition, withRecording("set_value", setValueTool));
4408
+ registerTool(scrollToolDefinition, instrument("scroll", scrollTool));
4409
+ registerTool(clickToolDefinition, instrument("click_element", clickTool));
4410
+ registerTool(setValueToolDefinition, instrument("set_value", setValueTool));
4107
4411
  registerTool(setCookieToolDefinition, setCookieTool);
4108
4412
  registerTool(deleteCookiesToolDefinition, deleteCookiesTool);
4109
- registerTool(tapElementToolDefinition, withRecording("tap_element", tapElementTool));
4110
- registerTool(swipeToolDefinition, withRecording("swipe", swipeTool));
4111
- registerTool(dragAndDropToolDefinition, withRecording("drag_and_drop", dragAndDropTool));
4413
+ registerTool(tapElementToolDefinition, instrument("tap_element", tapElementTool));
4414
+ registerTool(swipeToolDefinition, instrument("swipe", swipeTool));
4415
+ registerTool(dragAndDropToolDefinition, instrument("drag_and_drop", dragAndDropTool));
4112
4416
  registerTool(switchContextToolDefinition, switchContextTool);
4113
4417
  registerTool(rotateDeviceToolDefinition, rotateDeviceTool);
4114
4418
  registerTool(hideKeyboardToolDefinition, hideKeyboardTool);
4115
4419
  registerTool(setGeolocationToolDefinition, setGeolocationTool);
4116
- registerTool(executeScriptToolDefinition, withRecording("execute_script", executeScriptTool));
4420
+ registerTool(executeScriptToolDefinition, instrument("execute_script", executeScriptTool));
4117
4421
  registerTool(getElementsToolDefinition, getElementsTool);
4118
4422
  registerTool(listAppsToolDefinition, listAppsTool);
4119
4423
  registerTool(uploadAppToolDefinition, uploadAppTool);