testdriverai 6.2.2 → 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 (300) hide show
  1. package/.github/workflows/acceptance-linux.yml +75 -0
  2. package/.github/workflows/acceptance-sdk-tests.yml +133 -0
  3. package/.vscode/settings.json +5 -1
  4. package/AGENTS.md +550 -0
  5. package/CODEOWNERS +0 -1
  6. package/README.md +126 -0
  7. package/{testdriver → _testdriver}/acceptance/drag-and-drop.yaml +2 -2
  8. package/{testdriver → _testdriver}/acceptance/snippets/login.yaml +1 -1
  9. package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
  10. package/{testdriver → _testdriver}/examples/web/lifecycle/prerun.yaml +6 -1
  11. package/{testdriver → _testdriver}/lifecycle/postrun.yaml +3 -2
  12. package/_testdriver/lifecycle/prerun.yaml +15 -0
  13. package/{testdriver → _testdriver}/lifecycle/provision.yaml +7 -2
  14. package/agent/index.js +300 -85
  15. package/agent/interface.js +15 -0
  16. package/agent/lib/cache.js +142 -0
  17. package/agent/lib/commander.js +1 -39
  18. package/agent/lib/commands.js +910 -296
  19. package/agent/lib/redraw.js +129 -41
  20. package/agent/lib/sandbox.js +29 -6
  21. package/agent/lib/sdk.js +22 -0
  22. package/agent/lib/system.js +0 -3
  23. package/agent/lib/validation.js +1 -7
  24. package/debug-locate-response.js +82 -0
  25. package/debugger/index.html +15 -4
  26. package/docs/ARCHITECTURE.md +424 -0
  27. package/docs/AWESOME_LOGS_QUICK_REF.md +100 -0
  28. package/docs/MIGRATION.md +425 -0
  29. package/docs/PRESETS.md +210 -0
  30. package/docs/QUICK_START_TEST_RECORDING.md +215 -0
  31. package/docs/SDK_AWESOME_LOGS.md +468 -0
  32. package/docs/TEST_RECORDING.md +388 -0
  33. package/docs/docs.json +286 -152
  34. package/docs/guide/best-practices-polling.mdx +154 -0
  35. package/docs/sdk-browser-rendering.md +167 -0
  36. package/docs/v6/getting-started/self-hosting.mdx +407 -0
  37. package/docs/{guide → v6/guide}/dashcam.mdx +1 -1
  38. package/docs/{guide → v6/guide}/environment-variables.mdx +4 -5
  39. package/docs/{guide → v6/guide}/lifecycle.mdx +1 -1
  40. package/docs/v6/overview/comparison.mdx +101 -0
  41. package/docs/v7/README.md +135 -0
  42. package/docs/v7/api/ai.mdx +205 -0
  43. package/docs/v7/api/assert.mdx +285 -0
  44. package/docs/v7/api/assertions.mdx +403 -0
  45. package/docs/v7/api/click.mdx +287 -0
  46. package/docs/v7/api/client.mdx +322 -0
  47. package/docs/v7/api/dashcam.mdx +497 -0
  48. package/docs/v7/api/doubleClick.mdx +102 -0
  49. package/docs/v7/api/elements.mdx +479 -0
  50. package/docs/v7/api/exec.mdx +346 -0
  51. package/docs/v7/api/find.mdx +316 -0
  52. package/docs/v7/api/focusApplication.mdx +294 -0
  53. package/docs/v7/api/hover.mdx +279 -0
  54. package/docs/v7/api/mouseDown.mdx +161 -0
  55. package/docs/v7/api/mouseUp.mdx +164 -0
  56. package/docs/v7/api/pressKeys.mdx +349 -0
  57. package/docs/v7/api/rightClick.mdx +123 -0
  58. package/docs/v7/api/sandbox.mdx +404 -0
  59. package/docs/v7/api/scroll.mdx +300 -0
  60. package/docs/v7/api/type.mdx +314 -0
  61. package/docs/v7/commands/assert.mdx +45 -0
  62. package/docs/v7/commands/exec.mdx +282 -0
  63. package/docs/v7/commands/focus-application.mdx +44 -0
  64. package/docs/v7/commands/hover-image.mdx +69 -0
  65. package/docs/v7/commands/hover-text.mdx +47 -0
  66. package/docs/v7/commands/if.mdx +53 -0
  67. package/docs/v7/commands/match-image.mdx +67 -0
  68. package/docs/v7/commands/press-keys.mdx +87 -0
  69. package/docs/v7/commands/remember.mdx +49 -0
  70. package/docs/v7/commands/run.mdx +44 -0
  71. package/docs/v7/commands/scroll-until-image.mdx +66 -0
  72. package/docs/v7/commands/scroll-until-text.mdx +60 -0
  73. package/docs/v7/commands/scroll.mdx +69 -0
  74. package/docs/v7/commands/type.mdx +45 -0
  75. package/docs/v7/commands/wait-for-image.mdx +54 -0
  76. package/docs/v7/commands/wait-for-text.mdx +48 -0
  77. package/docs/v7/commands/wait.mdx +45 -0
  78. package/docs/v7/getting-started/configuration.mdx +380 -0
  79. package/docs/v7/getting-started/quickstart.mdx +332 -0
  80. package/docs/v7/guides/best-practices.mdx +486 -0
  81. package/docs/v7/guides/caching-ai.mdx +215 -0
  82. package/docs/v7/guides/caching-selectors.mdx +292 -0
  83. package/docs/v7/guides/caching.mdx +366 -0
  84. package/docs/v7/guides/ci-cd/azure.mdx +587 -0
  85. package/docs/v7/guides/ci-cd/circleci.mdx +523 -0
  86. package/docs/v7/guides/ci-cd/github-actions.mdx +457 -0
  87. package/docs/v7/guides/ci-cd/gitlab.mdx +498 -0
  88. package/docs/v7/guides/ci-cd/jenkins.mdx +664 -0
  89. package/docs/v7/guides/ci-cd/travis.mdx +438 -0
  90. package/docs/v7/guides/debugging.mdx +349 -0
  91. package/docs/v7/guides/faq.mdx +393 -0
  92. package/docs/v7/guides/migration.mdx +562 -0
  93. package/docs/v7/guides/performance.mdx +517 -0
  94. package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
  95. package/docs/v7/guides/troubleshooting.mdx +526 -0
  96. package/docs/v7/guides/vitest-plugin.mdx +477 -0
  97. package/docs/v7/guides/vitest.mdx +535 -0
  98. package/docs/v7/platforms/linux.mdx +308 -0
  99. package/docs/v7/platforms/macos.mdx +433 -0
  100. package/docs/v7/platforms/windows.mdx +430 -0
  101. package/docs/v7/playwright.mdx +342 -0
  102. package/docs/v7/presets/chrome-extension.mdx +223 -0
  103. package/docs/v7/presets/chrome.mdx +287 -0
  104. package/docs/v7/presets/electron.mdx +435 -0
  105. package/docs/v7/presets/vscode.mdx +398 -0
  106. package/docs/v7/presets/webapp.mdx +396 -0
  107. package/docs/v7/progressive-apis/CORE.md +459 -0
  108. package/docs/v7/progressive-apis/HOOKS.md +360 -0
  109. package/docs/v7/progressive-apis/PROGRESSIVE_DISCLOSURE.md +230 -0
  110. package/docs/v7/progressive-apis/PROVISION.md +266 -0
  111. package/eslint.config.js +19 -1
  112. package/interfaces/cli/lib/base.js +10 -4
  113. package/interfaces/logger.js +2 -1
  114. package/interfaces/shared-test-state.mjs +69 -0
  115. package/interfaces/vitest-plugin.mjs +830 -0
  116. package/package.json +29 -5
  117. package/schema.json +8 -29
  118. package/scripts/view-test-results.mjs +96 -0
  119. package/sdk-log-formatter.js +714 -0
  120. package/sdk.d.ts +1028 -0
  121. package/sdk.js +2567 -0
  122. package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
  123. package/setup/aws/cloudformation.yaml +9 -2
  124. package/src/core/Dashcam.js +469 -0
  125. package/src/core/index.d.ts +150 -0
  126. package/src/core/index.js +12 -0
  127. package/src/presets/index.mjs +331 -0
  128. package/src/vitest/extended.mjs +108 -0
  129. package/src/vitest/hooks.d.ts +119 -0
  130. package/src/vitest/hooks.mjs +298 -0
  131. package/src/vitest/index.mjs +64 -0
  132. package/src/vitest/lifecycle.mjs +277 -0
  133. package/src/vitest/utils.mjs +150 -0
  134. package/test/dashcam.test.js +137 -0
  135. package/test/mcp-example-test.yaml +27 -0
  136. package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +61 -0
  137. package/testdriver/acceptance-sdk/README.md +128 -0
  138. package/testdriver/acceptance-sdk/TEST_REPORTING.md +245 -0
  139. package/testdriver/acceptance-sdk/assert.test.mjs +26 -0
  140. package/testdriver/acceptance-sdk/auto-cache-key-demo.test.mjs +56 -0
  141. package/testdriver/acceptance-sdk/chrome-extension.test.mjs +89 -0
  142. package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +58 -0
  143. package/testdriver/acceptance-sdk/element-not-found.test.mjs +25 -0
  144. package/testdriver/acceptance-sdk/exec-js.test.mjs +43 -0
  145. package/testdriver/acceptance-sdk/exec-output.test.mjs +59 -0
  146. package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +57 -0
  147. package/testdriver/acceptance-sdk/focus-window.test.mjs +36 -0
  148. package/testdriver/acceptance-sdk/formatted-logging.test.mjs +26 -0
  149. package/testdriver/acceptance-sdk/hooks-example.test.mjs +38 -0
  150. package/testdriver/acceptance-sdk/hover-image.test.mjs +34 -0
  151. package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +38 -0
  152. package/testdriver/acceptance-sdk/hover-text.test.mjs +27 -0
  153. package/testdriver/acceptance-sdk/match-image.test.mjs +36 -0
  154. package/testdriver/acceptance-sdk/presets-example.test.mjs +87 -0
  155. package/testdriver/acceptance-sdk/press-keys.test.mjs +50 -0
  156. package/testdriver/acceptance-sdk/prompt.test.mjs +33 -0
  157. package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +38 -0
  158. package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +39 -0
  159. package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +28 -0
  160. package/testdriver/acceptance-sdk/scroll.test.mjs +41 -0
  161. package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
  162. package/testdriver/acceptance-sdk/setup/testHelpers.mjs +420 -0
  163. package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
  164. package/testdriver/acceptance-sdk/sully-ai.test.mjs +234 -0
  165. package/testdriver/acceptance-sdk/test-console-logs.test.mjs +42 -0
  166. package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
  167. package/testdriver/acceptance-sdk/type.test.mjs +45 -0
  168. package/verify-element-api.js +89 -0
  169. package/verify-types.js +0 -0
  170. package/vitest.config.example.js +19 -0
  171. package/vitest.config.mjs +66 -0
  172. package/vitest.config.mjs.bak +44 -0
  173. package/.github/workflows/acceptance-v6.yml +0 -169
  174. package/.vscode/mcp.json +0 -9
  175. package/docs/overview/comparison.mdx +0 -82
  176. package/testdriver/lifecycle/prerun.yaml +0 -17
  177. /package/{testdriver/examples/desktop/lifecycle/prerun.yaml → .env.example} +0 -0
  178. /package/{testdriver → _testdriver}/acceptance/assert.yaml +0 -0
  179. /package/{testdriver → _testdriver}/acceptance/dashcam.yaml +0 -0
  180. /package/{testdriver → _testdriver}/acceptance/embed.yaml +0 -0
  181. /package/{testdriver → _testdriver}/acceptance/exec-js.yaml +0 -0
  182. /package/{testdriver → _testdriver}/acceptance/exec-output.yaml +0 -0
  183. /package/{testdriver → _testdriver}/acceptance/exec-shell.yaml +0 -0
  184. /package/{testdriver → _testdriver}/acceptance/focus-window.yaml +0 -0
  185. /package/{testdriver → _testdriver}/acceptance/hover-image.yaml +0 -0
  186. /package/{testdriver → _testdriver}/acceptance/hover-text-with-description.yaml +0 -0
  187. /package/{testdriver → _testdriver}/acceptance/hover-text.yaml +0 -0
  188. /package/{testdriver → _testdriver}/acceptance/if-else.yaml +0 -0
  189. /package/{testdriver → _testdriver}/acceptance/match-image.yaml +0 -0
  190. /package/{testdriver → _testdriver}/acceptance/press-keys.yaml +0 -0
  191. /package/{testdriver → _testdriver}/acceptance/prompt.yaml +0 -0
  192. /package/{testdriver → _testdriver}/acceptance/remember.yaml +0 -0
  193. /package/{testdriver → _testdriver}/acceptance/screenshots/cart.png +0 -0
  194. /package/{testdriver → _testdriver}/acceptance/scroll-keyboard.yaml +0 -0
  195. /package/{testdriver → _testdriver}/acceptance/scroll-until-image.yaml +0 -0
  196. /package/{testdriver → _testdriver}/acceptance/scroll-until-text.yaml +0 -0
  197. /package/{testdriver → _testdriver}/acceptance/scroll.yaml +0 -0
  198. /package/{testdriver → _testdriver}/acceptance/snippets/match-cart.yaml +0 -0
  199. /package/{testdriver → _testdriver}/acceptance/type.yaml +0 -0
  200. /package/{testdriver → _testdriver}/behavior/failure.yaml +0 -0
  201. /package/{testdriver → _testdriver}/behavior/hover-text.yaml +0 -0
  202. /package/{testdriver → _testdriver}/behavior/lifecycle/postrun.yaml +0 -0
  203. /package/{testdriver → _testdriver}/behavior/lifecycle/prerun.yaml +0 -0
  204. /package/{testdriver → _testdriver}/behavior/lifecycle/provision.yaml +0 -0
  205. /package/{testdriver → _testdriver}/behavior/secrets.yaml +0 -0
  206. /package/{testdriver → _testdriver}/edge-cases/dashcam-chrome.yaml +0 -0
  207. /package/{testdriver → _testdriver}/edge-cases/exec-pwsh-multiline.yaml +0 -0
  208. /package/{testdriver → _testdriver}/edge-cases/js-exception.yaml +0 -0
  209. /package/{testdriver → _testdriver}/edge-cases/js-promise.yaml +0 -0
  210. /package/{testdriver → _testdriver}/edge-cases/lifecycle/postrun.yaml +0 -0
  211. /package/{testdriver → _testdriver}/edge-cases/prompt-in-middle.yaml +0 -0
  212. /package/{testdriver → _testdriver}/edge-cases/prompt-nested.yaml +0 -0
  213. /package/{testdriver → _testdriver}/edge-cases/success-test.yaml +0 -0
  214. /package/{testdriver → _testdriver}/examples/android/example.yaml +0 -0
  215. /package/{testdriver → _testdriver}/examples/android/lifecycle/postrun.yaml +0 -0
  216. /package/{testdriver → _testdriver}/examples/android/lifecycle/provision.yaml +0 -0
  217. /package/{testdriver → _testdriver}/examples/android/readme.md +0 -0
  218. /package/{testdriver → _testdriver}/examples/chrome-extension/lifecycle/provision.yaml +0 -0
  219. /package/{testdriver → _testdriver}/examples/desktop/lifecycle/provision.yaml +0 -0
  220. /package/{testdriver → _testdriver}/examples/vscode-extension/lifecycle/provision.yaml +0 -0
  221. /package/{testdriver → _testdriver}/examples/web/lifecycle/postrun.yaml +0 -0
  222. /package/docs/{account → v6/account}/dashboard.mdx +0 -0
  223. /package/docs/{account → v6/account}/enterprise.mdx +0 -0
  224. /package/docs/{account → v6/account}/pricing.mdx +0 -0
  225. /package/docs/{account → v6/account}/projects.mdx +0 -0
  226. /package/docs/{account → v6/account}/team.mdx +0 -0
  227. /package/docs/{action → v6/action}/ami.mdx +0 -0
  228. /package/docs/{action → v6/action}/performance.mdx +0 -0
  229. /package/docs/{action → v6/action}/secrets.mdx +0 -0
  230. /package/docs/{apps → v6/apps}/chrome-extensions.mdx +0 -0
  231. /package/docs/{apps → v6/apps}/desktop-apps.mdx +0 -0
  232. /package/docs/{apps → v6/apps}/mobile-apps.mdx +0 -0
  233. /package/docs/{apps → v6/apps}/static-websites.mdx +0 -0
  234. /package/docs/{apps → v6/apps}/tauri-apps.mdx +0 -0
  235. /package/docs/{bugs → v6/bugs}/jira.mdx +0 -0
  236. /package/docs/{cli → v6/cli}/overview.mdx +0 -0
  237. /package/docs/{commands → v6/commands}/assert.mdx +0 -0
  238. /package/docs/{commands → v6/commands}/exec.mdx +0 -0
  239. /package/docs/{commands → v6/commands}/focus-application.mdx +0 -0
  240. /package/docs/{commands → v6/commands}/hover-image.mdx +0 -0
  241. /package/docs/{commands → v6/commands}/hover-text.mdx +0 -0
  242. /package/docs/{commands → v6/commands}/if.mdx +0 -0
  243. /package/docs/{commands → v6/commands}/match-image.mdx +0 -0
  244. /package/docs/{commands → v6/commands}/press-keys.mdx +0 -0
  245. /package/docs/{commands → v6/commands}/remember.mdx +0 -0
  246. /package/docs/{commands → v6/commands}/run.mdx +0 -0
  247. /package/docs/{commands → v6/commands}/scroll-until-image.mdx +0 -0
  248. /package/docs/{commands → v6/commands}/scroll-until-text.mdx +0 -0
  249. /package/docs/{commands → v6/commands}/scroll.mdx +0 -0
  250. /package/docs/{commands → v6/commands}/type.mdx +0 -0
  251. /package/docs/{commands → v6/commands}/wait-for-image.mdx +0 -0
  252. /package/docs/{commands → v6/commands}/wait-for-text.mdx +0 -0
  253. /package/docs/{commands → v6/commands}/wait.mdx +0 -0
  254. /package/docs/{exporting → v6/exporting}/junit.mdx +0 -0
  255. /package/docs/{exporting → v6/exporting}/playwright.mdx +0 -0
  256. /package/docs/{features → v6/features}/auto-healing.mdx +0 -0
  257. /package/docs/{features → v6/features}/generation.mdx +0 -0
  258. /package/docs/{features → v6/features}/parallel-testing.mdx +0 -0
  259. /package/docs/{features → v6/features}/reusable-snippets.mdx +0 -0
  260. /package/docs/{features → v6/features}/selectorless.mdx +0 -0
  261. /package/docs/{features → v6/features}/visual-assertions.mdx +0 -0
  262. /package/docs/{getting-started → v6/getting-started}/ci.mdx +0 -0
  263. /package/docs/{getting-started → v6/getting-started}/cli.mdx +0 -0
  264. /package/docs/{getting-started → v6/getting-started}/editing.mdx +0 -0
  265. /package/docs/{getting-started → v6/getting-started}/playwright.mdx +0 -0
  266. /package/docs/{getting-started → v6/getting-started}/running.mdx +0 -0
  267. /package/docs/{getting-started → v6/getting-started}/vscode.mdx +0 -0
  268. /package/docs/{guide → v6/guide}/assertions.mdx +0 -0
  269. /package/docs/{guide → v6/guide}/authentication.mdx +0 -0
  270. /package/docs/{guide → v6/guide}/code.mdx +0 -0
  271. /package/docs/{guide → v6/guide}/locating.mdx +0 -0
  272. /package/docs/{guide → v6/guide}/protips.mdx +0 -0
  273. /package/docs/{guide → v6/guide}/variables.mdx +0 -0
  274. /package/docs/{guide → v6/guide}/waiting.mdx +0 -0
  275. /package/docs/{importing → v6/importing}/csv.mdx +0 -0
  276. /package/docs/{importing → v6/importing}/gherkin.mdx +0 -0
  277. /package/docs/{importing → v6/importing}/jira.mdx +0 -0
  278. /package/docs/{importing → v6/importing}/testrail.mdx +0 -0
  279. /package/docs/{integrations → v6/integrations}/electron.mdx +0 -0
  280. /package/docs/{integrations → v6/integrations}/netlify.mdx +0 -0
  281. /package/docs/{integrations → v6/integrations}/vercel.mdx +0 -0
  282. /package/docs/{interactive → v6/interactive}/explore.mdx +0 -0
  283. /package/docs/{interactive → v6/interactive}/run.mdx +0 -0
  284. /package/docs/{interactive → v6/interactive}/save.mdx +0 -0
  285. /package/docs/{overview → v6/overview}/faq.mdx +0 -0
  286. /package/docs/{overview → v6/overview}/performance.mdx +0 -0
  287. /package/docs/{overview → v6/overview}/quickstart.mdx +0 -0
  288. /package/docs/{overview → v6/overview}/what-is-testdriver.mdx +0 -0
  289. /package/docs/{scenarios → v6/scenarios}/ai-chatbot.mdx +0 -0
  290. /package/docs/{scenarios → v6/scenarios}/cookie-banner.mdx +0 -0
  291. /package/docs/{scenarios → v6/scenarios}/file-upload.mdx +0 -0
  292. /package/docs/{scenarios → v6/scenarios}/form-filling.mdx +0 -0
  293. /package/docs/{scenarios → v6/scenarios}/log-in.mdx +0 -0
  294. /package/docs/{scenarios → v6/scenarios}/pdf-generation.mdx +0 -0
  295. /package/docs/{scenarios → v6/scenarios}/spell-check.mdx +0 -0
  296. /package/docs/{security → v6/security}/action.mdx +0 -0
  297. /package/docs/{security → v6/security}/agent.mdx +0 -0
  298. /package/docs/{security → v6/security}/platform.mdx +0 -0
  299. /package/docs/{tutorials → v6/tutorials}/advanced-test.mdx +0 -0
  300. /package/docs/{tutorials → v6/tutorials}/basic-test.mdx +0 -0
