testdriverai 7.2.78 → 7.2.80

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.
@@ -0,0 +1,426 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+
5
+ /**
6
+ * Initialize a TestDriver project with all necessary files and configuration
7
+ * @param {Object} options - Initialization options
8
+ * @param {string} [options.targetDir] - Target directory (defaults to current working directory)
9
+ * @param {string} [options.apiKey] - TestDriver API key (will be saved to .env)
10
+ * @param {boolean} [options.skipInstall=false] - Skip npm install step
11
+ * @param {boolean} [options.interactive=false] - Whether to prompt for missing values (CLI mode)
12
+ * @returns {Promise<{success: boolean, results: string[], errors: string[]}>}
13
+ */
14
+ async function initProject(options = {}) {
15
+ const targetDir = options.targetDir || process.cwd();
16
+ const results = [];
17
+ const errors = [];
18
+
19
+ try {
20
+ // Create target directory if it doesn't exist
21
+ if (!fs.existsSync(targetDir)) {
22
+ fs.mkdirSync(targetDir, { recursive: true });
23
+ results.push(`✓ Created directory: ${targetDir}`);
24
+ }
25
+
26
+ // 1. Setup package.json
27
+ const packageJsonPath = path.join(targetDir, "package.json");
28
+ if (!fs.existsSync(packageJsonPath)) {
29
+ const packageJson = {
30
+ name: path.basename(targetDir),
31
+ version: "1.0.0",
32
+ description: "TestDriver.ai test suite",
33
+ type: "module",
34
+ scripts: {
35
+ test: "vitest run",
36
+ "test:watch": "vitest",
37
+ "test:ui": "vitest --ui",
38
+ },
39
+ keywords: ["testdriver", "testing", "e2e"],
40
+ author: "",
41
+ license: "ISC",
42
+ engines: {
43
+ node: ">=20.19.0",
44
+ },
45
+ };
46
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
47
+ results.push("✓ Created package.json");
48
+ } else {
49
+ results.push("⊘ package.json already exists, skipping");
50
+ }
51
+
52
+ // 2. Create test directory and example files
53
+ const testDir = path.join(targetDir, "tests");
54
+ if (!fs.existsSync(testDir)) {
55
+ fs.mkdirSync(testDir, { recursive: true });
56
+ }
57
+
58
+ // Create login snippet file
59
+ const loginSnippetFile = path.join(testDir, "login.js");
60
+ if (!fs.existsSync(loginSnippetFile)) {
61
+ const loginSnippetContent = `/**
62
+ * Login snippet - reusable login function
63
+ *
64
+ * This demonstrates how to create reusable test snippets that can be
65
+ * imported and used across multiple test files.
66
+ */
67
+ export async function login(testdriver) {
68
+
69
+ // The password is displayed on screen, have TestDriver extract it
70
+ const password = await testdriver.extract('the password');
71
+
72
+ // Find the username field
73
+ const usernameField = await testdriver.find(
74
+ 'username input'
75
+ );
76
+ await usernameField.click();
77
+
78
+ // Type username
79
+ await testdriver.type('standard_user');
80
+
81
+ // Enter password form earlier
82
+ // Marked as secret so it's not logged or stored
83
+ await testdriver.pressKeys(['tab']);
84
+ await testdriver.type(password, { secret: true });
85
+
86
+ // Submit the form
87
+ await testdriver.find('submit button on the login form').click();
88
+ }
89
+ `;
90
+ fs.writeFileSync(loginSnippetFile, loginSnippetContent);
91
+ results.push("✓ Created login snippet: tests/login.js");
92
+ }
93
+
94
+ // Create example test file
95
+ const testFile = path.join(testDir, "example.test.js");
96
+ if (!fs.existsSync(testFile)) {
97
+ const vitestContent = `import { test, expect } from 'vitest';
98
+ import { TestDriver } from 'testdriverai/vitest/hooks';
99
+ import { login } from './login.js';
100
+
101
+ test('should login and add item to cart', async (context) => {
102
+
103
+ // Create TestDriver instance - automatically connects to sandbox
104
+ const testdriver = TestDriver(context);
105
+
106
+ // Launch chrome and navigate to demo app
107
+ await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
108
+
109
+ // Use the login snippet to handle authentication
110
+ // This demonstrates how to reuse test logic across multiple tests
111
+ await login(testdriver);
112
+
113
+ // Add item to cart
114
+ const addToCartButton = await testdriver.find(
115
+ 'add to cart button under TestDriver Hat'
116
+ );
117
+ await addToCartButton.click();
118
+
119
+ // Open cart
120
+ const cartButton = await testdriver.find(
121
+ 'cart button in the top right corner'
122
+ );
123
+ await cartButton.click();
124
+
125
+ // Verify item in cart
126
+ const result = await testdriver.assert('There is an item in the cart');
127
+ expect(result).toBeTruthy();
128
+
129
+ });
130
+ `;
131
+ fs.writeFileSync(testFile, vitestContent);
132
+ results.push("✓ Created test file: tests/example.test.js");
133
+ }
134
+
135
+ // 3. Create vitest.config.js
136
+ const configFile = path.join(targetDir, "vitest.config.js");
137
+ if (!fs.existsSync(configFile)) {
138
+ const configContent = `import { defineConfig } from 'vitest/config';
139
+ import TestDriver from 'testdriverai/vitest';
140
+
141
+ // Note: dotenv is loaded automatically by the TestDriver SDK
142
+ export default defineConfig({
143
+ test: {
144
+ testTimeout: 300000,
145
+ hookTimeout: 300000,
146
+ reporters: [
147
+ 'default',
148
+ TestDriver(),
149
+ ],
150
+ setupFiles: ['testdriverai/vitest/setup'],
151
+ },
152
+ });
153
+ `;
154
+ fs.writeFileSync(configFile, configContent);
155
+ results.push("✓ Created vitest.config.js");
156
+ }
157
+
158
+ // 4. Create/update .gitignore
159
+ const gitignorePath = path.join(targetDir, ".gitignore");
160
+ let gitignoreContent = "";
161
+ if (fs.existsSync(gitignorePath)) {
162
+ gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
163
+ if (!gitignoreContent.includes(".env")) {
164
+ const ignoresToAdd = [
165
+ "",
166
+ "# TestDriver.ai",
167
+ ".env",
168
+ "node_modules/",
169
+ "test-results/",
170
+ "*.log",
171
+ ];
172
+ const newContent = gitignoreContent.trim() + "\n" + ignoresToAdd.join("\n") + "\n";
173
+ fs.writeFileSync(gitignorePath, newContent);
174
+ results.push("✓ Updated .gitignore");
175
+ } else {
176
+ results.push("⊘ .env already in .gitignore");
177
+ }
178
+ } else {
179
+ const ignoresToAdd = [
180
+ "# TestDriver.ai",
181
+ ".env",
182
+ "node_modules/",
183
+ "test-results/",
184
+ "*.log",
185
+ ];
186
+ fs.writeFileSync(gitignorePath, ignoresToAdd.join("\n") + "\n");
187
+ results.push("✓ Created .gitignore");
188
+ }
189
+
190
+ // 5. Create GitHub Actions workflow
191
+ const workflowDir = path.join(targetDir, ".github", "workflows");
192
+ if (!fs.existsSync(workflowDir)) {
193
+ fs.mkdirSync(workflowDir, { recursive: true });
194
+ }
195
+
196
+ const workflowFile = path.join(workflowDir, "testdriver.yml");
197
+ if (!fs.existsSync(workflowFile)) {
198
+ const workflowContent = `name: TestDriver.ai Tests
199
+
200
+ on:
201
+ push:
202
+ branches: [ main, master ]
203
+ pull_request:
204
+ branches: [ main, master ]
205
+
206
+ jobs:
207
+ test:
208
+ runs-on: ubuntu-latest
209
+
210
+ steps:
211
+ - uses: actions/checkout@v4
212
+
213
+ - name: Setup Node.js
214
+ uses: actions/setup-node@v4
215
+ with:
216
+ node-version: '20'
217
+ cache: 'npm'
218
+
219
+ - name: Install dependencies
220
+ run: npm ci
221
+
222
+ - name: Run TestDriver.ai tests
223
+ env:
224
+ TD_API_KEY: \${{ secrets.TD_API_KEY }}
225
+ run: npx vitest run
226
+
227
+ - name: Upload test results
228
+ if: always()
229
+ uses: actions/upload-artifact@v4
230
+ with:
231
+ name: test-results
232
+ path: test-results/
233
+ retention-days: 30
234
+ `;
235
+ fs.writeFileSync(workflowFile, workflowContent);
236
+ results.push("✓ Created GitHub workflow: .github/workflows/testdriver.yml");
237
+ } else {
238
+ results.push("⊘ GitHub workflow already exists");
239
+ }
240
+
241
+ // 6. Create VSCode MCP config
242
+ const vscodeDir = path.join(targetDir, ".vscode");
243
+ if (!fs.existsSync(vscodeDir)) {
244
+ fs.mkdirSync(vscodeDir, { recursive: true });
245
+ }
246
+
247
+ const mcpConfigFile = path.join(vscodeDir, "mcp.json");
248
+ if (!fs.existsSync(mcpConfigFile)) {
249
+ const mcpConfig = {
250
+ inputs: [
251
+ {
252
+ type: "promptString",
253
+ id: "testdriver-api-key",
254
+ description: "TestDriver API Key From https://console.testdriver.ai/team",
255
+ password: true,
256
+ },
257
+ ],
258
+ servers: {
259
+ testdriver: {
260
+ command: "npx",
261
+ args: ["-p", "testdriverai@beta", "testdriverai-mcp"],
262
+ env: {
263
+ TD_API_KEY: "${input:testdriver-api-key}",
264
+ },
265
+ },
266
+ },
267
+ };
268
+ fs.writeFileSync(mcpConfigFile, JSON.stringify(mcpConfig, null, 2) + "\n");
269
+ results.push("✓ Created MCP config: .vscode/mcp.json");
270
+ } else {
271
+ results.push("⊘ MCP config already exists");
272
+ }
273
+
274
+ // 7. Create VSCode extensions recommendations
275
+ const extensionsFile = path.join(vscodeDir, "extensions.json");
276
+ if (!fs.existsSync(extensionsFile)) {
277
+ const extensionsConfig = {
278
+ recommendations: [
279
+ "vitest.explorer",
280
+ ],
281
+ };
282
+ fs.writeFileSync(extensionsFile, JSON.stringify(extensionsConfig, null, 2) + "\n");
283
+ results.push("✓ Created extensions config: .vscode/extensions.json");
284
+ } else {
285
+ results.push("⊘ Extensions config already exists");
286
+ }
287
+
288
+ // 8. Copy TestDriver skills
289
+ const skillsDestDir = path.join(targetDir, ".github", "skills");
290
+ const possibleSkillsSources = [
291
+ path.join(targetDir, "node_modules", "testdriverai", "ai", "skills"),
292
+ path.join(__dirname, "..", "ai", "skills"),
293
+ ];
294
+
295
+ let skillsSourceDir = null;
296
+ for (const source of possibleSkillsSources) {
297
+ if (fs.existsSync(source)) {
298
+ skillsSourceDir = source;
299
+ break;
300
+ }
301
+ }
302
+
303
+ if (skillsSourceDir) {
304
+ if (!fs.existsSync(skillsDestDir)) {
305
+ fs.mkdirSync(skillsDestDir, { recursive: true });
306
+ }
307
+
308
+ const skillDirs = fs.readdirSync(skillsSourceDir);
309
+ let copiedCount = 0;
310
+
311
+ for (const skillDir of skillDirs) {
312
+ const sourcePath = path.join(skillsSourceDir, skillDir);
313
+ const destPath = path.join(skillsDestDir, skillDir);
314
+
315
+ if (fs.statSync(sourcePath).isDirectory()) {
316
+ if (!fs.existsSync(destPath)) {
317
+ fs.mkdirSync(destPath, { recursive: true });
318
+ }
319
+
320
+ const skillFile = path.join(sourcePath, "SKILL.md");
321
+ if (fs.existsSync(skillFile)) {
322
+ fs.copyFileSync(skillFile, path.join(destPath, "SKILL.md"));
323
+ copiedCount++;
324
+ }
325
+ }
326
+ }
327
+
328
+ if (copiedCount > 0) {
329
+ results.push(`✓ Copied ${copiedCount} skills to .github/skills/`);
330
+ }
331
+ } else {
332
+ results.push("⚠ Skills directory not found (will be available after npm install)");
333
+ }
334
+
335
+ // 9. Copy TestDriver agents
336
+ const agentsDestDir = path.join(targetDir, ".github", "agents");
337
+ const possibleAgentsSources = [
338
+ path.join(targetDir, "node_modules", "testdriverai", "ai", "agents"),
339
+ path.join(__dirname, "..", "ai", "agents"),
340
+ ];
341
+
342
+ let agentsSourceDir = null;
343
+ for (const source of possibleAgentsSources) {
344
+ if (fs.existsSync(source)) {
345
+ agentsSourceDir = source;
346
+ break;
347
+ }
348
+ }
349
+
350
+ if (agentsSourceDir) {
351
+ if (!fs.existsSync(agentsDestDir)) {
352
+ fs.mkdirSync(agentsDestDir, { recursive: true });
353
+ }
354
+
355
+ const agentFiles = fs.readdirSync(agentsSourceDir).filter(f => f.endsWith(".md"));
356
+ let copiedCount = 0;
357
+
358
+ for (const agentFile of agentFiles) {
359
+ const sourcePath = path.join(agentsSourceDir, agentFile);
360
+ const agentName = agentFile.replace(".md", "");
361
+ const destPath = path.join(agentsDestDir, `${agentName}.agent.md`);
362
+
363
+ if (!fs.existsSync(destPath)) {
364
+ fs.copyFileSync(sourcePath, destPath);
365
+ copiedCount++;
366
+ }
367
+ }
368
+
369
+ if (copiedCount > 0) {
370
+ results.push(`✓ Copied ${copiedCount} agent(s) to .github/agents/`);
371
+ }
372
+ } else {
373
+ results.push("⚠ Agents directory not found (will be available after npm install)");
374
+ }
375
+
376
+ // 10. Handle API key if provided
377
+ if (options.apiKey) {
378
+ const envPath = path.join(targetDir, ".env");
379
+ let envContent = "";
380
+
381
+ if (fs.existsSync(envPath)) {
382
+ envContent = fs.readFileSync(envPath, "utf8");
383
+ if (!envContent.includes("TD_API_KEY=")) {
384
+ envContent += "\n";
385
+ } else {
386
+ // Replace existing key
387
+ envContent = envContent.replace(/^TD_API_KEY=.*$/m, "");
388
+ }
389
+ }
390
+
391
+ const newEnvContent = envContent.trim() + `\nTD_API_KEY=${options.apiKey}\n`;
392
+ fs.writeFileSync(envPath, newEnvContent);
393
+ results.push("✓ Saved API key to .env");
394
+ } else {
395
+ results.push("ℹ No API key provided - add it to .env manually:");
396
+ results.push(" TD_API_KEY=your_api_key");
397
+ }
398
+
399
+ // 11. Install dependencies (unless skipped)
400
+ if (!options.skipInstall) {
401
+ results.push("\n📦 Installing dependencies...");
402
+ try {
403
+ execSync("npm install -D vitest testdriverai@beta && npm install dotenv", {
404
+ cwd: targetDir,
405
+ stdio: "pipe",
406
+ });
407
+ results.push("✓ Dependencies installed successfully");
408
+ } catch (error) {
409
+ errors.push("Failed to install dependencies. Run manually:");
410
+ errors.push(" npm install -D vitest testdriverai@beta");
411
+ errors.push(" npm install dotenv");
412
+ }
413
+ } else {
414
+ results.push("\nℹ Skipped dependency installation. Run manually:");
415
+ results.push(" npm install -D vitest testdriverai@beta");
416
+ results.push(" npm install dotenv");
417
+ }
418
+
419
+ return { success: true, results, errors };
420
+ } catch (error) {
421
+ errors.push(`Initialization failed: ${error.message}`);
422
+ return { success: false, results, errors };
423
+ }
424
+ }
425
+
426
+ module.exports = { initProject };
@@ -14,7 +14,7 @@ import * as Sentry from "@sentry/node";
14
14
  import * as fs from "fs";
