testdriverai 7.2.75 → 7.2.77

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.
@@ -8,6 +8,7 @@ const DEFAULT_REDRAW_OPTIONS = {
8
8
  enabled: true, // Master switch to enable/disable redraw detection
9
9
  screenRedraw: true, // Enable screen redraw detection
10
10
  networkMonitor: true, // Enable network activity monitoring
11
+ noChangeTimeoutMs: 1500, // Exit early if no screen change detected after this time
11
12
  };
12
13
 
13
14
  // Factory function that creates redraw functionality with the provided system instance
@@ -235,7 +236,7 @@ const createRedraw = (
235
236
  }
236
237
 
237
238
  async function checkCondition(resolve, startTime, timeoutMs, options) {
238
- const { enabled, screenRedraw, networkMonitor } = options;
239
+ const { enabled, screenRedraw, networkMonitor, noChangeTimeoutMs = 1500 } = options;
239
240
 
240
241
  // If redraw is disabled, resolve immediately
241
242
  if (!enabled) {
@@ -248,6 +249,9 @@ const createRedraw = (
248
249
  let diffFromInitial = 0;
249
250
  let diffFromLast = 0;
250
251
  let isTimeout = timeElapsed > timeoutMs;
252
+
253
+ // Early exit: if no screen change detected after noChangeTimeoutMs, assume action had no visual effect
254
+ const noChangeTimeout = screenRedraw && !hasChangedFromInitial && timeElapsed > noChangeTimeoutMs;
251
255
 
252
256
  // Screen stability detection:
253
257
  // 1. Check if screen has changed from initial (detect transition)
@@ -276,8 +280,14 @@ const createRedraw = (
276
280
  lastScreenImage = nowImage;
277
281
  }
278
282
 
279
- // Screen is settled when: it has changed from initial AND consecutive frames are now stable
280
- const screenSettled = hasChangedFromInitial && consecutiveFramesStable;
283
+ // Screen is settled when:
284
+ // 1. It has changed from initial AND consecutive frames are now stable, OR
285
+ // 2. No change was detected after noChangeTimeoutMs (action had no visual effect)
286
+ const screenSettled = (hasChangedFromInitial && consecutiveFramesStable) || noChangeTimeout;
287
+
288
+ if (noChangeTimeout && !hasChangedFromInitial) {
289
+ emitter.emit(events.log.debug, `[redraw] No screen change detected after ${noChangeTimeoutMs}ms, settling early`);
290
+ }
281
291
 
282
292
  // If screen redraw is disabled, consider it as "settled"
283
293
  const effectiveScreenSettled = screenRedraw ? screenSettled : true;
@@ -334,12 +344,13 @@ const createRedraw = (
334
344
  networkSettled: effectiveNetworkSettled,
335
345
  isTimeout,
336
346
  timeElapsed,
347
+ noChangeTimeout,
337
348
  });
338
349
  resolve("true");
339
350
  } else {
340
351
  setTimeout(() => {
341
352
  checkCondition(resolve, startTime, timeoutMs, options);
342
- }, 500);
353
+ }, 250);
343
354
  }
344
355
  }
345
356
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: testdriver
3
3
  description: An expert at creating and refining automated tests using TestDriver.ai
4
- tools:
4
+ tools: ["*"]
5
5
  mcp-servers:
6
6
  testdriver:
7
7
  command: npx
@@ -11,6 +11,7 @@ mcp-servers:
11
11
  - testdriverai-mcp
12
12
  env:
13
13
  TD_API_KEY: ${TD_API_KEY}
14
+ tools: ["testdriverai"]
14
15
  ---
15
16
 
16
17
  # TestDriver Expert
@@ -43,7 +44,7 @@ Use this agent when the user asks to:
43
44
  4. **⚠️ WRITE CODE IMMEDIATELY**: After EVERY successful action, append the generated code to the test file RIGHT AWAY. Do NOT wait until the end.
44
45
  5. **Verify Actions**: Use `check` after actions to verify they succeeded (for YOUR understanding only).
