@testdriverai/agent 7.11.229-canary → 7.11.231-test
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/docs/_data/examples-manifest.json +16 -16
- package/docs/_scripts/generate-examples.js +38 -45
- package/docs/agent.mdx +174 -93
- package/docs/assert.mdx +73 -269
- package/docs/aws-setup.mdx +196 -314
- package/docs/bring-your-own-machine.mdx +139 -206
- package/docs/cache.mdx +144 -151
- package/docs/caching.mdx +99 -89
- package/docs/captcha.mdx +126 -111
- package/docs/ci-cd.mdx +154 -543
- package/docs/click.mdx +74 -229
- package/docs/client.mdx +177 -231
- package/docs/copilot/auto-healing.mdx +86 -370
- package/docs/copilot/running-tests.mdx +121 -930
- package/docs/customizing-devices.mdx +74 -251
- package/docs/dashcam.mdx +72 -405
- package/docs/debugging-with-screenshots.mdx +105 -371
- package/docs/docs.json +75 -18
- package/docs/double-click.mdx +48 -68
- package/docs/errors.mdx +102 -176
- package/docs/events.mdx +170 -327
- package/docs/examples/{hover-text-with-description.mdx → add-to-cart.mdx} +30 -35
- package/docs/examples/assert.mdx +28 -41
- package/docs/examples/click-button.mdx +71 -0
- package/docs/examples/{hover-image.mdx → click-icon.mdx} +32 -28
- package/docs/examples/element-not-found.mdx +27 -26
- package/docs/examples/findall-coffee-icons.mdx +28 -32
- package/docs/examples/installer.mdx +61 -25
- package/docs/examples/launch-vscode-linux.mdx +28 -51
- package/docs/examples/navigate-and-scroll.mdx +82 -0
- package/docs/examples/parse.mdx +24 -28
- package/docs/examples/press-keys.mdx +28 -25
- package/docs/examples/scroll.mdx +25 -27
- package/docs/examples/type.mdx +26 -21
- package/docs/exec.mdx +92 -256
- package/docs/extract.mdx +69 -170
- package/docs/find.mdx +140 -736
- package/docs/focus-application.mdx +69 -246
- package/docs/generating-tests.mdx +103 -137
- package/docs/guide/best-practices-polling.mdx +65 -141
- package/docs/hosted.mdx +80 -144
- package/docs/hover.mdx +62 -235
- package/docs/locating-elements.mdx +74 -369
- package/docs/making-assertions.mdx +116 -74
- package/docs/mcp-setup.mdx +80 -63
- package/docs/mouse-down.mdx +48 -125
- package/docs/mouse-up.mdx +47 -129
- package/docs/options.mdx +75 -48
- package/docs/parse.mdx +95 -184
- package/docs/performing-actions.mdx +104 -94
- package/docs/press-keys.mdx +59 -299
- package/docs/provision.mdx +137 -182
- package/docs/quickstart-claude.mdx +85 -0
- package/docs/quickstart-github.mdx +62 -32
- package/docs/quickstart-manual.mdx +60 -33
- package/docs/quickstart-vscode.mdx +97 -0
- package/docs/quickstart.mdx +25 -9
- package/docs/redraw.mdx +101 -165
- package/docs/reusable-code.mdx +97 -200
- package/docs/right-click.mdx +47 -89
- package/docs/screenshot.mdx +47 -201
- package/docs/screenshots.mdx +87 -137
- package/docs/scroll.mdx +71 -154
- package/docs/secrets.mdx +96 -91
- package/docs/self-hosted.mdx +83 -142
- package/docs/snippets/enterprise.mdx +15 -0
- package/docs/styles.css +68 -0
- package/docs/test-results-json.mdx +167 -204
- package/docs/type.mdx +56 -304
- package/docs/variables.mdx +121 -81
- package/docs/wait.mdx +33 -31
- package/docs/what-is-testdriver.mdx +80 -32
- package/examples/{hover-text-with-description.test.mjs → add-to-cart.test.mjs} +4 -5
- package/examples/assert.test.mjs +3 -20
- package/examples/{hover-text.test.mjs → click-button.test.mjs} +3 -4
- package/examples/{hover-image.test.mjs → click-icon.test.mjs} +5 -6
- package/examples/element-not-found.test.mjs +3 -5
- package/examples/findall-coffee-icons.test.mjs +3 -3
- package/examples/installer.test.mjs +1 -1
- package/examples/launch-vscode-linux.test.mjs +6 -33
- package/examples/{scroll-keyboard.test.mjs → navigate-and-scroll.test.mjs} +4 -5
- package/examples/parse.test.mjs +1 -2
- package/examples/press-keys.test.mjs +4 -4
- package/examples/scroll.test.mjs +3 -6
- package/examples/type.test.mjs +5 -2
- package/lib/provision.js +6 -2
- package/package.json +1 -1
- package/sdk.d.ts +4 -2
- package/sdk.js +1 -1
- package/docs/claude-mcp-plugin.mdx +0 -244
- package/docs/elements.mdx +0 -606
- package/docs/examples/ai.mdx +0 -78
- package/docs/examples/captcha-api.mdx +0 -92
- package/docs/examples/chrome-extension.mdx +0 -156
- package/docs/examples/drag-and-drop.mdx +0 -100
- package/docs/examples/formatted-logging.mdx +0 -74
- package/docs/examples/hover-text.mdx +0 -70
- package/docs/examples/match-image.mdx +0 -97
- package/docs/examples/scroll-keyboard.mdx +0 -80
- package/docs/examples/scroll-until-text.mdx +0 -109
package/docs/wait.mdx
CHANGED
|
@@ -1,52 +1,54 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "wait"
|
|
2
|
+
title: "wait()"
|
|
3
3
|
sidebarTitle: "wait"
|
|
4
|
-
description: "Pause the
|
|
4
|
+
description: "Pause the test for a fixed number of milliseconds."
|
|
5
5
|
icon: "clock"
|
|
6
|
-
|
|
6
|
+
mode: "wide"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Pause the test for a fixed time, for example while an animation finishes:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
```javascript
|
|
12
|
+
await testdriver.find('Menu toggle').click();
|
|
13
|
+
await testdriver.wait(1000);
|
|
14
|
+
```
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
To wait for an element to appear, don't use `wait()`. Use [`find()`](/find) with a `timeout`, which continues as soon as the element shows up:
|
|
14
17
|
|
|
15
18
|
```javascript
|
|
16
|
-
await testdriver.
|
|
19
|
+
await testdriver.find('Order confirmation message', { timeout: 30000 });
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Signature
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
testdriver.wait(timeout?: number): Promise<void>
|
|
17
26
|
```
|
|
18
27
|
|
|
19
|
-
##
|
|
28
|
+
## Parameters
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
30
|
+
<ParamField path="timeout" type="number" default="3000">
|
|
31
|
+
How long to pause, in milliseconds.
|
|
32
|
+
</ParamField>
|
|
24
33
|
|
|
25
34
|
## Examples
|
|
26
35
|
|
|
36
|
+
### Let a transition finish
|
|
37
|
+
|
|
27
38
|
```javascript
|
|
28
|
-
|
|
29
|
-
await testdriver.
|
|
30
|
-
await testdriver.
|
|
39
|
+
await testdriver.find('Settings tab').click();
|
|
40
|
+
await testdriver.wait(500);
|
|
41
|
+
await testdriver.assert('the Settings panel is fully open');
|
|
42
|
+
```
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
await testdriver.wait(5000);
|
|
44
|
+
### Pause for the default time
|
|
34
45
|
|
|
35
|
-
|
|
36
|
-
await testdriver.wait();
|
|
46
|
+
```javascript
|
|
47
|
+
await testdriver.wait(); // 3 seconds
|
|
37
48
|
```
|
|
38
49
|
|
|
39
|
-
##
|
|
40
|
-
|
|
41
|
-
- **Use it for simple delays**. Wait for animations, transitions, or state changes after an action.
|
|
42
|
-
- **Do not use it to wait for an element**. If you wait for a specific element to show, use `find()` with a `timeout` option:
|
|
43
|
-
```javascript
|
|
44
|
-
// ✅ Better for waiting for elements
|
|
45
|
-
const element = await testdriver.find('success message', { timeout: 30000 });
|
|
50
|
+
## Related
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
- Do not use very long timeouts. This keeps the tests efficient.
|
|
52
|
-
- Use it only when necessary. The TestDriver [redraw detection](/performing-actions#waiting-for-dynamic-content) waits automatically for the screen and the network to become stable after each action.
|
|
52
|
+
- [find()](/find): wait for an element with a `timeout`
|
|
53
|
+
- [Wait for a changing UI](/performing-actions#wait-for-a-changing-ui): choose the right way to wait
|
|
54
|
+
- [Redraw detection](/redraw): wait for the screen to settle after each action
|
|
@@ -1,54 +1,102 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "What is TestDriver?"
|
|
3
|
-
description: "
|
|
3
|
+
description: "TestDriver tests apps by seeing the screen and controlling a real machine, without selectors."
|
|
4
4
|
icon: "circle-info"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
TestDriver is an end-to-end testing platform that tests your app the way a person uses it.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- It looks at the screen.
|
|
10
|
+
- It finds elements from plain-language descriptions.
|
|
11
|
+
- It controls a real machine with the mouse and keyboard.
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
It doesn't rely on CSS or XPath selectors. So it can test flows that selector-based tools can't reach, such as desktop apps, browser extensions, and content that changes every time it renders.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|-----------|---------|----------|
|
|
15
|
-
| **Teams that move quickly** | They change the UI structure frequently. This breaks the CSS and XPath selectors. | Agile teams, startups, vibe-coders |
|
|
16
|
-
| **Dynamic content** | You cannot select it with selectors. | AI chatbots, PDFs, images, videos |
|
|
17
|
-
| **Software that you do not own** | It can have no correct accessibility attributes. | Other websites, extensions, third-party applications |
|
|
18
|
-
| **Workflows with more than one application** | You cannot test them with web-only tools. | Desktop apps, browser extensions, IDEs |
|
|
19
|
-
| **Visual states** | You cannot make a check of them with code selectors. | Charts, graphs, videos, images, spelling errors, UI layout |
|
|
15
|
+
## Where selector-based tests fall short
|
|
20
16
|
|
|
21
|
-
|
|
17
|
+
Tools such as Playwright and Cypress drive a single web page through its DOM. They locate elements with selectors.
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
That works until a test needs something the DOM can't describe reliably:
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
| Situation | Why selectors break down | Examples |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| **A fast-changing UI** | Each redesign changes the markup and breaks CSS and XPath selectors. | Startups, teams shipping AI-generated code |
|
|
24
|
+
| **Dynamic content** | Generated content has no stable structure to select. | AI chat responses, PDFs, images, video |
|
|
25
|
+
| **Software you don't own** | Third-party markup often lacks stable IDs or accessibility attributes. | Other websites, embedded widgets, third-party apps |
|
|
26
|
+
| **More than one application** | Web-only tools can't leave the browser tab. | Desktop apps, browser extensions, IDEs, installers |
|
|
27
|
+
| **Visual state** | Whether something *looks* right isn't in the DOM. | Charts, layout, spelling, rendered media |
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
## How TestDriver works
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
import { describe, expect, it } from "vitest";
|
|
31
|
-
import { TestDriver } from "testdriverai/vitest/hooks";
|
|
31
|
+
A TestDriver test is a [Vitest](https://vitest.dev) test. It runs against a cloud sandbox, a fresh Linux machine, or Windows on [Enterprise](https://testdriver.ai/enterprise/) plans.
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
const testdriver = TestDriver(context);
|
|
33
|
+
Each step describes what a user would see or do:
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
1. **[Provision](/provision)** launches the app under test in the sandbox, such as Chrome at a URL, a Chrome extension, VS Code, an Electron app, or a downloaded installer.
|
|
36
|
+
2. **[Find](/locating-elements)** takes a description like `'Sign in button'` and uses AI vision to locate the matching element on screen.
|
|
37
|
+
3. **[Act](/performing-actions)** clicks, types, scrolls, and presses keys on the real machine. The app receives the same input a person would produce.
|
|
38
|
+
4. **[Assert](/making-assertions)** checks a plain-language statement about the screen, such as `'the cart shows one item'`.
|
|
39
|
+
|
|
40
|
+
This test installs the Loom extension from the Chrome Web Store. It checks that Loom appears in Chrome's extensions menu, a flow that a DOM-only tool can't drive:
|
|
41
|
+
|
|
42
|
+
```javascript tests/loom-extension.test.mjs
|
|
43
|
+
import { describe, expect, it } from 'vitest';
|
|
44
|
+
import { TestDriver } from 'testdriverai/vitest/hooks';
|
|
45
|
+
|
|
46
|
+
describe('Loom extension', () => {
|
|
47
|
+
it('appears in the Chrome extensions menu', async (context) => {
|
|
48
|
+
const testdriver = TestDriver(context);
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
50
|
+
// Launch Chrome with Loom installed by its Chrome Web Store ID
|
|
51
|
+
await testdriver.provision.chromeExtension({
|
|
52
|
+
extensionId: 'liecbddmkiiihnedobmlmillhodjkdmb',
|
|
53
|
+
});
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
55
|
+
const extensionsButton = await testdriver.find('puzzle-piece extensions icon in the Chrome toolbar');
|
|
56
|
+
await extensionsButton.click();
|
|
57
|
+
|
|
58
|
+
const loomExtension = await testdriver.find('Loom in the extensions dropdown');
|
|
59
|
+
expect(loomExtension.found()).toBe(true);
|
|
60
|
+
});
|
|
48
61
|
});
|
|
49
62
|
```
|
|
50
63
|
|
|
64
|
+
To run this yourself, follow the [Quickstart](/quickstart).
|
|
65
|
+
|
|
66
|
+
## What's included
|
|
67
|
+
|
|
68
|
+
- **The SDK**, the `testdriverai` npm package. Use it in Vitest tests to provision apps, find elements, act, and assert.
|
|
69
|
+
- **Cloud sandboxes** that run your tests on fresh Linux machines, or Windows on Enterprise plans. Enterprise customers can also [self-host](/self-hosted) them.
|
|
70
|
+
- **Debugging tools**: screen recordings of every run, [screenshots](/debugging-with-screenshots), and [element caching](/caching) for faster, more stable repeat runs.
|
|
71
|
+
- **[The TestDriver agent](/agent)**, which reviews your pull requests and writes and maintains tests from GitHub.
|
|
72
|
+
|
|
73
|
+
## When to use TestDriver
|
|
74
|
+
|
|
75
|
+
TestDriver is built for flows that are hard to cover any other way:
|
|
76
|
+
|
|
77
|
+
- Multi-application workflows
|
|
78
|
+
- Desktop software
|
|
79
|
+
- Browser extensions
|
|
80
|
+
- Third-party UIs
|
|
81
|
+
- Visual checks
|
|
82
|
+
|
|
83
|
+
Each step runs on a real machine and uses AI vision. So a TestDriver test takes longer per step than a test that talks to the DOM directly.
|
|
84
|
+
|
|
85
|
+
If stable, selector-based tests already cover a flow, keep them. Use TestDriver for the parts they can't reach.
|
|
51
86
|
|
|
52
|
-
|
|
87
|
+
## Related
|
|
53
88
|
|
|
54
|
-
|
|
89
|
+
<CardGroup cols={2}>
|
|
90
|
+
<Card title="Quickstart" icon="rocket" href="/quickstart">
|
|
91
|
+
Install TestDriver and run your first test.
|
|
92
|
+
</Card>
|
|
93
|
+
<Card title="Locate elements" icon="crosshairs" href="/locating-elements">
|
|
94
|
+
Write descriptions that find the right element reliably.
|
|
95
|
+
</Card>
|
|
96
|
+
<Card title="The TestDriver agent" icon="hand-horns" href="/agent">
|
|
97
|
+
Let the agent review pull requests and build tests from GitHub.
|
|
98
|
+
</Card>
|
|
99
|
+
<Card title="Hosted sandboxes" icon="cloud" href="/hosted">
|
|
100
|
+
Run tests on TestDriver's cloud infrastructure.
|
|
101
|
+
</Card>
|
|
102
|
+
</CardGroup>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Logs in to the demo store, adds the TestDriver Hat to the cart, and
|
|
3
|
+
* asserts that the cart has an item.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
@@ -8,8 +8,8 @@ import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
|
8
8
|
import { getDefaults } from "./config.mjs";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* @param {
|
|
11
|
+
* Log in to the TestDriver sandbox demo store.
|
|
12
|
+
* @param {object} client - TestDriver instance
|
|
13
13
|
* @param {string} username - Username (default: 'standard_user')
|
|
14
14
|
*/
|
|
15
15
|
async function performLogin(client, username = "standard_user") {
|
|
@@ -34,7 +34,6 @@ describe("Hover Text With Description Test", () => {
|
|
|
34
34
|
url: "http://testdriver-sandbox.vercel.app/login",
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
//
|
|
38
37
|
// Perform login first
|
|
39
38
|
await performLogin(testdriver);
|
|
40
39
|
|
package/examples/assert.test.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* TestDriver
|
|
3
|
-
* Converted from: testdriver/acceptance/assert.yaml
|
|
2
|
+
* Opens the TestDriver sandbox login page and asserts that it loaded.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
import { describe, expect, it } from "vitest";
|
|
@@ -12,13 +11,12 @@ describe("Assert Test", () => {
|
|
|
12
11
|
const testdriver = TestDriver(context, {
|
|
13
12
|
...getDefaults(context),
|
|
14
13
|
});
|
|
15
|
-
|
|
16
|
-
// provision.chrome() automatically calls ready() and starts dashcam
|
|
14
|
+
|
|
17
15
|
await testdriver.provision.chrome({
|
|
18
16
|
url: 'http://testdriver-sandbox.vercel.app/login',
|
|
19
17
|
});
|
|
20
18
|
|
|
21
|
-
//
|
|
19
|
+
// Save a screenshot to .testdriver/screenshots for debugging
|
|
22
20
|
await testdriver.screenshot();
|
|
23
21
|
|
|
24
22
|
// Assert the TestDriver.ai Sandbox login page is displayed
|
|
@@ -28,20 +26,5 @@ describe("Assert Test", () => {
|
|
|
28
26
|
|
|
29
27
|
expect(result).toBeTruthy();
|
|
30
28
|
});
|
|
31
|
-
// it("should assert the testdriver login page shows 2", async (context) => {
|
|
32
|
-
// const testdriver = TestDriver(context);
|
|
33
|
-
|
|
34
|
-
// // provision.chrome() automatically calls ready() and starts dashcam
|
|
35
|
-
// await testdriver.provision.chrome({
|
|
36
|
-
// url: 'http://testdriver-sandbox.vercel.app/login',
|
|
37
|
-
// });
|
|
38
|
-
|
|
39
|
-
// // Assert the TestDriver.ai Sandbox login page is displayed
|
|
40
|
-
// const result = await testdriver.assert(
|
|
41
|
-
// "the TestDriver.ai Sandbox login page is displayed",
|
|
42
|
-
// );
|
|
43
|
-
|
|
44
|
-
// expect(result).toBeTruthy();
|
|
45
|
-
// });
|
|
46
29
|
});
|
|
47
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Clicks Sign In on an empty login form and asserts that a
|
|
3
|
+
* required-fields error appears.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
@@ -12,8 +12,7 @@ describe("Hover Text Test", () => {
|
|
|
12
12
|
const testdriver = TestDriver(context, { ...getDefaults(context) });
|
|
13
13
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
|
|
15
|
+
// Submit the login form without filling it in
|
|
17
16
|
const signInButton = await testdriver.find(
|
|
18
17
|
"Sign In, black button below the password field",
|
|
19
18
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Logs in to the demo store, clicks the shopping cart icon, and asserts
|
|
3
|
+
* that the cart is empty.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
@@ -8,8 +8,8 @@ import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
|
8
8
|
import { getDefaults } from "./config.mjs";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
12
|
-
* @param {
|
|
11
|
+
* Log in to the TestDriver sandbox demo store.
|
|
12
|
+
* @param {object} client - TestDriver instance
|
|
13
13
|
* @param {string} username - Username (default: 'standard_user')
|
|
14
14
|
*/
|
|
15
15
|
async function performLogin(client, username = "standard_user") {
|
|
@@ -29,8 +29,7 @@ async function performLogin(client, username = "standard_user") {
|
|
|
29
29
|
describe("Hover Image Test", () => {
|
|
30
30
|
it("should click on shopping cart icon and verify empty cart", async (context) => {
|
|
31
31
|
const testdriver = TestDriver(context, { ...getDefaults(context) });
|
|
32
|
-
|
|
33
|
-
// provision.chrome() automatically calls ready() and starts dashcam
|
|
32
|
+
|
|
34
33
|
await testdriver.provision.chrome({
|
|
35
34
|
url: 'http://testdriver-sandbox.vercel.app/login'
|
|
36
35
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Searches for an element that isn't on screen and checks that find()
|
|
3
|
+
* returns an unfound element instead of throwing.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
@@ -12,8 +12,6 @@ describe("Element Not Found Test", () => {
|
|
|
12
12
|
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
13
13
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
|
|
17
15
|
// Try to find an element that definitely doesn't exist
|
|
18
16
|
const element = await testdriver.find(
|
|
19
17
|
"a purple unicorn dancing on the moon",
|
|
@@ -22,6 +20,6 @@ describe("Element Not Found Test", () => {
|
|
|
22
20
|
// Should return an element that is not found
|
|
23
21
|
expect(element.found()).toBe(false);
|
|
24
22
|
expect(element.coordinates).toBeNull();
|
|
25
|
-
});
|
|
23
|
+
});
|
|
26
24
|
});
|
|
27
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Loads a random icon grid and uses findAll() to locate and click every
|
|
3
|
+
* coffee cup icon on it.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
@@ -27,7 +27,7 @@ describe("FindAll Coffee Icons", () => {
|
|
|
27
27
|
console.log(` Icon ${i + 1}: (${icon.x}, ${icon.y}) center=(${icon.centerX}, ${icon.centerY})`);
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
//
|
|
30
|
+
// The grid has 4 coffee cups; allow one miss, matching the assertion below
|
|
31
31
|
expect(coffeeIcons.length).toBeGreaterThanOrEqual(3);
|
|
32
32
|
expect(coffeeIcons.length).toBeLessThanOrEqual(4);
|
|
33
33
|
|
|
@@ -62,7 +62,7 @@ describe("Provision Installer", () => {
|
|
|
62
62
|
// Download a shell script (nvm installer)
|
|
63
63
|
const filePath = await testdriver.provision.installer({
|
|
64
64
|
url: 'https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh',
|
|
65
|
-
launch: false, //
|
|
65
|
+
launch: false, // Download the script without running it
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
// Verify the file was downloaded
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launches VS Code in a Linux sandbox and checks that its window opens.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { describe, expect, it } from "vitest";
|
|
2
6
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
3
7
|
import { getDefaults } from "./config.mjs";
|
|
@@ -9,11 +13,10 @@ describe("Launch VS Code on Linux", () => {
|
|
|
9
13
|
"should launch VS Code on Debian/Ubuntu",
|
|
10
14
|
async (context) => {
|
|
11
15
|
const testdriver = TestDriver(context, { ...getDefaults(context) });
|
|
12
|
-
|
|
13
|
-
// provision.vscode() automatically calls ready() and starts dashcam
|
|
16
|
+
|
|
14
17
|
await testdriver.provision.vscode();
|
|
15
18
|
|
|
16
|
-
//
|
|
19
|
+
// Poll for up to 60 seconds while VS Code starts
|
|
17
20
|
const vsCodeWindow = await testdriver.find(
|
|
18
21
|
"Visual Studio Code window",
|
|
19
22
|
{ timeout: 60000 }
|
|
@@ -22,34 +25,4 @@ describe("Launch VS Code on Linux", () => {
|
|
|
22
25
|
},
|
|
23
26
|
);
|
|
24
27
|
|
|
25
|
-
// it.skipIf(!isLinux)(
|
|
26
|
-
// "should install and use a VS Code extension",
|
|
27
|
-
// async (context) => {
|
|
28
|
-
// const testdriver = TestDriver(context, { ...getDefaults(context) });
|
|
29
|
-
|
|
30
|
-
// // Launch VS Code with the Prettier extension installed
|
|
31
|
-
// await testdriver.provision.vscode({
|
|
32
|
-
// extensions: ["esbenp.prettier-vscode"],
|
|
33
|
-
// });
|
|
34
|
-
|
|
35
|
-
// const vsCodeWindow = await testdriver.find(
|
|
36
|
-
// "Visual Studio Code window",
|
|
37
|
-
// { timeout: 60000 }
|
|
38
|
-
// );
|
|
39
|
-
|
|
40
|
-
// expect(vsCodeWindow.found()).toBeTruthy();
|
|
41
|
-
|
|
42
|
-
// // Open the extensions panel to verify Prettier is installed
|
|
43
|
-
// await testdriver.pressKeys(["ctrl", "shift", "x"]);
|
|
44
|
-
|
|
45
|
-
// // Wait for extensions panel to open
|
|
46
|
-
// await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
47
|
-
|
|
48
|
-
// // Assert that Prettier extension is visible in the installed extensions
|
|
49
|
-
// const prettierVisible = await testdriver.assert(
|
|
50
|
-
// "Prettier extension is visible in the extensions panel or sidebar",
|
|
51
|
-
// );
|
|
52
|
-
// expect(prettierVisible).toBeTruthy();
|
|
53
|
-
// },
|
|
54
|
-
// );
|
|
55
28
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Navigates to a page by typing in Chrome's address bar, then scrolls
|
|
3
|
+
* down and asserts that the heading has scrolled out of view.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
@@ -12,8 +12,7 @@ describe("Scroll Keyboard Test", () => {
|
|
|
12
12
|
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
13
13
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
14
14
|
|
|
15
|
-
//
|
|
16
|
-
// Navigate to https://www.webhamster.com/
|
|
15
|
+
// Navigate to https://www.webhamster.com/ through the address bar
|
|
17
16
|
await testdriver.focusApplication("Google Chrome");
|
|
18
17
|
const urlBar = await testdriver.find(
|
|
19
18
|
"the URL in the omnibox", {zoom: true}
|
|
@@ -23,7 +22,7 @@ describe("Scroll Keyboard Test", () => {
|
|
|
23
22
|
await testdriver.type("https://www.webhamster.com/");
|
|
24
23
|
await testdriver.pressKeys(["enter"]);
|
|
25
24
|
|
|
26
|
-
//
|
|
25
|
+
// Click the page so it has focus, then scroll down
|
|
27
26
|
const heading = await testdriver.find(
|
|
28
27
|
"The Hamster Dance, large heading at top of page",
|
|
29
28
|
);
|
package/examples/parse.test.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Uses keyboard shortcuts to open a new tab, open DevTools, and navigate
|
|
3
|
+
* to a URL.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { describe, expect, it } from "vitest";
|
|
@@ -20,7 +20,7 @@ describe("Press Keys Test", () => {
|
|
|
20
20
|
// Open new tab
|
|
21
21
|
await testdriver.pressKeys(["ctrl", "t"]);
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Wait for the new tab page to load
|
|
24
24
|
let imagesLink = await testdriver.find("Images", {timeout: 5000});
|
|
25
25
|
|
|
26
26
|
expect(imagesLink.found()).toBeTruthy();
|
|
@@ -28,7 +28,7 @@ describe("Press Keys Test", () => {
|
|
|
28
28
|
// Open DevTools
|
|
29
29
|
await testdriver.pressKeys(["ctrl", "shift", "i"]);
|
|
30
30
|
|
|
31
|
-
//
|
|
31
|
+
// Wait for the DevTools Elements panel
|
|
32
32
|
let elements = await testdriver.find("Elements", {timeout: 5000});
|
|
33
33
|
expect(elements.found()).toBeTruthy();
|
|
34
34
|
|
package/examples/scroll.test.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* UPDATED: Now using chrome preset for automatic setup
|
|
2
|
+
* Opens a page, scrolls down, and asserts that the heading has scrolled
|
|
3
|
+
* out of view.
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
6
|
import { describe, expect, it } from "vitest";
|
|
@@ -14,8 +12,7 @@ describe("Scroll Test", () => {
|
|
|
14
12
|
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
15
13
|
await testdriver.provision.chrome({ url: 'https://www.webhamster.com/' });
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
// Wait for page to load and click heading
|
|
15
|
+
// Click the page so it has focus before scrolling
|
|
19
16
|
const heading = await testdriver.find(
|
|
20
17
|
"The Hamster Dance, large heading at top of page",
|
|
21
18
|
);
|
package/examples/type.test.mjs
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types into the login form and asserts on the result, including the
|
|
3
|
+
* browser's validation message for a missing password.
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import { describe, expect, it } from "vitest";
|
|
2
7
|
import { TestDriver } from "../lib/vitest/hooks.mjs";
|
|
3
8
|
import { getDefaults } from "./config.mjs";
|
|
@@ -7,7 +12,6 @@ describe("Type Test", () => {
|
|
|
7
12
|
const testdriver = TestDriver(context, { ...getDefaults(context), headless: true });
|
|
8
13
|
await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
|
|
9
14
|
|
|
10
|
-
//
|
|
11
15
|
const usernameField = await testdriver.find(
|
|
12
16
|
"Username, input field for username",
|
|
13
17
|
);
|
|
@@ -31,7 +35,6 @@ describe("Type Test", () => {
|
|
|
31
35
|
await usernameField.click();
|
|
32
36
|
await testdriver.type("standard_user");
|
|
33
37
|
|
|
34
|
-
//
|
|
35
38
|
const signInButton = await testdriver.find(
|
|
36
39
|
"Sign in, black button below the password field",
|
|
37
40
|
);
|
package/lib/provision.js
CHANGED
|
@@ -482,7 +482,8 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
|
|
482
482
|
* @param {boolean} [options.launch=true] - Whether to launch the app after installation.
|
|
483
483
|
* For a .deb on Linux this starts the app from its `.desktop` entry (or, when
|
|
484
484
|
* `appName` is set and there is no `.desktop` entry, its first `/usr/bin` binary),
|
|
485
|
-
* then focuses `appName`.
|
|
485
|
+
* then focuses `appName`. A downloaded .sh script is only run when this is true.
|
|
486
|
+
* On other platforms it only focuses `appName`.
|
|
486
487
|
* @returns {Promise<string>} Path to the downloaded file
|
|
487
488
|
* @example
|
|
488
489
|
* // Install a .deb package on Linux (auto-detected). Missing dependencies
|
|
@@ -620,7 +621,10 @@ with zipfile.ZipFile(io.BytesIO(zip_data)) as zf:
|
|
|
620
621
|
} else if (ext === "appimage") {
|
|
621
622
|
installCommand = `chmod +x "${actualFilePath}"`;
|
|
622
623
|
} else if (ext === "sh") {
|
|
623
|
-
|
|
624
|
+
// Running a script is launching it, so `launch: false` only makes it executable
|
|
625
|
+
installCommand = launch
|
|
626
|
+
? `chmod +x "${actualFilePath}" && "${actualFilePath}"`
|
|
627
|
+
: `chmod +x "${actualFilePath}"`;
|
|
624
628
|
}
|
|
625
629
|
} else if (self.os === "darwin") {
|
|
626
630
|
if (ext === "dmg") {
|
package/package.json
CHANGED
package/sdk.d.ts
CHANGED
|
@@ -281,6 +281,8 @@ export interface TestDriverOptions {
|
|
|
281
281
|
reconnect?: boolean;
|
|
282
282
|
/** Explicit sandbox id to reconnect to. Implies `newSandbox: false` unless explicitly overridden. */
|
|
283
283
|
sandboxId?: string;
|
|
284
|
+
/** Keep the sandbox running for this many milliseconds after disconnect, so you can reconnect to it (default: 60000). Capped at 30 minutes. 0 uses the default; there is no immediate-termination value. */
|
|
285
|
+
keepAlive?: number;
|
|
284
286
|
/** Enable/disable Dashcam video recording (default: true) */
|
|
285
287
|
dashcam?: boolean;
|
|
286
288
|
/**
|
|
@@ -359,7 +361,7 @@ export interface ConnectOptions {
|
|
|
359
361
|
headless?: boolean;
|
|
360
362
|
/** Reuse recent connection if available (default: true) */
|
|
361
363
|
reuseConnection?: boolean;
|
|
362
|
-
/** Keep sandbox
|
|
364
|
+
/** Keep the sandbox running for this many milliseconds after disconnect, so you can reconnect to it (default: 60000). Capped at 30 minutes. 0 uses the default; there is no immediate-termination value. */
|
|
363
365
|
keepAlive?: number;
|
|
364
366
|
}
|
|
365
367
|
|
|
@@ -903,7 +905,7 @@ export interface ProvisionInstallerOptions {
|
|
|
903
905
|
filename?: string;
|
|
904
906
|
/** Application name to focus after install */
|
|
905
907
|
appName?: string;
|
|
906
|
-
/** Whether to launch the app after installation (default: true). For a .deb on Linux this starts the app from its .desktop entry, then focuses appName. */
|
|
908
|
+
/** Whether to launch the app after installation (default: true). For a .deb on Linux this starts the app from its .desktop entry, then focuses appName. A downloaded .sh script only runs when this is true. Elsewhere it only focuses appName. */
|
|
907
909
|
launch?: boolean;
|
|
908
910
|
}
|
|
909
911
|
|
package/sdk.js
CHANGED
|
@@ -443,7 +443,7 @@ class Element {
|
|
|
443
443
|
* @returns {Promise<Element>} This element instance
|
|
444
444
|
*/
|
|
445
445
|
async find(newDescription, options) {
|
|
446
|
-
// Handle timeout/polling option (default:
|
|
446
|
+
// Handle timeout/polling option (default: 10s, polling every 5s)
|
|
447
447
|
const timeout = typeof options === "object" && options?.timeout !== undefined
|
|
448
448
|
? options.timeout
|
|
449
449
|
: 10000;
|