15
15
  import * as os from "os";
16
16
  import * as path from "path";
17
- import { fileURLToPath } from "url";
17
+ import { fileURLToPath, pathToFileURL } from "url";
18
18
  import { z } from "zod";
19
19
  import { generateActionCode } from "./codegen.js";
20
20
  import { getProvisionOptions, SessionStartInputSchema } from "./provision-types.js";
@@ -406,7 +406,26 @@ Debug mode (connect to existing sandbox):
406
406
  logger.debug("session_start: Preview mode", { preview: previewMode });
407
407
  // Get IP from params or environment (for self-hosted instances)
408
408
  const instanceIp = params.ip || process.env.TD_IP;
409
- sdk = new TestDriverSDK(process.env.TD_API_KEY || "", {
409
+ // Get API key - check multiple sources for GitHub Copilot coding agent compatibility
410
+ // 1. TD_API_KEY (standard environment variable)
411
+ // 2. COPILOT_MCP_TD_API_KEY (fallback for GitHub Copilot coding agent)
412
+ const apiKey = process.env.TD_API_KEY || process.env.COPILOT_MCP_TD_API_KEY || "";
413
+ if (!apiKey) {
414
+ logger.error("session_start: No API key found", {
415
+ hasTD_API_KEY: !!process.env.TD_API_KEY,
416
+ hasCOPILOT_MCP_TD_API_KEY: !!process.env.COPILOT_MCP_TD_API_KEY,
417
+ availableEnvVars: Object.keys(process.env).filter(k => k.includes('TD') || k.includes('COPILOT_MCP'))
418
+ });
419
+ return createToolResult(false, "No API key found. Please set TD_API_KEY or COPILOT_MCP_TD_API_KEY environment variable.", {
420
+ error: "Missing API key",
421
+ hint: "For GitHub Copilot coding agent, create a Copilot environment secret named COPILOT_MCP_TD_API_KEY"
422
+ });
423
+ }
424
+ logger.debug("session_start: API key found", {
425
+ source: process.env.TD_API_KEY ? "TD_API_KEY" : "COPILOT_MCP_TD_API_KEY",
426
+ keyPrefix: apiKey.substring(0, 7) + "..."
427
+ });
428
+ sdk = new TestDriverSDK(apiKey, {
410
429
  os: params.os,
411
430
  logging: false,
412
431
  apiRoot,
@@ -992,7 +1011,29 @@ registerAppTool(server, "find_and_click", {
992
1011
  const found = element.found();
993
1012
  if (!found) {
994
1013
  logger.warn("find_and_click: Element not found", { description: params.description });
995
- return createToolResult(false, `Element not found: "${params.description}"`, { error: "Element not found", duration: Date.now() - startTime });
1014
+ // Capture screenshot to show current state even when element not found
1015
+ const rawResponse = element._response || {};
1016
+ const duration = Date.now() - startTime;
1017
+ // Store cropped image (screenshot) for resource serving
1018
+ let croppedImageResourceUri;
1019
+ const croppedImage = rawResponse.croppedImage;
1020
+ if (croppedImage) {
1021
+ const imageData = croppedImage.startsWith('data:')
1022
+ ? croppedImage.replace(/^data:image\/\w+;base64,/, '')
1023
+ : croppedImage;
1024
+ croppedImageResourceUri = storeImage(imageData, "screenshot");
1025
+ delete rawResponse.croppedImage;
1026
+ }
1027
+ // Remove extractedText and pixelDiffImage from response to reduce context bloat
1028
+ delete rawResponse.extractedText;
1029
+ delete rawResponse.pixelDiffImage;
1030
+ return createToolResult(false, `Element not found: "${params.description}"`, {
1031
+ ...rawResponse,
1032
+ action: "find_and_click",
1033
+ error: "Element not found",
1034
+ croppedImageResourceUri,
1035
+ duration
1036
+ });
996
1037
  }
997
1038
  const coords = element.getCoordinates();
998
1039
  // Store element ref for later use (in case user wants to interact again)
@@ -1587,6 +1628,80 @@ Only use 'screenshot' when you explicitly want to show something to the human us
1587
1628
  return createToolResult(false, `Screenshot failed: ${error}`, { error: String(error) });
1588
1629
  }
1589
1630
  });
1631
+ // Init - Initialize a new TestDriver project
1632
+ server.registerTool("init", {
1633
+ description: `Initialize a new TestDriver project with Vitest SDK examples.
1634
+
1635
+ This creates:
1636
+ - package.json with proper dependencies
1637
+ - Example test files (tests/example.test.js, tests/login.js)
1638
+ - vitest.config.js
1639
+ - .gitignore
1640
+ - GitHub Actions workflow (.github/workflows/testdriver.yml)
1641
+ - VSCode MCP config (.vscode/mcp.json)
1642
+ - VSCode extensions recommendations (.vscode/extensions.json)
1643
+ - TestDriver skills (.github/skills/)
1644
+ - TestDriver agents (.github/agents/)
1645
+ - .env file with API key (if provided)
1646
+
1647
+ API Key: The apiKey parameter is optional. If not provided, you'll need to manually add TD_API_KEY to the .env file after initialization. The project structure will still be created successfully.`,
1648
+ inputSchema: z.object({
1649
+ directory: z.string().optional().describe("Target directory (defaults to current working directory)"),
1650
+ apiKey: z.string().optional().describe("TestDriver API key (will be saved to .env)"),
1651
+ skipInstall: z.boolean().default(false).describe("Skip npm install step"),
1652
+ }),
1653
+ }, async (params) => {
1654
+ const startTime = Date.now();
1655
+ const targetDir = params.directory ? path.resolve(params.directory) : process.cwd();
1656
+ logger.info("init: Starting", { targetDir, hasApiKey: !!params.apiKey, skipInstall: params.skipInstall });
1657
+ try {
1658
+ // Import the shared init logic (dynamic import for ESM/CJS compatibility)
1659
+ const initProjectPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "lib", "init-project.js");
1660
+ const { initProject } = await import(pathToFileURL(initProjectPath).href);
1661
+ // Run the shared init logic
1662
+ const result = await initProject({
1663
+ targetDir,
1664
+ apiKey: params.apiKey,
1665
+ skipInstall: params.skipInstall,
1666
+ });
1667
+ const duration = Date.now() - startTime;
1668
+ logger.info("init: Completed", { targetDir, duration, success: result.success });
1669
+ const nextSteps = `
1670
+
1671
+ 📚 Next steps:
1672
+
1673
+ 1. Run your tests:
1674
+ npx vitest run
1675
+
1676
+ 2. Use AI agents to write tests:
1677
+ Open VSCode/Cursor and use @testdriver agent
1678
+
1679
+ 3. MCP server configured:
1680
+ TestDriver tools available via MCP in .vscode/mcp.json
1681
+
1682
+ 4. For CI/CD, add TD_API_KEY to your GitHub repository secrets:
1683
+ Settings → Secrets → Actions → New repository secret
1684
+
1685
+ Learn more at https://docs.testdriver.ai/v7/getting-started/
1686
+ `;
1687
+ const allMessages = [...result.results, ...result.errors.map((e) => `⚠️ ${e}`)];
1688
+ return createToolResult(result.success, result.success
1689
+ ? `✅ TestDriver project initialized successfully!\n\n${allMessages.join("\n")}${nextSteps}`
1690
+ : `⚠️ TestDriver project initialization completed with errors:\n\n${allMessages.join("\n")}`, {
1691
+ action: "init",
1692
+ targetDir,
1693
+ filesCreated: result.results.length,
1694
+ hasApiKey: !!params.apiKey,
1695
+ errors: result.errors,
1696
+ duration
1697
+ });
1698
+ }
1699
+ catch (error) {
1700
+ logger.error("init: Failed", { error: String(error), targetDir });
1701
+ captureException(error, { tags: { tool: "init" }, extra: { targetDir } });
1702
+ throw error;
1703
+ }
1704
+ });
1590
1705
  // Start the server
1591
1706
  async function main() {
1592
1707
  logger.info("Starting TestDriver MCP Server", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.2.78",
3
+ "version": "7.2.80",
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",
@@ -62,7 +62,8 @@
62
62
  "serve-report": "npx http-server . -p 8080 -o report.html",
63
63
  "report": "npm run generate-report && npm run serve-report",
64
64
  "generate:skills": "node scripts/generate-skills.js",
65
- "prepack": "cd mcp-server && npm ci && npm run build"
65
+ "build:mcp": "cd mcp-server && npm ci && npm run build",
66
+ "prepublishOnly": "npm run build:mcp"
66
67
  },
67
68
  "author": "",
68
69
  "license": "ISC",
package/sdk.js CHANGED
@@ -1279,12 +1279,24 @@ class TestDriverSDK {
1279
1279
  // Use provided API key or fall back to environment variable
1280
1280
  const resolvedApiKey = apiKey || process.env.TD_API_KEY;
1281
1281
 
1282
+ // Handle preview mode with backwards compatibility for headless option
1283
+ // Preview can be "browser" (default), "ide", or "none" (headless)
1284
+ let previewMode = options.preview || process.env.TD_PREVIEW;
1285
+
1286
+ // Backwards compatibility: headless: true maps to preview: "none"
1287
+ if (options.headless === true && !options.preview) {
1288
+ previewMode = "none";
1289
+ } else if (!previewMode) {
1290
+ previewMode = "browser"; // default
1291
+ }
1292
+
1282
1293
  // Set up environment with API key
1283
1294
  const environment = {
1284
1295
  TD_API_KEY: resolvedApiKey,
1285
1296
  TD_API_ROOT: options.apiRoot || "https://testdriver-api.onrender.com",
1286
1297
  TD_RESOLUTION: options.resolution || "1366x768",
1287
1298
  TD_ANALYTICS: options.analytics !== false,
1299
+ TD_PREVIEW: previewMode,
1288
1300
  ...options.environment,
1289
1301
  };
1290
1302