testdriverai 7.2.38 → 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/interfaces/vitest-plugin.d.ts +101 -0
- package/jsconfig.json +26 -0
- package/lib/core/Dashcam.js +0 -17
- package/lib/vitest/hooks.d.ts +26 -76
- package/package.json +23 -8
- package/sdk.d.ts +132 -2
- package/sdk.js +22 -1
- package/setup/aws/cloudformation.yaml +2 -2
- package/test/testdriver/hover-text.test.mjs +1 -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.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript definitions for TestDriver Vitest Plugin
|
|
3
|
+
* @module testdriverai/vitest/plugin
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import TestDriverSDK, { TestDriverOptions } from '../sdk';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Plugin state object
|
|
10
|
+
*/
|
|
11
|
+
export interface PluginState {
|
|
12
|
+
dashcamUrls: Map<string, { url: string; platform: string }>;
|
|
13
|
+
suiteTestRuns: Map<string, any>;
|
|
14
|
+
testDriverOptions: TestDriverOptions;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Current plugin state
|
|
19
|
+
*/
|
|
20
|
+
export const pluginState: PluginState;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register a Dashcam URL for a test
|
|
24
|
+
*/
|
|
25
|
+
export function registerDashcamUrl(testId: string, url: string, platform: string): void;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get Dashcam URL for a test
|
|
29
|
+
*/
|
|
30
|
+
export function getDashcamUrl(testId: string): { url: string; platform: string } | undefined;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Clear all Dashcam URLs
|
|
34
|
+
*/
|
|
35
|
+
export function clearDashcamUrls(): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get suite test run data
|
|
39
|
+
*/
|
|
40
|
+
export function getSuiteTestRun(suiteId: string): any;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Set suite test run data
|
|
44
|
+
*/
|
|
45
|
+
export function setSuiteTestRun(suiteId: string, runData: any): void;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Clear suite test run data
|
|
49
|
+
*/
|
|
50
|
+
export function clearSuiteTestRun(suiteId: string): void;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get the current plugin state
|
|
54
|
+
*/
|
|
55
|
+
export function getPluginState(): PluginState;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Authenticate with API key
|
|
59
|
+
*/
|
|
60
|
+
export function authenticateWithApiKey(apiKey: string, apiRoot?: string): Promise<string>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create a test run directly via API
|
|
64
|
+
*/
|
|
65
|
+
export function createTestRunDirect(token: string, apiRoot: string, testRunData: any): Promise<any>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Record a test case directly via API
|
|
69
|
+
*/
|
|
70
|
+
export function recordTestCaseDirect(token: string, apiRoot: string, testCaseData: any): Promise<any>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Create a TestDriver instance
|
|
74
|
+
*/
|
|
75
|
+
export function createTestDriver(options?: TestDriverOptions): Promise<TestDriverSDK>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Register a test with TestDriver
|
|
79
|
+
*/
|
|
80
|
+
export function registerTest(testdriver: TestDriverSDK, context: any): void;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Cleanup a TestDriver instance
|
|
84
|
+
*/
|
|
85
|
+
export function cleanupTestDriver(testdriver: TestDriverSDK): Promise<void>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Plugin options
|
|
89
|
+
*/
|
|
90
|
+
export interface TestDriverPluginOptions extends TestDriverOptions {
|
|
91
|
+
/**
|
|
92
|
+
* API key (defaults to TD_API_KEY env var)
|
|
93
|
+
*/
|
|
94
|
+
apiKey?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* TestDriver Vitest Plugin
|
|
99
|
+
* @param options - Plugin configuration options
|
|
100
|
+
*/
|
|
101
|
+
export default function testDriverPlugin(options?: TestDriverPluginOptions): any;
|
package/jsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "ESNext",
|
|
4
|
+
"moduleResolution": "bundler",
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"checkJs": true,
|
|
7
|
+
"strict": false,
|
|
8
|
+
"allowSyntheticDefaultImports": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"baseUrl": ".",
|
|
11
|
+
"typeRoots": ["."]
|
|
12
|
+
},
|
|
13
|
+
"include": [
|
|
14
|
+
"**/*.js",
|
|
15
|
+
"**/*.mjs",
|
|
16
|
+
"**/*.d.ts",
|
|
17
|
+
"sdk.d.ts",
|
|
18
|
+
"lib/**/*.d.ts",
|
|
19
|
+
"interfaces/**/*.d.ts"
|
|
20
|
+
],
|
|
21
|
+
"exclude": [
|
|
22
|
+
"node_modules",
|
|
23
|
+
"dist",
|
|
24
|
+
"build"
|
|
25
|
+
]
|
|
26
|
+
}
|
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/lib/vitest/hooks.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @module testdriverai/vitest/hooks
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import TestDriverSDK, { TestDriverOptions } from '../../sdk';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Vitest test context (from test function parameter)
|
|
@@ -21,99 +21,49 @@ export interface VitestContext {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Options for
|
|
24
|
+
* Options for TestDriver hook
|
|
25
25
|
*/
|
|
26
|
-
export interface
|
|
26
|
+
export interface TestDriverHookOptions extends TestDriverOptions {
|
|
27
27
|
/**
|
|
28
|
-
*
|
|
28
|
+
* Force creation of a new sandbox (default: true)
|
|
29
29
|
*/
|
|
30
|
-
|
|
30
|
+
newSandbox?: boolean;
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* Reconnect to the last used sandbox
|
|
34
34
|
*/
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Options for useDashcam hook
|
|
40
|
-
*/
|
|
41
|
-
export interface UseDashcamOptions extends DashcamOptions {
|
|
42
|
-
/**
|
|
43
|
-
* Automatically authenticate (default: true)
|
|
44
|
-
*/
|
|
45
|
-
autoAuth?: boolean;
|
|
35
|
+
reconnect?: boolean;
|
|
46
36
|
|
|
47
37
|
/**
|
|
48
|
-
*
|
|
38
|
+
* Direct IP address to connect to a running sandbox instance
|
|
49
39
|
*/
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Automatically stop recording at test end (default: false)
|
|
54
|
-
*/
|
|
55
|
-
autoStop?: boolean;
|
|
40
|
+
ip?: string;
|
|
56
41
|
}
|
|
57
42
|
|
|
58
43
|
/**
|
|
59
|
-
*
|
|
60
|
-
*
|
|
44
|
+
* Create a TestDriver client for use in Vitest tests
|
|
45
|
+
* Manages lifecycle automatically (connects on first use, disconnects after test)
|
|
61
46
|
*
|
|
62
47
|
* @param context - Vitest test context (from async (context) => {})
|
|
63
48
|
* @param options - TestDriver options
|
|
64
|
-
* @returns TestDriver
|
|
49
|
+
* @returns TestDriver SDK instance
|
|
65
50
|
*
|
|
66
51
|
* @example
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* await client.find('Login button').click();
|
|
70
|
-
* });
|
|
71
|
-
*/
|
|
72
|
-
export function useTestDriver(context: VitestContext, options?: UseTestDriverOptions): TestDriver;
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Use Dashcam in a test
|
|
76
|
-
* Creates and manages Dashcam instance for the current test
|
|
77
|
-
*
|
|
78
|
-
* @param context - Vitest test context
|
|
79
|
-
* @param client - TestDriver client instance (from useTestDriver)
|
|
80
|
-
* @param options - Dashcam options
|
|
81
|
-
* @returns Dashcam instance
|
|
52
|
+
* import { describe, expect, it } from "vitest";
|
|
53
|
+
* import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs";
|
|
82
54
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
|
|
94
|
-
export function useDashcam(context: VitestContext, client: TestDriver, options?: UseDashcamOptions): Dashcam;
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Use TestDriver with Dashcam in one call
|
|
98
|
-
* Combined hook for the simplest usage pattern
|
|
99
|
-
*
|
|
100
|
-
* @param context - Vitest test context
|
|
101
|
-
* @param options - Combined options for TestDriver and Dashcam
|
|
102
|
-
* @returns Object with client and dashcam instances
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* test('my test', async (context) => {
|
|
106
|
-
* const { client, dashcam } = useTestDriverWithDashcam(context, {
|
|
107
|
-
* os: 'linux'
|
|
55
|
+
* describe("My Test Suite", () => {
|
|
56
|
+
* it("should do something", async (context) => {
|
|
57
|
+
* const testdriver = TestDriver(context, { newSandbox: true, headless: false });
|
|
58
|
+
*
|
|
59
|
+
* await testdriver.provision.chrome({ url: 'https://example.com' });
|
|
60
|
+
*
|
|
61
|
+
* const button = await testdriver.find("Sign In button");
|
|
62
|
+
* await button.click();
|
|
63
|
+
*
|
|
64
|
+
* const result = await testdriver.assert("the dashboard is visible");
|
|
65
|
+
* expect(result).toBeTruthy();
|
|
108
66
|
* });
|
|
109
|
-
*
|
|
110
|
-
* await client.find('button').click();
|
|
111
67
|
* });
|
|
112
68
|
*/
|
|
113
|
-
export function
|
|
114
|
-
context: VitestContext,
|
|
115
|
-
options?: UseTestDriverOptions & UseDashcamOptions
|
|
116
|
-
): {
|
|
117
|
-
client: TestDriver;
|
|
118
|
-
dashcam: Dashcam;
|
|
119
|
-
};
|
|
69
|
+
export function TestDriver(context: VitestContext, options?: TestDriverHookOptions): TestDriverSDK;
|
package/package.json
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testdriverai",
|
|
3
|
-
"version": "7.2.
|
|
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
|
+
"types": "sdk.d.ts",
|
|
6
7
|
"exports": {
|
|
7
|
-
".":
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./sdk.d.ts",
|
|
10
|
+
"default": "./sdk.js"
|
|
11
|
+
},
|
|
12
|
+
"./core": {
|
|
13
|
+
"types": "./lib/core/index.d.ts",
|
|
14
|
+
"default": "./lib/core/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./vitest": {
|
|
17
|
+
"types": "./interfaces/vitest-plugin.d.ts",
|
|
18
|
+
"default": "./interfaces/vitest-plugin.mjs"
|
|
19
|
+
},
|
|
20
|
+
"./vitest/plugin": {
|
|
21
|
+
"types": "./interfaces/vitest-plugin.d.ts",
|
|
22
|
+
"default": "./interfaces/vitest-plugin.mjs"
|
|
23
|
+
},
|
|
11
24
|
"./vitest/setup": "./lib/vitest/setup.mjs",
|
|
12
|
-
"./vitest/hooks":
|
|
25
|
+
"./vitest/hooks": {
|
|
26
|
+
"types": "./lib/vitest/hooks.d.ts",
|
|
27
|
+
"default": "./lib/vitest/hooks.mjs"
|
|
28
|
+
},
|
|
13
29
|
"./presets": "./lib/presets/index.mjs"
|
|
14
30
|
},
|
|
15
31
|
"bin": {
|
|
@@ -47,12 +63,12 @@
|
|
|
47
63
|
"license": "ISC",
|
|
48
64
|
"dependencies": {
|
|
49
65
|
"@npmcli/redact": "^3.2.2",
|
|
50
|
-
"@octokit/rest": "^20.1.1",
|
|
51
66
|
"@oclif/core": "^4.5.0",
|
|
52
67
|
"@oclif/plugin-commands": "^4.1.28",
|
|
53
68
|
"@oclif/plugin-help": "^6.2.30",
|
|
54
69
|
"@oclif/plugin-not-found": "^3.2.59",
|
|
55
70
|
"@oclif/plugin-warn-if-update-available": "^3.1.43",
|
|
71
|
+
"@octokit/rest": "^20.1.1",
|
|
56
72
|
"@sentry/node": "^9.47.1",
|
|
57
73
|
"@stoplight/yaml-ast-parser": "^0.0.50",
|
|
58
74
|
"ajv": "^8.17.1",
|
|
@@ -107,7 +123,6 @@
|
|
|
107
123
|
"mocha": "^10.8.2",
|
|
108
124
|
"node-addon-api": "^8.0.0",
|
|
109
125
|
"prettier": "3.3.3",
|
|
110
|
-
"testdriverai": "^7.2.3",
|
|
111
126
|
"vitest": "^4.0.16"
|
|
112
127
|
},
|
|
113
128
|
"optionalDependencies": {
|
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;
|
|
@@ -683,9 +683,139 @@ export class Element {
|
|
|
683
683
|
readonly label: string | null;
|
|
684
684
|
}
|
|
685
685
|
|
|
686
|
+
// ====================================
|
|
687
|
+
// Provision API Interfaces
|
|
688
|
+
// ====================================
|
|
689
|
+
|
|
690
|
+
/** Options for provision.chrome */
|
|
691
|
+
export interface ProvisionChromeOptions {
|
|
692
|
+
/** URL to navigate to (default: 'http://testdriver-sandbox.vercel.app/') */
|
|
693
|
+
url?: string;
|
|
694
|
+
/** Start maximized (default: true) */
|
|
695
|
+
maximized?: boolean;
|
|
696
|
+
/** Use guest mode (default: false) */
|
|
697
|
+
guest?: boolean;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** Options for provision.chromeExtension */
|
|
701
|
+
export interface ProvisionChromeExtensionOptions {
|
|
702
|
+
/** Local filesystem path to the unpacked extension directory */
|
|
703
|
+
extensionPath?: string;
|
|
704
|
+
/** Chrome Web Store extension ID */
|
|
705
|
+
extensionId?: string;
|
|
706
|
+
/** Start maximized (default: true) */
|
|
707
|
+
maximized?: boolean;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** Options for provision.vscode */
|
|
711
|
+
export interface ProvisionVSCodeOptions {
|
|
712
|
+
/** Path to workspace or folder to open */
|
|
713
|
+
workspace?: string;
|
|
714
|
+
/** Array of extension IDs to install */
|
|
715
|
+
extensions?: string[];
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/** Options for provision.installer */
|
|
719
|
+
export interface ProvisionInstallerOptions {
|
|
720
|
+
/** URL to download the installer from */
|
|
721
|
+
url: string;
|
|
722
|
+
/** Filename to save as (auto-detected from URL if not provided) */
|
|
723
|
+
filename?: string;
|
|
724
|
+
/** Application name to focus after install */
|
|
725
|
+
appName?: string;
|
|
726
|
+
/** Whether to launch the app after installation (default: true) */
|
|
727
|
+
launch?: boolean;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/** Options for provision.electron */
|
|
731
|
+
export interface ProvisionElectronOptions {
|
|
732
|
+
/** Path to Electron app (required) */
|
|
733
|
+
appPath: string;
|
|
734
|
+
/** Additional electron args */
|
|
735
|
+
args?: string[];
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/** Provision API for launching applications */
|
|
739
|
+
export interface ProvisionAPI {
|
|
740
|
+
/**
|
|
741
|
+
* Launch Chrome browser
|
|
742
|
+
* @param options - Chrome launch options
|
|
743
|
+
*/
|
|
744
|
+
chrome(options?: ProvisionChromeOptions): Promise<void>;
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Launch Chrome browser with a custom extension loaded
|
|
748
|
+
* @param options - Chrome extension launch options
|
|
749
|
+
*/
|
|
750
|
+
chromeExtension(options?: ProvisionChromeExtensionOptions): Promise<void>;
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Launch VS Code
|
|
754
|
+
* @param options - VS Code launch options
|
|
755
|
+
*/
|
|
756
|
+
vscode(options?: ProvisionVSCodeOptions): Promise<void>;
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Download and install an application
|
|
760
|
+
* @param options - Installer options
|
|
761
|
+
* @returns Path to the downloaded file
|
|
762
|
+
*/
|
|
763
|
+
installer(options: ProvisionInstallerOptions): Promise<string>;
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Launch Electron app
|
|
767
|
+
* @param options - Electron launch options
|
|
768
|
+
*/
|
|
769
|
+
electron(options: ProvisionElectronOptions): Promise<void>;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/** Dashcam API for screen recording */
|
|
773
|
+
export interface DashcamAPI {
|
|
774
|
+
/**
|
|
775
|
+
* Start recording
|
|
776
|
+
*/
|
|
777
|
+
start(): Promise<void>;
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Stop recording and get replay URL
|
|
781
|
+
*/
|
|
782
|
+
stop(): Promise<string | null>;
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Check if currently recording
|
|
786
|
+
*/
|
|
787
|
+
isRecording(): boolean;
|
|
788
|
+
}
|
|
789
|
+
|
|
686
790
|
export default class TestDriverSDK {
|
|
687
791
|
constructor(apiKey: string, options?: TestDriverOptions);
|
|
688
792
|
|
|
793
|
+
/**
|
|
794
|
+
* Whether the SDK is currently connected to a sandbox
|
|
795
|
+
*/
|
|
796
|
+
readonly connected: boolean;
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* The operating system of the sandbox
|
|
800
|
+
*/
|
|
801
|
+
readonly os: 'windows' | 'linux';
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Provision API for launching applications
|
|
805
|
+
*/
|
|
806
|
+
readonly provision: ProvisionAPI;
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Dashcam API for screen recording
|
|
810
|
+
*/
|
|
811
|
+
readonly dashcam: DashcamAPI;
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Wait for the sandbox to be ready
|
|
815
|
+
* Called automatically by provision methods
|
|
816
|
+
*/
|
|
817
|
+
ready(): Promise<void>;
|
|
818
|
+
|
|
689
819
|
/**
|
|
690
820
|
* Authenticate with TestDriver API
|
|
691
821
|
*/
|
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
|
/**
|
|
@@ -168,8 +168,8 @@ Resources:
|
|
|
168
168
|
IpProtocol: tcp,
|
|
169
169
|
FromPort: 8765,
|
|
170
170
|
ToPort: 8765,
|
|
171
|
-
CidrIp:
|
|
172
|
-
Description: "pyautogui-cli WebSockets
|
|
171
|
+
CidrIp: !Ref AllowedIngressCidr,
|
|
172
|
+
Description: "pyautogui-cli WebSockets"
|
|
173
173
|
}
|
|
174
174
|
- {
|
|
175
175
|
IpProtocol: tcp,
|
|
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
|
|
|
8
8
|
|
|
9
9
|
describe("Hover Text Test", () => {
|
|
10
10
|
it("should click Sign In and verify error message", async (context) => {
|
|
11
|
-
const testdriver = TestDriver(context, { headless: false,
|
|
11
|
+
const testdriver = TestDriver(context, { headless: false, ip: '3.138.116.105'});
|
|
12
12
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
13
13
|
|
|
14
14
|
// Click on Sign In button using new find() API
|
|
@@ -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
|
});
|