45
46
  6. **Add Assertions**: Use `assert` for test conditions that should be in the final test file.
46
- 7. **⚠️ RUN THE TEST YOURSELF**: Use `npx vitest run <testFile>` to run the test - do NOT tell the user to run it. Iterate until it passes.
47
+ 7. **⚠️ RUN THE TEST YOURSELF**: Use `npx vitest run <testFile> --reporter=dot` to run the test - do NOT tell the user to run it. Iterate until it passes.
47
48
 
48
49
  ## Prerequisites
49
50
 
@@ -293,11 +294,15 @@ assert({ assertion: "the dashboard is visible" })
293
294
  **⚠️ YOU must run the test - do NOT tell the user to run it:**
294
295
 
295
296
  ```bash
296
- npx vitest run tests/login.test.mjs
297
+ npx vitest run tests/login.test.mjs --reporter=dot
297
298
  ```
298
299
 
300
+ **Always use `--reporter=dot`** for cleaner, more concise output that's easier to parse.
301
+
299
302
  Analyze the output, fix any issues, and iterate until the test passes.
300
303
 
304
+ **⚠️ ALWAYS share the test report link with the user.** After each test run, look for the "View Report" URL in the test output (e.g., `https://app.testdriver.ai/projects/.../reports/...`) and share it with the user so they can review the recording and results.
305
+
301
306
  ### MCP Tools Reference
302
307
 
303
308
  | Tool | Description |
@@ -462,16 +467,17 @@ const result = await testdriver.assert("dashboard is visible");
462
467
  ## Tips for Agents
463
468
 
464
469
  1. **⚠️ WRITE CODE IMMEDIATELY** - After EVERY successful MCP action, append the generated code to the test file RIGHT AWAY. Do NOT wait until the session ends.
465
- 2. **⚠️ RUN TESTS YOURSELF** - Do NOT tell the user to run tests. YOU must run the tests using `npx vitest run <testFile>`. Analyze the output and iterate until the test passes.
470
+ 2. **⚠️ RUN TESTS YOURSELF** - Do NOT tell the user to run tests. YOU must run the tests using `npx vitest run <testFile> --reporter=dot`. Always use `--reporter=dot` for cleaner output. Analyze the output and iterate until the test passes. **Always share the test report link** (e.g., `https://app.testdriver.ai/projects/.../reports/...`) with the user after each run.
466
471
  3. **⚠️ ADD SCREENSHOTS LIBERALLY** - Include `await testdriver.screenshot()` throughout your tests: after provision, before/after clicks, after typing, and before assertions. This creates a visual trail that makes debugging failures much easier.
467
- 4. **Use MCP tools for development** - Build tests interactively with visual feedback
468
- 5. **Always check `sdk.d.ts`** for method signatures and types when debugging generated tests
469
- 6. **Look at test samples** in `node_modules/testdriverai/test` for working examples
470
- 7. **Use `check` to understand screen state** - This is how you verify what the sandbox shows during MCP development.
471
- 8. **Use `check` after actions, `assert` for test files** - `check` gives detailed AI analysis (no code), `assert` gives boolean pass/fail (generates code)
472
- 9. **Be specific with element descriptions** - "blue Sign In button in the header" > "button"
473
- 10. **Start simple** - get one step working before adding more
474
- 11. **Always `await` async methods** - TestDriver will warn if you forget, but for TypeScript projects, add `@typescript-eslint/no-floating-promises` to your ESLint config to catch missing `await` at compile time:
472
+ 4. **⚠️ NEVER USE `.wait()`** - Do NOT use any `.wait()` method. Instead, use `find()` with a `timeout` option to poll for elements, or use `assert()` / `check()` to verify state. Explicit waits are flaky and slow.
473
+ 5. **Use MCP tools for development** - Build tests interactively with visual feedback
474
+ 6. **Always check `sdk.d.ts`** for method signatures and types when debugging generated tests
475
+ 7. **Look at test samples** in `node_modules/testdriverai/test` for working examples
476
+ 8. **Use `check` to understand screen state** - This is how you verify what the sandbox shows during MCP development.
477
+ 9. **Use `check` after actions, `assert` for test files** - `check` gives detailed AI analysis (no code), `assert` gives boolean pass/fail (generates code)
478
+ 10. **Be specific with element descriptions** - "blue Sign In button in the header" > "button"
479
+ 11. **Start simple** - get one step working before adding more
480
+ 12. **Always `await` async methods** - TestDriver will warn if you forget, but for TypeScript projects, add `@typescript-eslint/no-floating-promises` to your ESLint config to catch missing `await` at compile time:
475
481
 
