testdriverai 7.8.0-test.50 → 7.8.0-test.52

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.
@@ -29,7 +29,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
29
29
  this._lastConnectParams = null;
30
30
  this._teamId = null;
31
31
  this._sandboxId = null;
32
- this._disconnectedAt = null; // tracks when Ably connection dropped (for timeout extension on reconnect)
32
+ this._disconnectedAt = null; // tracks when Realtime connection dropped (for timeout extension on reconnect)
33
33
 
34
34
  // Rate limiting state for Ably publishes (Ably limits to 50 msg/sec per connection)
35
35
  this._publishLastTime = 0;
@@ -85,21 +85,21 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
85
85
  suspendedRetryTimeout: 15000, // retry from suspended every 15s (default 30s)
86
86
  });
87
87
 
88
- logger.log(`[ably] Connecting as sdk-${this._sandboxId}...`);
88
+ logger.log(`[realtime] Connecting as sdk-${this._sandboxId}...`);
89
89
 
90
90
  await new Promise(function (resolve, reject) {
91
91
  self._ably.connection.on("connected", resolve);
92
92
  self._ably.connection.on("failed", function () {
93
- reject(new Error("Ably connection failed"));
93
+ reject(new Error("Realtime connection failed"));
94
94
  });
95
95
  setTimeout(function () {
96
- reject(new Error("Ably connection timeout"));
96
+ reject(new Error("Realtime connection timeout"));
97
97
  }, 30000);
98
98
  });
99
99
 
100
100
  this._sessionChannel = this._ably.channels.get(channelName);
101
101
 
102
- logger.log(`[ably] Channel initialized: ${channelName}`);
102
+ logger.log(`[realtime] Channel initialized: ${channelName}`);
103
103
 
104
104
  // Enter presence on the session channel so the API can count connected SDK clients
105
105
  try {
@@ -107,7 +107,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
107
107
  sandboxId: this._sandboxId,
108
108
  connectedAt: Date.now(),
109
109
  });
