@testdriverai/agent 7.11.229-test → 7.11.232-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.
Files changed (100) hide show
  1. package/docs/_data/examples-manifest.json +16 -16
  2. package/docs/_scripts/generate-examples.js +38 -45
  3. package/docs/agent.mdx +174 -93
  4. package/docs/assert.mdx +73 -269
  5. package/docs/aws-setup.mdx +196 -314
  6. package/docs/bring-your-own-machine.mdx +139 -206
  7. package/docs/cache.mdx +144 -151
  8. package/docs/caching.mdx +99 -89
  9. package/docs/captcha.mdx +126 -111
  10. package/docs/ci-cd.mdx +154 -543
  11. package/docs/click.mdx +74 -229
  12. package/docs/client.mdx +177 -231
  13. package/docs/copilot/auto-healing.mdx +86 -370
  14. package/docs/copilot/running-tests.mdx +121 -930
  15. package/docs/customizing-devices.mdx +74 -251
  16. package/docs/dashcam.mdx +72 -405
  17. package/docs/debugging-with-screenshots.mdx +105 -371
  18. package/docs/docs.json +79 -22
  19. package/docs/double-click.mdx +48 -68
  20. package/docs/errors.mdx +102 -176
  21. package/docs/events.mdx +170 -327
  22. package/docs/examples/{hover-text-with-description.mdx → add-to-cart.mdx} +30 -35
  23. package/docs/examples/assert.mdx +28 -41
  24. package/docs/examples/click-button.mdx +71 -0
  25. package/docs/examples/{hover-image.mdx → click-icon.mdx} +32 -28
  26. package/docs/examples/element-not-found.mdx +27 -26
  27. package/docs/examples/findall-coffee-icons.mdx +28 -32
  28. package/docs/examples/installer.mdx +61 -25
  29. package/docs/examples/launch-vscode-linux.mdx +28 -51
  30. package/docs/examples/navigate-and-scroll.mdx +82 -0
  31. package/docs/examples/parse.mdx +24 -28
  32. package/docs/examples/press-keys.mdx +28 -25
  33. package/docs/examples/scroll.mdx +25 -27
  34. package/docs/examples/type.mdx +26 -21
  35. package/docs/exec.mdx +92 -256
  36. package/docs/extract.mdx +69 -170
  37. package/docs/find.mdx +140 -736
  38. package/docs/focus-application.mdx +69 -246
  39. package/docs/generating-tests.mdx +103 -137
  40. package/docs/guide/best-practices-polling.mdx +65 -141
  41. package/docs/hosted.mdx +80 -144
  42. package/docs/hover.mdx +62 -235
  43. package/docs/locating-elements.mdx +74 -369
  44. package/docs/making-assertions.mdx +116 -74
  45. package/docs/mcp-setup.mdx +80 -63
  46. package/docs/mouse-down.mdx +48 -125
  47. package/docs/mouse-up.mdx +47 -129
  48. package/docs/options.mdx +75 -48
  49. package/docs/parse.mdx +95 -184
  50. package/docs/performing-actions.mdx +104 -94
  51. package/docs/press-keys.mdx +59 -299
  52. package/docs/provision.mdx +137 -182
  53. package/docs/quickstart-claude.mdx +85 -0
  54. package/docs/quickstart-github.mdx +62 -32
  55. package/docs/quickstart-manual.mdx +60 -33
  56. package/docs/quickstart-vscode.mdx +97 -0
  57. package/docs/quickstart.mdx +26 -9
  58. package/docs/redraw.mdx +101 -165
  59. package/docs/reusable-code.mdx +97 -200
  60. package/docs/right-click.mdx +47 -89
  61. package/docs/screenshot.mdx +47 -201
  62. package/docs/screenshots.mdx +87 -137
  63. package/docs/scroll.mdx +71 -154
  64. package/docs/secrets.mdx +96 -91
  65. package/docs/self-hosted.mdx +83 -142
  66. package/docs/snippets/enterprise.mdx +15 -0
  67. package/docs/styles.css +68 -0
  68. package/docs/test-results-json.mdx +167 -204
  69. package/docs/type.mdx +56 -304
  70. package/docs/variables.mdx +121 -81
  71. package/docs/wait.mdx +33 -31
  72. package/docs/what-is-testdriver.mdx +80 -32
  73. package/examples/{hover-text-with-description.test.mjs → add-to-cart.test.mjs} +4 -5
  74. package/examples/assert.test.mjs +3 -20
  75. package/examples/{hover-text.test.mjs → click-button.test.mjs} +3 -4
  76. package/examples/{hover-image.test.mjs → click-icon.test.mjs} +5 -6
  77. package/examples/element-not-found.test.mjs +3 -5
  78. package/examples/findall-coffee-icons.test.mjs +3 -3
  79. package/examples/installer.test.mjs +1 -1
  80. package/examples/launch-vscode-linux.test.mjs +6 -33
  81. package/examples/{scroll-keyboard.test.mjs → navigate-and-scroll.test.mjs} +4 -5
  82. package/examples/parse.test.mjs +1 -2
  83. package/examples/press-keys.test.mjs +4 -4
  84. package/examples/scroll.test.mjs +3 -6
  85. package/examples/type.test.mjs +5 -2
  86. package/lib/provision.js +6 -2
  87. package/package.json +1 -1
  88. package/sdk.d.ts +4 -2
  89. package/sdk.js +1 -1
  90. package/docs/claude-mcp-plugin.mdx +0 -244
  91. package/docs/elements.mdx +0 -606
  92. package/docs/examples/ai.mdx +0 -78
  93. package/docs/examples/captcha-api.mdx +0 -92
  94. package/docs/examples/chrome-extension.mdx +0 -156
  95. package/docs/examples/drag-and-drop.mdx +0 -100
  96. package/docs/examples/formatted-logging.mdx +0 -74
  97. package/docs/examples/hover-text.mdx +0 -70
  98. package/docs/examples/match-image.mdx +0 -97
  99. package/docs/examples/scroll-keyboard.mdx +0 -80
  100. 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 execution of the script for a specified duration."
