testdriverai 7.2.39 → 7.2.41
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/_drafts/best-practices.mdx +1 -1
- package/docs/v7/_drafts/configuration.mdx +0 -2
- package/docs/v7/_drafts/core.mdx +0 -1
- package/docs/v7/_drafts/migration.mdx +2 -2
- package/docs/v7/_drafts/provision.mdx +12 -12
- package/docs/v7/_drafts/screenshot.mdx +1 -1
- package/docs/v7/_drafts/troubleshooting.mdx +1 -1
- package/docs/v7/ai.mdx +1 -1
- package/docs/v7/assert.mdx +1 -1
- package/docs/v7/ci-cd.mdx +0 -2
- package/docs/v7/click.mdx +1 -1
- package/docs/v7/client.mdx +3 -3
- package/docs/v7/customizing-devices.mdx +20 -26
- package/docs/v7/device-config.mdx +4 -5
- package/docs/v7/exec.mdx +1 -1
- package/docs/v7/find.mdx +1 -1
- package/docs/v7/focus-application.mdx +1 -1
- package/docs/v7/hover.mdx +1 -1
- package/docs/v7/press-keys.mdx +1 -1
- package/docs/v7/reusable-code.mdx +4 -4
- package/docs/v7/scroll.mdx +1 -1
- package/docs/v7/type.mdx +1 -1
- package/lib/core/Dashcam.js +0 -37
- package/lib/vitest/hooks.d.ts +4 -16
- package/lib/vitest/hooks.mjs +3 -8
- package/package.json +1 -1
- package/sdk.d.ts +3 -3
- package/sdk.js +49 -10
- package/test/testdriver/ai.test.mjs +1 -1
- package/test/testdriver/assert.test.mjs +2 -2
- package/test/testdriver/chrome-extension.test.mjs +42 -19
- package/test/testdriver/hover-image.test.mjs +3 -3
- package/test/testdriver/hover-text.test.mjs +1 -1
- package/test/testdriver/installer.test.mjs +2 -2
- package/test/testdriver/launch-vscode-linux.test.mjs +2 -2
- 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";
|
|
@@ -49,7 +49,6 @@ const client = await TestDriver.create({
|
|
|
49
49
|
// Sandbox Configuration
|
|
50
50
|
os: 'linux',
|
|
51
51
|
resolution: '1920x1080',
|
|
52
|
-
newSandbox: true,
|
|
53
52
|
sandboxId: null,
|
|
54
53
|
|
|
55
54
|
// API Configuration
|
|
@@ -137,7 +136,6 @@ const { testdriver, dashcam } = await chrome(context, {
|
|
|
137
136
|
resolution: '1920x1080',
|
|
138
137
|
|
|
139
138
|
// Chrome-specific options
|
|
140
|
-
headless: false,
|
|
141
139
|
incognito: false,
|
|
142
140
|
});
|
|
143
141
|
```
|
package/docs/v7/_drafts/core.mdx
CHANGED
|
@@ -61,7 +61,7 @@ describe('Login Test', () => {
|
|
|
61
61
|
os: 'windows'
|
|
62
62
|
});
|
|
63
63
|
await testdriver.auth();
|
|
64
|
-
await testdriver.connect(
|
|
64
|
+
await testdriver.connect();
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
afterAll(async () => {
|
|
@@ -489,7 +489,7 @@ describe('Login Flow', () => {
|
|
|
489
489
|
});
|
|
490
490
|
|
|
491
491
|
await testdriver.auth();
|
|
492
|
-
await testdriver.connect(
|
|
492
|
+
await testdriver.connect();
|
|
493
493
|
|
|
494
494
|
// Prerun
|
|
495
495
|
await testdriver.exec('pwsh', 'dashcam start', 5000, true);
|
|
@@ -10,7 +10,7 @@ import { TestDriver } from 'testdriverai/lib/vitest/hooks.mjs';
|
|
|
10
10
|
|
|
11
11
|
describe('My Test Suite', () => {
|
|
12
12
|
it('should test Chrome browser', async (context) => {
|
|
13
|
-
const testdriver = TestDriver(context,
|
|
13
|
+
const testdriver = TestDriver(context, ());
|
|
14
14
|
|
|
15
15
|
await testdriver.provision.chrome({
|
|
16
16
|
url: 'https://example.com'
|
|
@@ -53,7 +53,7 @@ await testdriver.provision.chrome({
|
|
|
53
53
|
|
|
54
54
|
```javascript
|
|
55
55
|
it('should login to my app', async (context) => {
|
|
56
|
-
const testdriver = TestDriver(context,
|
|
56
|
+
const testdriver = TestDriver(context, ());
|
|
57
57
|
|
|
58
58
|
await testdriver.provision.chrome({
|
|
59
59
|
url: 'https://myapp.com/login'
|
|
@@ -91,7 +91,7 @@ await testdriver.provision.vscode({
|
|
|
91
91
|
|
|
92
92
|
```javascript
|
|
93
93
|
it('should launch VS Code', async (context) => {
|
|
94
|
-
const testdriver = TestDriver(context,
|
|
94
|
+
const testdriver = TestDriver(context, ());
|
|
95
95
|
|
|
96
96
|
await testdriver.provision.vscode();
|
|
97
97
|
|
|
@@ -106,7 +106,7 @@ it('should launch VS Code', async (context) => {
|
|
|
106
106
|
|
|
107
107
|
```javascript
|
|
108
108
|
it('should install and use a VS Code extension', async (context) => {
|
|
109
|
-
const testdriver = TestDriver(context,
|
|
109
|
+
const testdriver = TestDriver(context, ());
|
|
110
110
|
|
|
111
111
|
// Launch VS Code with extensions installed
|
|
112
112
|
await testdriver.provision.vscode({
|
|
@@ -164,7 +164,7 @@ const filePath = await testdriver.provision.installer({
|
|
|
164
164
|
|
|
165
165
|
```javascript
|
|
166
166
|
it('should install a .deb package', async (context) => {
|
|
167
|
-
const testdriver = TestDriver(context,
|
|
167
|
+
const testdriver = TestDriver(context, ());
|
|
168
168
|
|
|
169
169
|
const filePath = await testdriver.provision.installer({
|
|
170
170
|
url: 'https://github.com/sharkdp/bat/releases/download/v0.24.0/bat_0.24.0_amd64.deb'
|
|
@@ -179,7 +179,7 @@ it('should install a .deb package', async (context) => {
|
|
|
179
179
|
|
|
180
180
|
```javascript
|
|
181
181
|
it('should download a script', async (context) => {
|
|
182
|
-
const testdriver = TestDriver(context,
|
|
182
|
+
const testdriver = TestDriver(context, ());
|
|
183
183
|
|
|
184
184
|
const filePath = await testdriver.provision.installer({
|
|
185
185
|
url: 'https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh',
|
|
@@ -195,7 +195,7 @@ it('should download a script', async (context) => {
|
|
|
195
195
|
|
|
196
196
|
```javascript
|
|
197
197
|
it('should run AppImage with custom flags', async (context) => {
|
|
198
|
-
const testdriver = TestDriver(context,
|
|
198
|
+
const testdriver = TestDriver(context, ());
|
|
199
199
|
|
|
200
200
|
const filePath = await testdriver.provision.installer({
|
|
201
201
|
url: 'https://example.com/app.AppImage',
|
|
@@ -235,7 +235,7 @@ await testdriver.provision.electron({
|
|
|
235
235
|
|
|
236
236
|
```javascript
|
|
237
237
|
it('should launch Electron app', async (context) => {
|
|
238
|
-
const testdriver = TestDriver(context,
|
|
238
|
+
const testdriver = TestDriver(context, ());
|
|
239
239
|
|
|
240
240
|
await testdriver.provision.electron({
|
|
241
241
|
appPath: '/path/to/my-electron-app',
|
|
@@ -271,7 +271,7 @@ import { TestDriver } from 'testdriverai/lib/vitest/hooks.mjs';
|
|
|
271
271
|
|
|
272
272
|
describe('Application Testing', () => {
|
|
273
273
|
it('should test Chrome login flow', async (context) => {
|
|
274
|
-
const testdriver = TestDriver(context,
|
|
274
|
+
const testdriver = TestDriver(context, ());
|
|
275
275
|
|
|
276
276
|
await testdriver.provision.chrome({
|
|
277
277
|
url: 'https://myapp.com/login'
|
|
@@ -286,7 +286,7 @@ describe('Application Testing', () => {
|
|
|
286
286
|
});
|
|
287
287
|
|
|
288
288
|
it('should test VS Code extension', async (context) => {
|
|
289
|
-
const testdriver = TestDriver(context,
|
|
289
|
+
const testdriver = TestDriver(context, ());
|
|
290
290
|
|
|
291
291
|
await testdriver.provision.vscode({
|
|
292
292
|
extensions: ['ms-python.python']
|
|
@@ -301,7 +301,7 @@ describe('Application Testing', () => {
|
|
|
301
301
|
});
|
|
302
302
|
|
|
303
303
|
it('should install and test CLI tool', async (context) => {
|
|
304
|
-
const testdriver = TestDriver(context,
|
|
304
|
+
const testdriver = TestDriver(context, ());
|
|
305
305
|
|
|
306
306
|
await testdriver.provision.installer({
|
|
307
307
|
url: 'https://github.com/sharkdp/bat/releases/download/v0.24.0/bat_0.24.0_amd64.deb'
|
|
@@ -315,7 +315,7 @@ describe('Application Testing', () => {
|
|
|
315
315
|
|
|
316
316
|
## Best Practices
|
|
317
317
|
|
|
318
|
-
1. **Use
|
|
318
|
+
1. **Use clean environments** - Each test gets a fresh sandbox by default
|
|
319
319
|
2. **Enable Dashcam** - Great for debugging test failures (enabled by default)
|
|
320
320
|
3. **Check assertions** - Always verify the expected state after actions
|
|
321
321
|
4. **Use appropriate provision method** - Match the method to your test target
|
|
@@ -113,7 +113,7 @@ const screenshot = await testdriver.screenshot(1, false, true);
|
|
|
113
113
|
|
|
114
114
|
```javascript
|
|
115
115
|
it('should complete checkout', async (context) => {
|
|
116
|
-
const testdriver = TestDriver(context,
|
|
116
|
+
const testdriver = TestDriver(context, ());
|
|
117
117
|
await testdriver.provision.chrome({ url: 'https://shop.example.com' });
|
|
118
118
|
|
|
119
119
|
try {
|
package/docs/v7/ai.mdx
CHANGED
|
@@ -167,7 +167,7 @@ describe('E-commerce Flow with AI', () => {
|
|
|
167
167
|
beforeAll(async () => {
|
|
168
168
|
client = new TestDriver(process.env.TD_API_KEY);
|
|
169
169
|
await testdriver.auth();
|
|
170
|
-
await testdriver.connect(
|
|
170
|
+
await testdriver.connect(());
|
|
171
171
|
});
|
|
172
172
|
|
|
173
173
|
afterAll(async () => {
|
package/docs/v7/assert.mdx
CHANGED
|
@@ -214,7 +214,7 @@ describe('Assertions', () => {
|
|
|
214
214
|
beforeAll(async () => {
|
|
215
215
|
client = new TestDriver(process.env.TD_API_KEY);
|
|
216
216
|
await testdriver.auth();
|
|
217
|
-
await testdriver.connect(
|
|
217
|
+
await testdriver.connect(());
|
|
218
218
|
});
|
|
219
219
|
|
|
220
220
|
afterAll(async () => {
|
package/docs/v7/ci-cd.mdx
CHANGED
package/docs/v7/click.mdx
CHANGED
|
@@ -235,7 +235,7 @@ describe('Click Interactions', () => {
|
|
|
235
235
|
beforeAll(async () => {
|
|
236
236
|
client = new TestDriver(process.env.TD_API_KEY);
|
|
237
237
|
await testdriver.auth();
|
|
238
|
-
await testdriver.connect(
|
|
238
|
+
await testdriver.connect(());
|
|
239
239
|
});
|
|
240
240
|
|
|
241
241
|
afterAll(async () => {
|
package/docs/v7/client.mdx
CHANGED
|
@@ -133,7 +133,7 @@ await testdriver.connect(options)
|
|
|
133
133
|
|
|
134
134
|
**Basic connection:**
|
|
135
135
|
```javascript
|
|
136
|
-
await testdriver.connect(
|
|
136
|
+
await testdriver.connect(());
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
**Reconnect to existing sandbox:**
|
|
@@ -273,7 +273,7 @@ describe('My Test Suite', () => {
|
|
|
273
273
|
|
|
274
274
|
// Authenticate and connect
|
|
275
275
|
await testdriver.auth();
|
|
276
|
-
const instance = await testdriver.connect(
|
|
276
|
+
const instance = await testdriver.connect(());
|
|
277
277
|
|
|
278
278
|
console.log('Connected to sandbox:', instance.instanceId);
|
|
279
279
|
});
|
|
@@ -300,7 +300,7 @@ describe('My Test Suite', () => {
|
|
|
300
300
|
|
|
301
301
|
```javascript
|
|
302
302
|
try {
|
|
303
|
-
await testdriver.connect(
|
|
303
|
+
await testdriver.connect(());
|
|
304
304
|
} catch (error) {
|
|
305
305
|
console.error('Failed to connect:', error.message);
|
|
306
306
|
throw error;
|
|
@@ -10,32 +10,10 @@ Configure TestDriver behavior with options passed to the `TestDriver()` function
|
|
|
10
10
|
|
|
11
11
|
```javascript
|
|
12
12
|
const testdriver = TestDriver(context, {
|
|
13
|
-
headless: false,
|
|
14
13
|
reconnect: false,
|
|
15
14
|
});
|
|
16
15
|
```
|
|
17
16
|
|
|
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
17
|
### Operating System
|
|
40
18
|
|
|
41
19
|
Set the `os` property to run tests on a specific operating system. Available options are `linux` (default) and `windows`.
|
|
@@ -77,6 +55,26 @@ steps:
|
|
|
77
55
|
- run: TD_OS=${{ matrix.os }} npx vitest run
|
|
78
56
|
```
|
|
79
57
|
|
|
58
|
+
### Headless Mode
|
|
59
|
+
|
|
60
|
+
Run tests without a visible browser window. Useful for CI/CD pipeline or spawning multiple tests locally:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const testdriver = TestDriver(context, {
|
|
64
|
+
headless: true, // No visible browser window
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### IP Target
|
|
69
|
+
|
|
70
|
+
If self-hosting TestDriver, use `ip` to specify the device IP. See [Self-Hosting TestDriver](../self-hosting.md) for details.
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
const testdriver = TestDriver(context, {
|
|
74
|
+
ip: "203.0.113.42", // Your allowlisted IP
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
80
78
|
## Keepalive
|
|
81
79
|
|
|
82
80
|
By default, sandboxes terminate immediately when the test finishes. Set this value to keep the sandbox alive for reconnection.
|
|
@@ -97,8 +95,6 @@ Split your test into two files: one for known-good steps that set up the desired
|
|
|
97
95
|
|
|
98
96
|
```javascript known-good.test.mjs
|
|
99
97
|
const testdriver = TestDriver(context, {
|
|
100
|
-
newSandbox: true,
|
|
101
|
-
headless: false,
|
|
102
98
|
keepAlive: 60000, // Keep sandbox alive for 60 seconds after test
|
|
103
99
|
});
|
|
104
100
|
```
|
|
@@ -106,8 +102,6 @@ const testdriver = TestDriver(context, {
|
|
|
106
102
|
```javascript work-in-progress.test.mjs
|
|
107
103
|
// Second test file: experiment.test.mjs (run within keepAlive window)
|
|
108
104
|
const testdriver = TestDriver(context, {
|
|
109
|
-
newSandbox: true,
|
|
110
|
-
headless: false,
|
|
111
105
|
reconnect: true, // Reconnect to existing sandbox
|
|
112
106
|
});
|
|
113
107
|
```
|
|
@@ -32,7 +32,7 @@ import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs";
|
|
|
32
32
|
|
|
33
33
|
describe("Login Flow", () => {
|
|
34
34
|
it("should log in successfully", async (context) => {
|
|
35
|
-
const testdriver = TestDriver(context,
|
|
35
|
+
const testdriver = TestDriver(context, ());
|
|
36
36
|
|
|
37
37
|
await testdriver.provision.chrome({
|
|
38
38
|
url: 'https://myapp.com/login',
|
|
@@ -117,7 +117,7 @@ import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs";
|
|
|
117
117
|
|
|
118
118
|
describe("Chrome Extension Test", () => {
|
|
119
119
|
it("should load and interact with extension", async (context) => {
|
|
120
|
-
const testdriver = TestDriver(context,
|
|
120
|
+
const testdriver = TestDriver(context, ());
|
|
121
121
|
|
|
122
122
|
// Clone extension from GitHub
|
|
123
123
|
await testdriver.ready();
|
|
@@ -192,7 +192,7 @@ import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs";
|
|
|
192
192
|
|
|
193
193
|
describe("Desktop App Test", () => {
|
|
194
194
|
it("should install and launch app", async (context) => {
|
|
195
|
-
const testdriver = TestDriver(context,
|
|
195
|
+
const testdriver = TestDriver(context, ());
|
|
196
196
|
|
|
197
197
|
// Download and install
|
|
198
198
|
const installerPath = await testdriver.provision.installer({
|
|
@@ -215,7 +215,6 @@ import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs";
|
|
|
215
215
|
describe("Windows App Test", () => {
|
|
216
216
|
it("should install on Windows", async (context) => {
|
|
217
217
|
const testdriver = TestDriver(context, {
|
|
218
|
-
newSandbox: true,
|
|
219
218
|
os: 'windows'
|
|
220
219
|
});
|
|
221
220
|
|
|
@@ -282,7 +281,7 @@ import { TestDriver } from "testdriverai/lib/vitest/hooks.mjs";
|
|
|
282
281
|
|
|
283
282
|
describe("VS Code Test", () => {
|
|
284
283
|
it("should open workspace with extensions", async (context) => {
|
|
285
|
-
const testdriver = TestDriver(context,
|
|
284
|
+
const testdriver = TestDriver(context, ());
|
|
286
285
|
|
|
287
286
|
// Create a test project
|
|
288
287
|
await testdriver.ready();
|
package/docs/v7/exec.mdx
CHANGED
|
@@ -282,7 +282,7 @@ describe('Code Execution', () => {
|
|
|
282
282
|
beforeAll(async () => {
|
|
283
283
|
client = new TestDriver(process.env.TD_API_KEY);
|
|
284
284
|
await testdriver.auth();
|
|
285
|
-
await testdriver.connect(
|
|
285
|
+
await testdriver.connect(());
|
|
286
286
|
});
|
|
287
287
|
|
|
288
288
|
afterAll(async () => {
|
package/docs/v7/find.mdx
CHANGED
|
@@ -299,7 +299,7 @@ describe('Element Finding', () => {
|
|
|
299
299
|
beforeAll(async () => {
|
|
300
300
|
client = new TestDriver(process.env.TD_API_KEY);
|
|
301
301
|
await testdriver.auth();
|
|
302
|
-
await testdriver.connect(
|
|
302
|
+
await testdriver.connect(());
|
|
303
303
|
});
|
|
304
304
|
|
|
305
305
|
afterAll(async () => {
|
|
@@ -209,7 +209,7 @@ describe('Multi-Application Workflow', () => {
|
|
|
209
209
|
beforeAll(async () => {
|
|
210
210
|
client = new TestDriver(process.env.TD_API_KEY);
|
|
211
211
|
await testdriver.auth();
|
|
212
|
-
await testdriver.connect(
|
|
212
|
+
await testdriver.connect(());
|
|
213
213
|
});
|
|
214
214
|
|
|
215
215
|
afterAll(async () => {
|
package/docs/v7/hover.mdx
CHANGED
|
@@ -204,7 +204,7 @@ describe('Hover Interactions', () => {
|
|
|
204
204
|
beforeAll(async () => {
|
|
205
205
|
client = new TestDriver(process.env.TD_API_KEY);
|
|
206
206
|
await testdriver.auth();
|
|
207
|
-
await testdriver.connect(
|
|
207
|
+
await testdriver.connect(());
|
|
208
208
|
});
|
|
209
209
|
|
|
210
210
|
afterAll(async () => {
|
package/docs/v7/press-keys.mdx
CHANGED
|
@@ -286,7 +286,7 @@ describe('Keyboard Navigation', () => {
|
|
|
286
286
|
beforeAll(async () => {
|
|
287
287
|
client = new TestDriver(process.env.TD_API_KEY);
|
|
288
288
|
await testdriver.auth();
|
|
289
|
-
await testdriver.connect(
|
|
289
|
+
await testdriver.connect(());
|
|
290
290
|
});
|
|
291
291
|
|
|
292
292
|
afterAll(async () => {
|
|
@@ -45,7 +45,7 @@ import { login } from './helpers/auth.js';
|
|
|
45
45
|
|
|
46
46
|
describe("Checkout", () => {
|
|
47
47
|
it("should complete checkout as logged in user", async (context) => {
|
|
48
|
-
const testdriver = TestDriver(context,
|
|
48
|
+
const testdriver = TestDriver(context, ());
|
|
49
49
|
|
|
50
50
|
await testdriver.provision.chrome({
|
|
51
51
|
url: 'https://shop.example.com',
|
|
@@ -116,7 +116,7 @@ import { LoginPage } from './pages/LoginPage.js';
|
|
|
116
116
|
|
|
117
117
|
describe("Authentication", () => {
|
|
118
118
|
it("should show error for invalid credentials", async (context) => {
|
|
119
|
-
const testdriver = TestDriver(context,
|
|
119
|
+
const testdriver = TestDriver(context, ());
|
|
120
120
|
|
|
121
121
|
await testdriver.provision.chrome({
|
|
122
122
|
url: 'https://app.example.com/login',
|
|
@@ -131,7 +131,7 @@ describe("Authentication", () => {
|
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
it("should redirect to dashboard on success", async (context) => {
|
|
134
|
-
const testdriver = TestDriver(context,
|
|
134
|
+
const testdriver = TestDriver(context, ());
|
|
135
135
|
|
|
136
136
|
await testdriver.provision.chrome({
|
|
137
137
|
url: 'https://app.example.com/login',
|
|
@@ -186,7 +186,7 @@ import { testUsers, testUrls, setupAuthenticatedSession } from './fixtures/index
|
|
|
186
186
|
|
|
187
187
|
describe("Admin Panel", () => {
|
|
188
188
|
it("should access admin settings", async (context) => {
|
|
189
|
-
const testdriver = TestDriver(context,
|
|
189
|
+
const testdriver = TestDriver(context, ());
|
|
190
190
|
|
|
191
191
|
await testdriver.provision.chrome({
|
|
192
192
|
url: `${testUrls.staging}/login`,
|
package/docs/v7/scroll.mdx
CHANGED
|
@@ -242,7 +242,7 @@ describe('Scrolling', () => {
|
|
|
242
242
|
beforeAll(async () => {
|
|
243
243
|
client = new TestDriver(process.env.TD_API_KEY);
|
|
244
244
|
await testdriver.auth();
|
|
245
|
-
await testdriver.connect(
|
|
245
|
+
await testdriver.connect(());
|
|
246
246
|
});
|
|
247
247
|
|
|
248
248
|
afterAll(async () => {
|
package/docs/v7/type.mdx
CHANGED
|
@@ -296,7 +296,7 @@ describe('Form Filling with Type', () => {
|
|
|
296
296
|
beforeAll(async () => {
|
|
297
297
|
client = new TestDriver(process.env.TD_API_KEY);
|
|
298
298
|
await testdriver.auth();
|
|
299
|
-
await testdriver.connect(
|
|
299
|
+
await testdriver.connect(());
|
|
300
300
|
});
|
|
301
301
|
|
|
302
302
|
afterAll(async () => {
|
package/lib/core/Dashcam.js
CHANGED
|
@@ -117,36 +117,11 @@ class Dashcam {
|
|
|
117
117
|
const shell = this._getShell();
|
|
118
118
|
const apiRoot = this._getApiRoot();
|
|
119
119
|
|
|
120
|
-
let install = await this.client.exec(
|
|
121
|
-
shell,
|
|
122
|
-
'npm ls dashcam -g || echo "not installed"',
|
|
123
|
-
40000,
|
|
124
|
-
true
|
|
125
|
-
);
|
|
126
|
-
this._log('debug', 'Dashcam install check:', install);
|
|
127
|
-
|
|
128
120
|
if (this.client.os === 'windows') {
|
|
129
121
|
|
|
130
122
|
const dashcamPath = await this._getDashcamPath();
|
|
131
123
|
this._log('debug', 'Dashcam executable path:', dashcamPath);
|
|
132
124
|
|
|
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
125
|
// Authenticate with TD_API_ROOT
|
|
151
126
|
const authOutput = await this.client.exec(
|
|
152
127
|
shell,
|
|
@@ -292,18 +267,6 @@ class Dashcam {
|
|
|
292
267
|
|
|
293
268
|
if (this.client.os === 'windows') {
|
|
294
269
|
|
|
295
|
-
const dashcamPath = await this._getDashcamPath();
|
|
296
|
-
this._log('debug', 'Dashcam path:', dashcamPath);
|
|
297
|
-
|
|
298
|
-
// Verify dashcam exists
|
|
299
|
-
const dashcamExists = await this.client.exec(
|
|
300
|
-
shell,
|
|
301
|
-
`Test-Path "${dashcamPath}"`,
|
|
302
|
-
10000,
|
|
303
|
-
true
|
|
304
|
-
);
|
|
305
|
-
this._log('debug', 'Dashcam.cmd exists:', dashcamExists);
|
|
306
|
-
|
|
307
270
|
// Start dashcam record and redirect output with TD_API_ROOT
|
|
308
271
|
const outputFile = 'C:\\Users\\testdriver\\.dashcam-cli\\dashcam-start.log';
|
|
309
272
|
// const titleArg = this.title ? ` --title=\`"${this.title.replace(/"/g, '`"')}\`"` : '';
|
package/lib/vitest/hooks.d.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* @module testdriverai/vitest/hooks
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import TestDriverSDK
|
|
6
|
+
import type TestDriverSDK from '../../sdk';
|
|
7
|
+
import type { TestDriverOptions } from '../../sdk';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Vitest test context (from test function parameter)
|
|
@@ -21,23 +22,10 @@ export interface VitestContext {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
|
-
* Options for TestDriver hook
|
|
25
|
+
* Options for TestDriver hook (includes all TestDriverOptions)
|
|
25
26
|
*/
|
|
26
27
|
export interface TestDriverHookOptions extends TestDriverOptions {
|
|
27
|
-
|
|
28
|
-
* Force creation of a new sandbox (default: true)
|
|
29
|
-
*/
|
|
30
|
-
newSandbox?: boolean;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Reconnect to the last used sandbox
|
|
34
|
-
*/
|
|
35
|
-
reconnect?: boolean;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Direct IP address to connect to a running sandbox instance
|
|
39
|
-
*/
|
|
40
|
-
ip?: string;
|
|
28
|
+
// All TestDriverOptions are already included via extends
|
|
41
29
|
}
|
|
42
30
|
|
|
43
31
|
/**
|
package/lib/vitest/hooks.mjs
CHANGED
|
@@ -169,14 +169,9 @@ const lifecycleHandlers = new WeakMap();
|
|
|
169
169
|
/**
|
|
170
170
|
* Create a TestDriver client in a Vitest test with automatic lifecycle management
|
|
171
171
|
*
|
|
172
|
-
* @param {
|
|
173
|
-
* @param {
|
|
174
|
-
* @
|
|
175
|
-
* @param {boolean} [options.headless] - Run sandbox in headless mode
|
|
176
|
-
* @param {boolean} [options.newSandbox] - Create new sandbox
|
|
177
|
-
* @param {number} [options.timeout=0] - Sandbox timeout (TTL) in milliseconds. 0 = use provider default (5 min for E2B Linux)
|
|
178
|
-
* @param {boolean} [options.autoConnect=true] - Automatically connect to sandbox
|
|
179
|
-
* @returns {TestDriver} TestDriver client instance
|
|
172
|
+
* @param {import('vitest').TestContext} context - Vitest test context (from async (context) => {})
|
|
173
|
+
* @param {import('../../sdk.js').TestDriverOptions} [options] - TestDriver options (passed directly to TestDriver constructor)
|
|
174
|
+
* @returns {import('../../sdk.js').default} TestDriver client instance
|
|
180
175
|
*
|
|
181
176
|
* @example
|
|
182
177
|
* test('my test', async (context) => {
|
package/package.json
CHANGED
package/sdk.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export type ClickAction =
|
|
|
13
13
|
export type ScrollDirection = "up" | "down" | "left" | "right";
|
|
14
14
|
export type ScrollMethod = "keyboard" | "mouse";
|
|
15
15
|
export type TextMatchMethod = "ai" | "turbo";
|
|
16
|
-
export type ExecLanguage = "js" | "pwsh";
|
|
16
|
+
export type ExecLanguage = "js" | "pwsh" | "sh";
|
|
17
17
|
export type KeyboardKey =
|
|
18
18
|
| "\t"
|
|
19
19
|
| "\n"
|
|
@@ -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,29 @@ 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
|
*/
|
|
1368
|
+
/**
|
|
1369
|
+
* Get the path to the dashcam-chrome extension
|
|
1370
|
+
* Uses preinstalled dashcam-chrome on both Linux and Windows
|
|
1371
|
+
* @returns {Promise<string>} Path to dashcam-chrome/build directory
|
|
1372
|
+
* @private
|
|
1373
|
+
*/
|
|
1374
|
+
async _getDashcamChromeExtensionPath() {
|
|
1375
|
+
if (this.os !== 'windows') {
|
|
1376
|
+
return '/usr/lib/node_modules/dashcam-chrome/build';
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// dashcam-chrome is preinstalled on Windows at C:\Program Files\nodejs\node_modules\dashcam-chrome\build
|
|
1380
|
+
// Use the actual long path - we'll handle quoting in the chrome launch
|
|
1381
|
+
return 'C:\\PROGRA~1\\nodejs\\node_modules\\dashcam-chrome\\build';
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1367
1384
|
_createProvisionAPI() {
|
|
1368
|
-
|
|
1385
|
+
const self = this;
|
|
1386
|
+
|
|
1387
|
+
const provisionMethods = {
|
|
1369
1388
|
/**
|
|
1370
1389
|
* Launch Chrome browser
|
|
1371
1390
|
* @param {Object} options - Chrome launch options
|
|
@@ -1473,9 +1492,10 @@ class TestDriverSDK {
|
|
|
1473
1492
|
if (guest) chromeArgs.push('--guest');
|
|
1474
1493
|
chromeArgs.push('--disable-fre', '--no-default-browser-check', '--no-first-run', '--no-experiments', '--disable-infobars', `--user-data-dir=${userDataDir}`);
|
|
1475
1494
|
|
|
1476
|
-
// Add dashcam-chrome extension
|
|
1477
|
-
|
|
1478
|
-
|
|
1495
|
+
// Add dashcam-chrome extension
|
|
1496
|
+
const dashcamChromePath = await this._getDashcamChromeExtensionPath();
|
|
1497
|
+
if (dashcamChromePath) {
|
|
1498
|
+
chromeArgs.push(`--load-extension=${dashcamChromePath}`);
|
|
1479
1499
|
}
|
|
1480
1500
|
|
|
1481
1501
|
// Launch Chrome
|
|
@@ -1484,7 +1504,7 @@ class TestDriverSDK {
|
|
|
1484
1504
|
const argsString = chromeArgs.map(arg => `"${arg}"`).join(', ');
|
|
1485
1505
|
await this.exec(
|
|
1486
1506
|
shell,
|
|
1487
|
-
`Start-Process "C
|
|
1507
|
+
`Start-Process "C:\\ChromeForTesting\\chrome-win64\\chrome.exe" -ArgumentList ${argsString}, "${url}"`,
|
|
1488
1508
|
30000
|
|
1489
1509
|
);
|
|
1490
1510
|
} else {
|
|
@@ -1743,11 +1763,12 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
|
|
1743
1763
|
chromeArgs.push('--disable-fre', '--no-default-browser-check', '--no-first-run', '--no-experiments', '--disable-infobars', '--disable-features=ChromeLabs', `--user-data-dir=${userDataDir}`);
|
|
1744
1764
|
|
|
1745
1765
|
// Add user extension and dashcam-chrome extension
|
|
1746
|
-
|
|
1766
|
+
const dashcamChromePath = await this._getDashcamChromeExtensionPath();
|
|
1767
|
+
if (dashcamChromePath) {
|
|
1747
1768
|
// Load both user extension and dashcam-chrome for web log capture
|
|
1748
|
-
chromeArgs.push(`--load-extension=${extensionPath}
|
|
1749
|
-
} else
|
|
1750
|
-
//
|
|
1769
|
+
chromeArgs.push(`--load-extension=${extensionPath},${dashcamChromePath}`);
|
|
1770
|
+
} else {
|
|
1771
|
+
// If dashcam-chrome unavailable, just load user extension
|
|
1751
1772
|
chromeArgs.push(`--load-extension=${extensionPath}`);
|
|
1752
1773
|
}
|
|
1753
1774
|
|
|
@@ -1756,7 +1777,7 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
|
|
1756
1777
|
const argsString = chromeArgs.map(arg => `"${arg}"`).join(', ');
|
|
1757
1778
|
await this.exec(
|
|
1758
1779
|
shell,
|
|
1759
|
-
`Start-Process "C
|
|
1780
|
+
`Start-Process "C:\\ChromeForTesting\\chrome-win64\\chrome.exe" -ArgumentList ${argsString}`,
|
|
1760
1781
|
30000
|
|
1761
1782
|
);
|
|
1762
1783
|
} else {
|
|
@@ -2073,6 +2094,24 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
|
|
2073
2094
|
await this.focusApplication('Electron');
|
|
2074
2095
|
},
|
|
2075
2096
|
};
|
|
2097
|
+
|
|
2098
|
+
// Wrap all provision methods with reconnect check using Proxy
|
|
2099
|
+
return new Proxy(provisionMethods, {
|
|
2100
|
+
get(target, prop) {
|
|
2101
|
+
const method = target[prop];
|
|
2102
|
+
if (typeof method === 'function') {
|
|
2103
|
+
return async (...args) => {
|
|
2104
|
+
// Skip provisioning if reconnecting to existing sandbox
|
|
2105
|
+
if (self.reconnect) {
|
|
2106
|
+
console.log(`[provision.${prop}] Skipping provisioning (reconnect mode)`);
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
return method(...args);
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
return method;
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2076
2115
|
}
|
|
2077
2116
|
|
|
2078
2117
|
/**
|
|
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
|
|
|
8
8
|
|
|
9
9
|
describe("AI Test", () => {
|
|
10
10
|
it("should use ai to search for testdriver on Google", async (context) => {
|
|
11
|
-
const testdriver = TestDriver(context
|
|
11
|
+
const testdriver = TestDriver(context);
|
|
12
12
|
|
|
13
13
|
// provision.chrome() automatically calls ready() and starts dashcam
|
|
14
14
|
await testdriver.provision.chrome({
|
|
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
|
|
|
8
8
|
|
|
9
9
|
describe("Assert Test", () => {
|
|
10
10
|
it("should assert the testdriver login page shows", async (context) => {
|
|
11
|
-
const testdriver = TestDriver(context, {
|
|
11
|
+
const testdriver = TestDriver(context, {ip: process.env.TD_IP});
|
|
12
12
|
|
|
13
13
|
// provision.chrome() automatically calls ready() and starts dashcam
|
|
14
14
|
await testdriver.provision.chrome({
|
|
@@ -23,7 +23,7 @@ describe("Assert Test", () => {
|
|
|
23
23
|
expect(result).toBeTruthy();
|
|
24
24
|
});
|
|
25
25
|
// it("should assert the testdriver login page shows 2", async (context) => {
|
|
26
|
-
// const testdriver = TestDriver(context
|
|
26
|
+
// const testdriver = TestDriver(context);
|
|
27
27
|
|
|
28
28
|
// // provision.chrome() automatically calls ready() and starts dashcam
|
|
29
29
|
// await testdriver.provision.chrome({
|
|
@@ -10,39 +10,52 @@
|
|
|
10
10
|
import { describe, expect, it } from "vitest";
|
|
11
11
|
import { TestDriver } from "../../lib/vitest/hooks.mjs";
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
describe("Chrome Extension Test", () => {
|
|
14
|
+
it("should load hello-world Chrome extension from local path", async (context) => {
|
|
14
15
|
|
|
16
|
+
console.log('connecting to', process.env.TD_IP)
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
it.skipIf(!isLinux)("should load hello-world Chrome extension from local path", async (context) => {
|
|
18
|
-
const testdriver = TestDriver(context, { headless: false, newSandbox: true });
|
|
18
|
+
const testdriver = TestDriver(context, { ip: process.env.TD_IP });
|
|
19
19
|
|
|
20
20
|
// Wait for connection to be ready before running exec
|
|
21
21
|
await testdriver.ready();
|
|
22
22
|
|
|
23
|
+
// Determine OS-specific paths and commands
|
|
24
|
+
const shell = testdriver.os === 'windows' ? 'pwsh' : 'sh';
|
|
25
|
+
const extensionsDir = testdriver.os === 'windows'
|
|
26
|
+
? 'C:\\Users\\testdriver\\Downloads\\chrome-extensions-samples'
|
|
27
|
+
: '/tmp/chrome-extensions-samples';
|
|
28
|
+
const extensionPath = testdriver.os === 'windows'
|
|
29
|
+
? `${extensionsDir}\\functional-samples\\tutorial.hello-world`
|
|
30
|
+
: `${extensionsDir}/functional-samples/tutorial.hello-world`;
|
|
31
|
+
|
|
23
32
|
// Clone the Chrome extensions samples repo
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
);
|
|
33
|
+
const cloneCmd = testdriver.os === 'windows'
|
|
34
|
+
? `git clone --depth 1 https://github.com/GoogleChrome/chrome-extensions-samples.git "${extensionsDir}"`
|
|
35
|
+
: `git clone --depth 1 https://github.com/GoogleChrome/chrome-extensions-samples.git ${extensionsDir}`;
|
|
36
|
+
|
|
37
|
+
await testdriver.exec(shell, cloneCmd, 60000, true);
|
|
30
38
|
|
|
31
39
|
// Launch Chrome with the hello-world extension loaded
|
|
32
40
|
await testdriver.provision.chromeExtension({
|
|
33
|
-
extensionPath:
|
|
34
|
-
url: 'https://testdriver.ai'
|
|
41
|
+
extensionPath: extensionPath
|
|
35
42
|
});
|
|
36
43
|
|
|
44
|
+
// Navigate to testdriver.ai (extensions don't load on New Tab)
|
|
45
|
+
const addressBar = await testdriver.find("Chrome address bar");
|
|
46
|
+
await addressBar.click();
|
|
47
|
+
await testdriver.type("testdriver.ai");
|
|
48
|
+
await testdriver.pressKeys(["enter"]);
|
|
49
|
+
|
|
50
|
+
// Wait for page to load
|
|
51
|
+
const pageResult = await testdriver.assert("I can see testdriver.ai");
|
|
52
|
+
expect(pageResult).toBeTruthy();
|
|
53
|
+
|
|
37
54
|
// The hello-world extension adds a puzzle piece icon to the toolbar
|
|
38
55
|
// When clicked, it shows a popup with "Hello Extensions"
|
|
39
|
-
|
|
40
|
-
// First, let's verify Chrome loaded and we can see the page
|
|
41
|
-
const pageResult = await testdriver.assert("chrome is running");
|
|
42
|
-
expect(pageResult).toBeTruthy();
|
|
43
56
|
|
|
44
57
|
// Click on the extensions button (puzzle piece icon) in Chrome toolbar
|
|
45
|
-
const extensionsButton = await testdriver.find("The
|
|
58
|
+
const extensionsButton = await testdriver.find("The extensions button in the Chrome toolbar");
|
|
46
59
|
await extensionsButton.click();
|
|
47
60
|
|
|
48
61
|
// Look for the hello world extension in the extensions menu
|
|
@@ -54,8 +67,8 @@ describe("Chrome Extension Test", () => {
|
|
|
54
67
|
expect(popupResult).toBeTruthy();
|
|
55
68
|
});
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
const testdriver = TestDriver(context
|
|
70
|
+
it("should load Loom from Chrome Web Store by extensionId", async (context) => {
|
|
71
|
+
const testdriver = TestDriver(context);
|
|
59
72
|
|
|
60
73
|
// Launch Chrome with Loom loaded by its Chrome Web Store ID
|
|
61
74
|
// Loom ID: liecbddmkiiihnedobmlmillhodjkdmb
|
|
@@ -63,6 +76,16 @@ describe("Chrome Extension Test", () => {
|
|
|
63
76
|
extensionId: 'liecbddmkiiihnedobmlmillhodjkdmb'
|
|
64
77
|
});
|
|
65
78
|
|
|
79
|
+
// Navigate to testdriver.ai (extensions don't load on New Tab)
|
|
80
|
+
const addressBar = await testdriver.find("Chrome address bar");
|
|
81
|
+
await addressBar.click();
|
|
82
|
+
await testdriver.type("testdriver.ai");
|
|
83
|
+
await testdriver.pressKeys(["enter"]);
|
|
84
|
+
|
|
85
|
+
// Wait for page to load
|
|
86
|
+
const pageResult = await testdriver.assert("I can see testdriver.ai");
|
|
87
|
+
expect(pageResult).toBeTruthy();
|
|
88
|
+
|
|
66
89
|
// Click on the extensions button (puzzle piece icon) in Chrome toolbar
|
|
67
90
|
const extensionsButton = await testdriver.find("The puzzle-shaped icon in the Chrome toolbar.");
|
|
68
91
|
await extensionsButton.click();
|
|
@@ -8,7 +8,7 @@ import { TestDriver } from "../../lib/vitest/hooks.mjs";
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Perform login flow for SauceLabs demo app
|
|
11
|
-
* @param {
|
|
11
|
+
* @param {import('../../sdk.js').default} client - TestDriver client instance
|
|
12
12
|
* @param {string} username - Username (default: 'standard_user')
|
|
13
13
|
*/
|
|
14
14
|
async function performLogin(client, username = "standard_user") {
|
|
@@ -27,11 +27,11 @@ async function performLogin(client, username = "standard_user") {
|
|
|
27
27
|
|
|
28
28
|
describe("Hover Image Test", () => {
|
|
29
29
|
it("should click on shopping cart icon and verify empty cart", async (context) => {
|
|
30
|
-
const testdriver = TestDriver(context, {
|
|
30
|
+
const testdriver = TestDriver(context, { ip: process.env.TD_IP });
|
|
31
31
|
|
|
32
32
|
// provision.chrome() automatically calls ready() and starts dashcam
|
|
33
33
|
await testdriver.provision.chrome({
|
|
34
|
-
url: 'http://testdriver-sandbox.vercel.app/login'
|
|
34
|
+
url: 'http://testdriver-sandbox.vercel.app/login'
|
|
35
35
|
});
|
|
36
36
|
|
|
37
37
|
// Perform login first
|
|
@@ -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, {
|
|
11
|
+
const testdriver = TestDriver(context, { 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
|
|
@@ -12,7 +12,7 @@ describe("Provision Installer", () => {
|
|
|
12
12
|
it.skipIf(!isLinux)(
|
|
13
13
|
"should download and install a .deb package on Linux",
|
|
14
14
|
async (context) => {
|
|
15
|
-
const testdriver = TestDriver(context
|
|
15
|
+
const testdriver = TestDriver(context);
|
|
16
16
|
|
|
17
17
|
// Install bat (a cat clone with syntax highlighting) using provision.installer
|
|
18
18
|
const filePath = await testdriver.provision.installer({
|
|
@@ -30,7 +30,7 @@ describe("Provision Installer", () => {
|
|
|
30
30
|
it.skipIf(!isLinux)(
|
|
31
31
|
"should download a shell script and verify it exists",
|
|
32
32
|
async (context) => {
|
|
33
|
-
const testdriver = TestDriver(context
|
|
33
|
+
const testdriver = TestDriver(context);
|
|
34
34
|
|
|
35
35
|
// Download a shell script (nvm installer)
|
|
36
36
|
const filePath = await testdriver.provision.installer({
|
|
@@ -7,7 +7,7 @@ describe("Launch VS Code on Linux", () => {
|
|
|
7
7
|
it.skipIf(!isLinux)(
|
|
8
8
|
"should launch VS Code on Debian/Ubuntu",
|
|
9
9
|
async (context) => {
|
|
10
|
-
const testdriver = TestDriver(context
|
|
10
|
+
const testdriver = TestDriver(context);
|
|
11
11
|
|
|
12
12
|
// provision.vscode() automatically calls ready() and starts dashcam
|
|
13
13
|
await testdriver.provision.vscode();
|
|
@@ -24,7 +24,7 @@ describe("Launch VS Code on Linux", () => {
|
|
|
24
24
|
it.skipIf(!isLinux)(
|
|
25
25
|
"should install and use a VS Code extension",
|
|
26
26
|
async (context) => {
|
|
27
|
-
const testdriver = TestDriver(context
|
|
27
|
+
const testdriver = TestDriver(context);
|
|
28
28
|
|
|
29
29
|
// Launch VS Code with the Prettier extension installed
|
|
30
30
|
await testdriver.provision.vscode({
|
|
@@ -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
|
});
|