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.
- package/agent/lib/sandbox.js +0 -32
- package/agents.md +6 -2
- package/docs/v7/customizing-devices.mdx +21 -21
- package/lib/core/Dashcam.js +0 -17
- package/package.json +1 -1
- package/sdk.d.ts +2 -2
- package/sdk.js +22 -1
- package/test/testdriver/scroll-keyboard.test.mjs +1 -1
- package/test/testdriver/scroll.test.mjs +1 -1
package/agent/lib/sandbox.js
CHANGED
|
@@ -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.
|
package/lib/core/Dashcam.js
CHANGED
|
@@ -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
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 (
|
|
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 (
|
|
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
|
-
|
|
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("
|
|
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("
|
|
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
|
});
|