testdriverai 7.2.63 → 7.2.64

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [7.2.64](https://github.com/testdriverai/testdriverai/compare/v7.2.63...v7.2.64) (2026-02-02)
2
+
3
+
4
+
1
5
  ## [7.2.63](https://github.com/testdriverai/testdriverai/compare/v7.2.62...v7.2.63) (2026-01-30)
2
6
 
3
7
 
package/agent/index.js CHANGED
@@ -110,8 +110,8 @@ class TestDriverAgent extends EventEmitter2 {
110
110
  this.sandbox = createSandbox(this.emitter, this.analytics, this.session);
111
111
 
112
112
  // Attach Sentry log listeners to capture CLI logs as breadcrumbs
113
- const sentry = require("../lib/sentry");
114
- sentry.attachLogListeners(this.emitter);
113
+ const sentry = require("../lib/sentry");
114
+ sentry.attachLogListeners(this.emitter);
115
115
 
116
116
  // Set the OS for the sandbox to use
117
117
  this.sandbox.os = this.sandboxOs;
@@ -191,7 +191,14 @@ class TestDriverAgent extends EventEmitter2 {
191
191
  // allows us to save the current state, run lifecycle hooks, and track analytics
192
192
  async exit(failed = true, shouldSave = false, shouldRunPostrun = false) {
193
193
  const { formatter } = require("../sdk-log-formatter.js");
194
- this.emitter.emit(events.log.narration, formatter.getPrefix("disconnect") + " " + theme.yellow.bold("Exiting") + theme.dim("..."), true);
194
+ this.emitter.emit(
195
+ events.log.narration,
196
+ formatter.getPrefix("disconnect") +
197
+ " " +
198
+ theme.yellow.bold("Exiting") +
199
+ theme.dim("..."),
200
+ true,
201
+ );
195
202
 
196
203
  // Clean up redraw interval
197
204
  if (this.redraw && this.redraw.cleanup) {
@@ -240,9 +247,7 @@ class TestDriverAgent extends EventEmitter2 {
240
247
  if (errorContext) {
241
248
  this.emitter.emit(events.error.fatal, errorContext);
242
249
  } else {
243
- this.emitter.emit(
244
- events.error.fatal,error,
245
- );
250
+ this.emitter.emit(events.error.fatal, error);
246
251
  }
247
252
 
248
253
  if (skipPostrun) {
@@ -436,15 +441,12 @@ class TestDriverAgent extends EventEmitter2 {
436
441
  let mousePosition = await this.system.getMousePosition();
437
442
  let activeWindow = await this.system.activeWin();
438
443
 
439
- let response = await this.sdk.req(
440
- "check",
441
- {
442
- tasks: this.tasks,
443
- images,
444
- mousePosition,
445
- activeWindow,
446
- }
447
- );
444
+ let response = await this.sdk.req("check", {
445
+ tasks: this.tasks,
446
+ images,
447
+ mousePosition,
448
+ activeWindow,
449
+ });
448
450
 
449
451
  // Use log.log (not markdown.static) so output goes through console spy to sandbox
450
452
  this.emitter.emit(events.log.log, response.data);
@@ -878,7 +880,7 @@ commands:
878
880
  currentTask,
879
881
  dry = false,
880
882
  validateAndLoop = false,
881
- shouldSave = true
883
+ shouldSave = true,
882
884
  ) {
883
885
  // Check if execution has been stopped
884
886
  if (this.stopped) {
@@ -901,15 +903,12 @@ commands:
901
903
 
902
904
  this.lastScreenshot = await this.system.captureScreenBase64();
903
905
 
904
- let message = await this.sdk.req(
905
- "input",
906
- {
907
- input: currentTask,
908
- mousePosition: await this.system.getMousePosition(),
909
- activeWindow: await this.system.activeWin(),
910
- image: this.lastScreenshot,
911
- }
912
- );
906
+ let message = await this.sdk.req("input", {
907
+ input: currentTask,
908
+ mousePosition: await this.system.getMousePosition(),
909
+ activeWindow: await this.system.activeWin(),
910
+ image: this.lastScreenshot,
911
+ });
913
912
 
914
913
  this.emitter.emit(events.log.log, message.data);
915
914
 
@@ -1626,8 +1625,8 @@ ${regression}
1626
1625
 
1627
1626
  // Returns the path to the last sandbox file
1628
1627
  getLastSandboxFilePath() {
1629
- const testdriverDir = path.join(process.cwd(), '.testdriver');
1630
- return path.join(testdriverDir, 'last-sandbox');
1628
+ const testdriverDir = path.join(process.cwd(), ".testdriver");
1629
+ return path.join(testdriverDir, "last-sandbox");
1631
1630
  }
1632
1631
 
1633
1632
  // Returns full sandbox info from last-sandbox file (no timeout - let API validate)
@@ -1648,7 +1647,7 @@ ${regression}
1648
1647
 
1649
1648
  return {
1650
1649
  sandboxId: sandboxInfo.sandboxId || sandboxInfo.instanceId || null,
1651
- os: sandboxInfo.os || 'linux',
1650
+ os: sandboxInfo.os || "linux",
1652
1651
  ami: sandboxInfo.ami || null,
1653
1652
  instanceType: sandboxInfo.instanceType || null,
1654
1653
  timestamp: sandboxInfo.timestamp || null,
@@ -1663,7 +1662,7 @@ ${regression}
1663
1662
  // Returns sandboxId to use if AMI/instance type match current requirements
1664
1663
  getRecentSandboxId() {
1665
1664
  const sandboxInfo = this.getLastSandboxId();
1666
-
1665
+
1667
1666
  if (!sandboxInfo || !sandboxInfo.sandboxId) {
1668
1667
  return null;
1669
1668
  }
@@ -1690,13 +1689,13 @@ ${regression}
1690
1689
  saveLastSandboxId(sandboxId, osType = "linux") {
1691
1690
  const lastSandboxFile = this.getLastSandboxFilePath();
1692
1691
  const testdriverDir = path.dirname(lastSandboxFile);
1693
-
1692
+
1694
1693
  try {
1695
1694
  // Ensure .testdriver directory exists
1696
1695
  if (!fs.existsSync(testdriverDir)) {
1697
1696
  fs.mkdirSync(testdriverDir, { recursive: true });
1698
1697
  }
1699
-
1698
+
1700
1699
  const sandboxInfo = {
1701
1700
  sandboxId: sandboxId,
1702
1701
  os: osType,
@@ -1757,15 +1756,9 @@ ${regression}
1757
1756
  // Also clear this.sandboxId to prevent reconnection attempts
1758
1757
  this.sandboxId = null;
1759
1758
  if (!this.config.CI && !this.newSandbox) {
1760
- this.emitter.emit(
1761
- events.log.log,
1762
- theme.dim("--`new` flag detected, will create a new sandbox"),
1763
- );
1759
+ this.emitter.emit(events.log.log, theme.dim("Creating a new sandbox"));
1764
1760
  } else if (this.newSandbox) {
1765
- this.emitter.emit(
1766
- events.log.log,
1767
- theme.dim("--new-sandbox flag detected, will create a new sandbox"),
1768
- );
1761
+ this.emitter.emit(events.log.log, theme.dim("Creating a new sandbox"));
1769
1762
  }
1770
1763
  }
1771
1764
 
@@ -1802,7 +1795,7 @@ ${regression}
1802
1795
  theme.dim(`using recent sandbox: ${recentId}`),
1803
1796
  );
1804
1797
  this.sandboxId = recentId;
1805
-
1798
+
1806
1799
  try {
1807
1800
  let instance = await this.connectToSandboxDirect(
1808
1801
  this.sandboxId,
@@ -1850,13 +1843,17 @@ ${regression}
1850
1843
  console.error("Failed to reconnect to sandbox:", error);
1851
1844
  }
1852
1845
  }
1853
-
1846
+
1854
1847
  // Create new sandbox (either because createNew is true, or no existing sandbox to connect to)
1855
1848
  if (!this.instance) {
1856
1849
  const { formatter } = require("../sdk-log-formatter.js");
1857
1850
  this.emitter.emit(
1858
1851
  events.log.narration,
1859
- formatter.getPrefix("connect") + " " + theme.green.bold("Creating") + " " + theme.cyan(`new sandbox...`),
1852
+ formatter.getPrefix("connect") +
1853
+ " " +
1854
+ theme.green.bold("Creating") +
1855
+ " " +
1856
+ theme.cyan(`new sandbox...`),
1860
1857
  );
1861
1858
  // We don't have resiliency/retries baked in, so let's at least give it 1 attempt
1862
1859
  // to see if that fixes the issue.
@@ -1869,11 +1866,12 @@ ${regression}
1869
1866
  });
1870
1867
 
1871
1868
  // Extract the sandbox ID from the newly created sandbox
1872
- this.sandboxId = newSandbox?.sandbox?.sandboxId || newSandbox?.sandbox?.instanceId;
1873
-
1869
+ this.sandboxId =
1870
+ newSandbox?.sandbox?.sandboxId || newSandbox?.sandbox?.instanceId;
1871
+
1874
1872
  // Use the configured sandbox OS type
1875
1873
  this.saveLastSandboxId(this.sandboxId, this.sandboxOs);
1876
-
1874
+
1877
1875
  let instance = await this.connectToSandboxDirect(
1878
1876
  this.sandboxId,
1879
1877
  true, // always persist by default
@@ -2002,7 +2000,6 @@ ${regression}
2002
2000
  }
2003
2001
 
2004
2002
  async renderSandbox(instance, headless = false) {
2005
-
2006
2003
  if (!headless) {
2007
2004
  let url;
2008
2005
 
@@ -2056,7 +2053,13 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2056
2053
  }
2057
2054
 
2058
2055
  const { formatter } = require("../sdk-log-formatter.js");
2059
- this.emitter.emit(events.log.narration, formatter.getPrefix("connect") + " " + theme.green.bold("Authenticating") + theme.dim("..."));
2056
+ this.emitter.emit(
2057
+ events.log.narration,
2058
+ formatter.getPrefix("connect") +
2059
+ " " +
2060
+ theme.green.bold("Authenticating") +
2061
+ theme.dim("..."),
2062
+ );
2060
2063
  let ableToAuth = await this.sandbox.auth(this.config.TD_API_KEY);
2061
2064
 
2062
2065
  if (!ableToAuth) {
@@ -2070,7 +2073,14 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2070
2073
 
2071
2074
  async connectToSandboxDirect(sandboxId, persist = false, keepAlive = null) {
2072
2075
  const { formatter } = require("../sdk-log-formatter.js");
2073
- this.emitter.emit(events.log.narration, formatter.getPrefix("connect") + " " + theme.green.bold("Connecting") + " " + theme.cyan(`to sandbox...`));
2076
+ this.emitter.emit(
2077
+ events.log.narration,
2078
+ formatter.getPrefix("connect") +
2079
+ " " +
2080
+ theme.green.bold("Connecting") +
2081
+ " " +
2082
+ theme.cyan(`to sandbox...`),
2083
+ );
2074
2084
  let reply = await this.sandbox.connect(sandboxId, persist, keepAlive);
2075
2085
 
2076
2086
  // reply includes { success, url, sandbox: {...} }
@@ -2112,15 +2122,18 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2112
2122
  let response = await this.sandbox.send(sandboxConfig, 60000 * 8);
2113
2123
 
2114
2124
  // Check if queued (all slots in use)
2115
- if (response.type === 'create.queued') {
2125
+ if (response.type === "create.queued") {
2116
2126
  this.emitter.emit(
2117
2127
  events.log.narration,
2118
- formatter.getPrefix("queue") + " " + theme.yellow.bold("Waiting") + " " +
2119
- theme.dim(response.message),
2128
+ formatter.getPrefix("queue") +
2129
+ " " +
2130
+ theme.yellow.bold("Waiting") +
2131
+ " " +
2132
+ theme.dim(response.message),
2120
2133
  );
2121
2134
 
2122
2135
  // Wait then retry
2123
- await new Promise(resolve => setTimeout(resolve, retryDelay));
2136
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
2124
2137
  continue;
2125
2138
  }
2126
2139
 
@@ -2139,10 +2152,14 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2139
2152
  // should be start of new session
2140
2153
  // If sandbox is connected, get system info; otherwise pass empty objects
2141
2154
  const isSandboxConnected = this.sandbox.apiSocketConnected;
2142
-
2155
+
2143
2156
  const sessionRes = await this.sdk.req("session/start", {
2144
- systemInformationOsInfo: isSandboxConnected ? await this.system.getSystemInformationOsInfo() : {},
2145
- mousePosition: isSandboxConnected ? await this.system.getMousePosition() : {},
2157
+ systemInformationOsInfo: isSandboxConnected
2158
+ ? await this.system.getSystemInformationOsInfo()
2159
+ : {},
2160
+ mousePosition: isSandboxConnected
2161
+ ? await this.system.getMousePosition()
2162
+ : {},
2146
2163
  activeWindow: isSandboxConnected ? await this.system.activeWin() : {},
2147
2164
  });
2148
2165
 
@@ -2153,7 +2170,7 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2153
2170
  }
2154
2171
 
2155
2172
  this.session.set(sessionRes.data.id);
2156
-
2173
+
2157
2174
  // Set Sentry session trace context for distributed tracing
2158
2175
  // This links CLI errors/logs to the same trace as API calls
2159
2176
  try {
@@ -10,14 +10,14 @@ const { events } = require("../events");
10
10
  */
11
11
  function getSentryTraceHeaders(sessionId) {
12
12
  if (!sessionId) return {};
13
-
13
+
14
14
  // Same logic as API: derive trace ID from session ID
15
- const traceId = crypto.createHash('md5').update(sessionId).digest('hex');
16
- const spanId = crypto.randomBytes(8).toString('hex');
17
-
15
+ const traceId = crypto.createHash("md5").update(sessionId).digest("hex");
16
+ const spanId = crypto.randomBytes(8).toString("hex");
17
+
18
18
  return {
19
- 'sentry-trace': `${traceId}-${spanId}-1`,
20
- 'baggage': `sentry-trace_id=${traceId},sentry-sample_rate=1.0,sentry-sampled=true`
19
+ "sentry-trace": `${traceId}-${spanId}-1`,
20
+ baggage: `sentry-trace_id=${traceId},sentry-sample_rate=1.0,sentry-sampled=true`,
21
21
  };
22
22
  }
23
23
 
@@ -36,6 +36,11 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
36
36
  this.os = null; // Store OS value to send with every message
37
37
  this.sessionInstance = sessionInstance; // Store session instance to include in messages
38
38
  this.traceId = null; // Sentry trace ID for debugging
39
+ this.reconnectAttempts = 0;
40
+ this.maxReconnectAttempts = 5;
41
+ this.intentionalDisconnect = false;
42
+ this.apiRoot = null;
43
+ this.apiKey = null;
39
44
  }
40
45
 
41
46
  /**
@@ -90,12 +95,16 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
90
95
  });
91
96
 
92
97
  const requestId = message.requestId;
93
-
98
+
94
99
  // Set up timeout to prevent hanging requests
95
100
  const timeoutId = setTimeout(() => {
96
101
  if (this.ps[requestId]) {
97
102
  delete this.ps[requestId];
98
- rejectPromise(new Error(`Sandbox message '${message.type}' timed out after ${timeout}ms`));
103
+ rejectPromise(
104
+ new Error(
105
+ `Sandbox message '${message.type}' timed out after ${timeout}ms`,
106
+ ),
107
+ );
99
108
  }
100
109
  }, timeout);
101
110
 
@@ -115,12 +124,13 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
115
124
 
116
125
  return p;
117
126
  }
118
-
127
+
119
128
  // Return a rejected promise if socket is not available
120
- return Promise.reject(new Error('Sandbox socket not connected'));
129
+ return Promise.reject(new Error("Sandbox socket not connected"));
121
130
  }
122
131
 
123
132
  async auth(apiKey) {
133
+ this.apiKey = apiKey;
124
134
  let reply = await this.send({
125
135
  type: "authenticate",
126
136
  apiKey,
@@ -128,15 +138,17 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
128
138
 
129
139
  if (reply.success) {
130
140
  this.authenticated = true;
131
-
141
+
132
142
  // Log and store the Sentry trace ID for debugging
133
143
  if (reply.traceId) {
134
144
  this.traceId = reply.traceId;
135
- console.log('');
145
+ console.log("");
136
146
  console.log(`🔗 View Trace:`);
137
- console.log(`https://testdriver.sentry.io/explore/traces/trace/${reply.traceId}`);
147
+ console.log(
148
+ `https://testdriver.sentry.io/explore/traces/trace/${reply.traceId}`,
149
+ );
138
150
  }
139
-
151
+
140
152
  emitter.emit(events.sandbox.authenticated, { traceId: reply.traceId });
141
153
  return true;
142
154
  }
@@ -161,24 +173,58 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
161
173
  }
162
174
  }
163
175
 
176
+ async handleConnectionLoss() {
177
+ if (this.intentionalDisconnect) return;
178
+
179
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
180
+ const errorMsg =
181
+ "Unable to reconnect to TestDriver sandbox after multiple attempts. Please check your internet connection.";
182
+ emitter.emit(events.error.sandbox, errorMsg);
183
+ console.error(errorMsg);
184
+ return;
185
+ }
186
+
187
+ this.reconnectAttempts++;
188
+ const delay = Math.min(1000 * 2 ** (this.reconnectAttempts - 1), 30000);
189
+
190
+ console.log(
191
+ `[Sandbox] Connection lost. Reconnecting in ${delay}ms... (Attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`,
192
+ );
193
+
194
+ setTimeout(async () => {
195
+ try {
196
+ await this.boot(this.apiRoot);
197
+ if (this.apiKey) {
198
+ await this.auth(this.apiKey);
199
+ }
200
+ console.log("[Sandbox] Reconnected successfully.");
201
+ } catch (e) {
202
+ // Ignore error here as the boot's error handler will trigger handleConnectionLoss again
203
+ }
204
+ }, delay);
205
+ }
206
+
164
207
  async boot(apiRoot) {
208
+ if (apiRoot) this.apiRoot = apiRoot;
165
209
  return new Promise((resolve, reject) => {
166
210
  // Get session ID for Sentry trace headers
167
211
  const sessionId = this.sessionInstance?.get();
168
-
212
+
169
213
  if (!sessionId) {
170
- console.warn('[Sandbox] No session ID available at boot time - Sentry tracing will not be available');
214
+ console.warn(
215
+ "[Sandbox] No session ID available at boot time - Sentry tracing will not be available",
216
+ );
171
217
  }
172
-
218
+
173
219
  const sentryHeaders = getSentryTraceHeaders(sessionId);
174
220
 
175
221
  // Build WebSocket URL with Sentry trace headers as query params
176
222
  const wsUrl = new URL(apiRoot.replace("https://", "wss://"));
177
- if (sentryHeaders['sentry-trace']) {
178
- wsUrl.searchParams.set('sentry-trace', sentryHeaders['sentry-trace']);
223
+ if (sentryHeaders["sentry-trace"]) {
224
+ wsUrl.searchParams.set("sentry-trace", sentryHeaders["sentry-trace"]);
179
225
  }
180
- if (sentryHeaders['baggage']) {
181
- wsUrl.searchParams.set('baggage', sentryHeaders['baggage']);
226
+ if (sentryHeaders["baggage"]) {
227
+ wsUrl.searchParams.set("baggage", sentryHeaders["baggage"]);
182
228
  }
183
229
 
184
230
  this.socket = new WebSocket(wsUrl.toString());
@@ -189,6 +235,7 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
189
235
  // Emit a clear error event for API key issues
190
236
  reject();
191
237
  this.apiSocketConnected = false;
238
+ this.handleConnectionLoss();
192
239
  });
193
240
 
194
241
  this.socket.on("error", (err) => {
@@ -197,10 +244,13 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
197
244
  clearInterval(this.heartbeat);
198
245
  emitter.emit(events.error.sandbox, err);
199
246
  this.apiSocketConnected = false;
200
- throw err;
247
+ this.handleConnectionLoss();
248
+ // We don't throw here to avoid crashing the process, let reconnection handle it
249
+ reject(err);
201
250
  });
202
251
 
203
252
  this.socket.on("open", async () => {
253
+ this.reconnectAttempts = 0;
204
254
  this.apiSocketConnected = true;
205
255
 
206
256
  this.heartbeat = setInterval(() => {
@@ -216,7 +266,7 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
216
266
  let message = JSON.parse(raw);
217
267
 
218
268
  // Handle progress messages (no requestId needed)
219
- if (message.type === 'sandbox.progress') {
269
+ if (message.type === "sandbox.progress") {
220
270
  emitter.emit(events.sandbox.progress, {
221
271
  step: message.step,
222
272
  message: message.message,
@@ -250,11 +300,12 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
250
300
  * Close the WebSocket connection and clean up resources
251
301
  */
252
302
  close() {
303
+ this.intentionalDisconnect = true;
253
304
  if (this.heartbeat) {
254
305
  clearInterval(this.heartbeat);
255
306
  this.heartbeat = null;
256
307
  }
257
-
308
+
258
309
  if (this.socket) {
259
310
  try {
260
311
  this.socket.close();
@@ -263,12 +314,12 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
263
314
  }
264
315
  this.socket = null;
265
316
  }
266
-
317
+
267
318
  this.apiSocketConnected = false;
268
319
  this.instanceSocketConnected = false;
269
320
  this.authenticated = false;
270
321
  this.instance = null;
271
-
322
+
272
323
  // Silently clear pending promises without rejecting
273
324
  // (rejecting causes unhandled promise rejections during cleanup)
274
325
  this.ps = {};
@@ -112,7 +112,7 @@ class Dashcam {
112
112
  shell,
113
113
  "npm prefix -g",
114
114
  40000,
115
- false,
115
+ process.env.DEBUG == "true" ? false : true,
116
116
  );
117
117
 
118
118
  if (this.client.os === "windows") {
@@ -140,7 +140,7 @@ class Dashcam {
140
140
  shell,
141
141
  `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" auth ${key}`,
142
142
  120000,
143
- false,
143
+ process.env.DEBUG == "true" ? false : true,
144
144
  );
145
145
  this._log("debug", "Auth output:", authOutput);
146
146
  } else {
@@ -149,7 +149,7 @@ class Dashcam {
149
149
  shell,
150
150
  `TD_API_ROOT="${apiRoot}" dashcam auth ${key}`,
151
151
  120000,
152
- false,
152
+ process.env.DEBUG == "true" ? false : true,
153
153
  );
154
154
  this._log("debug", "Auth output:", authOutput);
155
155
  }
@@ -173,7 +173,7 @@ class Dashcam {
173
173
  shell,
174
174
  `New-Item -ItemType File -Path "${path}" -Force`,
175
175
  10000,
176
- false,
176
+ process.env.DEBUG == "true" ? false : true,
177
177
  );
178
178
  this._log("debug", "Create log file output:", createFileOutput);
179
179
 
@@ -182,19 +182,24 @@ class Dashcam {
182
182
  shell,
183
183
  `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" logs --add --type=file --file="${path}" --name="${name}"`,
184
184
  120000,
185
- false,
185
+ process.env.DEBUG == "true" ? false : true,
186
186
  );
187
187
  this._log("debug", "Add log tracking output:", addLogOutput);
188
188
  } else {
189
189
  // Create log file
190
- await this.client.exec(shell, `touch ${path}`, 10000, false);
190
+ await this.client.exec(
191
+ shell,
192
+ `touch ${path}`,
193
+ 10000,
194
+ process.env.DEBUG == "true" ? false : true,
195
+ );
191
196
 
192
197
  // Add log tracking with TD_API_ROOT
193
198
  const addLogOutput = await this.client.exec(
194
199
  shell,
195
200
  `TD_API_ROOT="${apiRoot}" dashcam logs --add --type=file --file="${path}" --name="${name}"`,
196
201
  10000,
197
- false,
202
+ process.env.DEBUG == "true" ? false : true,
198
203
  );
199
204
  this._log("debug", "Add log tracking output:", addLogOutput);
200
205
  }
@@ -216,7 +221,7 @@ class Dashcam {
216
221
  shell,
217
222
  `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" logs --add --type=application --application="${application}" --name="${name}"`,
218
223
  120000,
219
- false,
224
+ process.env.DEBUG == "true" ? false : true,
220
225
  );
221
226
  this._log("debug", "Add application log tracking output:", addLogOutput);
222
227
  } else {
@@ -224,7 +229,7 @@ class Dashcam {
224
229
  shell,
225
230
  `TD_API_ROOT="${apiRoot}" dashcam logs --add --type=application --application="${application}" --name="${name}"`,
226
231
  10000,
227
- false,
232
+ process.env.DEBUG == "true" ? false : true,
228
233
  );
229
234
  this._log("debug", "Add application log tracking output:", addLogOutput);
230
235
  }
@@ -246,7 +251,7 @@ class Dashcam {
246
251
  shell,
247
252
  `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" logs --add --type=web --pattern="${pattern}" --name="${name}"`,
248
253
  120000,
249
- false,
254
+ process.env.DEBUG == "true" ? false : true,
250
255
  );
251
256
  this._log("debug", "Add web log tracking output:", addLogOutput);
252
257
  } else {
@@ -254,7 +259,7 @@ class Dashcam {
254
259
  shell,
255
260
  `TD_API_ROOT="${apiRoot}" dashcam logs --add --type=web --pattern="${pattern}" --name="${name}"`,
256
261
  10000,
257
- false,
262
+ process.env.DEBUG == "true" ? false : true,
258
263
  );
259
264
  this._log("debug", "Add web log tracking output:", addLogOutput);
260
265
  }
@@ -310,7 +315,7 @@ class Dashcam {
310
315
  shell,
311
316
  startScript,
312
317
  10000,
313
- false,
318
+ process.env.DEBUG == "true" ? false : true,
314
319
  );
315
320
  this._log("debug", "Start-Process output:", startOutput);
316
321
 
@@ -320,7 +325,7 @@ class Dashcam {
320
325
  shell,
321
326
  `Get-Content "${outputFile}" -ErrorAction SilentlyContinue`,
322
327
  10000,
323
- false,
328
+ process.env.DEBUG == "true" ? false : true,
324
329
  );
325
330
  this._log("debug", "Dashcam record output:", dashcamOutput);
326
331
 
@@ -339,6 +344,8 @@ class Dashcam {
339
344
  await this.client.exec(
340
345
  shell,
341
346
  `TD_API_ROOT="${apiRoot}" dashcam record${titleArg} >/dev/null 2>&1 &`,
347
+ 10000,
348
+ process.env.DEBUG == "true" ? false : true,
342
349
  );
343
350
  this._log("debug", "Dashcam recording started");
344
351
  }
@@ -382,7 +389,7 @@ class Dashcam {
382
389
  shell,
383
390
  `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" stop`,
384
391
  300000,
385
- process.env.DEBUG == "true" ? true : false,
392
+ process.env.DEBUG == "true" ? false : true,
386
393
  );
387
394
  this._log("debug", "Dashcam stop command output:", output);
388
395
  } else {
@@ -392,7 +399,7 @@ class Dashcam {
392
399
  shell,
393
400
  `TD_API_ROOT="${apiRoot}" "${dashcamPath}" stop`,
394
401
  300000,
395
- process.env.DEBUG == "true" ? true : false,
402
+ process.env.DEBUG == "true" ? false : true,
396
403
  );
397
404
  this._log("debug", "Dashcam command output:", output);
398
405
  }
@@ -114,12 +114,10 @@ function cleanupAllInstances() {
114
114
  // Register cleanup handlers for various exit scenarios
115
115
  process.on("exit", cleanupAllInstances);
116
116
  process.on("SIGINT", () => {
117
- console.log("\n[TestDriver] Received SIGINT, cleaning up instances...");
118
117
  cleanupAllInstances();
119
118
  // Don't call process.exit here - let the signal handler do its job
120
119
  });
121
120
  process.on("SIGTERM", () => {
122
- console.log("\n[TestDriver] Received SIGTERM, cleaning up instances...");
123
121
  cleanupAllInstances();
124
122
  // Don't call process.exit here - let the signal handler do its job
125
123
  });
@@ -142,14 +140,6 @@ beforeEach(async (context) => {
142
140
  return;
143
141
  }
144
142
 
145
- // Verify required parameters are available
146
- if (process.env.TD_OS === "windows") {
147
- console.log(
148
- "[TestDriver] startup check: TWOCAPTCHA_API_KEY is " +
149
- (process.env.TWOCAPTCHA_API_KEY ? "REQUIRED" : "MISSING"),
150
- );
151
- }
152
-
153
143
  if (!process.env.AWS_LAUNCH_TEMPLATE_ID || !process.env.AMI_ID) {
154
144
  throw new Error(
155
145
  "[TestDriver] TD_OS=windows requires AWS_LAUNCH_TEMPLATE_ID and AMI_ID environment variables",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.2.63",
3
+ "version": "7.2.64",
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
@@ -1247,6 +1247,7 @@ export default class TestDriverSDK {
1247
1247
  // AI Methods (Exploratory Loop)
1248
1248
 
1249
1249
  /**
1250
+ * @deprecated Use ai() instead
1250
1251
  * Execute a natural language task using AI
1251
1252
  * This is the SDK equivalent of the CLI's exploratory loop
1252
1253
  *
@@ -1256,11 +1257,11 @@ export default class TestDriverSDK {
1256
1257
  *
1257
1258
  * @example
1258
1259
  * // Simple execution
1259
- * await client.act('Click the submit button');
1260
+ * await client.ai('Click the submit button');
1260
1261
  *
1261
1262
  * @example
1262
1263
  * // With validation loop
1263
- * const result = await client.act('Fill out the contact form', { validateAndLoop: true });
1264
+ * const result = await client.ai('Fill out the contact form', { validateAndLoop: true });
1264
1265
  * console.log(result); // AI's final assessment
1265
1266
  */
1266
1267
  act(
@@ -1269,7 +1270,6 @@ export default class TestDriverSDK {
1269
1270
  ): Promise<string | void>;
1270
1271
 
1271
1272
  /**
1272
- * @deprecated Use act() instead
1273
1273
  * Execute a natural language task using AI
1274
1274
  *
1275
1275
  * @param task - Natural language description of what to do
package/sdk.js CHANGED
@@ -3434,28 +3434,28 @@ CAPTCHA_SOLVER_EOF`,
3434
3434
  *
3435
3435
  * @example
3436
3436
  * // Simple execution
3437
- * const result = await client.act('Click the submit button');
3437
+ * const result = await client.ai('Click the submit button');
3438
3438
  * console.log(result.success); // true
3439
3439
  *
3440
3440
  * @example
3441
3441
  * // With custom retry limit
3442
- * const result = await client.act('Fill out the contact form', { tries: 10 });
3442
+ * const result = await client.ai('Fill out the contact form', { tries: 10 });
3443
3443
  * console.log(`Completed in ${result.tries} tries`);
3444
3444
  *
3445
3445
  * @example
3446
3446
  * // Handle failures
3447
3447
  * try {
3448
- * await client.act('Complete the checkout process', { tries: 3 });
3448
+ * await client.ai('Complete the checkout process', { tries: 3 });
3449
3449
  * } catch (error) {
3450
3450
  * console.log(`Failed after ${error.tries} tries: ${error.message}`);
3451
3451
  * }
3452
3452
  */
3453
- async act(task, options = {}) {
3453
+ async ai(task, options = {}) {
3454
3454
  this._ensureConnected();
3455
3455
 
3456
3456
  const { tries = 7 } = options;
3457
3457
 
3458
- this.analytics.track("sdk.act", { task, tries });
3458
+ this.analytics.track("sdk.ai", { task, tries });
3459
3459
 
3460
3460
  const { events } = require("./agent/events.js");
3461
3461
  const startTime = Date.now();
@@ -3464,7 +3464,7 @@ CAPTCHA_SOLVER_EOF`,
3464
3464
  const originalCheckLimit = this.agent.checkLimit;
3465
3465
  this.agent.checkLimit = tries;
3466
3466
 
3467
- // Reset check count for this act() call
3467
+ // Reset check count for this ai() call
3468
3468
  const originalCheckCount = this.agent.checkCount;
3469
3469
  this.agent.checkCount = 0;
3470
3470
 
@@ -3531,7 +3531,7 @@ CAPTCHA_SOLVER_EOF`,
3531
3531
  }
3532
3532
 
3533
3533
  /**
3534
- * @deprecated Use act() instead
3534
+ * @deprecated Use ai() instead
3535
3535
  * Execute a natural language task using AI
3536
3536
  *
3537
3537
  * @param {string} task - Natural language description of what to do
@@ -3539,8 +3539,8 @@ CAPTCHA_SOLVER_EOF`,
3539
3539
  * @param {number} [options.tries=7] - Maximum number of check/retry attempts
3540
3540
  * @returns {Promise<ActResult>} Result object with success status and details
3541
3541
  */
3542
- async ai(task, options) {
3543
- return await this.act(task, options);
3542
+ async act(task, options) {
3543
+ return await this.ai(task, options);
3544
3544
  }
3545
3545
  }
3546
3546