testdriverai 7.2.74 → 7.2.76

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.
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  name: testdriver
3
3
  description: An expert at creating and refining automated tests using TestDriver.ai
4
- tools:
5
- - testdriver/*
4
+ tools: ["*"]
6
5
  mcp-servers:
7
6
  testdriver:
8
7
  command: npx
@@ -12,6 +11,7 @@ mcp-servers:
12
11
  - testdriverai-mcp
13
12
  env:
14
13
  TD_API_KEY: ${TD_API_KEY}
14
+ tools: ["testdriverai"]
15
15
  ---
16
16
 
17
17
  # TestDriver Expert
@@ -44,7 +44,7 @@ Use this agent when the user asks to:
44
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.
45
45
  5. **Verify Actions**: Use `check` after actions to verify they succeeded (for YOUR understanding only).
46
46
  6. **Add Assertions**: Use `assert` for test conditions that should be in the final test file.
47
- 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.
48
48
 
49
49
  ## Prerequisites
50
50
 
@@ -294,11 +294,15 @@ assert({ assertion: "the dashboard is visible" })
294
294
  **⚠️ YOU must run the test - do NOT tell the user to run it:**
295
295
 
296
296
  ```bash
297
- npx vitest run tests/login.test.mjs
297
+ npx vitest run tests/login.test.mjs --reporter=dot
298
298
  ```
299
299
 
300
+ **Always use `--reporter=dot`** for cleaner, more concise output that's easier to parse.
301
+
300
302
  Analyze the output, fix any issues, and iterate until the test passes.
301
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
+
302
306
  ### MCP Tools Reference
303
307
 
304
308
  | Tool | Description |
@@ -463,16 +467,17 @@ const result = await testdriver.assert("dashboard is visible");
463
467
  ## Tips for Agents
464
468
 
465
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.
466
- 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.
467
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.
468
- 4. **Use MCP tools for development** - Build tests interactively with visual feedback
469
- 5. **Always check `sdk.d.ts`** for method signatures and types when debugging generated tests
470
- 6. **Look at test samples** in `node_modules/testdriverai/test` for working examples
471
- 7. **Use `check` to understand screen state** - This is how you verify what the sandbox shows during MCP development.
472
- 8. **Use `check` after actions, `assert` for test files** - `check` gives detailed AI analysis (no code), `assert` gives boolean pass/fail (generates code)
473
- 9. **Be specific with element descriptions** - "blue Sign In button in the header" > "button"
474
- 10. **Start simple** - get one step working before adding more
475
- 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:
476
481
 
477
482
  ```json
478
483
  // eslint.config.js (for TypeScript projects)
@@ -356,11 +356,8 @@ test('should login and add item to cart', async (context) => {
356
356
  if (!fs.existsSync(configFile)) {
357
357
  const configContent = `import { defineConfig } from 'vitest/config';
358
358
  import TestDriver from 'testdriverai/vitest';
359
- import { config } from 'dotenv';
360
-
361
- // Load environment variables from .env file
362
- config();
363
359
 
360
+ // Note: dotenv is loaded automatically by the TestDriver SDK
364
361
  export default defineConfig({
365
362
  test: {
366
363
  testTimeout: 300000,
@@ -488,12 +485,20 @@ jobs:
488
485
 
489
486
  if (!fs.existsSync(mcpConfigFile)) {
490
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
+ ],
491
496
  servers: {
492
497
  testdriver: {
493
498
  command: "npx",
494
499
  args: ["-p", "testdriverai@beta", "testdriverai-mcp"],
495
500
  env: {
496
- TD_API_KEY: "${TD_API_KEY}",
501
+ TD_API_KEY: "${input:testdriver-api-key}",
497
502
  },
498
503
  },
499
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.74",
3
+ "version": "7.2.76",
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
@@ -867,7 +867,26 @@ export interface DashcamAPI {
867
867
  }
868
868
 
869
869
  export default class TestDriverSDK {
870
- constructor(apiKey: string, options?: TestDriverOptions);
870
+ /**
871
+ * Create a new TestDriverSDK instance
872
+ * Automatically loads environment variables from .env file via dotenv.
873
+ *
874
+ * @param apiKey - API key (optional, defaults to TD_API_KEY environment variable)
875
+ * @param options - SDK configuration options
876
+ *
877
+ * @example
878
+ * // API key loaded automatically from TD_API_KEY in .env
879
+ * const client = new TestDriver();
880
+ *
881
+ * @example
882
+ * // Pass options only (API key from .env)
883
+ * const client = new TestDriver({ os: 'windows' });
884
+ *
885
+ * @example
886
+ * // Or pass API key explicitly
887
+ * const client = new TestDriver('your-api-key');
888
+ */
889
+ constructor(apiKey?: string | TestDriverOptions, options?: TestDriverOptions);
871
890
 