@@ -3,9 +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
- const createRedraw = (emitter, system, sandbox) => {
8
- const redrawThresholdPercent = 0.1;
15
+ const createRedraw = (
16
+ emitter,
17
+ system,
18
+ sandbox,
19
+ defaultOptions = {},
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
+
9
28
  const networkUpdateInterval = 15000;
10
29
 
11
30
  let lastTxBytes = null;
@@ -69,7 +88,6 @@ const createRedraw = (emitter, system, sandbox) => {
69
88
  async function updateNetwork() {
70
89
  if (sandbox && sandbox.instanceSocketConnected) {
71
90
  let network = await sandbox.send({
72
- os: "linux",
73
91
  type: "system.network",
74
92
  });
75
93
  parseNetworkStats(
@@ -113,16 +131,13 @@ const createRedraw = (emitter, system, sandbox) => {
113
131
  { threshold: 0.1 },
114
132
  );
115
133
 
116
- if (differentPixels === 0) {
117
- return false;
118
- } else {
119
- // Calculate percentage difference based on pixel differences
120
- const diffPercentage = (differentPixels / totalPixels) * 100;
121
- return diffPercentage.toFixed(1);
122
- }
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));
123
138
  } catch (error) {
124
139
  console.error("Error comparing images:", error);
125
- return false;
140
+ return 0; // Return 0 on error instead of false
126
141
  }
127
142
  }
128
143
 
@@ -143,46 +158,104 @@ const createRedraw = (emitter, system, sandbox) => {
143
158
  }
144
159
  }
145
160
 
146
- 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
+
147
183
  resetState();
148
- startNetworkMonitoring();
149
- 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
+
150
196
  return startImage;
151
197
  }