476
482
  ```json
477
483
  // eslint.config.js (for TypeScript projects)
@@ -485,12 +485,20 @@ jobs:
485
485
 
486
486
  if (!fs.existsSync(mcpConfigFile)) {
487
487
  const mcpConfig = {
488
+ inputs: [
489
+ {
490
+ type: "promptString",
491
+ id: "testdriver-api-key",
492
+ description: "TestDriver API Key From https://console.testdriver.ai/team",
493
+ password: true,
494
+ },
495
+ ],
488
496
  servers: {
489
497
  testdriver: {
490
498
  command: "npx",
491
499
  args: ["-p", "testdriverai@beta", "testdriverai-mcp"],
492
500
  env: {
493
- TD_API_KEY: "${TD_API_KEY}",
501
+ TD_API_KEY: "${input:testdriver-api-key}",
494
502
  },
495
503
  },
496
504
  },
@@ -41,6 +41,7 @@ if (isSentryEnabled()) {
41
41
  release: `testdriverai-mcp@${version}`,
42
42
  sampleRate: 1.0,
43
43
  tracesSampleRate: 1.0,
44
+ sendDefaultPii: true,
44
45
  integrations: [Sentry.httpIntegration(), Sentry.nodeContextIntegration()],
45
46
  initialScope: {
46
47
  tags: {
@@ -265,11 +266,16 @@ function createToolResult(success, textContent, structuredData, generatedCode) {
265
266
  },
266
267
  };
267
268
  }
268
- // Create MCP server
269
- const server = new McpServer({
270
- name: "testdriver",
271
- version: "1.0.0",
272
- });
269
+ // Create MCP server wrapped with Sentry for automatic tracing
270
+ const server = isSentryEnabled()
271
+ ? Sentry.wrapMcpServerWithSentry(new McpServer({
272
+ name: "testdriver",
273
+ version: version,
274
+ }))
275
+ : new McpServer({
276
+ name: "testdriver",
277
+ version: version,
278
+ });
273
279
  // Element reference storage (for click/hover after find)
274
280
  // Stores actual Element instances - no raw coordinates as input
275
281
  const elementRefs = new Map();
@@ -1341,6 +1347,198 @@ server.registerTool("exec", {
1341
1347
  throw error;
1342
1348
  }
1343
1349
  });
1350
+ // List Local Screenshots - lists screenshots saved to .testdriver directory
1351
+ server.registerTool("list_local_screenshots", {
1352
+ description: `List screenshots saved in the .testdriver directory.
1353
+
1354
+ This tool helps you find screenshots that have been saved during test runs or via the screenshot tool.
1355
+ Screenshots are organized in subdirectories like 'mcp-screenshots' and 'screenshots'.
1356
+
1357
+ Returns a list of screenshot paths that can be viewed with the 'view_local_screenshot' tool.`,
1358
+ inputSchema: z.object({
1359
+ directory: z.string().optional().describe("Subdirectory to list (e.g., 'mcp-screenshots', 'screenshots'). If not provided, lists all subdirectories."),
1360
+ }),
1361
+ }, async (params) => {
1362
+ const startTime = Date.now();
1363
+ logger.info("list_local_screenshots: Starting", { directory: params.directory });
1364
+ try {
1365
+ // Find .testdriver directory - check current working directory and common locations
1366
+ const possiblePaths = [
1367
+ path.join(process.cwd(), ".testdriver"),
1368
+ path.join(os.homedir(), ".testdriver"),
1369
+ ];
1370
+ let testdriverDir = null;
1371
+ for (const p of possiblePaths) {
1372
+ if (fs.existsSync(p)) {
1373
+ testdriverDir = p;
1374
+ break;
1375
+ }
1376
+ }
1377
+ if (!testdriverDir) {
1378
+ logger.warn("list_local_screenshots: .testdriver directory not found");
1379
+ return createToolResult(false, "No .testdriver directory found. Screenshots are saved here during test runs.", { error: "Directory not found" });
1380
+ }
1381
+ const screenshots = [];
1382
+ // Function to recursively find PNG files
1383
+ const findPngFiles = (dir) => {
1384
+ if (!fs.existsSync(dir))
1385
+ return;
1386
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
1387
+ for (const entry of entries) {
1388
+ const fullPath = path.join(dir, entry.name);
1389
+ if (entry.isDirectory()) {
1390
+ // If a specific directory was requested, only search that one
1391
+ if (!params.directory || entry.name === params.directory || dir !== testdriverDir) {
1392
+ findPngFiles(fullPath);
1393
+ }
1394
+ }
1395
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith(".png")) {
1396
+ const stats = fs.statSync(fullPath);
1397
+ screenshots.push({
1398
+ path: fullPath,
1399
+ name: entry.name,
1400
+ modified: stats.mtime,
1401
+ size: stats.size,
1402
+ });
1403
+ }
1404
+ }
1405
+ };
1406
+ findPngFiles(testdriverDir);
1407
+ // Sort by modification time (newest first)
1408
+ screenshots.sort((a, b) => b.modified.getTime() - a.modified.getTime());
1409
+ const duration = Date.now() - startTime;
1410
+ logger.info("list_local_screenshots: Completed", { count: screenshots.length, duration });
1411
+ if (screenshots.length === 0) {
1412
+ return createToolResult(true, "No screenshots found in .testdriver directory.", {
1413
+ action: "list_local_screenshots",
1414
+ count: 0,
1415
+ directory: testdriverDir,
1416
+ duration
1417
+ });
1418
+ }
1419
+ // Format the list for display
1420
+ const screenshotList = screenshots.slice(0, 50).map((s, i) => {
1421
+ const relativePath = path.relative(testdriverDir, s.path);
1422
+ const sizeKB = Math.round(s.size / 1024);
1423
+ const timeAgo = formatTimeAgo(s.modified);
1424
+ return `${i + 1}. ${relativePath} (${sizeKB}KB, ${timeAgo})`;
1425
+ }).join("\n");
1426
+ const message = screenshots.length > 50
1427
+ ? `Found ${screenshots.length} screenshots (showing 50 most recent):\n\n${screenshotList}`
1428
+ : `Found ${screenshots.length} screenshot(s):\n\n${screenshotList}`;
1429
+ return createToolResult(true, message, {
1430
+ action: "list_local_screenshots",
1431
+ count: screenshots.length,
1432
+ directory: testdriverDir,
1433
+ screenshots: screenshots.slice(0, 50).map(s => ({
1434
+ path: s.path,
1435
+ relativePath: path.relative(testdriverDir, s.path),
1436
+ name: s.name,
1437
+ modified: s.modified.toISOString(),
1438
+ sizeBytes: s.size,
1439
+ })),
1440
+ duration
1441
+ });
1442
+ }
1443
+ catch (error) {
1444
+ logger.error("list_local_screenshots: Failed", { error: String(error) });
1445
+ captureException(error, { tags: { tool: "list_local_screenshots" } });
1446
+ throw error;
1447
+ }
1448
+ });
1449
+ // Helper to format time ago
1450
+ function formatTimeAgo(date) {
1451
+ const seconds = Math.floor((Date.now() - date.getTime()) / 1000);
1452
+ if (seconds < 60)
1453
+ return `${seconds}s ago`;
1454
+ const minutes = Math.floor(seconds / 60);
1455
+ if (minutes < 60)
1456
+ return `${minutes}m ago`;
1457
+ const hours = Math.floor(minutes / 60);
1458
+ if (hours < 24)
1459
+ return `${hours}h ago`;
1460
+ const days = Math.floor(hours / 24);
1461
+ return `${days}d ago`;
1462
+ }
1463
+ // View Local Screenshot - view a screenshot from .testdriver directory
1464
+ // Returns the image so AI clients that support images can see it
1465
+ // Also displays to the user via MCP App
1466
+ registerAppTool(server, "view_local_screenshot", {
1467
+ title: "View Local Screenshot",
1468
+ description: `View a screenshot from the .testdriver directory.
1469
+
1470
+ Use 'list_local_screenshots' first to see available screenshots, then use this tool to view one.
1471
+
1472
+ This tool returns the image content so AI clients that support images can see it directly.
1473
+ The image is also displayed to the user via the MCP App UI.
1474
+
1475
+ Useful for:
1476
+ - Reviewing screenshots from previous test runs
1477
+ - Debugging test failures by examining saved screenshots
1478
+ - Comparing current screen state to saved screenshots`,
1479
+ inputSchema: z.object({
1480
+ path: z.string().describe("Full path to the screenshot file (from list_local_screenshots)"),
1481
+ }),
1482
+ _meta: { ui: { resourceUri: RESOURCE_URI, expanded: true } },
1483
+ }, async (params) => {
1484
+ const startTime = Date.now();
1485
+ logger.info("view_local_screenshot: Starting", { path: params.path });
1486
+ try {
1487
+ // Validate the path exists and is a PNG
1488
+ if (!fs.existsSync(params.path)) {
1489
+ logger.warn("view_local_screenshot: File not found", { path: params.path });
1490
+ return createToolResult(false, `Screenshot not found: ${params.path}`, { error: "File not found" });
1491
+ }
1492
+ if (!params.path.toLowerCase().endsWith(".png")) {
1493
+ logger.warn("view_local_screenshot: Not a PNG file", { path: params.path });
1494
+ return createToolResult(false, "Only PNG files are supported", { error: "Invalid file type" });
1495
+ }
1496
+ // Security check - only allow files from .testdriver directory
1497
+ const normalizedPath = path.resolve(params.path);
1498
+ if (!normalizedPath.includes(".testdriver")) {
1499
+ logger.warn("view_local_screenshot: Path not in .testdriver", { path: normalizedPath });
1500
+ return createToolResult(false, "Can only view screenshots from .testdriver directory", { error: "Security: path not allowed" });
1501
+ }
1502
+ // Read the file
1503
+ const imageBuffer = fs.readFileSync(params.path);
1504
+ const imageBase64 = imageBuffer.toString("base64");
1505
+ // Store image for MCP App UI display
1506
+ const screenshotResourceUri = storeImage(imageBase64, "screenshot");
1507
+ const stats = fs.statSync(params.path);
1508
+ const sizeKB = Math.round(stats.size / 1024);
1509
+ const fileName = path.basename(params.path);
1510
+ const duration = Date.now() - startTime;
1511
+ logger.info("view_local_screenshot: Completed", { path: params.path, sizeKB, duration });
1512
+ // Return the image content for AI clients that support images
1513
+ // The content array includes both text and image for maximum compatibility
1514
+ const content = [
1515
+ { type: "text", text: `Screenshot: ${fileName} (${sizeKB}KB)` },
1516
+ {
1517
+ type: "image",
1518
+ data: imageBase64,
1519
+ mimeType: "image/png"
1520
+ },
1521
+ ];
1522
+ return {
1523
+ content,
1524
+ structuredContent: {
1525
+ action: "view_local_screenshot",
1526
+ success: true,
1527
+ path: params.path,
1528
+ fileName,
1529
+ sizeBytes: stats.size,
1530
+ modified: stats.mtime.toISOString(),
1531
+ screenshotResourceUri,
1532
+ duration
1533
+ },
1534
+ };
1535
+ }
1536
+ catch (error) {
1537
+ logger.error("view_local_screenshot: Failed", { error: String(error), path: params.path });
1538
+ captureException(error, { tags: { tool: "view_local_screenshot" }, extra: { path: params.path } });
1539
+ throw error;
1540
+ }
1541
+ });
1344
1542
  // Screenshot - captures full screen to show user the current state
1345
1543
  // NOTE: This is for SHOWING the user the screen, not for AI understanding.
1346
1544
  // Use 'check' tool for AI to understand screen state.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.2.75",
3
+ "version": "7.2.77",
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.d.ts CHANGED
@@ -879,10 +879,14 @@ export default class TestDriverSDK {
879
879
  * const client = new TestDriver();
880
880
  *
881
881
  * @example
882
+ * // Pass options only (API key from .env)
883
+ * const client = new TestDriver({ os: 'windows' });
884
+ *
885
+ * @example
882
886
  * // Or pass API key explicitly
883
887
  * const client = new TestDriver('your-api-key');
884
888
  */
885
- constructor(apiKey?: string, options?: TestDriverOptions);
889
+ constructor(apiKey?: string | TestDriverOptions, options?: TestDriverOptions);
886
890
 
887
891
  /**
888
892
  * Whether the SDK is currently connected to a sandbox
package/sdk.js CHANGED
@@ -3,7 +3,6 @@ const path = require("path");
3
3
  const os = require("os");
4
4
  const crypto = require("crypto");
5
5
  const { formatter } = require("./sdk-log-formatter");
6
- const logger = require("./agent/lib/logger");
7
6
 
8
7
  // Load .env file into process.env by default
9
8
  require("dotenv").config();
@@ -1245,16 +1244,15 @@ function createChainablePromise(promise) {
1245
1244
  * const client = new TestDriver();
1246
1245
  * await client.connect();
1247
1246
  *
1247
+ * // Pass options only (API key from .env)
1248
+ * const client = new TestDriver({ os: 'windows' });
1249
+ *
1248
1250
  * // Or pass API key explicitly
1249
1251
  * const client = new TestDriver('your-api-key');
1250
1252
  *
1251
1253
  * // New API
1252
1254
  * const element = await client.find('Submit button');
1253
1255
  * await element.click();
1254
- *
1255
- * // Legacy API (deprecated)
1256
- * await client.hoverText('Submit');
1257
- * await client.click();
1258
1256
  */
1259
1257
 
1260
1258
  /**
@@ -1524,7 +1522,64 @@ class TestDriverSDK {
1524
1522
  await this._dashcam.addWebLog("**", "Web Logs");
1525
1523
  }
1526
1524
 
1525
+ // Set up Chrome profile with preferences
1527
1526
  const shell = this.os === "windows" ? "pwsh" : "sh";
1527
+ const userDataDir =
1528
+ this.os === "windows"
1529
+ ? "C:\\Users\\testdriver\\AppData\\Local\\TestDriver\\Chrome"
1530
+ : "/tmp/testdriver-chrome-profile";
1531
+
1532
+ // Create user data directory and Default profile directory
1533
+ const defaultProfileDir =
1534
+ this.os === "windows"
1535
+ ? `${userDataDir}\\Default`
1536
+ : `${userDataDir}/Default`;
1537
+
1538
+ const createDirCmd =
1539
+ this.os === "windows"
1540
+ ? `New-Item -ItemType Directory -Path "${defaultProfileDir}" -Force | Out-Null`
1541
+ : `mkdir -p "${defaultProfileDir}"`;
1542
+
1543
+ await this.exec(shell, createDirCmd, 60000, true);
1544
+
1545
+ // Write Chrome preferences
1546
+ const chromePrefs = {
1547
+ credentials_enable_service: false,
1548
+ profile: {
1549
+ password_manager_enabled: false,
1550
+ default_content_setting_values: {},
1551
+ },
1552
+ signin: {
1553
+ allowed: false,
1554
+ },
1555
+ sync: {
1556
+ requested: false,
1557
+ first_setup_complete: true,
1558
+ sync_all_os_types: false,
1559
+ },
1560
+ autofill: {
1561
+ enabled: false,
1562
+ },
1563
+ local_state: {
1564
+ browser: {
1565
+ has_seen_welcome_page: true,
1566
+ },
1567
+ },
1568
+ };
1569
+
1570
+ const prefsPath =
1571
+ this.os === "windows"
1572
+ ? `${defaultProfileDir}\\Preferences`
1573
+ : `${defaultProfileDir}/Preferences`;
1574
+
1575
+ const prefsJson = JSON.stringify(chromePrefs, null, 2);
1576
+ const writePrefCmd =
1577
+ this.os === "windows"
1578
+ ? // Use compact JSON and [System.IO.File]::WriteAllText to avoid Set-Content hanging issues
1579
+ `[System.IO.File]::WriteAllText("${prefsPath}", '${JSON.stringify(chromePrefs).replace(/'/g, "''")}')`
1580
+ : `cat > "${prefsPath}" << 'EOF'\n${prefsJson}\nEOF`;
1581
+
1582
+ await this.exec(shell, writePrefCmd, 60000, true);
1528
1583
 
