testdriverai 7.2.39 → 7.2.40

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,5 +1,4 @@
1
1
  const WebSocket = require("ws");
2
- const marky = require("marky");
3
2
  const crypto = require("crypto");
4
3
  const { events } = require("../events");
5
4
 
@@ -83,10 +82,6 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
83
82
  }
84
83
  }
85
84
 
86
- // Start timing for this message
87
- const timingKey = `sandbox-${message.type}`;
88
- marky.mark(timingKey);
89
-
90
85
  let p = new Promise((resolve, reject) => {
91
86
  this.socket.send(JSON.stringify(message));
92
87
  emitter.emit(events.sandbox.sent, message);
@@ -99,13 +94,6 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
99
94
  // Set up timeout to prevent hanging requests
100
95
  const timeoutId = setTimeout(() => {
101
96
  if (this.ps[requestId]) {
102
- const pendingMessage = this.ps[requestId];
103
- // Stop the timing marker to prevent memory leak
104
- try {
105
- marky.stop(pendingMessage.timingKey);
106
- } catch (e) {
107
- // Ignore timing errors
108
- }
109
97
  delete this.ps[requestId];
110
98
  rejectPromise(new Error(`Sandbox message '${message.type}' timed out after ${timeout}ms`));
111
99
  }
@@ -122,7 +110,6 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
122
110
  rejectPromise(error);
123
111
  },
124
112
  message,
125
- timingKey,
126
113
  startTime: Date.now(),
127
114
  };
128
115
 
@@ -252,25 +239,6 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
252
239
  this.ps[message.requestId].reject(error);
253
240
  } else {
254
241
  emitter.emit(events.sandbox.received);
255
-
256
- // Get timing information for this message
257
- const pendingMessage = this.ps[message.requestId];
258
- if (pendingMessage) {
259
- const timing = marky.stop(pendingMessage.timingKey);
260
-
261
- // Track timing for each message type
262
- await analytics.track("sandbox", {
263
- operation: pendingMessage.message.type,
264
- timing,
265
- requestId: message.requestId,
266
- timestamp: Date.now(),
267
- data: {
268
- messageType: pendingMessage.message.type,
269
- ...pendingMessage.message,
270
- },
271
- });
272
- }
273
-
274
242
  this.ps[message.requestId]?.resolve(message);
275
243
  }
276
244
  delete this.ps[message.requestId];
package/agents.md CHANGED
@@ -241,6 +241,8 @@ describe("Experiment", () => {
241
241
  });
242
242
  ```
243
243
 
244
+ > ⚠️ **NEVER REMOVE `reconnect: true`**: If a test file already has `reconnect: true`, do NOT remove it. This option is intentional and removing it will break the two-file workflow. Only remove `reconnect: true` when explicitly combining files into a final standalone test.
245
+
244
246
  ### Step 4: Iterate in Experiment File
245
247
 
246
248
  - Run experiment, see output, fix issues
@@ -260,13 +262,15 @@ vitest run tests/experiment.test.mjs
260
262
 
261
263
  ### After Experimentation: Combine and Rename
262
264
 
263
- **IMPORTANT:** Once the test is working, combine both files into a single, properly-named test file:
265
+ **IMPORTANT:** Once the test is working AND the user explicitly asks to combine/finalize the test, combine both files into a single, properly-named test file:
264
266
 
265
267
  1. **Merge the code** - Copy the working steps from `experiment.test.mjs` into `setup.test.mjs`
266
- 2. **Remove reconnect options** - Delete `reconnect: true` since the final test runs standalone
268
+ 2. **Remove reconnect options** - Delete `reconnect: true` since the final test runs standalone (ONLY do this when creating the final combined test - never remove it from an existing experiment file!)
267
269
  3. **Rename the file** - Give it a meaningful name like `login-flow.test.mjs` or `checkout-process.test.mjs`
268
270
  4. **Delete the experiment file** - Clean up `experiment.test.mjs`
269
271
 
272
+ > ⚠️ **WARNING FOR AI AGENTS**: Do NOT remove `reconnect: true` from any existing test file unless you are explicitly combining files into a final test. If a test has `reconnect: true`, it is there intentionally.
273
+
270
274
  ```javascript
271
275
  // Final combined test: login-flow.test.mjs
272
276
  import { describe, expect, it } from "vitest";
@@ -15,27 +15,6 @@ const testdriver = TestDriver(context, {
15
15
  });
16
16
  ```
17
17
 
18
- ### Headless Mode
19
-
20
- Run tests without a visible browser window. Useful for CI/CD pipeline or spawning multiple tests locally:
21
-
22
- ```javascript
23
- const testdriver = TestDriver(context, {
24
- headless: true, // No visible browser window
25
- });
26
- ```
27
-
28
- ### IP Target
29
-
30
- If self-hosting TestDriver, use `ip` to specify the device IP. See [Self-Hosting TestDriver](../self-hosting.md) for details.
31
-
32
- ```javascript
33
- const testdriver = TestDriver(context, {
34
- newSandbox: true,
35
- ip: "203.0.113.42", // Your allowlisted IP
36
- });
37
- ```
38
-
39
18
  ### Operating System
40
19
 
41
20
  Set the `os` property to run tests on a specific operating system. Available options are `linux` (default) and `windows`.
@@ -77,6 +56,27 @@ steps:
77
56
  - run: TD_OS=${{ matrix.os }} npx vitest run
78
57
  ```
79
58
 
59
+ ### Headless Mode
60
+
61
+ Run tests without a visible browser window. Useful for CI/CD pipeline or spawning multiple tests locally:
62
+
63
+ ```javascript
64
+ const testdriver = TestDriver(context, {
65
+ headless: true, // No visible browser window
66
+ });
67
+ ```
68
+
69
+ ### IP Target
70
+
71
+ If self-hosting TestDriver, use `ip` to specify the device IP. See [Self-Hosting TestDriver](../self-hosting.md) for details.
72
+
73
+ ```javascript
74
+ const testdriver = TestDriver(context, {
75
+ newSandbox: true,
76
+ ip: "203.0.113.42", // Your allowlisted IP
77
+ });
78
+ ```
79
+
80
80
  ## Keepalive
81
81
 
82
82
  By default, sandboxes terminate immediately when the test finishes. Set this value to keep the sandbox alive for reconnection.
@@ -130,23 +130,6 @@ class Dashcam {
130
130
  const dashcamPath = await this._getDashcamPath();
131
131
  this._log('debug', 'Dashcam executable path:', dashcamPath);
132
132
 
133
- const installedVersion = await this.client.exec(
134
- shell,
135
- 'npm ls dashcam -g',
136
- 40000,
137
- true
138
- );
139
- this._log('debug', 'Installed dashcam version:', installedVersion);
140
-
141
- // Test version command
142
- const versionTest = await this.client.exec(
143
- shell,
144
- `& "${dashcamPath}" version`,
145
- 40000,
146
- true
147
- );
148
- this._log('debug', 'Dashcam version test:', versionTest);
149
-
150
133
  // Authenticate with TD_API_ROOT
151
134
  const authOutput = await this.client.exec(
152
135
  shell,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.2.39",
3
+ "version": "7.2.40",
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
@@ -242,7 +242,7 @@ export interface TestDriverOptions {
242
242
  sandboxInstance?: string;
243
243
  /** Cache key for element finding operations. If provided, enables caching tied to this key */
244
244
  cacheKey?: string;
245
- /** Reconnect to the last used sandbox (throws error if no last sandbox exists) */
245
+ /** Reconnect to the last used sandbox instead of creating a new one. When true, provision methods (chrome, vscode, installer, etc.) will be skipped since the application is already running. Throws error if no previous sandbox exists. */
246
246
  reconnect?: boolean;
247
247
  /** Redraw configuration for screen change detection */
248
248
  redraw?: boolean | {
@@ -266,7 +266,7 @@ export interface ConnectOptions {
266
266
  sandboxId?: string;
267
267
  /** Force creation of a new sandbox */
268
268
  newSandbox?: boolean;
269
- /** Reconnect to the last used sandbox (throws error if no last sandbox exists) */
269
+ /** Reconnect to the last used sandbox instead of creating a new one. When true, provision methods (chrome, vscode, installer, etc.) will be skipped since the application is already running. Throws error if no previous sandbox exists. */
270
270
  reconnect?: boolean;
271
271
  /** Direct IP address to connect to a running sandbox instance */
272
272
  ip?: string;
package/sdk.js CHANGED
@@ -1362,10 +1362,13 @@ class TestDriverSDK {
1362
1362
 
1363
1363
  /**
1364
1364
  * Create the provision API with methods for launching applications
1365
+ * Automatically skips provisioning when reconnect mode is enabled
1365
1366
  * @private
1366
1367
  */
1367
1368
  _createProvisionAPI() {
1368
- return {
1369
+ const self = this;
1370
+
1371
+ const provisionMethods = {
1369
1372
  /**
1370
1373
  * Launch Chrome browser
1371
1374
  * @param {Object} options - Chrome launch options
@@ -2073,6 +2076,24 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
2073
2076
  await this.focusApplication('Electron');
2074
2077
  },
2075
2078
  };
2079
+
2080
+ // Wrap all provision methods with reconnect check using Proxy
2081
+ return new Proxy(provisionMethods, {
2082
+ get(target, prop) {
2083
+ const method = target[prop];
2084
+ if (typeof method === 'function') {
2085
+ return async (...args) => {
2086
+ // Skip provisioning if reconnecting to existing sandbox
2087
+ if (self.reconnect) {
2088
+ console.log(`[provision.${prop}] Skipping provisioning (reconnect mode)`);
2089
+ return;
2090
+ }
2091
+ return method(...args);
2092
+ };
2093
+ }
2094
+ return method;
2095
+ }
2096
+ });
2076
2097
  }
2077
2098
 
2078
2099
  /**
@@ -30,7 +30,7 @@ describe("Scroll Keyboard Test", () => {
30
30
  await testdriver.scroll("down", { amount: 1000 });
31
31
 
32
32
  // Assert the page is scrolled down
33
- const result = await testdriver.assert("the page is scrolled down, the hamster dance h1 text heading is not visible on the webpage");
33
+ const result = await testdriver.assert("The text 'The Hamster Dance' is not visible on the webpage content. It's ok if it's visible in the tab title.");
34
34
 
35
35
  expect(result).toBeTruthy();
36
36
  });
@@ -35,7 +35,7 @@ describe("Scroll Test", () => {
35
35
  await testdriver.scroll("down", { amount: 1000 });
36
36
 
37
37
  // Assert page is scrolled
38
- const result = await testdriver.assert("the page is scrolled down, the hamster dance h1 text heading is not visible on the webpage");
38
+ const result = await testdriver.assert("The text 'The Hamster Dance' is not visible on the webpage content. It's ok if it's visible in the tab title.");
39
39
  expect(result).toBeTruthy();
40
40
  });
41
41
  });