4
+ description: "Pause the test for a fixed number of milliseconds."
5
5
  icon: "clock"
6
- "mode": "wide"
6
+ mode: "wide"
7
7
  ---
8
8
 
9
- ## Description
9
+ Pause the test for a fixed time, for example while an animation finishes:
10
10
 
11
- The `wait` method stops the test for a number of milliseconds. Then it continues. Use this to add delays between actions, to wait for animations to complete, or to let state changes become stable.
11
+ ```javascript
12
+ await testdriver.find('Menu toggle').click();
13
+ await testdriver.wait(1000);
14
+ ```
12
15
 
13
- ## Syntax
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.wait(timeout);
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
- ## Arguments
28
+ ## Parameters
20
29
 
21
- | Argument | Type | Default | Description |
22
- | --------- | -------- | ------- | ------------------------------------- |
23
- | `timeout` | `number` | `3000` | The duration in milliseconds to wait. |
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
- // Wait 2 seconds for an animation to complete
29
- await testdriver.find('submit button').click();
30
- await testdriver.wait(2000);
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
- // Wait 5 seconds
33
- await testdriver.wait(5000);
44
+ ### Pause for the default time
34
45
 
35
- // Wait with default timeout (3 seconds)
36
- await testdriver.wait();
46
+ ```javascript
47
+ await testdriver.wait(); // 3 seconds
37
48
  ```
38
49
 
39
- ## Best Practices
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
- // ❌ Don't do this for element waiting
48
- await testdriver.wait(5000);
49
- const element = await testdriver.find('success message');
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: "Reliably test your most difficult user flows"
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
- ## The problem with usual test tools
7
+ TestDriver is an end-to-end testing platform that tests your app the way a person uses it.
8
8
 
9
- Usual test tools such as Playwright test one web application. They use one browser tab and they use selectors.
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
- But selectors are frequently not reliable, or they are not available. This makes tests weak and not stable:
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
- | Challenge | Problem | Examples |
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
- ## The TestDriver solution
17
+ Tools such as Playwright and Cypress drive a single web page through its DOM. They locate elements with selectors.
22
18
 
23
- TestDriver is a complete test platform. It handles these conditions. It has a Javascript SDK, hosted infrastructure, and debug tools. These tools make it easy to write, run, and keep tests for your most difficult user flows.
19
+ That works until a test needs something the DOM can't describe reliably:
24
20
 
25
- ### Javascript SDK
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
- This is an example of a TestDriver test. The test installs a production Chrome extension from the Chrome Web Store. Then it makes sure that the extension is in the extensions menu:
29
+ ## How TestDriver works
28
30
 