152
198
 
153
- async function checkCondition(resolve, startTime, timeoutMs) {
154
- 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;
155
209
  let timeElapsed = Date.now() - startTime;
156
210
  let diffPercent = 0;
157
211
  let isTimeout = timeElapsed > timeoutMs;
158
212
 
159
- 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 });
160
216
  diffPercent = await imageDiffPercent(startImage, nowImage);
161
- 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);
162
224
  }
163
-
164
- // // log redraw as output
165
- let redrawText = screenHasRedrawn
166
- ? theme.green(`y`)
167
- : theme.dim(`${diffPercent}/${redrawThresholdPercent}%`);
168
- let networkText = networkSettled
169
- ? theme.green(`y`)
170
- : theme.dim(
171
- `${Math.trunc((diffRxBytes + diffTxBytes) / networkUpdateInterval)}b/s`,
172
- );
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
+ );
173
244
  let timeoutText = isTimeout
174
245
  ? theme.green(`y`)
175
246
  : theme.dim(`${Math.floor(timeElapsed / 1000)}/${timeoutMs / 1000}s`);
176
247
 
177
248
  emitter.emit(events.redraw.status, {
178
249
  redraw: {
179
- hasRedrawn: screenHasRedrawn,
250
+ enabled: screenRedraw,
251
+ hasRedrawn: effectiveScreenRedrawn,
180
252
  diffPercent,
181
- threshold: redrawThresholdPercent,
253
+ threshold: diffThreshold,
182
254
  text: redrawText,
183
255
  },
184
256
  network: {
185
- settled: networkSettled,
257
+ enabled: networkMonitor,
258
+ settled: effectiveNetworkSettled,
186
259
  rxBytes: diffRxBytes,
187
260
  txBytes: diffTxBytes,
188
261
  text: networkText,
@@ -195,27 +268,42 @@ const createRedraw = (emitter, system, sandbox) => {
195
268
  },
196
269
  });
197
270
 
198
- if ((screenHasRedrawn && networkSettled) || isTimeout) {
271
+ if ((effectiveScreenRedrawn && effectiveNetworkSettled) || isTimeout) {
199
272
  emitter.emit(events.redraw.complete, {
200
- screenHasRedrawn,
201
- networkSettled,
273
+ screenHasRedrawn: effectiveScreenRedrawn,
274
+ networkSettled: effectiveNetworkSettled,
202
275
  isTimeout,
203
276
  timeElapsed,
204
277
  });
205
278
  resolve("true");
206
279
  } else {
207
280
  setTimeout(() => {
208
- checkCondition(resolve, startTime, timeoutMs);
281
+ checkCondition(resolve, startTime, timeoutMs, options);
209
282
  }, 500);
210
283
  }
211
284
  }
212
285
 
213
- 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
+
214
300
  return new Promise((resolve) => {
215
301
  const startTime = Date.now();
216
- // Start network monitoring if not already started
217
- startNetworkMonitoring();
218
- 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);
219
307
  });