1529
1584
  // Build Chrome launch command
1530
1585
  const chromeArgs = [];
@@ -1536,6 +1591,7 @@ class TestDriverSDK {
1536
1591
  "--no-first-run",
1537
1592
  "--no-experiments",
1538
1593
  "--disable-infobars",
1594
+ `--user-data-dir=${userDataDir}`,
1539
1595
  );
1540
1596
 
1541
1597
  // Add remote debugging port for captcha solving support
@@ -1748,6 +1804,64 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
1748
1804
  await this._dashcam.addWebLog("**", "Web Logs");
1749
1805
  }
1750
1806
 
1807
+ // Set up Chrome profile with preferences
1808
+ const userDataDir =
1809
+ this.os === "windows"
1810
+ ? "C:\\Users\\testdriver\\AppData\\Local\\TestDriver\\Chrome"
1811
+ : "/tmp/testdriver-chrome-profile";
1812
+
1813
+ // Create user data directory and Default profile directory
1814
+ const defaultProfileDir =
1815
+ this.os === "windows"
1816
+ ? `${userDataDir}\\Default`
1817
+ : `${userDataDir}/Default`;
1818
+
1819
+ const createDirCmd =
1820
+ this.os === "windows"
1821
+ ? `New-Item -ItemType Directory -Path "${defaultProfileDir}" -Force | Out-Null`
1822
+ : `mkdir -p "${defaultProfileDir}"`;
1823
+
1824
+ await this.exec(shell, createDirCmd, 60000, true);
1825
+
1826
+ // Write Chrome preferences
1827
+ const chromePrefs = {
1828
+ credentials_enable_service: false,
1829
+ profile: {
1830
+ password_manager_enabled: false,
1831
+ default_content_setting_values: {},
1832
+ },
1833
+ signin: {
1834
+ allowed: false,
1835
+ },
1836
+ sync: {
1837
+ requested: false,
1838
+ first_setup_complete: true,
1839
+ sync_all_os_types: false,
1840
+ },
1841
+ autofill: {
1842
+ enabled: false,
1843
+ },
1844
+ local_state: {
1845
+ browser: {
1846
+ has_seen_welcome_page: true,
1847
+ },
1848
+ },
1849
+ };
1850
+
1851
+ const prefsPath =
1852
+ this.os === "windows"
1853
+ ? `${defaultProfileDir}\\Preferences`
1854
+ : `${defaultProfileDir}/Preferences`;
1855
+
1856
+ const prefsJson = JSON.stringify(chromePrefs, null, 2);
1857
+ const writePrefCmd =
1858
+ this.os === "windows"
1859
+ ? // Use compact JSON and [System.IO.File]::WriteAllText to avoid Set-Content hanging issues
1860
+ `[System.IO.File]::WriteAllText("${prefsPath}", '${JSON.stringify(chromePrefs).replace(/'/g, "''")}')`
1861
+ : `cat > "${prefsPath}" << 'EOF'\n${prefsJson}\nEOF`;
1862
+
1863
+ await this.exec(shell, writePrefCmd, 60000, true);
1864
+
1751
1865
  // Build Chrome launch command