110
- logger.log(`[ably] Entered presence on session channel (sandbox=${this._sandboxId})`);
110
+ logger.log(`[realtime] Entered presence on session channel (sandbox=${this._sandboxId})`);
111
111
  } catch (e) {
112
112
  // Non-fatal — presence is used for concurrency counting, not critical path
113
113
  logger.warn("Failed to enter presence on session channel: " + (e.message || e));
@@ -118,7 +118,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
118
118
  var message = msg.data;
119
119
  if (!message) return;
120
120
 
121
- logger.log(`[ably] Received response: type=${message.type || 'unknown'} (requestId=${message.requestId || 'none'})`);
121
+ logger.log(`[realtime] Received response: type=${message.type || 'unknown'} (requestId=${message.requestId || 'none'})`);
122
122
 
123
123
  if (message.type === "sandbox.progress") {
124
124
  emitter.emit(events.sandbox.progress, {
@@ -178,7 +178,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
178
178
  }).join(', ')
179
179
  : 'none';
180
180
  logger.warn(
181
- '[ably] No pending promise for requestId=' + (message.requestId || 'null') +
181
+ '[realtime] No pending promise for requestId=' + (message.requestId || 'null') +
182
182
  ' | response type=' + (message.type || 'unknown') +
183
183
  ' | error=' + (message.error ? (message.errorMessage || 'true') : 'false') +
184
184
  ' | currently pending: [' + pendingSummary + ']'
@@ -193,7 +193,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
193
193
  ? ((Date.now() - pendingEntry.startTime) / 1000).toFixed(1) + 's'
194
194
  : '?';
195
195
  logger.warn(
196
- '[ably] Promise REJECTED: requestId=' + message.requestId +
196
+ '[realtime] Promise REJECTED: requestId=' + message.requestId +
197
197
  ' | type=' + (pendingMessage ? pendingMessage.type : 'unknown') +
198
198
  ' | age=' + pendingAge +
199
199
  ' | error=' + (message.errorMessage || 'Sandbox error')
@@ -213,7 +213,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
213
213
  ? ((Date.now() - resolveEntry.startTime) / 1000).toFixed(1) + 's'
214
214
  : '?';
215
215
  logger.log(
216
- '[ably] Promise RESOLVED: requestId=' + message.requestId +
216
+ '[realtime] Promise RESOLVED: requestId=' + message.requestId +
217
217
  ' | type=' + (resolveEntry.message ? resolveEntry.message.type : 'unknown') +
218
218
  ' | age=' + resolveAge
219
219
  );
@@ -241,7 +241,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
241
241
  this._onFileMsg = function (msg) {
242
242
  var message = msg.data;
243
243
  if (!message) return;
244
- logger.log(`[ably] Received file: type=${message.type || 'unknown'} (requestId=${message.requestId || 'none'})`);
244
+ logger.log(`[realtime] Received file: type=${message.type || 'unknown'} (requestId=${message.requestId || 'none'})`);
245
245
  if (message.requestId && self.ps[message.requestId]) {
246
246
  emitter.emit(events.sandbox.received);
247
247
  self.ps[message.requestId].resolve(message);
@@ -260,7 +260,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
260
260
  const chState = this._sessionChannel ? this._sessionChannel.state : 'null';
261
261
  const pendingIds = Object.keys(this.ps);
262
262
  const pending = pendingIds.length;
263
- logger.log(`[ably][stats] connection=${connState} | sandbox=${this._sandboxId} | pending=${pending} | channel=${chState}`);
263
+ logger.log(`[realtime][stats] connection=${connState} | sandbox=${this._sandboxId} | pending=${pending} | channel=${chState}`);
264
264
  if (pending > 0) {
265
265
  const now = Date.now();
266
266
  for (const rid of pendingIds) {
@@ -268,20 +268,20 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
268
268
  if (!entry) continue;
269
269
  const type = entry.message ? entry.message.type : 'unknown';
270
270
  const ageSec = ((now - (entry.startTime || now)) / 1000).toFixed(1);
271
- logger.log(`[ably][stats] pending: requestId=${rid} | type=${type} | age=${ageSec}s`);
271
+ logger.log(`[realtime][stats] pending: requestId=${rid} | type=${type} | age=${ageSec}s`);
272
272
  }
273
273
  }
274
274
  }, 10000);
275
275
  if (this._statsInterval.unref) this._statsInterval.unref();
276
276
 
277
277
  this._ably.connection.on("disconnected", function () {
278
- logger.log("[ably] Connection: disconnected - will auto-reconnect");
278
+ logger.log("[realtime] Connection: disconnected - will auto-reconnect");
279
279
  self._disconnectedAt = Date.now();
280
280
  });
281
281
 
282
282
  this._ably.connection.on("connected", function () {
283
283
  // Log reconnection so the user knows the blip was recovered
284
- logger.log("[ably] Connection: reconnected");
284
+ logger.log("[realtime] Connection: reconnected");
285
285
  // Extend any pending command timeouts by the disconnection duration so
286
286
  // commands whose timer was counting down while the connection was down
287
287
  // don't get incorrectly timed out.
@@ -291,7 +291,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
291
291
  var pendingIds = Object.keys(self.ps);
292
292
  if (pendingIds.length > 0) {
293
293
  logger.log(
294
- '[ably] Extending ' + pendingIds.length + ' pending timeout(s) by ' +
294
+ '[realtime] Extending ' + pendingIds.length + ' pending timeout(s) by ' +
295
295
  disconnectionDurationMs + 'ms after disconnection'
296
296
  );
297
297
  for (var i = 0; i < pendingIds.length; i++) {
@@ -305,14 +305,14 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
305
305
  });
306
306
 
307
307
  this._ably.connection.on("suspended", function () {
308
- logger.warn("[ably] Connection: suspended - connection lost for extended period, will keep retrying");
308
+ logger.warn("[realtime] Connection: suspended - connection lost for extended period, will keep retrying");
309
309
  });
310
310
 
311
311
  this._ably.connection.on("failed", function () {
312
- logger.error("[ably] Connection: failed");
312
+ logger.error("[realtime] Connection: failed");
313
313
  self.apiSocketConnected = false;
314
314
  self.instanceSocketConnected = false;
315
- emitter.emit(events.error.sandbox, "Ably connection failed");
315
+ emitter.emit(events.error.sandbox, "Realtime connection failed");
316
316
  });
317
317
 
318
318
  // ─── Channel discontinuity detection ──────────────────────────────
@@ -326,7 +326,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
326
326
  var reasonMsg = reason ? (reason.message || reason.code || String(reason)) : '';
327
327
 
328
328
  if (current === 'attached' && stateChange.resumed === false && previous === 'attached') {
329
- logger.warn('[ably] Channel DISCONTINUITY detected (resumed=false)' + (reasonMsg ? ' — ' + reasonMsg : ''));
329
+ logger.warn('[realtime] Channel DISCONTINUITY detected (resumed=false)' + (reasonMsg ? ' — ' + reasonMsg : ''));
330
330
  emitter.emit(events.sandbox.progress, {
331
331
  step: 'discontinuity',
332
332
  message: 'Recovering missed messages after connection interruption...',
@@ -353,7 +353,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
353
353
  var entry = subs[i];
354
354
  if (!entry.sub) continue;
355
355
  try {
356
- logger.log('[ably] Discontinuity recovery: fetching historyBeforeSubscribe for ' + entry.name + '...');
356
+ logger.log('[realtime] Discontinuity recovery: fetching historyBeforeSubscribe for ' + entry.name + '...');
357
357
  var page = await entry.sub.historyBeforeSubscribe({ limit: 100 });
358
358
  var recovered = 0;
359
359
  while (page) {
@@ -363,25 +363,25 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
363
363
  recovered++;
364
364
  try {
365
365
  if (entry.handler) {
366
- logger.log('[ably] Replaying recovered ' + entry.name + ' message (requestId=' + (page.items[j].data && page.items[j].data.requestId || 'none') + ')');
366
+ logger.log('[realtime] Replaying recovered ' + entry.name + ' message (requestId=' + (page.items[j].data && page.items[j].data.requestId || 'none') + ')');
367
367
  entry.handler(page.items[j]);
368
368
  }
369
369
  } catch (replayErr) {
370
- logger.error('[ably] Error replaying recovered message: ' + (replayErr.message || replayErr));
370
+ logger.error('[realtime] Error replaying recovered message: ' + (replayErr.message || replayErr));
371
371
  }
372
372
  }
373
373
  page = page.hasNext() ? await page.next() : null;
374
374
  }
375
375
  totalRecovered += recovered;
376
- logger.log('[ably] Discontinuity recovery: replayed ' + recovered + ' ' + entry.name + ' message(s) from gap');
376
+ logger.log('[realtime] Discontinuity recovery: replayed ' + recovered + ' ' + entry.name + ' message(s) from gap');
377
377
  } catch (err) {
378
- logger.error('[ably] Discontinuity recovery failed for ' + entry.name + ': ' + (err.message || err));
378
+ logger.error('[realtime] Discontinuity recovery failed for ' + entry.name + ': ' + (err.message || err));
379
379
  }
380
380
  }
381
381
  if (totalRecovered > 0) {
382
- logger.warn('[ably] Recovered and replayed ' + totalRecovered + ' message(s) that were missed during connection interruption');
382
+ logger.warn('[realtime] Recovered and replayed ' + totalRecovered + ' message(s) that were missed during connection interruption');
383
383
  } else {
384
- logger.log('[ably] Discontinuity recovery: no missed messages found');
384
+ logger.log('[realtime] Discontinuity recovery: no missed messages found');
385
385
  }
386
386
  }
387
387
 
@@ -809,7 +809,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
809
809
  function onFailed() {
810
810
  clearTimeout(timer);
811
811
  self._ably.connection.off("connected", onConnected);
812
- reject(new Error("Ably connection failed while waiting to send"));
812
+ reject(new Error("Realtime connection failed while waiting to send"));
813
813
  }
814
814
  self._ably.connection.once("connected", onConnected);
815
815
  self._ably.connection.once("failed", onFailed);
@@ -881,7 +881,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
881
881
  return rid + '(' + (e && e.message ? e.message.type : '?') + ', ' + age + ')';
882
882
  }).join(', ');
883
883
  logger.error(
884
- '[ably] Promise TIMEOUT: requestId=' + requestId +
884
+ '[realtime] Promise TIMEOUT: requestId=' + requestId +
885
885
  ' | type=' + message.type +
886
886
  ' | timeout=' + timeout + 'ms' +
887
887
  ' | all pending: [' + pendingSummary + ']'
@@ -930,7 +930,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
930
930
  timeoutId = setTimeout(timeoutFn, newRemaining);
931
931
  if (timeoutId.unref) timeoutId.unref();
932
932
  logger.log(
933
- '[ably] Extended timeout for requestId=' + requestId +
933
+ '[realtime] Extended timeout for requestId=' + requestId +
934
934
  ' by ' + disconnectionDurationMs + 'ms (new remaining: ' + Math.round(newRemaining / 1000) + 's)'
935
935
  );
936
936
  },
@@ -1002,7 +1002,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
1002
1002
  }
1003
1003
 
1004
1004
  return channel.publish(eventName, message).then(function () {
1005
- logger.log(`[ably] Published: channel=${channel.name.split(':').pop()}, event=${eventName}, type=${message.type || 'unknown'} (requestId=${message.requestId || 'none'})`);
1005
+ logger.log(`[realtime] Published: channel=${channel.name.split(':').pop()}, event=${eventName}, type=${message.type || 'unknown'} (requestId=${message.requestId || 'none'})`);
1006
1006
  });
1007
1007
  }
1008
1008
 
@@ -1132,7 +1132,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
1132
1132
  // Send end-session control message to runner before disconnecting
1133
1133
  if (this._sessionChannel && this._ably?.connection?.state === 'connected') {
1134
1134
  try {
1135
- logger.log('[ably] Publishing control: type=end-session');
1135
+ logger.log('[realtime] Publishing control: type=end-session');
1136
1136
  await this._sessionChannel.publish('control', { type: 'end-session' });
1137
1137
  } catch (e) {
1138
1138
  // Ignore - best effort
@@ -1142,7 +1142,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
1142
1142
  // Leave presence on session channel
1143
1143
  if (this._sessionChannel) {
1144
1144
  try {
1145
- logger.log('[ably] Leaving presence on session channel');
1145
+ logger.log('[realtime] Leaving presence on session channel');
1146
1146
  await this._sessionChannel.presence.leave();
1147
1147
  } catch (e) {
1148
1148
  // ignore - best effort, Ably will auto-leave on disconnect
@@ -1150,7 +1150,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
1150
1150
  }
1151
1151
 
1152
1152
  try {
1153
- logger.log('[ably] Detaching session channel');
1153
+ logger.log('[realtime] Detaching session channel');
1154
1154
  if (this._sessionChannel) {
1155
1155
  await this._sessionChannel.detach();
1156
1156
  }
@@ -1160,7 +1160,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
1160
1160
 
1161
1161
  if (this._ably) {
1162
1162
  try {
1163
- logger.log('[ably] Closing Ably connection');
1163
+ logger.log('[realtime] Closing Realtime connection');
1164
1164
  this._ably.close();
1165
1165
  } catch (e) {
1166
1166
  /* ignore */
@@ -49,8 +49,8 @@ const element = await testdriver.find(description, options)
49
49
  - `"any"` — No wrapping, uses the description as-is (default behavior)
50
50
  </ParamField>
51
51
 
52
- <ParamField path="zoom" type="boolean" default={false}>
53
- Enable two-phase zoom mode for better precision in crowded UIs with many similar elements.
52
+ <ParamField path="zoom" type="boolean" default={true}>
53
+ Two-phase zoom mode for better precision in crowded UIs with many similar elements. Enabled by default.
54
54
  </ParamField>
55
55
 
56
56
  <ParamField path="ai" type="object">
@@ -332,14 +332,19 @@ The `timeout` option:
332
332
  - Returns the element (check `element.found()` if not throwing on failure)
333
333
  - Set to `0` to disable polling and make a single attempt
334
334
 
335
- ## Zoom Mode for Crowded UIs
335
+ ## Zoom Mode
336
336
 
337
- When dealing with many similar icons or elements clustered together (like browser toolbars), enable `zoom` mode for better precision:
337
+ Zoom mode is **enabled by default**. It uses a two-phase approach for better precision when locating elements, especially in crowded UIs with many similar elements.
338
+
339
+ To disable zoom for a specific find call, pass `zoom: false`:
338
340
 
339
341
  ```javascript
340
- // Enable zoom for better precision in crowded UIs
341
- const extensionsBtn = await testdriver.find('extensions puzzle icon in Chrome toolbar', { zoom: true });
342
+ // Zoom is on by default no option needed
343
+ const extensionsBtn = await testdriver.find('extensions puzzle icon in Chrome toolbar');
342
344
  await extensionsBtn.click();
345
+
346
+ // Disable zoom for a specific call if needed
347
+ const largeButton = await testdriver.find('big hero button', { zoom: false });
343
348
  ```
344
349
 
345
350
  ### How Zoom Mode Works
@@ -352,22 +357,11 @@ await extensionsBtn.click();
352
357
  This two-phase approach gives the AI a higher-resolution view of the target area, improving accuracy when multiple similar elements are close together.
353
358
 
354
359
  <Tip>
355
- Use `zoom: true` when:
356
- - Clicking small icons in toolbars
357
- - Selecting from a grid of similar items
358
- - Targeting elements in dense UI areas
359
- - The default locate is clicking the wrong similar element
360
- - You get an AI verification rejection like "The crosshair is located in the empty space of the browser's tab bar/title bar area" — this means the initial locate was imprecise and zoom will help the AI pinpoint the correct element
360
+ You may want to disable zoom with `zoom: false` when:
361
+ - Targeting large, isolated elements where the extra precision isn't needed
362
+ - You want to speed up find calls in simple UIs
361
363
  </Tip>
362
364
 
363
- ```javascript
364
- // Without zoom - may click wrong icon in toolbar
365
- const icon = await testdriver.find('settings icon');
366
-
367
- // With zoom - better precision for crowded areas
368
- const icon = await testdriver.find('settings icon', { zoom: true });
369
- ```
370
-
371
365
  ## Cache Options
372
366
 
373
367
  Control caching behavior to optimize performance, especially when using dynamic variables in prompts.
package/docs/v7/find.mdx CHANGED
@@ -50,8 +50,8 @@ const element = await testdriver.find(description, options)
50
50
  - `"any"` — No wrapping, uses the description as-is (default behavior)
51
51
  </ParamField>
52
52
 
53
- <ParamField path="zoom" type="boolean" default={false}>
54
- Enable two-phase zoom mode for better precision in crowded UIs with many similar elements.
53
+ <ParamField path="zoom" type="boolean" default={true}>
54
+ Two-phase zoom mode for better precision in crowded UIs with many similar elements. Enabled by default.
55
55
  </ParamField>
56
56
 
57
57
  <ParamField path="ai" type="object">
@@ -333,14 +333,19 @@ The `timeout` option:
333
333
  - Returns the element (check `element.found()` if not throwing on failure)
334
334
  - Set to `0` to disable polling and make a single attempt
335
335
 
336
- ## Zoom Mode for Crowded UIs
336
+ ## Zoom Mode
337
337
 
338
- When dealing with many similar icons or elements clustered together (like browser toolbars), enable `zoom` mode for better precision:
338
+ Zoom mode is **enabled by default**. It uses a two-phase approach for better precision when locating elements, especially in crowded UIs with many similar elements.
339
+
340
+ To disable zoom for a specific find call, pass `zoom: false`:
339
341
 
340
342
  ```javascript
341
- // Enable zoom for better precision in crowded UIs
342
- const extensionsBtn = await testdriver.find('extensions puzzle icon in Chrome toolbar', { zoom: true });
343
+ // Zoom is on by default no option needed
344
+ const extensionsBtn = await testdriver.find('extensions puzzle icon in Chrome toolbar');
343
345
  await extensionsBtn.click();
346
+
347
+ // Disable zoom for a specific call if needed
348
+ const largeButton = await testdriver.find('big hero button', { zoom: false });
344
349
  ```
345
350
 
346
351
  ### How Zoom Mode Works
@@ -353,22 +358,11 @@ await extensionsBtn.click();
353
358
  This two-phase approach gives the AI a higher-resolution view of the target area, improving accuracy when multiple similar elements are close together.
354
359
 
355
360
  <Tip>
356
- Use `zoom: true` when:
357
- - Clicking small icons in toolbars
358
- - Selecting from a grid of similar items
359
- - Targeting elements in dense UI areas
360
- - The default locate is clicking the wrong similar element
361
- - You get an AI verification rejection like "The crosshair is located in the empty space of the browser's tab bar/title bar area" — this means the initial locate was imprecise and zoom will help the AI pinpoint the correct element
361
+ You may want to disable zoom with `zoom: false` when:
362
+ - Targeting large, isolated elements where the extra precision isn't needed
363
+ - You want to speed up find calls in simple UIs
362
364
  </Tip>
363
365
 
364
- ```javascript
365
- // Without zoom - may click wrong icon in toolbar
366
- const icon = await testdriver.find('settings icon');
367
-
368
- // With zoom - better precision for crowded areas
369
- const icon = await testdriver.find('settings icon', { zoom: true });
370
- ```
371
-
372
366
  ## Cache Options
373
367
 
374
368
  Control caching behavior to optimize performance, especially when using dynamic variables in prompts.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.8.0-test.50",
3
+ "version": "7.8.0-test.52",
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",
package/sdk.js CHANGED
@@ -481,7 +481,7 @@ class Element {
481
481
  let cacheKey = null;
482
482
  let cacheThreshold = null;
483
483
  let perCommandThresholds = null; // Per-command { screen, element } override
484
- let zoom = false; // Default to disabled, enable with zoom: true
484
+ let zoom = true; // Default to enabled
485
485
  let perCommandAi = null; // Per-command AI config override
486
486
 
487
487
  let minConfidence = null; // Minimum confidence threshold
@@ -494,8 +494,8 @@ class Element {
494
494
  // New: options is an object with cacheKey and/or cacheThreshold
495
495
  cacheKey = options.cacheKey || null;
496
496
  cacheThreshold = options.cacheThreshold ?? null;
497
- // zoom defaults to false unless explicitly set to true
498
- zoom = options.zoom === true;
497
+ // zoom defaults to true unless explicitly set to false
498
+ zoom = options.zoom !== false;
499
499
  // Minimum confidence threshold: fail find if AI confidence is below this value
500
500
  minConfidence = options.confidence ?? null;
501
501
  // Element type hint for prompt wrapping