testdriverai 7.0.0 → 7.1.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.
Files changed (112) hide show
  1. package/AGENTS.md +550 -0
  2. package/CODEOWNERS +0 -1
  3. package/README.md +126 -0
  4. package/agent/index.js +43 -18
  5. package/agent/lib/commands.js +794 -135
  6. package/agent/lib/redraw.js +124 -39
  7. package/agent/lib/sandbox.js +10 -1
  8. package/agent/lib/sdk.js +21 -0
  9. package/docs/MIGRATION.md +425 -0
  10. package/docs/PRESETS.md +210 -0
  11. package/docs/docs.json +91 -37
  12. package/docs/guide/best-practices-polling.mdx +154 -0
  13. package/docs/v7/api/dashcam.mdx +497 -0
  14. package/docs/v7/api/doubleClick.mdx +102 -0
  15. package/docs/v7/api/mouseDown.mdx +161 -0
  16. package/docs/v7/api/mouseUp.mdx +164 -0
  17. package/docs/v7/api/rightClick.mdx +123 -0
  18. package/docs/v7/getting-started/configuration.mdx +380 -0
  19. package/docs/v7/getting-started/quickstart.mdx +273 -140
  20. package/docs/v7/guides/best-practices.mdx +486 -0
  21. package/docs/v7/guides/caching-ai.mdx +215 -0
  22. package/docs/v7/guides/caching-selectors.mdx +292 -0
  23. package/docs/v7/guides/caching.mdx +366 -0
  24. package/docs/v7/guides/ci-cd/azure.mdx +587 -0
  25. package/docs/v7/guides/ci-cd/circleci.mdx +523 -0
  26. package/docs/v7/guides/ci-cd/github-actions.mdx +457 -0
  27. package/docs/v7/guides/ci-cd/gitlab.mdx +498 -0
  28. package/docs/v7/guides/ci-cd/jenkins.mdx +664 -0
  29. package/docs/v7/guides/ci-cd/travis.mdx +438 -0
  30. package/docs/v7/guides/debugging.mdx +349 -0
  31. package/docs/v7/guides/faq.mdx +393 -0
  32. package/docs/v7/guides/performance.mdx +517 -0
  33. package/docs/v7/guides/troubleshooting.mdx +526 -0
  34. package/docs/v7/guides/vitest-plugin.mdx +477 -0
  35. package/docs/v7/guides/vitest.mdx +535 -0
  36. package/docs/v7/platforms/linux.mdx +308 -0
  37. package/docs/v7/platforms/macos.mdx +433 -0
  38. package/docs/v7/platforms/windows.mdx +430 -0
  39. package/docs/v7/presets/chrome-extension.mdx +223 -0
  40. package/docs/v7/presets/chrome.mdx +287 -0
  41. package/docs/v7/presets/electron.mdx +435 -0
  42. package/docs/v7/presets/vscode.mdx +398 -0
  43. package/docs/v7/presets/webapp.mdx +396 -0
  44. package/docs/v7/progressive-apis/CORE.md +459 -0
  45. package/docs/v7/progressive-apis/HOOKS.md +360 -0
  46. package/docs/v7/progressive-apis/PROGRESSIVE_DISCLOSURE.md +230 -0
  47. package/docs/v7/progressive-apis/PROVISION.md +266 -0
  48. package/interfaces/vitest-plugin.mjs +186 -100
  49. package/package.json +12 -1
  50. package/sdk.d.ts +335 -42
  51. package/sdk.js +756 -95
  52. package/src/core/Dashcam.js +469 -0
  53. package/src/core/index.d.ts +150 -0
  54. package/src/core/index.js +12 -0
  55. package/src/presets/index.mjs +331 -0
  56. package/src/vitest/extended.mjs +108 -0
  57. package/src/vitest/hooks.d.ts +119 -0
  58. package/src/vitest/hooks.mjs +298 -0
  59. package/src/vitest/index.mjs +64 -0
  60. package/src/vitest/lifecycle.mjs +277 -0
  61. package/src/vitest/utils.mjs +150 -0
  62. package/test/dashcam.test.js +137 -0
  63. package/testdriver/acceptance-sdk/assert.test.mjs +13 -31
  64. package/testdriver/acceptance-sdk/auto-cache-key-demo.test.mjs +56 -0
  65. package/testdriver/acceptance-sdk/chrome-extension.test.mjs +89 -0
  66. package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +7 -19
  67. package/testdriver/acceptance-sdk/element-not-found.test.mjs +6 -19
  68. package/testdriver/acceptance-sdk/exec-js.test.mjs +6 -18
  69. package/testdriver/acceptance-sdk/exec-output.test.mjs +8 -20
  70. package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +13 -25
  71. package/testdriver/acceptance-sdk/focus-window.test.mjs +8 -20
  72. package/testdriver/acceptance-sdk/formatted-logging.test.mjs +5 -20
  73. package/testdriver/acceptance-sdk/hooks-example.test.mjs +38 -0
  74. package/testdriver/acceptance-sdk/hover-image.test.mjs +10 -19
  75. package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +7 -19
  76. package/testdriver/acceptance-sdk/hover-text.test.mjs +5 -19
  77. package/testdriver/acceptance-sdk/match-image.test.mjs +7 -19
  78. package/testdriver/acceptance-sdk/presets-example.test.mjs +87 -0
  79. package/testdriver/acceptance-sdk/press-keys.test.mjs +5 -19
  80. package/testdriver/acceptance-sdk/prompt.test.mjs +6 -18
  81. package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +6 -20
  82. package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +6 -18
  83. package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +9 -23
  84. package/testdriver/acceptance-sdk/scroll.test.mjs +12 -21
  85. package/testdriver/acceptance-sdk/setup/testHelpers.mjs +124 -352
  86. package/testdriver/acceptance-sdk/sully-ai.test.mjs +234 -0
  87. package/testdriver/acceptance-sdk/test-console-logs.test.mjs +42 -0
  88. package/testdriver/acceptance-sdk/type.test.mjs +19 -58
  89. package/vitest.config.mjs +1 -0
  90. package/.vscode/mcp.json +0 -9
  91. package/MIGRATION.md +0 -389
  92. package/PLUGIN_MIGRATION.md +0 -222
  93. package/PROMPT_CACHE.md +0 -200
  94. package/SDK_LOGGING.md +0 -222
  95. package/SDK_MIGRATION.md +0 -474
  96. package/SDK_README.md +0 -1122
  97. package/debug-screenshot-1763401388589.png +0 -0
  98. package/examples/run-tests-with-recording.sh +0 -70
  99. package/examples/screenshot-example.js +0 -63
  100. package/examples/sdk-awesome-logs-demo.js +0 -177
  101. package/examples/sdk-cache-thresholds.js +0 -96
  102. package/examples/sdk-element-properties.js +0 -155
  103. package/examples/sdk-simple-example.js +0 -65
  104. package/examples/test-recording-example.test.js +0 -166
  105. package/mcp-server/AI_GUIDELINES.md +0 -57
  106. package/test-find-api.js +0 -73
  107. package/test-prompt-cache.js +0 -96
  108. package/test-sandbox-render.js +0 -28
  109. package/test-sdk-methods.js +0 -15
  110. package/test-sdk-refactor.js +0 -53
  111. package/test-stack-trace.mjs +0 -57
  112. package/testdriver/acceptance-sdk/setup/lifecycleHelpers.mjs +0 -239