29
- ```javascript Installing Loom from the Chrome Web Store
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
- describe("Chrome Extension Test", () => {
34
- const testdriver = TestDriver(context);
33
+ Each step describes what a user would see or do:
35
34
 
36
- // Launch Chrome with Loom loaded by its Chrome Web Store ID
37
- await testdriver.provision.chromeExtension({
38
- extensionId: 'liecbddmkiiihnedobmlmillhodjkdmb'
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
- // Click on the extensions button (puzzle piece icon) in Chrome toolbar
42
- const extensionsButton = await testdriver.find("The puzzle-shaped icon in the Chrome toolbar.");
43
- await extensionsButton.click();
50
+ // Launch Chrome with Loom installed by its Chrome Web Store ID
51
+ await testdriver.provision.chromeExtension({
52
+ extensionId: 'liecbddmkiiihnedobmlmillhodjkdmb',
53
+ });
44
54
 
45
- // Look for Loom in the extensions menu
46
- const loomExtension = await testdriver.find("Loom extension in the extensions dropdown");
47
- expect(loomExtension.found()).toBeTruthy();
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
- <Tip>[vitest](https://vitest.dev/) is the preferred test runner for TestDriver.</Tip>
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
- * TestDriver SDK - Hover Text With Description Test (Vitest)
3
- * Converted from: testdriver/acceptance/hover-text-with-description.yaml
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
- * Perform login flow for SauceLabs demo app
12
- * @param {TestDriver} client - TestDriver client
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
 
@@ -1,6 +1,5 @@
1
1
  /**
2
- * TestDriver SDK - Assert Test (Vitest)
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
- // Take a screenshot
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
- * TestDriver SDK - Hover Text Test (Vitest)
3
- * Converted from: testdriver/acceptance/hover-text.yaml
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
- // Click on Sign In button using new find() API
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
- * TestDriver SDK - Hover Image Test (Vitest)
3
- * Converted from: testdriver/acceptance/hover-image.yaml
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
- * Perform login flow for SauceLabs demo app
12
- * @param {import('../../sdk.js').default} client - TestDriver client instance
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
- * TestDriver SDK - Element Not Found Test
3
- * Tests that finding a non-existent element returns properly without timing out
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
- }); // 90 second timeout for the test (should complete much faster)
23
+ });
26
24
  });
27
25
 
@@ -1,6 +1,6 @@
1
1
  /**
2
- * TestDriver SDK - FindAll Coffee Icons Test
3
- * Loads a random icon grid and uses findAll() to locate and click all 4 coffee cup icons
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
- // Verify we found 3 or 4 coffee icons
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, // Don't auto-run the script
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
- // Wait for VS Code to launch (polls every 5s until found or timeout)
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
- * TestDriver SDK - Scroll Keyboard Test (Vitest)
3
- * Converted from: testdriver/acceptance/scroll-keyboard.yaml
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
- // Scroll down with keyboard 1000 pixels
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
  );
@@ -1,6 +1,5 @@
1
1
  /**
2
- * TestDriver SDK - Parse Test (Vitest)
3
- * Opens Airbnb and runs the .parse() SDK command to analyze the screen.
2
+ * Opens Airbnb and runs parse() to list the UI elements on screen.
4
3
  */
5
4
 
6
5
  import { describe, it } from "vitest";
@@ -1,6 +1,6 @@
1
1
  /**
2
- * TestDriver SDK - Press Keys Test (Vitest)
3
- * Converted from: testdriver/acceptance/press-keys.yaml
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
- // Poll for "Learn more" to appear
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
- // Poll for "Elements" to appear
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
 
@@ -1,8 +1,6 @@
1
1
  /**
2
- * TestDriver SDK - Scroll Test (Vitest)
3
- * Converted from: testdriver/acceptance/scroll.yaml
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
  );
@@ -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`. On other platforms it only 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
- installCommand = `chmod +x "${actualFilePath}" && "${actualFilePath}"`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testdriverai/agent",
3
- "version": "7.11.229-test",
3
+ "version": "7.11.232-test",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "sdk.js",
6
6
  "types": "sdk.d.ts",
package/sdk.d.ts CHANGED
@@ -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 alive for specified milliseconds after disconnect (default: 60000). Set to 0 to terminate immediately on disconnect. */
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: 30s)
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;