872
891
  /**
873
892
  * Whether the SDK is currently connected to a sandbox
package/sdk.js CHANGED
@@ -3,7 +3,9 @@ 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");
6
+
7
+ // Load .env file into process.env by default
8
+ require("dotenv").config();
7
9
 
8
10
  /**
9
11
  * Get the file path of the caller (the file that called TestDriver)
@@ -1233,20 +1235,24 @@ function createChainablePromise(promise) {
1233
1235
  * TestDriver SDK
1234
1236
  *
1235
1237
  * This SDK provides programmatic access to TestDriver's AI-powered testing capabilities.
1238
+ * Automatically loads environment variables from .env file via dotenv.
1236
1239
  *
1237
1240
  * @example
1238
1241
  * const TestDriver = require('testdriverai');
1239
1242
  *
1240
- * const client = new TestDriver(process.env.TD_API_KEY);
1243
+ * // API key loaded automatically from TD_API_KEY in .env
1244
+ * const client = new TestDriver();
1241
1245
  * await client.connect();
1242
1246
  *
1247
+ * // Pass options only (API key from .env)
1248
+ * const client = new TestDriver({ os: 'windows' });
1249
+ *
1250
+ * // Or pass API key explicitly
1251
+ * const client = new TestDriver('your-api-key');
1252
+ *
1243
1253
  * // New API
1244
1254
  * const element = await client.find('Submit button');
1245
1255
  * await element.click();
1246
- *
1247
- * // Legacy API (deprecated)
1248
- * await client.hoverText('Submit');
1249
- * await client.click();
1250
1256
  */
1251
1257
 
