@testdriverai/mcp 7.11.62-test → 7.11.63-test

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.
@@ -232,7 +232,10 @@ const createRedraw = (
232
232
 
233
233
  // Capture initial image for screen stability monitoring
234
234
  if (currentOptions.screenRedraw) {
235
- initialScreenImage = await system.captureScreenPNG(0.25, true);
235
+ // remoteResize=true: these frames exist only to be pixel-diffed at
236
+ // quarter scale, so let the runner shrink them before upload rather
237
+ // than shipping full-resolution PNGs on every poll.
238
+ initialScreenImage = await system.captureScreenPNG(0.25, true, false, true);
236
239
  lastScreenImage = initialScreenImage;
237
240
  emitter.emit(events.log.debug, `[redraw] start() - captured initial image: ${initialScreenImage}`);
238
241
  }
@@ -254,7 +257,7 @@ const createRedraw = (
254
257
  await updateNetwork();
255
258
  }
256
259
 
257
- let nowImage = screenRedraw ? await system.captureScreenPNG(0.25, true) : null;
260
+ let nowImage = screenRedraw ? await system.captureScreenPNG(0.25, true, false, true) : null;
258
261
  let timeElapsed = Date.now() - startTime;
259
262
  let diffFromInitial = 0;
260
263
  let diffFromLast = 0;
@@ -57,13 +57,21 @@ const createSystem = (emitter, sandbox, config) => {
57
57
  // which is one of:
58
58
  // { s3Key, width, height } — runner uploaded to S3 (Ably 64KB limit)
59
59
  // { base64 } — direct/local connection, bytes inline
60
- const captureRaw = async () => {
60
+ //
61
+ // `target` ({ width, height }) asks the runner to do the downscale before it
62
+ // uploads. Only pass it when the bytes are going to be shrunk to that size
63
+ // on arrival regardless — it saves a multi-megabyte round-trip per capture,
64
+ // but it also moves the resample from Jimp (here) to sharp (there), so the
65
+ // pixels differ slightly. Anything feeding vision or the selector cache must
66
+ // keep resizing locally.
67
+ const captureRaw = async (target = null) => {
61
68
  return await sandbox.send({
62
69
  type: "system.screenshot",
70
+ ...(target ? { targetWidth: target.width, targetHeight: target.height } : {}),
63
71
  });
64
72
  };
65
73
 
66
- const screenshot = async (options, rawResponse) => {
74
+ const screenshot = async (options, rawResponse, target = null) => {
67
75
  const MAX_RETRIES = 3;
68
76
  let lastError;
69
77
 
@@ -74,7 +82,7 @@ const createSystem = (emitter, sandbox, config) => {
74
82
  // capture fresh.
75
83
  let response = attempt === 0 && rawResponse
76
84
  ? rawResponse
77
- : await captureRaw();
85
+ : await captureRaw(target);
78
86
 
79
87
  let base64;
80
88
 
@@ -125,7 +133,11 @@ const createSystem = (emitter, sandbox, config) => {
125
133
  return path.join(os.tmpdir(), `td-${Date.now()}-${randomUUID().slice(0, 8)}-${countImages}.png`);
126
134
  };
127
135
 
128
- const captureAndResize = async (scale = 1, silent = false, mouse = false, rawResponse = null) => {
136
+ // `remoteResize` pushes the downscale onto the runner so the frame never
137
+ // crosses the wire at full resolution. Off by default: it changes which
138
+ // resampler produced the pixels, which callers feeding vision or the
139
+ // selector cache must not do. See `captureRaw`.
140
+ const captureAndResize = async (scale = 1, silent = false, mouse = false, rawResponse = null, remoteResize = false) => {
129
141
  try {
130
142
  if (!silent) {
131
143
  emitter.emit(events.screenCapture.start, {
@@ -138,21 +150,29 @@ const createSystem = (emitter, sandbox, config) => {
138
150
  let step1 = tmpFilename();
139
151
  let step2 = tmpFilename();
140
152
 
141
- await screenshot({ filename: step1, format: "png" }, rawResponse);
153
+ const targetWidth = Math.floor(config.TD_RESOLUTION[0] * scale);
154
+ const targetHeight = Math.floor(config.TD_RESOLUTION[1] * scale);
155
+
156
+ await screenshot(
157
+ { filename: step1, format: "png" },
158
+ rawResponse,
159
+ remoteResize ? { width: targetWidth, height: targetHeight } : null,
160
+ );
142
161
 
143
162
  // Load the screenshot image with Jimp
144
163
  let image = await Jimp.read(step1);
145
-
164
+
146
165
  // Validate the image was loaded correctly (not a 1x1 or tiny placeholder)
147
166
  if (image.getWidth() < 10 || image.getHeight() < 10) {
148
167
  throw new Error(`Screenshot appears corrupted: got ${image.getWidth()}x${image.getHeight()} pixels`);
149
168
  }
150
169
 
151
- // Resize the image
152
- image.resize(
153
- Math.floor(config.TD_RESOLUTION[0] * scale),
154
- Math.floor(config.TD_RESOLUTION[1] * scale),
155
- );
170
+ // Resize the image. Still unconditional when the runner already resized:
171
+ // an older runner ignores targetWidth/targetHeight and returns a
172
+ // full-size frame, and Jimp treats a resize to the current size as a
173
+ // no-op, so this both stays correct across versions and guarantees every
174
+ // frame comes out at the same dimensions for pixel diffing.
175
+ image.resize(targetWidth, targetHeight);
156
176
 
157
177
  if (mouse) {
158
178
  // Only get mouse position when needed to avoid unnecessary websocket calls
@@ -196,8 +216,8 @@ const createSystem = (emitter, sandbox, config) => {
196
216
  return fs.readFileSync(step2, "base64");
197
217
  };
198
218
 
199
- const captureScreenPNG = async (scale = 1, silent = false, mouse = false) => {
200
- return await captureAndResize(scale, silent, mouse);
219
+ const captureScreenPNG = async (scale = 1, silent = false, mouse = false, remoteResize = false) => {
220
+ return await captureAndResize(scale, silent, mouse, null, remoteResize);
201
221
  };
202
222
 
203
223
  // Build the image payload to send to the API for a command (find/assert/etc).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testdriverai/mcp",
3
- "version": "7.11.62-test",
3
+ "version": "7.11.63-test",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "sdk.js",
6
6
  "types": "sdk.d.ts",
@@ -1,14 +1,28 @@
1
1
  import { defineConfig } from "vitest/config";
2
+ import { dirname, resolve } from "path";
3
+ import { fileURLToPath } from "url";
2
4
 
3
- // Unit tests for SDK internals — pure, no sandbox, no network.
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const monoRoot = resolve(__dirname, "..");
7
+
8
+ // Unit tests for SDK and runner internals — pure, no sandbox, no network.
4
9
  //
5
10
  // Kept separate from vitest.config.mjs, which drives the real end-to-end
6
11
  // examples: that config provisions live sandboxes, resolves per-channel API
7
12
  // keys, and runs with an 8-minute timeout, so it can't host fast unit tests.
8
13
  // Run with `npm run test:unit`.
14
+ //
15
+ // Rooted at the mono repo so runner/ tests can live here too (same pattern as
16
+ // vitest.runner.config.mjs) — dependencies still resolve from sdk/node_modules
17
+ // for SDK tests and runner/node_modules for runner tests.
9
18
  export default defineConfig({
10
19
  test: {
11
- include: ["agent/**/*.test.mjs", "lib/**/*.test.mjs"],
20
+ root: monoRoot,
21
+ include: [
22
+ "sdk/agent/**/*.test.mjs",
23
+ "sdk/lib/**/*.test.mjs",
24
+ "runner/lib/**/*.test.mjs",
25
+ ],
12
26
  // Don't let the e2e suites' fixtures/setup leak in.
13
27
  setupFiles: [],
14
28
  },