1752
1866
  const chromeArgs = [];
1753
1867
  if (maximized) chromeArgs.push("--start-maximized");
@@ -1758,6 +1872,7 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
1758
1872
  "--no-experiments",
1759
1873
  "--disable-infobars",
1760
1874
  "--disable-features=ChromeLabs",
1875
+ `--user-data-dir=${userDataDir}`,
1761
1876
  );
1762
1877
 
1763
1878
  // Add remote debugging port for captcha solving support
@@ -3452,28 +3567,28 @@ CAPTCHA_SOLVER_EOF`,
3452
3567
  *
3453
3568
  * @example
3454
3569
  * // Simple execution
3455
- * const result = await client.ai('Click the submit button');
3570
+ * const result = await client.act('Click the submit button');
3456
3571
  * console.log(result.success); // true
3457
3572
  *
3458
3573
  * @example
3459
3574
  * // With custom retry limit
3460
- * const result = await client.ai('Fill out the contact form', { tries: 10 });
3575
+ * const result = await client.act('Fill out the contact form', { tries: 10 });
3461
3576
  * console.log(`Completed in ${result.tries} tries`);
3462
3577
  *
3463
3578
  * @example
3464
3579
  * // Handle failures
3465
3580
  * try {
3466
- * await client.ai('Complete the checkout process', { tries: 3 });
3581
+ * await client.act('Complete the checkout process', { tries: 3 });
3467
3582
  * } catch (error) {
3468
3583
  * console.log(`Failed after ${error.tries} tries: ${error.message}`);
3469
3584
  * }
3470
3585
  */
3471
- async ai(task, options = {}) {
3586
+ async act(task, options = {}) {
3472
3587
  this._ensureConnected();
3473
3588
 
3474
3589
  const { tries = 7 } = options;
3475
3590
 
3476
- this.analytics.track("sdk.ai", { task, tries });
3591
+ this.analytics.track("sdk.act", { task, tries });
3477
3592
 
3478
3593
  const { events } = require("./agent/events.js");
3479
3594
  const startTime = Date.now();
@@ -3482,7 +3597,7 @@ CAPTCHA_SOLVER_EOF`,
3482
3597
  const originalCheckLimit = this.agent.checkLimit;
3483
3598
  this.agent.checkLimit = tries;
3484
3599
 
3485
- // Reset check count for this ai() call
3600
+ // Reset check count for this act() call
3486
3601
  const originalCheckCount = this.agent.checkCount;
3487
3602
  this.agent.checkCount = 0;
3488
3603
 
@@ -3549,7 +3664,7 @@ CAPTCHA_SOLVER_EOF`,
3549
3664
  }
3550
3665
 
3551
3666
  /**
3552
- * @deprecated Use ai() instead
3667
+ * @deprecated Use act() instead
3553
3668
  * Execute a natural language task using AI
3554
3669
  *
3555
3670
  * @param {string} task - Natural language description of what to do
@@ -3557,8 +3672,8 @@ CAPTCHA_SOLVER_EOF`,
3557
3672
  * @param {number} [options.tries=7] - Maximum number of check/retry attempts
3558
3673
  * @returns {Promise<ActResult>} Result object with success status and details
3559
3674
  */
3560
- async act(task, options) {
3561
- return await this.ai(task, options);
3675
+ async ai(task, options) {
3676
+ return await this.act(task, options);
3562
3677
  }
3563
3678
  }
3564
3679