@@ -3,13 +3,28 @@ const fs = require("fs");
3
3
  const { events } = require("../events");
4
4
  const theme = require("./theme");
5
5
 
6
+ // Default redraw options
7
+ const DEFAULT_REDRAW_OPTIONS = {
8
+ enabled: true, // Master switch to enable/disable redraw detection
9
+ screenRedraw: true, // Enable screen redraw detection
10
+ networkMonitor: true, // Enable network activity monitoring
11
+ diffThreshold: 0.1, // Percentage threshold for screen diff (0.1 = 0.1%)
12
+ };
13
+
6
14
  // Factory function that creates redraw functionality with the provided system instance
7
15
  const createRedraw = (
8
16
  emitter,
9
17
  system,
10
18
  sandbox,
11
- redrawThresholdPercent = 0.1,
19
+ defaultOptions = {},
12
20
  ) => {
21
+ // Merge default options with provided defaults
22
+ const baseOptions = { ...DEFAULT_REDRAW_OPTIONS, ...defaultOptions };
23
+ // Support legacy redrawThresholdPercent number argument
24
+ if (typeof defaultOptions === 'number') {
25
+ baseOptions.diffThreshold = defaultOptions;
26
+ }
27
+
13
28
  const networkUpdateInterval = 15000;
14
29
 
15
30
  let lastTxBytes = null;
@@ -116,16 +131,13 @@ const createRedraw = (
116
131
  { threshold: 0.1 },
117
132
  );
118
133
 
119
- if (differentPixels === 0) {
120
- return false;
121
- } else {
122
- // Calculate percentage difference based on pixel differences
123
- const diffPercentage = (differentPixels / totalPixels) * 100;
124
- return diffPercentage.toFixed(1);
125
- }
134
+ // Calculate percentage difference based on pixel differences
135
+ // Always return a number (0 if no difference)
136
+ const diffPercentage = (differentPixels / totalPixels) * 100;
137
+ return parseFloat(diffPercentage.toFixed(2));
126
138
  } catch (error) {
127
139
  console.error("Error comparing images:", error);
128
- return false;
140
+ return 0; // Return 0 on error instead of false
129
141
  }