220
308
  }
221
309
 
@@ -223,7 +311,7 @@ const createRedraw = (emitter, system, sandbox) => {
223
311
  stopNetworkMonitoring(networkInterval);
224
312
  }
225
313
 
226
- return { start, wait, cleanup };
314
+ return { start, wait, cleanup, DEFAULT_OPTIONS: DEFAULT_REDRAW_OPTIONS };
227
315
  };
228
316
 
229
- 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;
@@ -14,6 +14,8 @@ const createSandbox = (emitter, analytics) => {
14
14
  this.instance = null;
15
15
  this.messageId = 0;
16
16
  this.uniqueId = Math.random().toString(36).substring(7);
17
+ this.os = null; // Store OS value to send with every message
18
+ this.sessionInstance = sessionInstance; // Store session instance to include in messages
17
19
  }
18
20
 
19
21
  send(message) {
@@ -24,6 +26,24 @@ const createSandbox = (emitter, analytics) => {
24
26
  this.messageId++;
25
27
  message.requestId = `${this.uniqueId}-${this.messageId}`;
26
28
 
29
+ // If os is set in the message, store it for future messages
30
+ if (message.os) {
31
+ this.os = message.os;
32
+ }
33
+
34
+ // Add os to every message if it's been set
35
+ if (this.os && !message.os) {
36
+ message.os = this.os;
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
+
27
47
  // Start timing for this message
28
48
  const timingKey = `sandbox-${message.type}`;
29
49
  marky.mark(timingKey);
@@ -51,7 +71,6 @@ const createSandbox = (emitter, analytics) => {
51
71
  async auth(apiKey) {
52
72
  let reply = await this.send({
53
73
  type: "authenticate",
54
- os: "linux",
55
74
  apiKey,
56
75
  });
57
76
 
@@ -65,7 +84,6 @@ const createSandbox = (emitter, analytics) => {
65
84
  async connect(sandboxId, persist = false) {
66
85
  let reply = await this.send({
67
86
  type: "connect",
68
- os: "linux",
69
87
  persist,
70
88
  sandboxId,
71
89
  });
@@ -73,9 +91,12 @@ const createSandbox = (emitter, analytics) => {
73
91
  if (reply.success) {
74
92
  this.instanceSocketConnected = true;
75
93
  emitter.emit(events.sandbox.connected);
94
+ // Return the full reply (includes url and sandbox)
95
+ return reply;
96
+ } else {
97
+ // Throw error to trigger fallback to creating new sandbox
98
+ throw new Error(reply.errorMessage || "Failed to connect to sandbox");
76
99
  }
77
-
78
- return reply.sandbox;
79
100
  }
80
101
 
81
102
  async boot(apiRoot) {
@@ -124,7 +145,9 @@ const createSandbox = (emitter, analytics) => {
124
145
 
125
146
  if (message.error) {
126
147
  emitter.emit(events.error.sandbox, message.errorMessage);
127
- this.ps[message.requestId].reject(JSON.stringify(message));
148
+ const error = new Error(message.errorMessage || "Sandbox error");
149
+ error.responseData = message;
150
+ this.ps[message.requestId].reject(error);
128
151
  } else {
129
152
  emitter.emit(events.sandbox.received);
130
153
 
package/agent/lib/sdk.js CHANGED
@@ -123,6 +123,7 @@ const createSDK = (emitter, config, sessionInstance) => {
123
123
  ...(token && { Authorization: `Bearer ${token}` }), // Add the authorization bearer token only if token is set
124
124
  },
125
125
  responseType: typeof onChunk === "function" ? "stream" : "json",
126
+ timeout: 60000, // 60 second timeout to prevent hanging requests
126
127
  data: {
127
128
  ...data,
128
129
  session: sessionInstance.get(),
@@ -193,6 +194,27 @@ const createSDK = (emitter, config, sessionInstance) => {
193
194
 
194
195
  return value;
195
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
+
196
218
  outputError(error);
197
219
  throw error; // Re-throw the error so calling code can handle it properly
198
220
  }
@@ -8,7 +8,6 @@ const { events } = require("../events.js");
8
8
  const createSystem = (emitter, sandbox, config) => {
9
9
  const screenshot = async (options) => {
10
10
  let { base64 } = await sandbox.send({
11
- os: "linux",
12
11
  type: "system.screenshot",
13
12
  });
14
13
 
@@ -113,7 +112,6 @@ const createSystem = (emitter, sandbox, config) => {
113
112
  const activeWin = async () => {
114
113
  // Get Mouse Position from command line
115
114
  let result = await sandbox.send({
116
- os: "linux",
117
115
  type: "system.get-active-window",
118
116
  });
119
117
 
@@ -123,7 +121,6 @@ const createSystem = (emitter, sandbox, config) => {
123
121
  const getMousePosition = async () => {
124
122
  // Get Mouse Position from command line
125
123
  let result = await sandbox.send({
126
- os: "linux",
127
124
  type: "system.get-mouse-position",
128
125
  });
129
126
 
@@ -62,33 +62,27 @@ const types = () =>
62
62
  },
63
63
  // - command: scroll # Scroll up or down. Make sure the correct portion of the page is focused before scrolling.
64
64
  // direction: down # Available directions are: up, down, left, right
65
- // method: keyboard # Optional. Available methods are: keyboard (default), mouse. Use mouse only if the prompt explicitly asks for it.
66
- // amount: 300 # Optional. The amount of pixels to scroll. Defaults to 300 for keyboard and 200 for mouse.
65
+ // amount: 300 # Optional. The amount of pixels to scroll. Defaults to 300.
67
66
  ScrollCommand: {
68
67
  command: '"scroll"',
69
68
  direction: '"up" | "down" | "left" | "right"',
70
- "method?": '"keyboard" | "mouse"',
71
69
  "amount?": "number",
72
70
  },
73
71
  // - command: scroll-until-text # Scroll until text is found
74
72
  // text: Sign Up # The text to find on screen. The longer and more unique the better.
75
73
  // direction: down # Available directions are: up, down, left, right
76
- // method: keyboard # Optional. Available methods are: keyboard (default), mouse. Use mouse only if the prompt explicitly asks for it.
77
74
  ScrollUntilTextCommand: {
78
75
  command: '"scroll-until-text"',
79
76
  text: "string",
80
77
  direction: '"up" | "down" | "left" | "right"',
81
- "method?": '"keyboard" | "mouse"',
82
78
  },
83
79
  // - command: scroll-until-image # Scroll until icon or image is found
84
80
  // description: Submit at the bottom of the form
85
81
  // direction: down # Available directions are: up, down, left, rights
86
- // method: keyboard # Optional. Available methods are: keyboard (default), mouse. Use mouse only if the prompt explicitly asks for it.
87
82
  ScrollUntilImageCommand: {
88
83
  command: '"scroll-until-image"',
89
84
  description: "string",
90
85
  direction: '"up" | "down" | "left" | "right"',
91
- "method?": '"keyboard" | "mouse"',
92
86
  },
93
87
  // - command: wait-for-text # Wait until text is seen on screen. Not recommended unless explicitly requested by user.
94
88
  // text: Copyright 2024 # The text to find on screen.
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Debug script to inspect the full locate API response
5
+ * Run this with: TD_API_KEY=your_key node debug-locate-response.js
6
+ */
7
+
8
+ const TestDriverSDK = require("./sdk.js");
9
+
10
+ async function debugLocateResponse() {
11
+ const client = new TestDriverSDK(process.env.TD_API_KEY);
12
+
13
+ try {
14
+ console.log("Connecting to sandbox (Linux)...");
15
+ await client.connect({ headless: true });
16
+
17
+ console.log("Opening a test page...");
18
+ await client.focusApplication("Google Chrome");
19
+ await client.type("https://example.com");
20
+ await client.pressKeys(["enter"]);
21
+
22
+ // Wait for page to load
23
+ await new Promise((resolve) => setTimeout(resolve, 3000));
24
+
25
+ console.log("\nFinding an element to inspect the response...");
26
+ const element = await client.find("the heading that says Example Domain");
27
+
28
+ console.log("\n=".repeat(60));
29
+ console.log("FULL LOCATE API RESPONSE:");
30
+ console.log("=".repeat(60));
31
+
32
+ const response = element.getResponse();
33
+ console.log(JSON.stringify(response, null, 2));
34
+
35
+ console.log("\n=".repeat(60));
36
+ console.log("RESPONSE KEYS:");
37
+ console.log("=".repeat(60));
38
+
39
+ if (response) {
40
+ Object.keys(response).forEach((key) => {
41
+ const value = response[key];
42
+ const type = Array.isArray(value) ? "array" : typeof value;
43
+ const preview =
44
+ typeof value === "string" && value.length > 100
45
+ ? `${value.substring(0, 100)}... (${value.length} chars)`
46
+ : typeof value === "object"
47
+ ? JSON.stringify(value)
48
+ : value;
49
+
50
+ console.log(` ${key} (${type}): ${preview}`);
51
+ });
52
+ }
53
+
54
+ console.log("\n=".repeat(60));
55
+ console.log("ELEMENT PROPERTIES:");
56
+ console.log("=".repeat(60));
57
+ console.log(" found:", element.found());
58
+ console.log(" x:", element.x);
59
+ console.log(" y:", element.y);
60
+ console.log(" centerX:", element.centerX);
61
+ console.log(" centerY:", element.centerY);
62
+ console.log(" width:", element.width);
63
+ console.log(" height:", element.height);
64
+ console.log(" confidence:", element.confidence);
65
+ console.log(" text:", element.text);
66
+ console.log(" label:", element.label);
67
+ console.log(
68
+ " screenshot:",
69
+ element.screenshot ? `${element.screenshot.length} chars` : null,
70
+ );
71
+ console.log(" boundingBox:", element.boundingBox);
72
+
73
+ await client.disconnect();
74
+ } catch (error) {
75
+ console.error("Error:", error.message);
76
+ console.error(error.stack);
77
+ await client.disconnect();
78
+ process.exit(1);
79
+ }
80
+ }
81
+
82
+ debugLocateResponse();
@@ -34,10 +34,14 @@
34
34
  top: 0;
35
35
  left: 0;
36
36
  transform-origin: top left;
37
+ overflow: hidden;
37
38
  }
38
39
 
40
+ html,
39
41
  body {
40
42
  overflow: hidden;
43
+ width: 100%;
44
+ height: 100%;
41
45
  }
42
46
 
43
47
  *::-webkit-scrollbar {
@@ -66,6 +70,7 @@
66
70
 
67
71
  .overlay {
68
72
  position: relative;
73
+ overflow: hidden;
69
74
  }
70
75
 
71
76
  .screenshot {
@@ -179,6 +184,7 @@
179
184
  width: 100%;
180
185
  height: 100%;
181
186
  z-index: 1;
187
+ overflow: hidden;
182
188
  }
183
189
 
184
190
  /* Loading screen styles */
@@ -354,6 +360,10 @@
354
360
 
355
361
  const iframe = document.querySelector("#vm-iframe");
356
362
 
363
+ // Detect if OS is Linux
364
+ const isLinux = parsedData.os === "linux";
365
+ const topBarOffset = isLinux ? 14 : 0;
366
+
357
367
  // set overlay width and height to match the given resolution
358
368
  const overlayWidth = parsedData.resolution[0];
359
369
  const overlayHeight = parsedData.resolution[1];
@@ -361,7 +371,8 @@
361
371
  iframe.style.display = "block";
362
372
  iframe.src = parsedData.url;
363
373
  iframe.style.width = overlayWidth + "px";
364
- iframe.style.height = overlayHeight + "px";
374
+ // Increase iframe height by 14px for Linux to account for top bar
375
+ iframe.style.height = overlayHeight + topBarOffset + "px";
365
376
 
366
377
  // Calculate scale factor to fit within window if needed
367
378
  const windowWidth = window.innerWidth;
@@ -513,7 +524,7 @@
513
524
  const boxElement = document.createElement("div");
514
525
  boxElement.className = "bounding-box";
515
526
  boxElement.style.left = toCss(box.x);
516
- boxElement.style.top = toCss(box.y);
527
+ boxElement.style.top = toCss(box.y + topBarOffset);
517
528
  boxElement.style.width = toCss(box.width);
518
529
  boxElement.style.height = toCss(box.height);
519
530
  effects.appendChild(boxElement);
@@ -541,14 +552,14 @@
541
552
  // Mouse event handlers
542
553
  addEventHandler(events.mouseMove, (event, { x, y } = {}) => {
543
554
  mouse.style.marginLeft = toCss(x);
544
- mouse.style.marginTop = toCss(y);
555
+ mouse.style.marginTop = toCss(y + topBarOffset);
545
556
  });
546
557
 
547
558
  addEventHandler(
548
559
  events.mouseClick,
549
560
  (event, { x, y, click = "single" } = {}) => {
550
561
  mouse.style.marginLeft = toCss(x);
551
- mouse.style.marginTop = toCss(y);
562
+ mouse.style.marginTop = toCss(y + topBarOffset);
552
563
  // Reset class so animation can restart
553
564
  mouse.setAttribute("class", "mouse");
554
565
  // Force reflow