testdriverai 7.5.25 → 7.6.0-test.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.
@@ -867,6 +867,7 @@ registerAppTool(
867
867
 
868
868
  // Store cropped image for resource serving (instead of inline data URL)
869
869
  let croppedImageResourceUri: string | undefined;
870
+ let screenshotResourceUri: string | undefined;
870
871
  const croppedImage = rawResponse.croppedImage;
871
872
  if (croppedImage) {
872
873
  const imageData = croppedImage.startsWith('data:')
@@ -875,6 +876,18 @@ registerAppTool(
875
876
  croppedImageResourceUri = storeImage(imageData, "cropped");
876
877
  // Remove croppedImage from response to avoid context bloat
877
878
  delete rawResponse.croppedImage;
879
+ } else if (!found) {
880
+ // Element not found and no cropped image - capture a fresh screenshot
881
+ // so the user can see what's currently visible on screen
882
+ try {
883
+ const screenshotBase64 = await sdk.agent.system.captureScreenBase64(1, false, true);
884
+ if (screenshotBase64) {
885
+ screenshotResourceUri = storeImage(screenshotBase64, "screenshot");
886
+ logger.debug("find: Captured screenshot for not-found state");
887
+ }
888
+ } catch (e) {
889
+ logger.warn("find: Failed to capture screenshot for not-found state", { error: String(e) });
890
+ }
878
891
  }
879
892
 
880
893
  // Remove extractedText and pixelDiffImage from response to reduce context bloat
@@ -904,6 +917,7 @@ registerAppTool(
904
917
  element: elementInfo,
905
918
  ref: elementRef,
906
919
  croppedImageResourceUri,
920
+ screenshotResourceUri,
907
921
  duration,
908
922
  },
909
923
  generatedCode
@@ -988,6 +1002,7 @@ registerAppTool(
988
1002
 
989
1003
  // Store cropped image for resource serving (instead of inline data URL)
990
1004
  let croppedImageResourceUri: string | undefined;
1005
+ let screenshotResourceUri: string | undefined;
991
1006
  const croppedImage = rawResponse.croppedImage;
992
1007
  if (croppedImage) {
993
1008
  const imageData = croppedImage.startsWith('data:')
@@ -996,6 +1011,18 @@ registerAppTool(
996
1011
  croppedImageResourceUri = storeImage(imageData, "cropped");
997
1012
  // Remove croppedImage from response to avoid context bloat
998
1013
  delete rawResponse.croppedImage;
1014
+ } else if (count === 0) {
1015
+ // No elements found and no cropped image - capture a fresh screenshot
1016
+ // so the user can see what's currently visible on screen
1017
+ try {
1018
+ const screenshotBase64 = await sdk.agent.system.captureScreenBase64(1, false, true);
1019
+ if (screenshotBase64) {
1020
+ screenshotResourceUri = storeImage(screenshotBase64, "screenshot");
1021
+ logger.debug("findall: Captured screenshot for not-found state");
1022
+ }
1023
+ } catch (e) {
1024
+ logger.warn("findall: Failed to capture screenshot for not-found state", { error: String(e) });
1025
+ }
999
1026
  }
1000
1027
 
1001
1028
  // Remove extractedText and pixelDiffImage from response to reduce context bloat
@@ -1019,6 +1046,7 @@ registerAppTool(
1019
1046
  refs,
1020
1047
  elements: elementInfos,
1021
1048
  croppedImageResourceUri,
1049
+ screenshotResourceUri,
1022
1050
  duration,
1023
1051
  },
1024
1052
  generatedCode
@@ -1317,6 +1345,7 @@ registerAppTool(
1317
1345
 
1318
1346
  // Store cropped image (screenshot) for resource serving
1319
1347
  let croppedImageResourceUri: string | undefined;
1348
+ let screenshotResourceUri: string | undefined;
1320
1349
  const croppedImage = rawResponse.croppedImage;
1321
1350
  if (croppedImage) {
1322
1351
  const imageData = croppedImage.startsWith('data:')
@@ -1324,6 +1353,18 @@ registerAppTool(
1324
1353
  : croppedImage;
1325
1354
  croppedImageResourceUri = storeImage(imageData, "screenshot");
1326
1355
  delete rawResponse.croppedImage;
1356
+ } else {
1357
+ // No cropped image - capture a fresh screenshot so the user can see
1358
+ // what's currently visible on screen when element was not found
1359
+ try {
1360
+ const screenshotBase64 = await sdk.agent.system.captureScreenBase64(1, false, true);
1361
+ if (screenshotBase64) {
1362
+ screenshotResourceUri = storeImage(screenshotBase64, "screenshot");
1363
+ logger.debug("find_and_click: Captured screenshot for not-found state");
1364
+ }
1365
+ } catch (e) {
1366
+ logger.warn("find_and_click: Failed to capture screenshot for not-found state", { error: String(e) });
1367
+ }
1327
1368
  }
1328
1369
 
1329
1370
  // Remove extractedText and pixelDiffImage from response to reduce context bloat
@@ -1338,6 +1379,7 @@ registerAppTool(
1338
1379
  action: "find_and_click",
1339
1380
  error: "Element not found",
1340
1381
  croppedImageResourceUri,
1382
+ screenshotResourceUri,
1341
1383
  duration
1342
1384
  }
1343
1385
  );
@@ -1760,9 +1802,9 @@ You can optionally provide a reference image URI to compare against a previous s
1760
1802
  server.registerTool(
1761
1803
  "exec",
1762
1804
  {
1763
- description: "Execute code in the sandbox (JavaScript, shell, or PowerShell)",
1805
+ description: "Execute shell or PowerShell commands in the sandbox",
1764
1806
  inputSchema: z.object({
1765
- language: z.enum(["js", "sh", "pwsh"]).default("js"),
1807
+ language: z.enum(["sh", "pwsh"]).default("sh"),
1766
1808
  code: z.string().describe("Code to execute"),
1767
1809
  timeout: z.number().default(30000).describe("Timeout in ms"),
1768
1810
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.5.25",
3
+ "version": "7.6.0-test.0",
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",
@@ -47,7 +47,11 @@
47
47
  "bundle": "node build.mjs",
48
48
  "test": "mocha test/*",
49
49
  "test:sdk": "vitest run",
50
- "test:sdk:windows": "TEST_PLATFORM=windows vitest run",
50
+ "test:sdk:dev": "vitest run --project dev",
51
+ "test:sdk:staging": "vitest run --project staging",
52
+ "test:sdk:canary": "vitest run --project canary",
53
+ "test:sdk:stable": "vitest run --project stable",
54
+ "test:sdk:windows": "TD_OS=windows vitest run",
51
55
  "test:sdk:mac": "TEST_PLATFORM=mac vitest run",
52
56
  "test:sdk:linux": "TEST_PLATFORM=linux vitest run",
53
57
  "test:sdk:watch": "vitest",
package/sdk.d.ts CHANGED
@@ -13,7 +13,7 @@ export type ClickAction =
13
13
  export type ScrollDirection = "up" | "down" | "left" | "right";
14
14
  export type ScrollMethod = "keyboard" | "mouse";
15
15
  export type TextMatchMethod = "ai" | "turbo";
16
- export type ExecLanguage = "js" | "pwsh" | "sh";
16
+ export type ExecLanguage = "sh" | "pwsh";
17
17
  /**
18
18
  * Preview mode for live test visualization
19
19
  * - "browser": Opens debugger in default browser (default)
@@ -218,7 +218,7 @@ export type KeyboardKey =
218
218
  | "optionright";
219
219
 
220
220
  export interface TestDriverOptions {
221
- /** API endpoint URL (default: 'https://v6.testdriver.ai') */
221
+ /** API endpoint URL (default depends on release channel: latest → 'https://api.testdriver.ai') */
222
222
  apiRoot?: string;
223
223
  /** Sandbox resolution (default: '1366x768') */
224
224
  resolution?: string;
package/sdk.js CHANGED
@@ -1402,7 +1402,7 @@ function normalizeRedrawOptions(opts) {
1402
1402
  * @typedef {'up' | 'down' | 'left' | 'right'} ScrollDirection
1403
1403
  * @typedef {'keyboard' | 'mouse'} ScrollMethod
1404
1404
  * @typedef {'ai' | 'turbo'} TextMatchMethod
1405
- * @typedef {'js' | 'pwsh'} ExecLanguage
1405
+ * @typedef {'sh' | 'pwsh'} ExecLanguage
1406
1406
  * @typedef {'\\t' | '\n' | '\r' | ' ' | '!' | '"' | '#' | '$' | '%' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | '-' | '.' | '/' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | ':' | ';' | '<' | '=' | '>' | '?' | '@' | '[' | '\\' | ']' | '^' | '_' | '`' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z' | '{' | '|' | '}' | '~' | 'accept' | 'add' | 'alt' | 'altleft' | 'altright' | 'apps' | 'backspace' | 'browserback' | 'browserfavorites' | 'browserforward' | 'browserhome' | 'browserrefresh' | 'browsersearch' | 'browserstop' | 'capslock' | 'clear' | 'convert' | 'ctrl' | 'ctrlleft' | 'ctrlright' | 'decimal' | 'del' | 'delete' | 'divide' | 'down' | 'end' | 'enter' | 'esc' | 'escape' | 'execute' | 'f1' | 'f10' | 'f11' | 'f12' | 'f13' | 'f14' | 'f15' | 'f16' | 'f17' | 'f18' | 'f19' | 'f2' | 'f20' | 'f21' | 'f22' | 'f23' | 'f24' | 'f3' | 'f4' | 'f5' | 'f6' | 'f7' | 'f8' | 'f9' | 'final' | 'fn' | 'hanguel' | 'hangul' | 'hanja' | 'help' | 'home' | 'insert' | 'junja' | 'kana' | 'kanji' | 'launchapp1' | 'launchapp2' | 'launchmail' | 'launchmediaselect' | 'left' | 'modechange' | 'multiply' | 'nexttrack' | 'nonconvert' | 'num0' | 'num1' | 'num2' | 'num3' | 'num4' | 'num5' | 'num6' | 'num7' | 'num8' | 'num9' | 'numlock' | 'pagedown' | 'pageup' | 'pause' | 'pgdn' | 'pgup' | 'playpause' | 'prevtrack' | 'print' | 'printscreen' | 'prntscrn' | 'prtsc' | 'prtscr' | 'return' | 'right' | 'scrolllock' | 'select' | 'separator' | 'shift' | 'shiftleft' | 'shiftright' | 'sleep' | 'space' | 'stop' | 'subtract' | 'tab' | 'up' | 'volumedown' | 'volumemute' | 'volumeup' | 'win' | 'winleft' | 'winright' | 'yen' | 'command' | 'option' | 'optionleft' | 'optionright'} KeyboardKey
1407
1407
  */
1408
1408
 
@@ -1871,6 +1871,8 @@ class TestDriverSDK {
1871
1871
  "--no-first-run",
1872
1872
  "--no-experiments",
1873
1873
  "--disable-infobars",
1874
+ "--disable-features=StartupBrowserCreator",
1875
+ "--disable-features=ChromeWhatsNewUI",
1874
1876
  `--user-data-dir=${userDataDir}`,
1875
1877
  );
1876
1878
 
@@ -2862,6 +2864,9 @@ CAPTCHA_SOLVER_EOF`,
2862
2864
  sandboxId: this.instance?.instanceId,
2863
2865
  });
2864
2866
 
2867
+ // Log environment info (non-blocking, skip on stable)
2868
+ this._logEnvironmentInfo();
2869
+
2865
2870
  return this.instance;
2866
2871
  }
2867
2872
 
@@ -2890,8 +2895,10 @@ CAPTCHA_SOLVER_EOF`,
2890
2895
 
2891
2896
  // Always close the sandbox WebSocket connection to clean up resources
2892
2897
  // This ensures we don't leave orphaned connections even if connect() failed
2898
+ // Must be awaited so presence.leave() completes before we return —
2899
+ // otherwise the concurrency counter on the API stays stale.
2893
2900
  if (this.sandbox && typeof this.sandbox.close === "function") {
2894
- this.sandbox.close();
2901
+ await this.sandbox.close();
2895
2902
  }
2896
2903
 
2897
2904
  // Remove all event listeners on the emitter to release references
@@ -3799,6 +3806,46 @@ CAPTCHA_SOLVER_EOF`,
3799
3806
  * Set up logging for the SDK
3800
3807
  * @private
3801
3808
  */
3809
+ /**
3810
+ * Log environment info (version, API URL, git commit) after connect.
3811
+ * Fires asynchronously so it never blocks the test.
3812
+ * Suppressed when the API reports the "stable" channel.
3813
+ * @private
3814
+ */
3815
+ _logEnvironmentInfo() {
3816
+ const apiRoot = this.config?.TD_API_ROOT || 'unknown';
3817
+ const sdkVersion = require('./package.json').version;
3818
+ const http = apiRoot.startsWith('https') ? require('https') : require('http');
3819
+
3820
+ const url = apiRoot + '/api/entrance/version';
3821
+ const req = http.get(url, { timeout: 5000 }, (res) => {
3822
+ let data = '';
3823
+ res.on('data', (chunk) => { data += chunk; });
3824
+ res.on('end', () => {
3825
+ try {
3826
+ const info = JSON.parse(data);
3827
+ if (info.channel === 'stable') return; // don't show on stable
3828
+ const commit = info.commit || 'unknown';
3829
+ const shortCommit = commit.substring(0, 7);
3830
+ const commitUrl = commit !== 'unknown'
3831
+ ? `https://github.com/testdriverai/mono/commit/${commit}`
3832
+ : null;
3833
+ const lines = [
3834
+ '',
3835
+ ` TestDriver SDK v${sdkVersion}`,
3836
+ ` API: ${apiRoot} (${info.channel || 'unknown'} v${info.version || '?'})`,
3837
+ commitUrl
3838
+ ? ` Commit: ${shortCommit} → ${commitUrl}`
3839
+ : ` Commit: ${shortCommit}`,
3840
+ '',
3841
+ ];
3842
+ console.log(lines.join('\n'));
3843
+ } catch (_) { /* ignore parse errors */ }
3844
+ });
3845
+ });
3846
+ req.on('error', () => { /* ignore network errors */ });
3847
+ }
3848
+
3802
3849
  _setupLogging() {
3803
3850
  // Track the last fatal error message to throw on exit
3804
3851
  let lastFatalError = null;
package/vitest.config.mjs CHANGED
@@ -1,5 +1,7 @@
1
1
  import TestDriver from "testdriverai/vitest";
2
2
  import { defineConfig } from "vitest/config";
3
+ import { readFileSync, existsSync } from "fs";
4
+ import { resolve } from "path";
3
5
 
4
6
  // Always include AWS setup - it will be a no-op unless TD_OS=windows
5
7
  // Note: dotenv is loaded automatically by the TestDriver SDK
@@ -8,19 +10,61 @@ const setupFiles = [
8
10
  "testdriverai/vitest/setup-aws"
9
11
  ];
10
12
 
13
+ const sharedTestConfig = {
14
+ retry: 0,
15
+ testTimeout: 900000,
16
+ hookTimeout: 900000,
17
+ maxConcurrency: 100,
18
+ disableConsoleIntercept: false,
19
+ silent: false,
20
+ reporters: [
21
+ "verbose",
22
+ TestDriver()
23
+ ],
24
+ setupFiles,
25
+ include: ["examples/**/*.test.mjs"],
26
+ };
27
+
28
+ // ── Parse a simple KEY=VALUE .env file ──────────────────────────────
29
+ function parseEnvFile(filePath) {
30
+ if (!existsSync(filePath)) return {};
31
+ const env = {};
32
+ for (const line of readFileSync(filePath, "utf-8").split("\n")) {
33
+ const trimmed = line.trim();
34
+ if (!trimmed || trimmed.startsWith("#")) continue;
35
+ const idx = trimmed.indexOf("=");
36
+ if (idx === -1) continue;
37
+ env[trimmed.slice(0, idx)] = trimmed.slice(idx + 1);
38
+ }
39
+ return env;
40
+ }
41
+
42
+ // ── Load base .env + per-environment overlay ────────────────────────
43
+ const monoRoot = resolve(import.meta.dirname, "..");
44
+ const baseEnv = parseEnvFile(resolve(monoRoot, ".env"));
45
+
46
+ const environments = ["dev", "test", "canary", "stable"];
47
+
48
+ function envForProject(envName) {
49
+ const overlay = parseEnvFile(resolve(monoRoot, "envs", `${envName}.env`));
50
+ return { ...baseEnv, ...overlay };
51
+ }
52
+
53
+ // ── If TD_ENV is set (e.g. from CLI), only run that environment ─────
54
+ // Usage: TD_ENV=dev vitest run
55
+ // TD_ENV=canary vitest run examples/assert.test.mjs
56
+ // vitest run --project dev
57
+ // vitest run --project canary --project stable
11
58
  export default defineConfig({
12
59
  test: {
13
- retry: 0,
14
- testTimeout: 900000,
15
- hookTimeout: 900000,
16
- maxConcurrency: 100,
17
- maxWorkers: 16,
18
- disableConsoleIntercept: false,
19
- silent: false,
20
- reporters: [
21
- "verbose",
22
- TestDriver()
23
- ],
24
- setupFiles,
60
+ ...sharedTestConfig,
61
+ env: envForProject(process.env.TD_ENV || "dev"),
62
+ projects: environments.map((envName) => ({
63
+ extends: true,
64
+ test: {
65
+ name: envName,
66
+ env: envForProject(envName),
67
+ },
68
+ })),
25
69
  },
26
70
  });