130
142
  }
131
143
 
@@ -146,46 +158,104 @@ const createRedraw = (
146
158
  }
147
159
  }
148
160
 
149
- async function start() {
161
+ // Current options for the active redraw cycle
162
+ let currentOptions = { ...baseOptions };
163
+
164
+ async function start(options = {}) {
165
+ // Merge base options with per-call options
166
+ currentOptions = { ...baseOptions, ...options };
167
+
168
+ console.log('[redraw] start() called with options:', JSON.stringify(currentOptions));
169
+
170
+ // If redraw is completely disabled, return early
171
+ if (!currentOptions.enabled) {
172
+ console.log('[redraw] start() - redraw disabled, returning null');
173
+ return null;
174
+ }
175
+
176
+ // If both screenRedraw and networkMonitor are disabled, disable redraw
177
+ if (!currentOptions.screenRedraw && !currentOptions.networkMonitor) {
178
+ currentOptions.enabled = false;
179
+ console.log('[redraw] start() - both screenRedraw and networkMonitor disabled, returning null');
180
+ return null;
181
+ }
182
+
150
183
  resetState();
151
- startNetworkMonitoring();
152
- startImage = await system.captureScreenPNG(0.25, true);
184
+
185
+ // Only start network monitoring if enabled
186
+ if (currentOptions.networkMonitor) {
187
+ startNetworkMonitoring();
188
+ }
189
+
190
+ // Only capture start image if screen redraw is enabled
191
+ if (currentOptions.screenRedraw) {
192
+ startImage = await system.captureScreenPNG(0.25, true);
193
+ console.log('[redraw] start() - captured startImage:', startImage);
194
+ }
195
+
153
196
  return startImage;
154
197
  }
155
198
 
156
- async function checkCondition(resolve, startTime, timeoutMs) {
157
- let nowImage = await system.captureScreenPNG(0.25, true);
199
+ async function checkCondition(resolve, startTime, timeoutMs, options) {
200
+ const { enabled, screenRedraw, networkMonitor, diffThreshold } = options;
201
+
202
+ // If redraw is disabled, resolve immediately
203
+ if (!enabled) {
204
+ resolve("true");
205
+ return;
206
+ }
207
+
208
+ let nowImage = screenRedraw ? await system.captureScreenPNG(0.25, true) : null;
158
209
  let timeElapsed = Date.now() - startTime;
159
210
  let diffPercent = 0;
160
211
  let isTimeout = timeElapsed > timeoutMs;
161
212
 
162
- if (!screenHasRedrawn) {
213
+ // Check screen redraw if enabled and we have a start image to compare against
214
+ if (screenRedraw && !screenHasRedrawn && startImage && nowImage) {
215
+ console.log('[redraw] checkCondition() - comparing images:', { startImage, nowImage });
163
216
  diffPercent = await imageDiffPercent(startImage, nowImage);
164
- screenHasRedrawn = diffPercent > redrawThresholdPercent;
217
+ console.log('[redraw] checkCondition() - diffPercent:', diffPercent, 'threshold:', diffThreshold);
218
+ screenHasRedrawn = diffPercent > diffThreshold;
219
+ console.log('[redraw] checkCondition() - screenHasRedrawn:', screenHasRedrawn);
220
+ } else if (screenRedraw && !startImage) {
221
+ // If no start image was captured, capture one now and wait for next check
222
+ console.log('[redraw] checkCondition() - no startImage, capturing now');
223
+ startImage = await system.captureScreenPNG(0.25, true);
165
224
  }
166
-
167
- // // log redraw as output
168
- let redrawText = screenHasRedrawn
169
- ? theme.green(`y`)
170
- : theme.dim(`${diffPercent}/${redrawThresholdPercent}%`);
171
- let networkText = networkSettled
172
- ? theme.green(`y`)
173
- : theme.dim(
174
- `${Math.trunc((diffRxBytes + diffTxBytes) / networkUpdateInterval)}b/s`,
175
- );
225
+
226
+ // If screen redraw is disabled, consider it as "redrawn"
227
+ const effectiveScreenRedrawn = screenRedraw ? screenHasRedrawn : true;
228
+ // If network monitor is disabled, consider it as "settled"
229
+ const effectiveNetworkSettled = networkMonitor ? networkSettled : true;
230
+
231
+ // Log redraw status
232
+ let redrawText = !screenRedraw
233
+ ? theme.dim(`disabled`)
234
+ : effectiveScreenRedrawn
235
+ ? theme.green(`y`)
236
+ : theme.dim(`${diffPercent}/${diffThreshold}%`);
237
+ let networkText = !networkMonitor
238
+ ? theme.dim(`disabled`)
239
+ : effectiveNetworkSettled
240
+ ? theme.green(`y`)
241
+ : theme.dim(
242
+ `${Math.trunc((diffRxBytes + diffTxBytes) / networkUpdateInterval)}b/s`,
243
+ );
176
244
  let timeoutText = isTimeout
177
245
  ? theme.green(`y`)
178
246
  : theme.dim(`${Math.floor(timeElapsed / 1000)}/${timeoutMs / 1000}s`);
179
247
 
180
248
  emitter.emit(events.redraw.status, {
181
249
  redraw: {
182
- hasRedrawn: screenHasRedrawn,
250
+ enabled: screenRedraw,
251
+ hasRedrawn: effectiveScreenRedrawn,
183
252
  diffPercent,
184
- threshold: redrawThresholdPercent,
253
+ threshold: diffThreshold,
185
254
  text: redrawText,
186
255
  },
187
256
  network: {
188
- settled: networkSettled,
257
+ enabled: networkMonitor,
258
+ settled: effectiveNetworkSettled,
189
259
  rxBytes: diffRxBytes,
190
260
  txBytes: diffTxBytes,
191
261
  text: networkText,
@@ -198,27 +268,42 @@ const createRedraw = (
198
268
  },
199
269
  });
200
270
 
201
- if ((screenHasRedrawn && networkSettled) || isTimeout) {
271
+ if ((effectiveScreenRedrawn && effectiveNetworkSettled) || isTimeout) {
202
272
  emitter.emit(events.redraw.complete, {
203
- screenHasRedrawn,
204
- networkSettled,
273
+ screenHasRedrawn: effectiveScreenRedrawn,
274
+ networkSettled: effectiveNetworkSettled,
205
275
  isTimeout,
206
276
  timeElapsed,
207
277
  });
208
278
  resolve("true");
209
279
  } else {
210
280
  setTimeout(() => {
211
- checkCondition(resolve, startTime, timeoutMs);
281
+ checkCondition(resolve, startTime, timeoutMs, options);
212
282
  }, 500);
213
283
  }
214
284
  }
215
285
 
216
- function wait(timeoutMs) {
286
+ function wait(timeoutMs, options = {}) {
287
+ // Merge current options with any per-call overrides
288
+ const waitOptions = { ...currentOptions, ...options };
289
+
290
+ // If redraw is disabled, resolve immediately
291
+ if (!waitOptions.enabled) {
292
+ return Promise.resolve("true");
293
+ }
294
+
295
+ // If both are disabled, resolve immediately
296
+ if (!waitOptions.screenRedraw && !waitOptions.networkMonitor) {
297
+ return Promise.resolve("true");
298
+ }
299
+
217
300
  return new Promise((resolve) => {
218
301
  const startTime = Date.now();
219
- // Start network monitoring if not already started
220
- startNetworkMonitoring();
221
- checkCondition(resolve, startTime, timeoutMs);
302
+ // Start network monitoring if not already started and enabled
303
+ if (waitOptions.networkMonitor) {
304
+ startNetworkMonitoring();
305
+ }
306
+ checkCondition(resolve, startTime, timeoutMs, waitOptions);
222
307
  });
223
308
  }
224
309
 
@@ -226,7 +311,7 @@ const createRedraw = (
226
311
  stopNetworkMonitoring(networkInterval);
227
312
  }
228
313
 
229
- return { start, wait, cleanup };
314
+ return { start, wait, cleanup, DEFAULT_OPTIONS: DEFAULT_REDRAW_OPTIONS };
230
315
  };
231
316
 
232
- module.exports = { createRedraw };
317
+ module.exports = { createRedraw, DEFAULT_REDRAW_OPTIONS };
@@ -2,7 +2,7 @@ const WebSocket = require("ws");
2
2
  const marky = require("marky");
3
3
  const { events } = require("../events");
4
4
 
5
- const createSandbox = (emitter, analytics) => {
5
+ const createSandbox = (emitter, analytics, sessionInstance) => {
6
6
  class Sandbox {
7
7
  constructor() {
8
8
  this.socket = null;
@@ -15,6 +15,7 @@ const createSandbox = (emitter, analytics) => {
15
15
  this.messageId = 0;
16
16
  this.uniqueId = Math.random().toString(36).substring(7);
17
17
  this.os = null; // Store OS value to send with every message
18
+ this.sessionInstance = sessionInstance; // Store session instance to include in messages
18
19
  }
19
20
 
20
21
  send(message) {
@@ -35,6 +36,14 @@ const createSandbox = (emitter, analytics) => {
35
36
  message.os = this.os;
36
37
  }
37
38
 
39
+ // Add session to every message if available (for interaction tracking)
40
+ if (this.sessionInstance && !message.session) {
41
+ const sessionId = this.sessionInstance.get();
42
+ if (sessionId) {
43
+ message.session = sessionId;
44
+ }
45
+ }
46
+
38
47
  // Start timing for this message
39
48
  const timingKey = `sandbox-${message.type}`;
40
49
  marky.mark(timingKey);
package/agent/lib/sdk.js CHANGED
@@ -194,6 +194,27 @@ const createSDK = (emitter, config, sessionInstance) => {
194
194
 
195
195
  return value;
196
196
  } catch (error) {
197
+ // Check if this is an API validation error with detailed problems
198
+ if (error.response?.data?.problems) {
199
+ const problems = error.response.data.problems;
200
+ const errorMessage = error.response.data.message || 'API validation error';
201
+ const detailedError = new Error(
202
+ `${errorMessage}\n\nDetails:\n${problems.map(p => ` - ${p}`).join('\n')}`
203
+ );
204
+ detailedError.originalError = error;
205
+ detailedError.problems = problems;
206
+
207
+ // Emit the formatted error
208
+ emitter.emit(events.error.sdk, {
209
+ message: detailedError.message,
210
+ code: error.response?.data?.code || error.code,
211
+ problems: problems,
212
+ fullError: error,
213
+ });
214
+
215
+ throw detailedError;
216
+ }
217
+
197
218
  outputError(error);
198
219
  throw error; // Re-throw the error so calling code can handle it properly
199
220
  }