1252
1258
  /**
@@ -1264,9 +1270,18 @@ const { createMarkdownLogger } = require("./interfaces/logger.js");
1264
1270
 
1265
1271
  class TestDriverSDK {
1266
1272
  constructor(apiKey, options = {}) {
1273
+ // Support calling with just options: new TestDriver({ os: 'windows' })
1274
+ if (typeof apiKey === 'object' && apiKey !== null) {
1275
+ options = apiKey;
1276
+ apiKey = null;
1277
+ }
1278
+
1279
+ // Use provided API key or fall back to environment variable
1280
+ const resolvedApiKey = apiKey || process.env.TD_API_KEY;
1281
+
1267
1282
  // Set up environment with API key
1268
1283
  const environment = {
1269
- TD_API_KEY: apiKey,
1284
+ TD_API_KEY: resolvedApiKey,
1270
1285
  TD_API_ROOT: options.apiRoot || "https://testdriver-api.onrender.com",
1271
1286
  TD_RESOLUTION: options.resolution || "1366x768",
1272
1287
  TD_ANALYTICS: options.analytics !== false,
@@ -1507,7 +1522,64 @@ class TestDriverSDK {
1507
1522
  await this._dashcam.addWebLog("**", "Web Logs");
1508
1523
  }
1509
1524
 
1525
+ // Set up Chrome profile with preferences
1510
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);
1511
1583
 
1512
1584
  // Build Chrome launch command
1513
1585
  const chromeArgs = [];
@@ -1519,6 +1591,7 @@ class TestDriverSDK {
1519
1591
  "--no-first-run",
1520
1592
  "--no-experiments",
1521
1593
  "--disable-infobars",
1594
+ `--user-data-dir=${userDataDir}`,
1522
1595
  );
1523
1596
 
1524
1597
  // Add remote debugging port for captcha solving support
@@ -1731,6 +1804,64 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
1731
1804
  await this._dashcam.addWebLog("**", "Web Logs");
1732
1805
  }
1733
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
+
1734
1865
  // Build Chrome launch command
1735
1866
  const chromeArgs = [];
1736
1867
  if (maximized) chromeArgs.push("--start-maximized");
@@ -1741,6 +1872,7 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
1741
1872
  "--no-experiments",
1742
1873
  "--disable-infobars",
1743
1874
  "--disable-features=ChromeLabs",
1875
+ `--user-data-dir=${userDataDir}`,
1744
1876
  );
1745
1877
 
1746
1878
  // Add remote debugging port for captcha solving support
@@ -3435,28 +3567,28 @@ CAPTCHA_SOLVER_EOF`,
3435
3567
  *
3436
3568
  * @example
3437
3569
  * // Simple execution
3438
- * const result = await client.ai('Click the submit button');
3570
+ * const result = await client.act('Click the submit button');
3439
3571
  * console.log(result.success); // true
3440
3572
  *
3441
3573
  * @example
3442
3574
  * // With custom retry limit
3443
- * 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 });
3444
3576
  * console.log(`Completed in ${result.tries} tries`);
3445
3577
  *
3446
3578
  * @example
3447
3579
  * // Handle failures
3448
3580
  * try {
3449
- * await client.ai('Complete the checkout process', { tries: 3 });
3581
+ * await client.act('Complete the checkout process', { tries: 3 });
3450
3582
  * } catch (error) {
3451
3583
  * console.log(`Failed after ${error.tries} tries: ${error.message}`);
3452
3584
  * }
3453
3585
  */
3454
- async ai(task, options = {}) {
3586
+ async act(task, options = {}) {
3455
3587
  this._ensureConnected();
3456
3588
 
3457
3589
  const { tries = 7 } = options;
3458
3590
 
3459
- this.analytics.track("sdk.ai", { task, tries });
3591
+ this.analytics.track("sdk.act", { task, tries });
3460
3592
 
3461
3593
  const { events } = require("./agent/events.js");
3462
3594
  const startTime = Date.now();
@@ -3465,7 +3597,7 @@ CAPTCHA_SOLVER_EOF`,
3465
3597
  const originalCheckLimit = this.agent.checkLimit;
3466
3598
  this.agent.checkLimit = tries;
3467
3599
 
3468
- // Reset check count for this ai() call
3600
+ // Reset check count for this act() call
3469
3601
  const originalCheckCount = this.agent.checkCount;
3470
3602
  this.agent.checkCount = 0;
3471
3603
 
@@ -3532,7 +3664,7 @@ CAPTCHA_SOLVER_EOF`,
3532
3664
  }
3533
3665
 
3534
3666
  /**
3535
- * @deprecated Use ai() instead
3667
+ * @deprecated Use act() instead
3536
3668
  * Execute a natural language task using AI
3537
3669
  *
3538
3670
  * @param {string} task - Natural language description of what to do
@@ -3540,8 +3672,8 @@ CAPTCHA_SOLVER_EOF`,
3540
3672
  * @param {number} [options.tries=7] - Maximum number of check/retry attempts
3541
3673
  * @returns {Promise<ActResult>} Result object with success status and details
3542
3674
  */
3543
- async act(task, options) {
3544
- return await this.ai(task, options);
3675
+ async ai(task, options) {
3676
+ return await this.act(task, options);
3545
3677
  }
3546
3678
  }
3547
3679