8 |
--------------------------------------------------------------------------------
/docs/snippets/lifecycle-warning.mdx:
--------------------------------------------------------------------------------
1 |
2 | Prerun scripts for deployed tests happen in your GitHub Actions workflow setup
3 | file as per below. Prerun scripts that are part of the test lifecycle are run
4 | during local and sandbox runners only. This is likely to change in a future
5 | version.
6 |
7 |
--------------------------------------------------------------------------------
/.vscode/mcp.json:
--------------------------------------------------------------------------------
1 | {
2 | "servers": {
3 | "testdriverai": {
4 | "type": "stdio",
5 | "command": "node",
6 | "args": ["/Users/ianjennings/Development/cli/mcp-server/dist/index.js"],
7 | "env": {
8 | "TD_API_KEY": "4e93d8bf-3886-4d26-a144-116c4063522d"
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/interfaces/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const { run } = require("@oclif/core");
4 |
5 | // Run oclif (with default command handling built-in)
6 | run()
7 | .then(() => {
8 | // Success
9 | })
10 | .catch((error) => {
11 | console.error("Failed to start TestDriver.ai agent:", error);
12 | process.exit(1);
13 | });
14 |
--------------------------------------------------------------------------------
/bin/testdriverai.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | // Set process priority if possible
4 | const os = require("os");
5 | try {
6 | const pid = process.pid;
7 | os.setPriority(pid, -20);
8 | // eslint-disable-next-line no-unused-vars
9 | } catch (error) {
10 | // Ignore if not permitted
11 | }
12 |
13 | // Run the CLI
14 | require("../interfaces/cli.js");
15 |
--------------------------------------------------------------------------------
/docs/snippets/tests/wait-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml highlight={5, 6}
2 | version: 6.0.0
3 | steps:
4 | - prompt: wait for 5 seconds and type testdriver
5 | commands:
6 | - command: wait
7 | timeout: 5000 # 5 seconds
8 | - command: type
9 | text: testdriver
10 | - command: press-keys
11 | keys:
12 | - enter
13 | ```
14 |
--------------------------------------------------------------------------------
/test/testdriver/setup/globalTeardown.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Vitest Global Teardown
3 | * Saves test results and dashcam URLs after all tests complete
4 | */
5 |
6 | import { saveTestResults } from "./testHelpers.mjs";
7 |
8 | export default async function globalTeardown() {
9 | console.log("\n🎬 Saving test results and dashcam URLs...");
10 | saveTestResults();
11 | }
12 |
--------------------------------------------------------------------------------
/docs/snippets/gitignore-warning.mdx:
--------------------------------------------------------------------------------
1 |
2 | Always remember to add a `.gitignore` file to your repository including a
3 | `.env` line so you never accidentally commit you TestDriver API key. This is
4 | important for security and to prevent exposing sensitive information. For more
5 | info see [GitHub
6 | Docs](https://docs.github.com/en/get-started/git-basics/ignoring-files).
7 |
8 |
--------------------------------------------------------------------------------
/agent/lib/theme.js:
--------------------------------------------------------------------------------
1 | const chalk = require("chalk");
2 | chalk.level = 3; // Force color outputs when using chalk, was added for VS Code support
3 |
4 | module.exports = {
5 | green: chalk.hex("#b3d334"),
6 | blue: chalk.hex("#34a3d3"),
7 | red: chalk.hex("#d33434"),
8 | yellow: chalk.hex("#ced334"),
9 | magenta: chalk.hex("#d334b3"),
10 | cyan: chalk.hex("#34d3d3"),
11 | dim: chalk.dim,
12 | gray: chalk.gray,
13 | white: chalk.white,
14 | };
15 |
--------------------------------------------------------------------------------
/docs/snippets/tests/hover-text-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/hover-text.yaml highlight={7-10}
2 | version: 6.0.0
3 | steps:
4 | - prompt: click on sign in
5 | commands:
6 | - command: focus-application
7 | name: Google Chrome
8 | - command: hover-text
9 | text: Sign In
10 | description: black button below the password field
11 | action: click
12 | - command: assert
13 | expect: an error shows that fields are required
14 | ```
15 |
--------------------------------------------------------------------------------
/docs/snippets/test-prereqs.mdx:
--------------------------------------------------------------------------------
1 | ## Prerequisites
2 |
3 | Before running the tests, ensure you have performed the following steps:
4 |
5 | 1. If you haven't already, signup for a Free Trial on the [TestDriver website](https://testdriver.ai/pricing)
6 | 2. Run the `init` command to set up the TestDriver configuration using the API key you got when you signed up for the trial:
7 |
8 | ```bash
9 | npx testdriverai@latest init
10 | ```
11 |
12 | Now you are ready to run the tests!
13 |
--------------------------------------------------------------------------------
/docs/snippets/tests/wait-for-text-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml highlight={14-17}
2 | version: 6.0.0
3 | steps:
4 | - prompt: go to testdriver.ai and verify the page loads
5 | commands:
6 | - command: press-keys
7 | keys:
8 | - ctrl
9 | - l
10 | - command: type
11 | text: testdriver.ai
12 | - command: press-keys
13 | keys:
14 | - enter
15 | - command: wait-for-text
16 | text: Book a Demo
17 | timeout: 10000 # 10 seconds
18 | ```
19 |
--------------------------------------------------------------------------------
/docs/snippets/tests/wait-for-image-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml highlight={14-17}
2 | version: 6.0.0
3 | steps:
4 | - prompt: go to testdriver.ai and wait for the hero image to load
5 | commands:
6 | - command: press-keys
7 | keys:
8 | - ctrl
9 | - l
10 | - command: type
11 | text: testdriver.ai
12 | - command: press-keys
13 | keys:
14 | - enter
15 | - command: wait-for-image
16 | description: the hero section image
17 | timeout: 10000 # 10 seconds
18 | ```
19 |
--------------------------------------------------------------------------------
/docs/snippets/tests/match-image-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/match-image.yaml highlight={9-11}
2 | version: 6.0.0
3 | steps:
4 | - prompt: login
5 | commands:
6 | - command: run
7 | file: snippets/login.yaml
8 | - prompt: assert the testdriver login page shows
9 | commands:
10 | - command: match-image
11 | path: screenshots/cart.png
12 | action: click
13 | - prompt: assert that you see an empty shopping cart
14 | commands:
15 | - command: assert
16 | expect: Your cart is empty
17 | ```
18 |
--------------------------------------------------------------------------------
/docs/snippets/tests/scroll-until-image-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/scroll-until-image.yaml highlight={5-7}
2 | version: 6.0.0
3 | steps:
4 | - prompt: scroll until image of a house appears, then click on it and assert the home insurance page is loaded
5 | commands:
6 | - command: scroll-until-image
7 | description: a pink colored house
8 | direction: down
9 | - command: hover-image
10 | description: a pink colored house
11 | action: click
12 | - command: assert
13 | expect: Home Insurance page appears
14 | ```
15 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.organizeImports": "explicit"
4 | },
5 | "editor.formatOnSave": true,
6 | "editor.defaultFormatter": "esbenp.prettier-vscode",
7 | "yaml.schemas": {
8 | "https://raw.githubusercontent.com/testdriverai/testdriverai/main/schema.json": "file:///Users/kid/Desktop/td/internal/testdriverai/testdriver.yaml"
9 | },
10 | "vitest.enable": true,
11 | "vitest.commandLine": "npx vitest --watch",
12 | "vitest.include": ["**/testdriver/acceptance-sdk/*.test.mjs"],
13 | "vitest.exclude": []
14 | }
15 |
--------------------------------------------------------------------------------
/docs/snippets/tests/scroll-until-text-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/scroll-until-text.yaml highlight={9-11}
2 | version: 6.0.0
3 | steps:
4 | - prompt: login
5 | commands:
6 | - command: run
7 | file: testdriver/snippets/login.yaml
8 | - prompt: scroll until text testdriver socks
9 | commands:
10 | - command: scroll-until-text
11 | text: testdriver socks
12 | direction: down
13 | - prompt: assert testdriver socks appears on screen
14 | commands:
15 | - command: assert
16 | expect: TestDriver Socks appears on screen
17 | ```
18 |
--------------------------------------------------------------------------------
/test/manual/test-sdk-methods.js:
--------------------------------------------------------------------------------
1 | const SDK = require("./sdk.js");
2 |
3 | const client = new SDK("test-key");
4 |
5 | // Get all public methods (non-private, non-constructor)
6 | const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(client))
7 | .filter((m) => !m.startsWith("_") && m !== "constructor")
8 | .sort();
9 |
10 | console.log("Public SDK Methods:");
11 | console.log(methods.join(", "));
12 | console.log("\nTotal:", methods.length, "methods");
13 |
14 | // Check if commands will be set up after connect
15 | console.log("\nCommands before connect:", client.commands);
16 |
--------------------------------------------------------------------------------
/docs/snippets/tests/exec-shell-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/exec-shell.yaml highlight={5-8}
2 | version: 6.0.0
3 | steps:
4 | - prompt: launch chrome
5 | commands:
6 | - command: exec
7 | lang: pwsh
8 | code: |
9 | Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--no-default-browser-check", "--no-first-run", "--guest", "${TD_WEBSITE}"
10 | - command: wait-for-text
11 | text: ${TD_WEBSITE}
12 | timeout: 30000
13 | - command: assert
14 | expect: ${TD_WEBSITE} is loaded
15 | ```
16 |
--------------------------------------------------------------------------------
/vitest.config.mjs:
--------------------------------------------------------------------------------
1 | import { config } from 'dotenv';
2 | import TestDriver from 'testdriverai/vitest';
3 | import { defineConfig } from 'vitest/config';
4 |
5 | // Load .env file early so it's available to the reporter (runs in main process)
6 | // and to worker processes
7 | config();
8 |
9 | export default defineConfig({
10 | test: {
11 | testTimeout: 900000,
12 | hookTimeout: 900000,
13 | reporters: [
14 | 'default',
15 | TestDriver(),
16 | ['junit', { outputFile: 'test-report.junit.xml' }]
17 | ],
18 | setupFiles: ['testdriverai/vitest/setup'],
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/agent/lib/analytics.js:
--------------------------------------------------------------------------------
1 | const { createSDK } = require("./sdk");
2 |
3 | // Factory function that creates analytics with the provided emitter, config, and session
4 | const createAnalytics = (emitter, config, sessionInstance) => {
5 | const sdk = createSDK(emitter, config, sessionInstance);
6 |
7 | return {
8 | track: async (event, data) => {
9 | if (!config["TD_ANALYTICS"]) {
10 | return;
11 | }
12 | if (Math.random() <= 0.01) {
13 | await sdk.req("analytics", { event, data });
14 | }
15 | },
16 | };
17 | };
18 |
19 | module.exports = { createAnalytics };
20 |
--------------------------------------------------------------------------------
/docs/snippets/tests/hover-image-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/hover-image.yaml highlight={9-11}
2 | version: 6.0.0
3 | steps:
4 | - prompt: login
5 | commands:
6 | - command: run
7 | file: testdriver/snippets/login.yaml
8 | - prompt: click on the image of a shopping cart
9 | commands:
10 | - command: hover-image
11 | description: shopping cart icon next to the Cart text in the top right corner
12 | action: click
13 | - prompt: assert that you see an empty shopping cart
14 | commands:
15 | - command: assert
16 | expect: Your cart is empty
17 | ```
18 |
--------------------------------------------------------------------------------
/agent/lib/session.js:
--------------------------------------------------------------------------------
1 | // Factory function to create session instance
2 | function createSession() {
3 | let session = null;
4 |
5 | return {
6 | get: () => {
7 | return session;
8 | },
9 | set: (s) => {
10 | if (s && !session) {
11 | session = s;
12 | }
13 | },
14 | };
15 | }
16 |
17 | // Export both factory function and legacy static instance for backward compatibility
18 | const staticSession = createSession();
19 |
20 | module.exports = {
21 | createSession,
22 | // Legacy static exports for backward compatibility
23 | get: staticSession.get,
24 | set: staticSession.set,
25 | };
26 |
--------------------------------------------------------------------------------
/docs/v6/account/dashboard.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Dashboard"
3 | sidebarTitle: "Dashboard"
4 | description: "Explore your TestDriver dashboard and its features."
5 | icon: "gauge"
6 | ---
7 |
8 | ## Welcome to your TestDriver dashboard
9 |
10 | Your dashboard is your command center for managing your TestDriver experience. Here, you can view your projects, monitor usage, and access various features to enhance your testing workflow.
11 |
12 | ## Key features
13 |
14 | - **Projects**: Get a quick snapshot of your projects, with all Dashcam replays.
15 | - **Team**: Manage your API key, and add/remove team members.
16 | - **Usage & Billing**: Monitor your usage and credits in real-time.
17 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Attach",
9 | "port": 9229,
10 | "request": "attach",
11 | "skipFiles": ["/**"],
12 | "type": "node"
13 | },
14 | {
15 | "type": "node",
16 | "request": "launch",
17 | "name": "Launch Program",
18 | "skipFiles": ["/**"],
19 | "program": "${workspaceFolder}/index.js"
20 | }
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/agent/lib/valid-version.js:
--------------------------------------------------------------------------------
1 | const semver = require("semver");
2 | const packageJson = require("../../package.json");
3 |
4 | // Function to check if the new version's minor version is >= current version's minor version
5 | module.exports = (inputVersion) => {
6 | const currentParsed = semver.parse(packageJson.version);
7 | const inputParsed = semver.parse(inputVersion.replace("v", ""));
8 |
9 | if (!currentParsed || !inputParsed) {
10 | throw new Error("Invalid version format");
11 | }
12 |
13 | // Compare major and minor versions
14 | if (
15 | inputParsed.major === currentParsed.major &&
16 | inputParsed.minor <= currentParsed.minor
17 | ) {
18 | return true;
19 | }
20 | return false;
21 | };
22 |
--------------------------------------------------------------------------------
/test/manual/test-provision-auth.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Quick test to verify provision() authentication works
3 | */
4 |
5 | import { test } from 'vitest';
6 | import { provision } from '../../lib/presets/index.mjs';
7 |
8 | test('provision auth test', async (context) => {
9 | console.log('Starting provision...');
10 |
11 | const { testdriver, dashcam } = await provision('chrome', {
12 | url: 'http://testdriver-sandbox.vercel.app/',
13 | }, context);
14 |
15 | console.log('✅ Provision complete!');
16 | console.log('TestDriver:', testdriver);
17 | console.log('Dashcam:', dashcam);
18 |
19 | // Try a simple find
20 | const result = await testdriver.find('any element');
21 | console.log('Find result:', result);
22 | });
23 |
--------------------------------------------------------------------------------
/agent/lib/outputs.js:
--------------------------------------------------------------------------------
1 | // Factory function to create outputs instance
2 | function createOutputs() {
3 | let outputs = {};
4 |
5 | return {
6 | getAll: () => {
7 | return outputs;
8 | },
9 | get: (key) => {
10 | return outputs[key] || null;
11 | },
12 | set: (key, value) => {
13 | if (key && value) {
14 | outputs[key] = value;
15 | }
16 | },
17 | };
18 | }
19 |
20 | // Export both factory function and legacy static instance for backward compatibility
21 | const staticOutputs = createOutputs();
22 |
23 | module.exports = {
24 | createOutputs,
25 | // Legacy static exports for backward compatibility
26 | getAll: staticOutputs.getAll,
27 | get: staticOutputs.get,
28 | set: staticOutputs.set,
29 | };
30 |
--------------------------------------------------------------------------------
/docs/snippets/tests/type-repeated-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/type-repeated.yaml highlight={11, 12}
2 | version: 6.0.0
3 | steps:
4 | - prompt: enter aabbcc within the username field
5 | commands:
6 | - command: focus-application
7 | name: Google Chrome
8 | - command: hover-text
9 | text: Username
10 | description: input field for username
11 | action: click
12 | - command: type
13 | text: aabbcc
14 | - prompt: assert that aabbcc shows in the username field
15 | commands:
16 | - command: hover-text
17 | text: password
18 | description: input field for password
19 | action: click
20 | - command: assert
21 | expect: the username field contains the text "aabbcc"
22 | ```
23 |
--------------------------------------------------------------------------------
/docs/snippets/tests/hover-text-with-description-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/hover-text-with-description.yaml highlight={9-12}
2 | version: 6.0.0
3 | steps:
4 | - prompt: login
5 | commands:
6 | - command: run
7 | file: testdriver/snippets/login.yaml
8 | - prompt: click on add to cart under the testdriver hat
9 | commands:
10 | - command: hover-text
11 | text: Add to Cart
12 | description: add to cart button under TestDriver Hat
13 | action: click
14 | - prompt: click on the cart
15 | commands:
16 | - command: hover-text
17 | text: Cart
18 | description: cart button in the top right corner
19 | action: click
20 | - prompt: assert the testdriver hat is in the cart
21 | commands:
22 | - command: assert
23 | expect: TestDriver Hat is in the cart
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/v6/scenarios/ai-chatbot.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "AI Chatbots"
3 | sidebarTitle: "AI Chatbots"
4 | description: "Integrate AI chatbots into your testing workflow."
5 | icon: "message-bot"
6 | ---
7 |
8 | import TestPrereqs from "/snippets/test-prereqs.mdx";
9 |
10 | # Testing AI Chatbots with TestDriver
11 |
12 | TestDriver can be used to test AI chatbots, or any two way assertions like:
13 |
14 | - Approval workflows
15 | - Chatbots
16 | - Email or SMS notifications
17 | - And more!
18 |
19 | This scenario is an example of how to set up and run tests for an AI chatbot using TestDriver.
20 |
21 |
22 |
23 | ## Scenario overview
24 |
25 | 1. Load a website with an AI chatbot.
26 | 2. Type a chat message or ask a question.
27 | 3. Wait for the chatbot to respond.
28 | 4. Verify that the chatbot's response is correct and meets the expected criteria.
29 |
--------------------------------------------------------------------------------
/test/manual/test-sandbox-render.js:
--------------------------------------------------------------------------------
1 | const TestDriver = require("./sdk.js");
2 |
3 | async function test() {
4 | console.log("Testing sandbox rendering...");
5 |
6 | const client = new TestDriver(process.env.TD_API_KEY, {
7 | os: process.env.TEST_PLATFORM || "linux",
8 | headless: false, // Should open browser
9 | logging: true,
10 | });
11 |
12 | try {
13 | console.log("Connecting to sandbox...");
14 | const instance = await client.connect();
15 | console.log("Connected to instance:", instance);
16 |
17 | // Wait a bit to see if browser opens
18 | await new Promise((resolve) => setTimeout(resolve, 5000));
19 |
20 | await client.disconnect();
21 | console.log("Test completed successfully");
22 | } catch (error) {
23 | console.error("Test failed:", error);
24 | process.exit(1);
25 | }
26 | }
27 |
28 | test();
29 |
--------------------------------------------------------------------------------
/docs/snippets/tests/remember-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/remember.yaml highlight={5-7, 17, 18}
2 | version: 6.0.0
3 | steps:
4 | - prompt: focus chrome, remember the password, enter the username and the remembered password and login
5 | commands:
6 | - command: remember
7 | description: the password
8 | output: my_password
9 | - command: hover-text
10 | text: Username
11 | description: username input field
12 | action: click
13 | - command: type
14 | text: standard_user
15 | - command: press-keys
16 | keys:
17 | - tab
18 | - command: type
19 | text: ${OUTPUT.my_password}
20 | - command: press-keys
21 | keys:
22 | - tab
23 | - command: press-keys
24 | keys:
25 | - enter
26 | - command: assert
27 | expect: the homepage is visible
28 | ```
29 |
--------------------------------------------------------------------------------
/docs/snippets/tests/scroll-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/scroll.yaml highlight={22-24}
2 | version: 6.0.0
3 | steps:
4 | - prompt: Navigate to https://developer.mozilla.org/en-US/docs/Web/HTML
5 | commands:
6 | - command: focus-application
7 | name: Google Chrome
8 | - command: press-keys
9 | keys:
10 | - ctrl
11 | - l
12 | - command: type
13 | text: https://developer.mozilla.org/en-US/docs/Web/HTML
14 | - command: press-keys
15 | keys:
16 | - enter
17 | - prompt: scroll down with the mouse 1000 pixels
18 | commands:
19 | - command: hover-text
20 | text: "HTML: HyperText Markup Language"
21 | description: main heading of the article
22 | action: click
23 | - command: scroll
24 | direction: down
25 | amount: 1000
26 | - prompt: assert the page is scrolled
27 | commands:
28 | - command: assert
29 | expect: the page is scrolled down
30 | ```
31 |
--------------------------------------------------------------------------------
/docs/v6/apps/mobile-apps.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Mobile Apps"
3 | sidebarTitle: "Mobile Apps"
4 | description: "Test mobile apps with TestDriver"
5 | icon: "mobile"
6 | ---
7 |
8 | # Testing mobile apps with TestDriver
9 |
10 | ## How do I test mobile apps?
11 |
12 | TestDriver provides a powerful solution for testing mobile apps. With our platform, you can easily create and run tests to ensure your mobile app functions as expected across different devices and operating systems.
13 |
14 |
15 | You will need to install a mobile emulator for your deployed tests or use a
16 | device farm to test your mobile app.
17 |
18 |
19 | ## Gettins started
20 |
21 | To get started with testing mobile apps using TestDriver, follow these steps:
22 |
23 | 1. **Follow our [quick start guide](/overview/quickstart) to set up your TestDriver account.**
24 | 2. **Create a new test project** in your TestDriver dashboard.
25 | 3. **Add your mobile emulator to the test settings.**
26 | 4. **Add your mobile app URL** to the test settings.
27 |
--------------------------------------------------------------------------------
/docs/v6/scenarios/pdf-generation.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "PDF Generation"
3 | sidebarTitle: "PDF Generation"
4 | description: "Test PDF generation functionality with TestDriver"
5 | icon: "file-pdf"
6 | ---
7 |
8 | import TestPrereqs from "/snippets/test-prereqs.mdx";
9 |
10 | # Testing PDF Generation with TestDriver
11 |
12 | Test PDF generation functionality with TestDriver. This scenario demonstrates how to automate testing the PDF generation process for a web application using TestDriver's capabilities.
13 |
14 |
15 |
16 | ## Scenario overview
17 |
18 | 1. Open a webpage or desktop app that will allow you to generate a PDF.
19 | 2. Trigger the PDF generation process (for example, by clicking a button).
20 | 3. Verify that the PDF is generated successfully and contains the expected content.
21 | 4. Optionally, check the file size and format of the generated PDF.
22 |
23 | ## What's next
24 |
25 | From here you can automate and add coverage to your PDF generation process, removing manual testing from the equation.
26 |
--------------------------------------------------------------------------------
/docs/snippets/tests/exec-js-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/exec-js.yaml highlight={6-17}
2 | version: 6.0.0
3 | steps:
4 | - prompt: fetch user data from API
5 | commands:
6 | - command: exec
7 | output: user
8 | lang: js
9 | code: |
10 | const response = await
11 | fetch('https://jsonplaceholder.typicode.com/users');
12 |
13 | const user = await response.json();
14 |
15 | console.log('user', user[0]);
16 |
17 | result = user[0].email;
18 | - command: hover-text
19 | text: Username
20 | description: input field for username
21 | action: click
22 | - command: type
23 | text: ${OUTPUT.user}
24 | - prompt: assert that the username field shows a valid email address
25 | commands:
26 | - command: focus-application
27 | name: Google Chrome
28 | - command: assert
29 | expect: >-
30 | the username field contains "Sincere@april.biz" which is a valid email
31 | address
32 | ```
33 |
--------------------------------------------------------------------------------
/test/testdriver/element-not-found.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Element Not Found Test
3 | * Tests that finding a non-existent element returns properly without timing out
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe("Element Not Found Test", () => {
10 | it("should handle non-existent element gracefully without timing out", async (context) => {
11 | const testdriver = TestDriver(context, { headless: true });
12 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13 |
14 | //
15 |
16 | // Try to find an element that definitely doesn't exist
17 | const element = await testdriver.find(
18 | "a purple unicorn dancing on the moon",
19 | );
20 |
21 | // Should return an element that is not found
22 | expect(element.found()).toBe(false);
23 | expect(element.coordinates).toBeNull();
24 | }); // 90 second timeout for the test (should complete much faster)
25 | });
26 |
27 |
--------------------------------------------------------------------------------
/docs/snippets/tests/press-keys-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/press-keys.yaml highlight={11-14, 17-21, 24-27}
2 | version: 6.0.0
3 | steps:
4 | - prompt: create a new tab and inspect elements
5 | commands:
6 | - command: focus-application
7 | name: Google Chrome
8 | - command: hover-text
9 | text: Sign In
10 | description: black button below the password field
11 | action: click
12 | - command: press-keys
13 | keys:
14 | - ctrl
15 | - t
16 | - command: wait-for-text
17 | text: "Learn more"
18 | - command: press-keys
19 | keys:
20 | - ctrl
21 | - shift
22 | - i
23 | - command: wait-for-text
24 | text: "Elements"
25 | - command: press-keys
26 | keys:
27 | - ctrl
28 | - t
29 | - command: type
30 | text: google.com
31 | - command: press-keys
32 | keys:
33 | - enter
34 | - command: assert
35 | expect: google appears
36 | ```
37 |
--------------------------------------------------------------------------------
/docs/snippets/tests/type-yaml.mdx:
--------------------------------------------------------------------------------
1 | ```yaml testdriver/type.yaml highlight={11, 12}
2 | version: 6.0.0
3 | steps:
4 | - prompt: enter standard_user within the username field
5 | commands:
6 | - command: focus-application
7 | name: Google Chrome
8 | - command: hover-text
9 | text: Username
10 | description: input field for username
11 | action: click
12 | - command: type
13 | text: standard_user
14 | - prompt: assert that standard_user shows in the username field
15 | commands:
16 | - command: assert
17 | expect: the username field contains "standard_user"
18 | - prompt: click on sign in
19 | commands:
20 | - command: hover-text
21 | text: Sign in
22 | description: black button below the password field
23 | action: click
24 | - prompt: assert that "please fill out this field" shows in the password field
25 | commands:
26 | - command: assert
27 | expect: Please fill out this field is visible near the password field
28 | ```
29 |
--------------------------------------------------------------------------------
/test/testdriver/formatted-logging.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Formatted Logging Demo
3 | * Demonstrates nice Vitest-style formatted logs for Dashcam replay
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe("Formatted Logging Test", () => {
10 | it("should demonstrate formatted logs in dashcam replay", async (context) => {
11 | const testdriver = TestDriver(context, { headless: true });
12 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13 |
14 | // Find and click - logs will be nicely formatted
15 | const signInButton = await testdriver.find(
16 | "Sign In, black button below the password field",
17 | );
18 | await signInButton.click();
19 |
20 | // Assert - logs will show pass/fail with nice formatting
21 | const result = await testdriver.assert(
22 | "an error shows that fields are required",
23 | );
24 | expect(result).toBeTruthy();
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/docs/v6/scenarios/spell-check.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Spell Check"
3 | sidebarTitle: "Spell Check"
4 | description: "Test spell check functionality with TestDriver"
5 | icon: "spell-check"
6 | ---
7 |
8 | import TestPrereqs from "/snippets/test-prereqs.mdx";
9 |
10 | # Testing Spell Check with TestDriver
11 |
12 | This scenario demonstrates how to automate testing the spell check functionality of a web application using TestDriver's capabilities. The test will check if the spell checker correctly identifies and suggests corrections for misspelled words in a text input field.
13 |
14 |
15 |
16 | ## Scenario overview
17 |
18 | 1. Open up a webpage that has text input and a spell check function.
19 | 2. Enter a misspelled word into the text input field.
20 | 3. Trigger the spell check function (for example, by clicking a button or losing focus on the input field).
21 | 4. Verify that the spell checker identifies the misspelled word and provides suggestions for correction.
22 | 5. Optionally, select a suggestion and apply it to the text input field.
23 |
--------------------------------------------------------------------------------
/test/testdriver/hover-text.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Hover Text Test (Vitest)
3 | * Converted from: testdriver/acceptance/hover-text.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe("Hover Text Test", () => {
10 | it("should click Sign In and verify error message", async (context) => {
11 | const testdriver = TestDriver(context, { headless: true, newSandbox: true, cacheKey: 'hover-text-test' });
12 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13 |
14 | // Click on Sign In button using new find() API
15 |
16 | const signInButton = await testdriver.find(
17 | "Sign In, black button below the password field",
18 | );
19 | await signInButton.click();
20 |
21 | // Assert that an error shows that fields are required
22 | const result = await testdriver.assert(
23 | "an error shows that fields are required",
24 | );
25 | expect(result).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/docs/images/content/extension/windsurf.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/v6/guide/environment-variables.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Environment Variables"
3 | sidebarTitle: "Environment Variables"
4 | description: "Learn how which environment variables are supported by TestDriver."
5 | icon: "sliders-up"
6 | mode: "wide"
7 | ---
8 |
9 | import GitignoreWarning from "/snippets/gitignore-warning.mdx";
10 |
11 | The supported environment variables in TestDriver are:
12 |
13 |
14 | | Variable | Type | Description |
15 | |:---------------:|:------------:|:-------------------------------------------------------------------------------:|
16 | | `TD_ANALYTICS` | `boolean` | Send analytics to TestDriver servers. This helps provide feedback to inform our roadmap. |
17 | | `TD_API_KEY` | `string` | Set this to spawn VMs with TestDriver Pro. |
18 |
19 |
20 | ## Example
21 |
22 | ```bash .env
23 | TD_API_KEY=your_api_key
24 | ```
25 |
26 | In this example `.env` file, we're running a website test in a local Linux VM with a resolution of 1024x768. The terminal will be minimized, and the overlay won't be shown. Analytics will be sent to TestDriver servers.
27 |
--------------------------------------------------------------------------------
/test/testdriver/setup/vitestSetup.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Vitest Setup File
3 | * Runs once before all tests in each worker process
4 | * This ensures the TestDriver plugin global state is available in test processes
5 | */
6 |
7 | // Import the plugin functions
8 | import {
9 | authenticateWithApiKey,
10 | clearDashcamUrls,
11 | clearSuiteTestRun,
12 | createTestRunDirect,
13 | getDashcamUrl,
14 | getPluginState,
15 | getSuiteTestRun,
16 | pluginState,
17 | recordTestCaseDirect,
18 | registerDashcamUrl,
19 | setSuiteTestRun,
20 | } from "../../../interfaces/vitest-plugin.mjs";
21 |
22 | // Make the plugin API available globally in the test worker process
23 | if (typeof globalThis !== "undefined") {
24 | globalThis.__testdriverPlugin = {
25 | registerDashcamUrl,
26 | getDashcamUrl,
27 | clearDashcamUrls,
28 | authenticateWithApiKey,
29 | createTestRunDirect,
30 | recordTestCaseDirect,
31 | getSuiteTestRun,
32 | setSuiteTestRun,
33 | clearSuiteTestRun,
34 | getPluginState,
35 | state: pluginState,
36 | };
37 | console.log(
38 | "[Vitest Setup] TestDriver plugin API initialized in worker process",
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/docs/v6/scenarios/cookie-banner.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Cookie Banner"
3 | sidebarTitle: "Cookie Banner"
4 | description: "Test cookie banners with TestDriver"
5 | icon: "cookie"
6 | ---
7 |
8 | import TestPrereqs from "/snippets/test-prereqs.mdx";
9 |
10 |
19 |
20 | ---
21 |
22 | # Testing Cookie Banners with TestDriver
23 |
24 | With TestDriver, you can automate the testing of cookie banners on your web application. This scenario demonstrates how to check if a cookie banner appears when a user visits the site and how to interact with it.
25 |
26 |
27 |
28 | ## Scenario overview
29 |
30 | 1. **Visit the Site**: The test will navigate to the target URL.
31 | 2. **Check for Cookie Banner**: It will verify if the cookie banner is displayed.
32 | 3. **Decline Cookies**: The test will simulate a user clicking the "Decline All" button on the cookie banner.
33 |
--------------------------------------------------------------------------------
/docs/v6/account/pricing.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Pricing"
3 | sidebarTitle: "Pricing"
4 | description: "Explore TestDriver's pricing plans and features."
5 | icon: "dollar-sign"
6 | ---
7 |
8 | ## TestDriver pricing plans
9 |
10 | TestDriver offers a range of pricing plans to suit different needs, from individual developers to large enterprises. Our plans are designed to provide flexibility and scalability, ensuring you can choose the right option for your testing requirements.
11 |
12 |
13 |
14 | Local agent testing. Always free.
15 |
16 |
17 | Use local or hosted runners on demand or integrated with CI for as low as
18 | $0.05/minute.
19 |
20 |
21 | Need advanced features? Contact us for tailored solutions. Starting at
22 | $2,000/month.
23 |
24 |
25 |
26 |
27 | Every plan comes with access to the Playwright SDK to get you off the starting
28 | line!
29 |
30 |
31 | ## Compare plans
32 |
33 | To view the latest pricing and features, please visit our [Pricing Page](https://testdriver.ai/pricing).
34 |
--------------------------------------------------------------------------------
/docs/v6/scenarios/file-upload.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "File Upload"
3 | sidebarTitle: "File Upload"
4 | description: "Test file upload functionality with TestDriver"
5 | icon: "upload"
6 | ---
7 |
8 | import TestPrereqs from "/snippets/test-prereqs.mdx";
9 |
10 |
19 |
20 | ---
21 |
22 | ## Testing file upload functionality with TestDriver
23 |
24 | This scenario demonstrates how to automate testing the file upload functionality of a web application using TestDriver's capabilities. The test will check if the file upload process works correctly and if the uploaded file is processed as expected.
25 |
26 |
27 |
28 | ## Scenario overview
29 |
30 | 1. Load a website with a file upload feature.
31 | 2. Generate a file that will be used for the upload.
32 | 3. Use TestDriver to perform the file upload process.
33 | 4. Check if the uploaded file is processed correctly by the application.
34 |
--------------------------------------------------------------------------------
/docs/v6/scenarios/form-filling.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Form Filling"
3 | sidebarTitle: "Form Filling"
4 | description: "Test form filling functionality with TestDriver"
5 | icon: "list"
6 | ---
7 |
8 | import TestPrereqs from "/snippets/test-prereqs.mdx";
9 |
10 |
19 |
20 | ---
21 |
22 | # Testing Form Filling with TestDriver
23 |
24 | TestDriver provides a powerful solution for testing form filling functionality on web applications. This scenario demonstrates how to automate the process of filling out forms, such as login or registration forms, using TestDriver's capabilities.
25 |
26 |
27 |
28 | ## Scenario overview
29 |
30 | 1. Open a webpage containing a form (Log in, Registration, etc.).
31 | 2. Use TestDriver to fill in the form fields with test data.
32 | 3. Submit the form and verify the expected outcome (for example, successful login, registration confirmation, redirect etc.).
33 |
--------------------------------------------------------------------------------
/docs/v6/tutorials/basic-test.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Tutorial: Basic Test"
3 | description: "Learn how to create and run a basic test using TestDriver."
4 | sidebarTitle: "Basic Test"
5 | icon: "test-tube"
6 | ---
7 |
8 | # Basic test tutorial
9 |
10 | This tutorial will guide you through the process of creating and running a basic test using TestDriver.
11 |
12 | ## Prerequisites
13 |
14 | Before you begin, ensure you have the following:
15 |
16 | - TestDriver installed on your system.
17 | - Basic understanding of how TestDriver works.
18 |
19 | ## Step 1: Create a test
20 |
21 | To create a test, follow these steps:
22 |
23 | 1. Open your code editor.
24 | 2. Write the test script using TestDriver's syntax.
25 | 3. Save the test script with a `.yaml` extension.
26 |
27 | Example:
28 |
29 | ```yaml
30 |
31 | ```
32 |
33 | ## Step 2: Run the test
34 |
35 | To run the test, use the following command:
36 |
37 | ```bash
38 | npx testdriverai@latest run example-test.yaml
39 | ```
40 |
41 | This will execute the test and display the results in the console.
42 |
43 | ## Conclusion
44 |
45 | You have successfully created and run a basic test using TestDriver. Explore more advanced features to enhance your testing capabilities.
46 |
--------------------------------------------------------------------------------
/lib/vitest/setup.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Vitest Setup File for TestDriver
3 | *
4 | * This file is run by Vitest before each test file to set up
5 | * the TestDriver plugin state and global helpers.
6 | *
7 | * Usage in vitest.config.mjs:
8 | * ```js
9 | * export default defineConfig({
10 | * test: {
11 | * setupFiles: ['testdriverai/vitest/setup'],
12 | * },
13 | * });
14 | * ```
15 | */
16 |
17 | import {
18 | clearDashcamUrls,
19 | clearSuiteTestRun,
20 | getDashcamUrl,
21 | getPluginState,
22 | getSuiteTestRun,
23 | pluginState,
24 | registerDashcamUrl,
25 | setSuiteTestRun,
26 | } from '../../interfaces/vitest-plugin.mjs';
27 |
28 | // Set up global TestDriver plugin interface
29 | // This allows tests and the SDK to communicate with the reporter
30 | globalThis.__testdriverPlugin = {
31 | state: pluginState,
32 | registerDashcamUrl,
33 | getDashcamUrl,
34 | clearDashcamUrls,
35 | getPluginState,
36 | getSuiteTestRun,
37 | setSuiteTestRun,
38 | clearSuiteTestRun,
39 | };
40 |
41 | // Log that setup is complete (only in debug mode)
42 | if (process.env.TD_LOG_LEVEL?.toLowerCase() === 'debug') {
43 | console.log('[TestDriver] Setup file initialized, global plugin interface available');
44 | }
45 |
--------------------------------------------------------------------------------
/test/testdriver/hover-image.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Hover Image Test (Vitest)
3 | * Converted from: testdriver/acceptance/hover-image.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 | import { performLogin } from "./setup/testHelpers.mjs";
9 |
10 | describe("Hover Image Test", () => {
11 | it("should click on shopping cart icon and verify empty cart", async (context) => {
12 | const testdriver = TestDriver(context, { headless: true });
13 |
14 | // provision.chrome() automatically calls ready() and starts dashcam
15 | await testdriver.provision.chrome({
16 | url: 'http://testdriver-sandbox.vercel.app/login',
17 | });
18 |
19 | // Perform login first
20 | await performLogin(testdriver);
21 |
22 | // Click on the shopping cart icon
23 | await testdriver.focusApplication("Google Chrome");
24 | const cartIcon = await testdriver.find(
25 | "shopping cart icon next to the Cart text in the top right corner",
26 | );
27 |
28 | await cartIcon.click();
29 |
30 | // Assert that you see an empty shopping cart
31 | const result = await testdriver.assert("Your cart is empty");
32 | expect(result).toBeTruthy();
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/test/testdriver/focus-window.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Focus Window Test (Vitest)
3 | * Converted from: testdriver/acceptance/focus-window.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe("Focus Window Test", () => {
10 | it.skip(
11 | "should click Microsoft Edge icon and focus Google Chrome",
12 | async (context) => {
13 | const testdriver = TestDriver(context, { headless: true });
14 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
15 |
16 | //
17 | // Show desktop
18 | await testdriver.pressKeys(["winleft", "d"]);
19 |
20 | // Click on the Microsoft Edge icon
21 | const edgeIcon = await testdriver.find(
22 | "a blue and green swirl icon on the taskbar representing Microsoft Edge",
23 | );
24 | await edgeIcon.click();
25 |
26 | // Focus Google Chrome
27 | await testdriver.focusApplication("Google Chrome");
28 |
29 | // Assert Chrome is focused (implicit through successful focus)
30 | const result = await testdriver.assert(
31 | "Google Chrome is the focused application",
32 | );
33 | expect(result).toBeTruthy();
34 | },
35 | );
36 | });
37 |
--------------------------------------------------------------------------------
/test/testdriver/prompt.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Prompt Test (Vitest)
3 | * Converted from: testdriver/acceptance/prompt.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe.skip("Prompt Test", () => {
10 | it("should execute AI-driven prompts", async (context) => {
11 | const testdriver = TestDriver(context, { headless: true });
12 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13 |
14 | //
15 | // Note: The SDK doesn't have a direct equivalent to YAML prompts without commands.
16 | // This would typically be handled by the AI agent interpreting natural language.
17 | // For SDK usage, you need to use explicit commands.
18 |
19 | // Original prompts were:
20 | // 1. "log in"
21 | // 2. "add an item to the cart"
22 | // 3. "click on the cart icon"
23 | // 4. "complete checkout"
24 |
25 | // This test is skipped as it requires explicit SDK implementation
26 | // You would need to implement these as explicit SDK calls
27 |
28 | await testdriver.act("log in");
29 |
30 | const result = await testdriver.assert("the testdriver sandbox is visible");
31 | expect(result).toBeTruthy();
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yaml:
--------------------------------------------------------------------------------
1 | name: Publish Beta
2 | permissions:
3 | contents: write
4 | on:
5 | push:
6 | branches: [ main ]
7 |
8 | jobs:
9 | publish-beta:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v4
14 | with:
15 | fetch-depth: 0
16 | token: ${{ secrets.GITHUB_TOKEN }}
17 |
18 | - name: Setup Node.js
19 | uses: actions/setup-node@v4
20 | with:
21 | node-version: '20'
22 | registry-url: 'https://registry.npmjs.org/'
23 |
24 | - name: Configure Git
25 | run: |
26 | git config user.name "github-actions[bot]"
27 | git config user.email "github-actions[bot]@users.noreply.github.com"
28 |
29 | - name: Install dependencies
30 | run: npm ci
31 |
32 | - name: Bump version (prerelease beta)
33 | run: npm version prerelease --preid=beta --no-git-tag-version
34 |
35 | - name: Commit and push version bump
36 | run: |
37 | git add package.json package-lock.json
38 | git commit -m "chore: bump beta version to $(node -p "require('./package.json').version")"
39 | git push
40 |
41 | - name: Publish to npm under beta tag
42 | run: npm publish --tag beta
43 | env:
44 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
45 |
--------------------------------------------------------------------------------
/docs/styles.css:
--------------------------------------------------------------------------------
1 | .comparison-table th {
2 | font-size: 18px;
3 | padding: 12px 30px;
4 | }
5 |
6 | .comparison-table td {
7 | padding: 12px 30px;
8 | font-size: 16px;
9 | line-height: 1.2;
10 | text-align: start;
11 | }
12 |
13 | .comparison-table code {
14 | font-size: 1em;
15 | }
16 |
17 | .env-vars-table table {
18 | width: 120%;
19 | }
20 |
21 | .env-vars-table th {
22 | font-size: 18px;
23 | padding: 12px 30px;
24 | border: 1.5px solid #05f1d9;
25 | }
26 |
27 | .env-vars-table td {
28 | padding: 12px 30px;
29 | font-size: 16px;
30 | line-height: 1.5;
31 | border: 1.5px solid #05f1d9;
32 | vertical-align: middle;
33 | }
34 |
35 | .env-vars-table code {
36 | font-size: 0.9em;
37 | }
38 |
39 | .prose
40 | :where(a code):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
41 | border-bottom: none !important;
42 | padding-bottom: 0 !important;
43 | text-decoration: none !important;
44 | }
45 |
46 | .card img {
47 | background-color: transparent !important;
48 | }
49 |
50 | .replay-block {
51 | width: 100vw;
52 | margin-left: 50%;
53 | transform: translateX(-50%);
54 | justify-content: center;
55 | display: flex;
56 | }
57 |
58 | img[src$=".svg"] {
59 | background: transparent !important;
60 | }
61 |
62 | img[src$="https://tauri.app/favicon.svg"]
63 | {
64 | opacity: 0.3;
65 | }
66 |
--------------------------------------------------------------------------------
/docs/v6/account/projects.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "TestDriver Projects"
3 | sidebarTitle: "Projects"
4 | description: "Manage your projects and workflows with TestDriver."
5 | icon: "folder"
6 | ---
7 |
8 | ## Dashboard project view
9 |
10 | In the Project tab, select a Project to see the replays for that project.
11 |
12 |
13 |
14 |
15 |
16 | From the Project view, you can see all the replays (Dashes) stored for that project. Click one to see it. These will also be linked in CI for any pull requests or branches that have been tested with TestDriver.
17 |
18 |
19 |
20 |
21 |
22 | ## New project
23 |
24 | When you create a new Project, you can also enable the Jira integration to create issues automatically each time a replay (Dash) is created.
25 |
26 |
27 | The project ID can be used in conjunction with your `lifecycle/postrun.yaml`
28 | script to automatically assign a replay to a project. For more info see the
29 | (Dashcam section)[/guide/dashcam].
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/test/testdriver/hover-text-with-description.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Hover Text With Description Test (Vitest)
3 | * Converted from: testdriver/acceptance/hover-text-with-description.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 | import { performLogin } from "./setup/testHelpers.mjs";
9 |
10 | describe("Hover Text With Description Test", () => {
11 | it("should add TestDriver Hat to cart and verify", async (context) => {
12 | const testdriver = TestDriver(context, { headless: true });
13 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
14 |
15 | //
16 | // Perform login first
17 | await performLogin(testdriver);
18 |
19 | // Click on "Add to Cart" under TestDriver Hat
20 | const addToCartButton = await testdriver.find(
21 | "Add to Cart, add to cart button under TestDriver Hat",
22 | );
23 | await addToCartButton.click();
24 |
25 | // Click on the cart
26 | const cartButton = await testdriver.find(
27 | "Cart, cart button in the top right corner",
28 | );
29 | await cartButton.click();
30 |
31 | // Assert the TestDriver Hat is in the cart
32 | const result = await testdriver.assert("TestDriver Hat is in the cart");
33 | expect(result).toBeTruthy();
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/test/manual/test-init.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Test script for the init command
4 | # Usage: ./test-init.sh
5 |
6 | set -e
7 |
8 | echo "🧪 Testing testdriverai init command..."
9 | echo ""
10 |
11 | # Cleanup function
12 | cleanup() {
13 | echo "🧹 Cleaning up test directories..."
14 | rm -rf /tmp/testdriver-test-*
15 | }
16 |
17 | trap cleanup EXIT
18 |
19 | # Test 1: Full init with all features
20 | echo "1️⃣ Testing full initialization (package.json, tests, workflow, npm install)..."
21 | cd /tmp
22 | mkdir -p testdriver-test-init
23 | cd testdriver-test-init
24 | node /Users/ianjennings/Development/cli/bin/testdriverai.js init
25 | echo ""
26 | echo "✅ Files created:"
27 | find . -type f -not -path "./node_modules/*" | sort
28 | echo ""
29 | echo "📄 package.json:"
30 | cat package.json
31 | echo ""
32 | echo "📄 vitest.config.js:"
33 | cat vitest.config.js
34 | echo ""
35 | echo "📄 GitHub workflow:"
36 | cat .github/workflows/testdriver.yml
37 | echo ""
38 | echo "📄 First 15 lines of tests/example.test.js:"
39 | head -15 tests/example.test.js
40 | echo ""
41 | echo "📦 Installed dependencies:"
42 | npm list --depth=0
43 | echo ""
44 | echo "---"
45 | echo ""
46 |
47 | # Test 2: Help command
48 | echo "2️⃣ Testing help command..."
49 | node /Users/ianjennings/Development/cli/bin/testdriverai.js init --help
50 | echo ""
51 | echo "---"
52 | echo ""
53 |
54 | echo "✅ All tests passed!"
55 |
--------------------------------------------------------------------------------
/test/testdriver/match-image.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Match Image Test (Vitest)
3 | * Converted from: testdriver/acceptance/match-image.yaml
4 | */
5 |
6 | import path, { dirname } from "path";
7 | import { fileURLToPath } from "url";
8 | import { describe, expect, it } from "vitest";
9 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
10 | import { performLogin } from "./setup/testHelpers.mjs";
11 |
12 | // Get the directory of the current module
13 | const __filename = fileURLToPath(import.meta.url);
14 | const __dirname = dirname(__filename);
15 |
16 | describe("Match Image Test", () => {
17 | it.skip("should match shopping cart image and verify empty cart", async (context) => {
18 | const testdriver = TestDriver(context, { headless: true });
19 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
20 |
21 | //
22 | // Perform login first
23 | await performLogin(testdriver);
24 |
25 | // Match and click the shopping cart icon
26 | const cartImagePath = path.resolve(
27 | __dirname,
28 | "../../_testdriver/acceptance/screenshots/cart.png",
29 | );
30 | await testdriver.matchImage(cartImagePath, "click");
31 |
32 | // Assert that you see an empty shopping cart
33 | const result = await testdriver.assert("Your cart is empty");
34 | expect(result).toBeTruthy();
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/agent/lib/debugger.js:
--------------------------------------------------------------------------------
1 | const { eventsArray } = require("../events.js");
2 | const { startDebugger, broadcastEvent } = require("./debugger-server.js");
3 |
4 | module.exports.createDebuggerProcess = (config = {}, emitter) => {
5 | // Start the web server-based debugger instead of Electron
6 | return startDebugger(config, emitter)
7 | .then(({ url }) => {
8 | // Return a mock process object to maintain compatibility
9 | return {
10 | pid: process.pid,
11 | url: url, // Include the debugger URL
12 | kill: () => {
13 | const { stopDebugger } = require("./debugger-server.js");
14 | stopDebugger();
15 | },
16 | on: (event, callback) => {
17 | // Mock process events
18 | if (event === "exit") {
19 | process.on("exit", callback);
20 | }
21 | },
22 | };
23 | })
24 | .catch((error) => {
25 | throw error;
26 | });
27 | };
28 |
29 | module.exports.connectToDebugger = (emitter) => {
30 | return new Promise((resolve, reject) => {
31 | // Set up event broadcasting instead of IPC
32 | try {
33 | eventsArray.forEach((event) => {
34 | emitter.on(event, (data) => {
35 | broadcastEvent(event, data);
36 | });
37 | });
38 |
39 | // Resolve immediately since we don't need to wait for IPC connection
40 | resolve();
41 | } catch (error) {
42 | reject(error);
43 | }
44 | });
45 | };
46 |
--------------------------------------------------------------------------------
/test/testdriver/scroll-until-image.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Scroll Until Image Test (Vitest)
3 | * Converted from: testdriver/acceptance/scroll-until-image.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe("Scroll Until Image Test", () => {
10 | it.skip("should scroll until brown colored house image appears", async (context) => {
11 | const testdriver = TestDriver(context, { headless: true });
12 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13 |
14 | //
15 | // Navigate to Wikipedia page
16 | await testdriver.pressKeys(["ctrl", "l"]);
17 | await testdriver.type("https://en.wikipedia.org/wiki/Leonardo_da_Vinci");
18 | await testdriver.pressKeys(["enter"]);
19 |
20 | // sleep for 5 seconds
21 | await new Promise((r) => setTimeout(r, 5000));
22 |
23 | // Click on heading
24 | const heading = await testdriver.find(
25 | "Leonardo Da Vinci, the page heading",
26 | 0,
27 | );
28 | await heading.click();
29 |
30 | // Scroll until image appears
31 | await testdriver.scrollUntilImage("a brown colored house", "down", 10000);
32 |
33 | // Assert image of brown colored house appears on screen
34 | const result = await testdriver.assert(
35 | "image of brown colored house appears on screen",
36 | );
37 | expect(result).toBeTruthy();
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/docs/v6/commands/type.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "type"
3 | sidebarTitle: "type"
4 | description: "Simulate typing a string using keyboard emulation."
5 | icon: "typewriter"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/type-replay.mdx";
10 | import Example from "/snippets/tests/type-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `type` command is used to simulate typing a string using keyboard emulation. This is useful for entering text into input fields or performing text-based interactions.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :------: | :------: | :----------------------- |
23 | | `text` | `string` | The text string to type. |
24 |
25 | ## Example usage
26 |
27 | ```yaml
28 | command: type
29 | text: Hello World
30 | ```
31 |
32 | ## Protips
33 |
34 | - Use this command to input text into fields, such as search bars or forms.
35 | - Ensure the target application or input field is in focus before using the `type` command to avoid unexpected behavior.
36 | - It can also be used to insert values from variables, refer our [variable's guide](/guide/variables) for more information.
37 |
38 | ## Gotchas
39 |
40 | - If the input field isn't active or in focus, the text may not be typed correctly.
41 | - Special characters in the `text` string may require additional handling depending on the application.
42 |
43 | ---
44 |
45 | The `type` command is ideal for automating text input in tests.
46 |
--------------------------------------------------------------------------------
/docs/v7/commands/type.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "type"
3 | sidebarTitle: "type"
4 | description: "Simulate typing a string using keyboard emulation."
5 | icon: "typewriter"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/type-replay.mdx";
10 | import Example from "/snippets/tests/type-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `type` command is used to simulate typing a string using keyboard emulation. This is useful for entering text into input fields or performing text-based interactions.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :------: | :------: | :----------------------- |
23 | | `text` | `string` | The text string to type. |
24 |
25 | ## Example usage
26 |
27 | ```yaml
28 | command: type
29 | text: Hello World
30 | ```
31 |
32 | ## Protips
33 |
34 | - Use this command to input text into fields, such as search bars or forms.
35 | - Ensure the target application or input field is in focus before using the `type` command to avoid unexpected behavior.
36 | - It can also be used to insert values from variables, refer our [variable's guide](/guide/variables) for more information.
37 |
38 | ## Gotchas
39 |
40 | - If the input field isn't active or in focus, the text may not be typed correctly.
41 | - Special characters in the `text` string may require additional handling depending on the application.
42 |
43 | ---
44 |
45 | The `type` command is ideal for automating text input in tests.
46 |
--------------------------------------------------------------------------------
/docs/v6/getting-started/running.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Running Tests with TestDriver"
3 | sidebarTitle: "Running Tests"
4 | description: "Execute regression test files for debugging and validation."
5 | icon: "play"
6 | ---
7 |
8 | Run tests using previously created test scripts. This command replays the steps defined in the YAML file, performing each action sequentially.
9 |
10 | You can run a test script by specifying the file path in the terminal or interactively within the TestDriver CLI.
11 |
12 | ### Example in terminal
13 |
14 | ```bash
15 | npx testdriverai@latest run path/to/test.yaml
16 | ```
17 |
18 | This will execute the test script located at `path/to/test.yaml`. If the test passes, the command will exit with a status code of `0`. If it fails, the command will exit with a status code of `1`.
19 |
20 |
21 | The path to the test file provided should be relative to the current working
22 | directory.
23 |
24 |
25 | ### Interactive mode
26 |
27 | To run a test interactively:
28 |
29 | 1. Launch the TestDriver CLI:
30 |
31 | ```bash
32 | npx testdriverai@latest edit
33 | ```
34 |
35 | 2. At the `>` prompt, type:
36 |
37 | ```bash
38 | > /run path/to/test.yaml
39 | ```
40 |
41 | ## Notes
42 |
43 | - Ensure the YAML file is valid and follows the TestDriver specification.
44 | - The default file path is `testdriver/testdriver.yaml` if no file is specified.
45 |
46 |
47 | Run tests with Hosted Runnings in GitHub Actions
48 |
49 |
--------------------------------------------------------------------------------
/test/testdriver/scroll-keyboard.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Scroll Keyboard Test (Vitest)
3 | * Converted from: testdriver/acceptance/scroll-keyboard.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe("Scroll Keyboard Test", () => {
10 | it("should navigate to webhamster.com and scroll with keyboard", async (context) => {
11 | const testdriver = TestDriver(context, { headless: true });
12 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13 |
14 | //
15 | // Navigate to https://www.webhamster.com/
16 | await testdriver.focusApplication("Google Chrome");
17 | const urlBar = await testdriver.find(
18 | "testdriver-sandbox.vercel.app/login, the URL in the omnibox showing the current page",
19 | );
20 | await urlBar.click();
21 | await testdriver.pressKeys(["ctrl", "a"]);
22 | await testdriver.type("https://www.webhamster.com/");
23 | await testdriver.pressKeys(["enter"]);
24 |
25 | // Scroll down with keyboard 1000 pixels
26 | const heading = await testdriver.find(
27 | "The Hamster Dance, large heading at top of page",
28 | );
29 | await heading.click();
30 | await testdriver.scroll("down", { amount: 1000 });
31 |
32 | // Assert the page is scrolled down
33 | const result = await testdriver.assert(
34 | "the hamster dance heading is not visible",
35 | );
36 | expect(result).toBeTruthy();
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/test/testdriver/assert.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Assert Test (Vitest)
3 | * Converted from: testdriver/acceptance/assert.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe("Assert Test", () => {
10 | it("should assert the testdriver login page shows", async (context) => {
11 | const testdriver = TestDriver(context, { newSandbox: true });
12 |
13 | // provision.chrome() automatically calls ready() and starts dashcam
14 | await testdriver.provision.chrome({
15 | url: 'http://testdriver-sandbox.vercel.app/login',
16 | });
17 |
18 | // Assert the TestDriver.ai Sandbox login page is displayed
19 | const result = await testdriver.assert(
20 | "the TestDriver.ai Sandbox login page is displayed",
21 | );
22 |
23 | expect(result).toBeTruthy();
24 | });
25 | // it("should assert the testdriver login page shows 2", async (context) => {
26 | // const testdriver = TestDriver(context, { newSandbox: true });
27 |
28 | // // provision.chrome() automatically calls ready() and starts dashcam
29 | // await testdriver.provision.chrome({
30 | // url: 'http://testdriver-sandbox.vercel.app/login',
31 | // });
32 |
33 | // // Assert the TestDriver.ai Sandbox login page is displayed
34 | // const result = await testdriver.assert(
35 | // "the TestDriver.ai Sandbox login page is displayed",
36 | // );
37 |
38 | // expect(result).toBeTruthy();
39 | // });
40 | });
41 |
42 |
--------------------------------------------------------------------------------
/test/manual/test-console-logs.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Console Log Test
3 | * Tests that console.log statements are captured and sent to dashcam
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe("Console Log Test", () => {
10 | it("should capture console logs and send them to dashcam", async (context) => {
11 | console.log("🎬 Test starting - this should appear in dashcam");
12 |
13 | const testdriver = TestDriver(context, { headless: true });
14 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
15 |
16 | console.log("✅ Chrome launched successfully");
17 |
18 | // Give Chrome a moment to fully render the UI
19 | await new Promise(resolve => setTimeout(resolve, 2000));
20 |
21 | console.log("🔍 Looking for Sign In button");
22 |
23 | // Find and click the sign in button
24 | const signInButton = await testdriver.find(
25 | "Sign In, black button below the password field",
26 | );
27 |
28 | console.log("👆 Clicking Sign In button");
29 | await signInButton.click();
30 |
31 | console.log("🧪 Asserting error message appears");
32 |
33 | // Assert error shows
34 | const result = await testdriver.assert(
35 | "an error shows that fields are required",
36 | );
37 |
38 | console.log("✅ Test completed successfully - all logs should be in dashcam");
39 |
40 | expect(result).toBeTruthy();
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/docs/v6/commands/wait.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "wait"
3 | sidebarTitle: "wait"
4 | description: "Pause the execution of the script for a specified duration."
5 | icon: "clock"
6 | "mode": "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/wait-replay.mdx";
10 | import Example from "/snippets/tests/wait-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `wait` command pauses the execution of the script for a specified number of milliseconds before continuing. This is useful for adding delays between commands or waiting for certain conditions to stabilize.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | --------- | -------- | ------------------------------------- |
23 | | `timeout` | `number` | The duration in milliseconds to wait. |
24 |
25 | ## Example usage
26 |
27 | ```yaml
28 | command: wait
29 | timeout: 5000
30 | ```
31 |
32 | ## Protips
33 |
34 | - Use the `wait` command to handle timing issues, such as waiting for animations to complete or elements to load.
35 | - Avoid using excessively long timeouts to keep tests efficient.
36 |
37 | ## Gotchas
38 |
39 | - Overusing the `wait` command can slow down test execution. Use it only when necessary.
40 | - Ensure the timeout value is appropriate for the scenario to avoid unnecessary delays.
41 |
42 | ## Notes
43 |
44 | - The `wait` command is ideal for introducing controlled pauses in your test scripts.
45 | - Whenever you are waiting for something to load, use the [wait-for-text](/commands/wait-for-text) or [wait-for-image](/commands/wait-for-image) commands instead to make the test more efficient.
46 |
--------------------------------------------------------------------------------
/docs/v7/commands/wait.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "wait"
3 | sidebarTitle: "wait"
4 | description: "Pause the execution of the script for a specified duration."
5 | icon: "clock"
6 | "mode": "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/wait-replay.mdx";
10 | import Example from "/snippets/tests/wait-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `wait` command pauses the execution of the script for a specified number of milliseconds before continuing. This is useful for adding delays between commands or waiting for certain conditions to stabilize.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | --------- | -------- | ------------------------------------- |
23 | | `timeout` | `number` | The duration in milliseconds to wait. |
24 |
25 | ## Example usage
26 |
27 | ```yaml
28 | command: wait
29 | timeout: 5000
30 | ```
31 |
32 | ## Protips
33 |
34 | - Use the `wait` command to handle timing issues, such as waiting for animations to complete or elements to load.
35 | - Avoid using excessively long timeouts to keep tests efficient.
36 |
37 | ## Gotchas
38 |
39 | - Overusing the `wait` command can slow down test execution. Use it only when necessary.
40 | - Ensure the timeout value is appropriate for the scenario to avoid unnecessary delays.
41 |
42 | ## Notes
43 |
44 | - The `wait` command is ideal for introducing controlled pauses in your test scripts.
45 | - Whenever you are waiting for something to load, use the [wait-for-text](/commands/wait-for-text) or [wait-for-image](/commands/wait-for-image) commands instead to make the test more efficient.
46 |
--------------------------------------------------------------------------------
/test/testdriver/scroll.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Scroll Test (Vitest)
3 | * Converted from: testdriver/acceptance/scroll.yaml
4 | *
5 | * UPDATED: Now using chrome preset for automatic setup
6 | */
7 |
8 | import { describe, expect, it } from "vitest";
9 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
10 |
11 | describe("Scroll Test", () => {
12 | it("should navigate and scroll down the page", async (context) => {
13 | const testdriver = TestDriver(context, { headless: true });
14 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
15 |
16 | // Give Chrome a moment to fully render the UI
17 | await new Promise(resolve => setTimeout(resolve, 2000));
18 |
19 | // Navigate to webhamster.com - just look for the domain, not the full path
20 | const urlBar = await testdriver.find(
21 | "testdriver-sandbox.vercel.app, the URL in the address bar",
22 | );
23 | await urlBar.click();
24 | await testdriver.pressKeys(["ctrl", "a"]);
25 | await testdriver.type("https://www.webhamster.com/");
26 | await testdriver.pressKeys(["enter"]);
27 |
28 | // Wait for page to load and click heading
29 | const heading = await testdriver.find(
30 | "The Hamster Dance, large heading at top of page",
31 | );
32 | await heading.click();
33 |
34 | // Scroll down
35 | await testdriver.scroll("down", { amount: 1000 });
36 |
37 | // Assert page is scrolled
38 | const result = await testdriver.assert("the page is scrolled down, the hamster dance heading is not visible on the page");
39 | expect(result).toBeTruthy();
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/docs/v6/commands/focus-application.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "focus-application"
3 | sidebarTitle: "focus-application"
4 | description: "Bring a specific application window to the foreground."
5 | icon: "window-restore"
6 | mode: "wide"
7 | ---
8 |
9 | ## Description
10 |
11 | The `focus-application` command displays a specific application window to the foreground. This ensures that subsequent commands interact with the correct application during a test.
12 |
13 | ## Arguments
14 |
15 | | Argument | Type | Description |
16 | | -------- | -------- | --------------------------------------------------------------------------------------- |
17 | | `name` | `string` | The name of the application to focus. This should match the application's display name. |
18 |
19 | ## Example usage
20 |
21 | ```yaml
22 | command: focus-application
23 | name: Google Chrome
24 | ```
25 |
26 | ## Protips
27 |
28 | - Ensure the application name matches the exact name displayed in your operating system's task manager.
29 | - Use this command at the start of a test or before interacting with an application to avoid focus-related issues.
30 |
31 |
32 | If the specified application isn't running, the command will fail. Ensure the application is open before using this command.
33 | For example, to launch chrome try using the exec command:
34 |
35 | ```yaml
36 | - command: exec
37 | lang: pwsh
38 | code: start chrome
39 | ```
40 |
41 |
42 | ## Notes
43 |
44 | - The `focus-application` command is useful for multi-application workflows where you need to switch between different apps during a test.
45 |
--------------------------------------------------------------------------------
/docs/v7/commands/focus-application.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "focus-application"
3 | sidebarTitle: "focus-application"
4 | description: "Bring a specific application window to the foreground."
5 | icon: "window-restore"
6 | mode: "wide"
7 | ---
8 |
9 | ## Description
10 |
11 | The `focus-application` command displays a specific application window to the foreground. This ensures that subsequent commands interact with the correct application during a test.
12 |
13 | ## Arguments
14 |
15 | | Argument | Type | Description |
16 | | -------- | -------- | --------------------------------------------------------------------------------------- |
17 | | `name` | `string` | The name of the application to focus. This should match the application's display name. |
18 |
19 | ## Example usage
20 |
21 | ```yaml
22 | command: focus-application
23 | name: Google Chrome
24 | ```
25 |
26 | ## Protips
27 |
28 | - Ensure the application name matches the exact name displayed in your operating system's task manager.
29 | - Use this command at the start of a test or before interacting with an application to avoid focus-related issues.
30 |
31 |
32 | If the specified application isn't running, the command will fail. Ensure the application is open before using this command.
33 | For example, to launch chrome try using the exec command:
34 |
35 | ```yaml
36 | - command: exec
37 | lang: pwsh
38 | code: start chrome
39 | ```
40 |
41 |
42 | ## Notes
43 |
44 | - The `focus-application` command is useful for multi-application workflows where you need to switch between different apps during a test.
45 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | const globals = require("globals");
2 | const pluginJs = require("@eslint/js");
3 |
4 | module.exports = [
5 | pluginJs.configs.recommended,
6 | {
7 | // Base config for all JavaScript files - provides Node.js globals
8 | languageOptions: {
9 | sourceType: "commonjs",
10 | globals: {
11 | ...globals.node,
12 | },
13 | },
14 | },
15 | {
16 | // Specific config for interface and agent files - adds browser globals
17 | files: ["./interfaces/**/*.js", "./agent/**/*.js"],
18 | languageOptions: {
19 | sourceType: "commonjs",
20 | globals: {
21 | ...globals.browser,
22 | ...globals.node,
23 | },
24 | },
25 | },
26 | {
27 | // Specific config for test files - adds Jest and Mocha globals
28 | files: ["test/**/*.js"],
29 | languageOptions: {
30 | sourceType: "commonjs",
31 | globals: {
32 | ...globals.node,
33 | ...globals.jest,
34 | ...globals.mocha,
35 | },
36 | },
37 | },
38 | {
39 | // Config for ES Module files (.mjs) - used in SDK tests
40 | files: ["**/*.mjs"],
41 | languageOptions: {
42 | sourceType: "module",
43 | ecmaVersion: 2022,
44 | globals: {
45 | ...globals.node,
46 | },
47 | },
48 | },
49 | {
50 | // this needs to be it's own object for some reason
51 | // https://github.com/eslint/eslint/issues/17400
52 | ignores: [
53 | "agent/lib/subimage/**",
54 | "node_modules/**",
55 | ".git",
56 | "test-results/**",
57 | "examples/test-recording-example.test.js",
58 | "vitest.config.example.js",
59 | ],
60 | },
61 | ];
62 |
--------------------------------------------------------------------------------
/test/testdriver/type.test.mjs:
--------------------------------------------------------------------------------
1 | import { describe, expect, it } from "vitest";
2 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
3 |
4 | describe("Type Test", () => {
5 | it("should enter standard_user in username field", async (context) => {
6 | const testdriver = TestDriver(context, { headless: true });
7 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
8 |
9 | //
10 | const usernameField = await testdriver.find(
11 | "Username, input field for username",
12 | );
13 | await usernameField.click();
14 | await testdriver.type("standard_user");
15 |
16 | const result = await testdriver.assert(
17 | 'the username field contains "standard_user"',
18 | );
19 | expect(result).toBeTruthy();
20 | });
21 |
22 | it("should show validation message when clicking Sign In without password", async (context) => {
23 | const testdriver = TestDriver(context, { headless: true });
24 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
25 |
26 | // First fill in username
27 | const usernameField = await testdriver.find(
28 | "Username, input field for username",
29 | );
30 | await usernameField.click();
31 | await testdriver.type("standard_user");
32 |
33 | //
34 | const signInButton = await testdriver.find(
35 | "Sign in, black button below the password field",
36 | );
37 | await signInButton.click();
38 |
39 | await testdriver.focusApplication("Google Chrome");
40 | const result = await testdriver.assert(
41 | "Please fill out this field is visible near the password field",
42 | );
43 | expect(result).toBeTruthy();
44 | });
45 | });
46 |
--------------------------------------------------------------------------------
/docs/v6/interactive/run.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "/run"
3 | sidebarTitle: "/run"
4 | description: "Run a test file from a file."
5 | icon: "play"
6 | ---
7 |
8 | ## Description
9 |
10 | The `/run` command is used to execute a test file from a specified file. This command performs each step defined in the test file and outputs the results.
11 |
12 | ## Usage
13 |
14 | ```bash
15 | npx testdriverai@latest edit
16 | /run [other-filename.yaml] # if no path is present it will run the `filename.yaml`
17 | ```
18 |
19 | ## Example usage
20 |
21 | ```bash
22 | testdriverai edit
23 | > /run helloworld.yaml
24 | ```
25 |
26 | This command runs the `helloworld.yaml` test file, executing each command in the file sequentially.
27 |
28 |
29 | The `helloworld.yaml` file needs to be present in the present directory. If
30 | its present outside, need to specify the relative path like
31 | `../snippets/helloworld.yaml`
32 |
33 |
34 | ## Behavior
35 |
36 | - TestDriver will execute the test file, performing each command as defined in the file.
37 | - If the test completes successfully, the program will exit with code `0`.
38 | - If any failures occur during the test, the program will output the errors and exit with code `1`.
39 |
40 | ## Protips
41 |
42 | - Ensure the test file path is correct and accessible before running the command.
43 | - Use descriptive filenames for your test files to make them easier to identify.
44 |
45 | ## Gotchas
46 |
47 | - Any errors in the test file (for example, invalid commands or missing arguments) will cause the test to fail.
48 |
49 | ## Notes
50 |
51 | - The `/run` command is ideal for executing pre-created test files in an interactive session.
52 | - Use this command to validate and debug your test files during development.
53 |
--------------------------------------------------------------------------------
/test/testdriver/scroll-until-text.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Scroll Until Text Test (Vitest)
3 | * Converted from: testdriver/acceptance/scroll-until-text.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 | import { performLogin } from "./setup/testHelpers.mjs";
9 |
10 | describe("Scroll Until Text Test", () => {
11 | it('should scroll until "testdriver socks" appears', async (context) => {
12 | const testdriver = TestDriver(context, { headless: true, reconnect: false });
13 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
14 |
15 | //
16 | // Perform login first
17 | await performLogin(testdriver);
18 |
19 | // Scroll until text appears
20 | await testdriver.focusApplication("Google Chrome");
21 |
22 | await testdriver.find('TestDriver.ai Sandbox heading').click();
23 |
24 | // Scroll until text appears
25 | let found = false;
26 | let scrollCount = 0;
27 | const maxScrolls = 10;
28 |
29 | while (!found && scrollCount < maxScrolls) {
30 | const findResult = await testdriver.find("testdriver socks product text is fully visible");
31 |
32 | if (findResult.found()) {
33 | found = true;
34 | } else {
35 | await testdriver.scroll();
36 | scrollCount++;
37 | }
38 | }
39 |
40 | if (!found) {
41 | throw new Error(`Failed to find "testdriver socks" after ${maxScrolls} scrolls`);
42 | }
43 |
44 | // Assert testdriver socks appears on screen
45 | await testdriver.focusApplication("Google Chrome");
46 | const result = await testdriver.assert("TestDriver Socks appears on screen");
47 | expect(result).toBeTruthy();
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/agent/lib/config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file contains application config factory.
3 | * It is responsible for creating config instances from environment variables,
4 | * supplying defaults, and formatting values
5 | */
6 |
7 | // Parse out true and false string values
8 | function parseValue(value) {
9 | if (typeof value === "string") {
10 | const normalizedValue = value.toLowerCase().trim();
11 | if (["true", "false"].includes(normalizedValue)) {
12 | return JSON.parse(normalizedValue);
13 | }
14 | }
15 |
16 | return value;
17 | }
18 |
19 | // Factory function that creates a config instance
20 | const createConfig = (environment = {}) => {
21 | // Start with defaults
22 | const config = {
23 | TD_ANALYTICS: true,
24 | TD_API_ROOT: "https://v6.testdriver.ai",
25 | TD_API_KEY: null,
26 | TD_PROFILE: false,
27 | TD_RESOLUTION: [1366, 768],
28 | };
29 |
30 | // Store the full environment for interpolation purposes
31 | config._environment = environment;
32 |
33 | // Find all env vars starting with TD_
34 | for (let key in environment) {
35 | if (key == "TD_RESOLUTION") {
36 | config[key] = environment[key].split("x").map((x) => parseInt(x.trim()));
37 | continue;
38 | }
39 |
40 | if (key.startsWith("TD_")) {
41 | config[key] = parseValue(environment[key]);
42 | }
43 | }
44 |
45 | // Add support for CI environment variable
46 | if (environment.CI) {
47 | config.CI = parseValue(environment.CI);
48 | }
49 |
50 | return config;
51 | };
52 |
53 | // Create a default config instance for backward compatibility
54 | const defaultConfig = createConfig(process.env);
55 |
56 | // Export both the factory function and the default instance
57 | module.exports = defaultConfig;
58 | module.exports.createConfig = createConfig;
59 |
--------------------------------------------------------------------------------
/interfaces/shared-test-state.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Shared Test State Module
3 | *
4 | * This module uses Node.js module caching to share state between
5 | * the reporter process and worker processes in Vitest.
6 | *
7 | * Since Node.js caches modules, all imports of this file will
8 | * receive the same state object instance.
9 | */
10 |
11 | // Shared state object that persists across all imports
12 | const sharedState = {
13 | testRun: null,
14 | testRunId: null,
15 | token: null,
16 | apiKey: null,
17 | apiRoot: null,
18 | startTime: null,
19 | };
20 |
21 | /**
22 | * Set the test run information
23 | */
24 | export function setTestRunInfo(info) {
25 |
26 | if (info.testRun) sharedState.testRun = info.testRun;
27 | if (info.testRunId) sharedState.testRunId = info.testRunId;
28 | if (info.token) sharedState.token = info.token;
29 | if (info.apiKey) sharedState.apiKey = info.apiKey;
30 | if (info.apiRoot) sharedState.apiRoot = info.apiRoot;
31 | if (info.startTime) sharedState.startTime = info.startTime;
32 | }
33 |
34 | /**
35 | * Get the test run information
36 | */
37 | export function getTestRunInfo() {
38 | return {
39 | testRun: sharedState.testRun,
40 | testRunId: sharedState.testRunId,
41 | token: sharedState.token,
42 | apiKey: sharedState.apiKey,
43 | apiRoot: sharedState.apiRoot,
44 | startTime: sharedState.startTime,
45 | };
46 | }
47 |
48 | /**
49 | * Clear the test run information
50 | */
51 | export function clearTestRunInfo() {
52 | console.log("[SharedState] Clearing test run info");
53 | sharedState.testRun = null;
54 | sharedState.testRunId = null;
55 | sharedState.token = null;
56 | sharedState.startTime = null;
57 | }
58 |
59 | /**
60 | * Direct access to state (for debugging)
61 | */
62 | export function getState() {
63 | return sharedState;
64 | }
65 |
--------------------------------------------------------------------------------
/test/manual/test-stack-trace.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * Quick test to verify stack trace filtering works
3 | */
4 |
5 | // Mock the MatchError similar to commands.js
6 | class MatchError extends Error {
7 | constructor(message, fatal = false) {
8 | super(message);
9 | this.fatal = fatal;
10 | this.attachScreenshot = true;
11 | }
12 | }
13 |
14 | // Simulate SDK wrapper with stack filtering (improved version)
15 | class TestSDK {
16 | constructor() {
17 | const command = async (message) => {
18 | // Simulate the command throwing an error
19 | throw new MatchError(`AI Assertion failed: ${message}`, true);
20 | };
21 |
22 | // Wrap the method with proper stack trace handling
23 | this.assert = async function (...args) {
24 | // Capture the call site for better error reporting
25 | const callSite = {};
26 | Error.captureStackTrace(callSite, this.assert);
27 |
28 | try {
29 | return await command(...args);
30 | } catch (error) {
31 | // Replace the stack trace to point to the actual caller
32 | if (Error.captureStackTrace && callSite.stack) {
33 | const errorMessage = error.stack?.split("\n")[0];
34 | const callerStack = callSite.stack?.split("\n").slice(1);
35 | error.stack = errorMessage + "\n" + callerStack.join("\n");
36 | }
37 | throw error;
38 | }
39 | }.bind(this);
40 | }
41 | }
42 |
43 | // Test it
44 | async function runTest() {
45 | const client = new TestSDK();
46 |
47 | try {
48 | console.log("Testing stack trace...\n");
49 | await client.assert("home page appears"); // Line 42 - this should show in stack
50 | } catch (error) {
51 | console.log("Error caught!");
52 | console.log("Stack trace:");
53 | console.log(error.stack);
54 | }
55 | }
56 |
57 | runTest();
58 |
--------------------------------------------------------------------------------
/docs/v6/commands/assert.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "assert"
3 | sidebarTitle: "assert"
4 | description: "Validate conditions during a test using the assert command."
5 | icon: "check"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/assert-replay.mdx";
10 | import Example from "/snippets/tests/assert-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `assert` command validates that a specific condition is true. It ensures that a task completed successfully by checking the screen for the expected outcome. If the condition isn't met, the test will fail.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | -------- | --------- | ------------------------------------------------------------------------------------------------------------------ |
23 | | `expect` | `string` | The condition to check. This should describe what you expect to see on the screen. |
24 | | `async` | `boolean` | (Optional) If set to `true`, the test will continue without waiting for the assertion to pass. Default is `false`. |
25 | | `invert` | `boolean` | (Optional) If set to `true`, will fail if the assertion passes. |
26 |
27 | ## Example usage
28 |
29 | ```yaml
30 | command: assert
31 | expect: the video is playing
32 | ```
33 |
34 | ### Example with `async`
35 |
36 | ```yaml
37 | command: assert
38 | expect: There is no error message
39 | async: true
40 | ```
41 |
42 | ## Notes
43 |
44 | - Use `async: true` to speed up tests by allowing non-blocking assertions. However, the test will still fail if the condition isn't met.
45 | - Ensure the `expect` string clearly describes the condition to avoid ambiguity.
46 |
--------------------------------------------------------------------------------
/docs/v7/commands/assert.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "assert"
3 | sidebarTitle: "assert"
4 | description: "Validate conditions during a test using the assert command."
5 | icon: "check"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/assert-replay.mdx";
10 | import Example from "/snippets/tests/assert-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `assert` command validates that a specific condition is true. It ensures that a task completed successfully by checking the screen for the expected outcome. If the condition isn't met, the test will fail.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | -------- | --------- | ------------------------------------------------------------------------------------------------------------------ |
23 | | `expect` | `string` | The condition to check. This should describe what you expect to see on the screen. |
24 | | `async` | `boolean` | (Optional) If set to `true`, the test will continue without waiting for the assertion to pass. Default is `false`. |
25 | | `invert` | `boolean` | (Optional) If set to `true`, will fail if the assertion passes. |
26 |
27 | ## Example usage
28 |
29 | ```yaml
30 | command: assert
31 | expect: the video is playing
32 | ```
33 |
34 | ### Example with `async`
35 |
36 | ```yaml
37 | command: assert
38 | expect: There is no error message
39 | async: true
40 | ```
41 |
42 | ## Notes
43 |
44 | - Use `async: true` to speed up tests by allowing non-blocking assertions. However, the test will still fail if the condition isn't met.
45 | - Ensure the `expect` string clearly describes the condition to avoid ambiguity.
46 |
--------------------------------------------------------------------------------
/test/testdriver/press-keys.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Press Keys Test (Vitest)
3 | * Converted from: testdriver/acceptance/press-keys.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe("Press Keys Test", () => {
10 | it("should create tabs and navigate using keyboard shortcuts", async (context) => {
11 | const testdriver = TestDriver(context, { headless: true });
12 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
13 |
14 | const signInButton = await testdriver.find(
15 | "Sign In, black button below the password field",
16 | );
17 | await signInButton.click();
18 |
19 | // Open new tab
20 | await testdriver.pressKeys(["ctrl", "t"]);
21 |
22 | // Poll for "Learn more" to appear
23 | let learnMore = await testdriver.find("Learn more");
24 | for (let i = 0; i < 10; i++) {
25 | learnMore = await learnMore.find();
26 | if (learnMore.found()) break;
27 | await new Promise((resolve) => setTimeout(resolve, 500));
28 | }
29 |
30 | // Open DevTools
31 | await testdriver.pressKeys(["ctrl", "shift", "i"]);
32 |
33 | // Poll for "Elements" to appear
34 | let elements = await testdriver.find("Elements");
35 | for (let i = 0; i < 10; i++) {
36 | elements = await elements.find();
37 | if (elements.found()) break;
38 | await new Promise((resolve) => setTimeout(resolve, 500));
39 | }
40 |
41 | // Open another tab and navigate
42 | await testdriver.pressKeys(["ctrl", "t"]);
43 | await testdriver.type("google.com");
44 | await testdriver.pressKeys(["enter"]);
45 |
46 | // Assert Google appears
47 | const result = await testdriver.assert("google appears");
48 | expect(result).toBeTruthy();
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/docs/v6/account/team.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Team"
3 | sidebarTitle: "Team"
4 | description: "Manage your team and collaborate with TestDriver."
5 | icon: "users"
6 | ---
7 |
8 | ## Important settings in one place
9 |
10 | In the Team tab, you can manage your team name, team members, API key, and subscription.
11 |
12 | ### Let's take a look at the team settings.
13 |
14 |
15 |
16 |
17 |
18 | 1. **Team Name**: The name of your team. This is used to identify your team in TestDriver.
19 | 2. **API Key**: Your API key is used to authenticate your requests to the TestDriver API. You can regenerate it if you suspect it has been compromised.
20 | 3. **Usage & Billing**: Monitor your usage and billing information. This includes your current plan, usage limits, and billing history.
21 |
22 | Note: this button is where you 'Upgrade to Pro' to start your free trial.
23 |
24 | 4. **Replay Visibility**: Toggle whether your replays are public or private by default. This defaults to **private**.
25 | 5. **Don't forget to save your changes!**: After making any changes, be sure to click the **Update** button to apply them.
26 |
27 | ### Team members
28 |
29 |
30 |
31 |
32 |
33 | 1. **Email Invite**: Invite new team members by entering their email address and clicking the **Invite** button. They will receive an email invitation to join your team. You can also copy the link above this option to send to everyone at once.
34 | 2. **Removing Team Members**: To remove a team member, click the **Remove** button next to their name. This will revoke their access to your TestDriver account.
35 | 3. **Invite Status**: The status of the invite will be shown here. If it says **Invited**, the invite hasn't been accepted yet. To resend it, click here.
36 |
--------------------------------------------------------------------------------
/docs/v6/commands/run.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "run"
3 | sidebarTitle: "run"
4 | description: "Embed and execute another file within the current script."
5 | icon: "play"
6 | mode: "wide"
7 | ---
8 |
9 | ## Description
10 |
11 | The `run` command is used to embed and execute another file within the current script. This is **useful for reusing common sequences of commands or modularizing your scripts** for better organization and maintainability.
12 |
13 | ## Arguments
14 |
15 | | Argument | Type | Description |
16 | | -------- | -------- | :-------------------------------------------------------------------------------------------- |
17 | | `file` | `string` | The path to the file to embed and run. Should be relative to the root of your git repository. |
18 |
19 | ## Example usage
20 |
21 | ```yaml
22 | command: run
23 | file: relative/path/to/another-script.yaml
24 | ```
25 |
26 | ## Protips
27 |
28 | - Use the `run` command to centralize reusable logic, such as login flows or setup steps, into separate files.
29 | - Ensure the file path is relative to the current test file's directory. For example, if the test file is at `testdriver/homepage/chat-functionalities.yaml` and say the login snippet is present at the conventional `testdriver/snippets/login.yaml` it should be referrenced as :
30 |
31 | ```yaml highlight={2}
32 | command: run
33 | file: ../snippets/login.yaml
34 | ```
35 |
36 | ## Gotchas
37 |
38 | - If the specified file doesn't exist or contains errors, the command will fail.
39 | - Ensure the embedded file is compatible with the current script's context, such as variable dependencies or session requirements.
40 |
41 | ## Notes
42 |
43 | - The `run` command is ideal for creating modular and maintainable test scripts by reusing common workflows.
44 | - This command supports nesting, allowing you to call scripts that themselves use the `run` command.
45 |
--------------------------------------------------------------------------------
/docs/v7/commands/run.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "run"
3 | sidebarTitle: "run"
4 | description: "Embed and execute another file within the current script."
5 | icon: "play"
6 | mode: "wide"
7 | ---
8 |
9 | ## Description
10 |
11 | The `run` command is used to embed and execute another file within the current script. This is **useful for reusing common sequences of commands or modularizing your scripts** for better organization and maintainability.
12 |
13 | ## Arguments
14 |
15 | | Argument | Type | Description |
16 | | -------- | -------- | :-------------------------------------------------------------------------------------------- |
17 | | `file` | `string` | The path to the file to embed and run. Should be relative to the root of your git repository. |
18 |
19 | ## Example usage
20 |
21 | ```yaml
22 | command: run
23 | file: relative/path/to/another-script.yaml
24 | ```
25 |
26 | ## Protips
27 |
28 | - Use the `run` command to centralize reusable logic, such as login flows or setup steps, into separate files.
29 | - Ensure the file path is relative to the current test file's directory. For example, if the test file is at `testdriver/homepage/chat-functionalities.yaml` and say the login snippet is present at the conventional `testdriver/snippets/login.yaml` it should be referrenced as :
30 |
31 | ```yaml highlight={2}
32 | command: run
33 | file: ../snippets/login.yaml
34 | ```
35 |
36 | ## Gotchas
37 |
38 | - If the specified file doesn't exist or contains errors, the command will fail.
39 | - Ensure the embedded file is compatible with the current script's context, such as variable dependencies or session requirements.
40 |
41 | ## Notes
42 |
43 | - The `run` command is ideal for creating modular and maintainable test scripts by reusing common workflows.
44 | - This command supports nesting, allowing you to call scripts that themselves use the `run` command.
45 |
--------------------------------------------------------------------------------
/docs/v6/interactive/save.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "/save"
3 | sidebarTitle: "/save"
4 | description: "Save the current test script to a file."
5 | icon: "floppy-disk"
6 | ---
7 |
8 | ## Description
9 |
10 | The `/save` command saves the current state of the test script to a file. This command generates a YAML file containing the history of executed commands and tasks, allowing you to reuse or modify the test script later.
11 |
12 | ## Usage
13 |
14 | ```bash
15 | /save
16 | ```
17 |
18 | ## Example usage
19 |
20 | ```bash
21 | testdriverai edit
22 | > /save
23 |
24 | saving...
25 |
26 | Current test script:
27 |
28 | version: 6.0.0
29 | steps:
30 | - prompt: navigate to fiber.google.com
31 | commands:
32 | - command: focus-application
33 | name: Google Chrome
34 | - command: hover-text
35 | text: Search Google or type a URL
36 | description: main google search
37 | action: click
38 | - command: type
39 | text: fiber.google.com
40 | - command: press-keys
41 | keys:
42 | - enter
43 | ```
44 |
45 | ## Behavior
46 |
47 | - The `/save` command generates a YAML file with the current test script, including all executed steps and commands.
48 | - The file can be used as a reusable test file for future executions.
49 |
50 | ## Protips
51 |
52 | - Use `/save` frequently during interactive sessions to preserve your progress and avoid losing work.
53 | - Combine `/save` with `/run` to quickly test and iterate on your scripts.
54 |
55 | ## Gotchas
56 |
57 | - Ensure you have write permissions in the directory where the file will be saved.
58 | - The saved script reflects the current state of the session. Any unexecuted commands won't be included.
59 |
60 | ## Notes
61 |
62 | - The `/save` command is ideal for creating reusable test scripts from interactive sessions.
63 | - Use this command to document and share your test workflows with your team.
64 |
--------------------------------------------------------------------------------
/test/manual/test-sdk-refactor.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Quick test to verify SDK refactoring works correctly
5 | */
6 |
7 | const TestDriver = require("./sdk.js");
8 |
9 | async function test() {
10 | console.log("Testing SDK refactor...\n");
11 |
12 | // Test 1: SDK construction
13 | console.log("✓ Test 1: Creating SDK instance");
14 | const client = new TestDriver(process.env.TD_API_KEY || "test-key", {
15 | logging: false,
16 | });
17 |
18 | console.log(" - agent exists:", !!client.agent);
19 | console.log(" - emitter exists:", !!client.emitter);
20 | console.log(" - config exists:", !!client.config);
21 | console.log(" - session exists:", !!client.session);
22 | console.log(" - apiClient exists:", !!client.apiClient);
23 | console.log(" - analytics exists:", !!client.analytics);
24 | console.log(" - sandbox exists:", !!client.sandbox);
25 | console.log(" - system exists:", !!client.system);
26 |
27 | // Test 2: Check agent methods are accessible
28 | console.log("\n✓ Test 2: Checking agent methods");
29 | console.log(
30 | " - agent.exploratoryLoop exists:",
31 | typeof client.agent.exploratoryLoop,
32 | );
33 | console.log(" - agent.buildEnv exists:", typeof client.agent.buildEnv);
34 | console.log(
35 | " - agent.getRecentSandboxId exists:",
36 | typeof client.agent.getRecentSandboxId,
37 | );
38 |
39 | // Test 3: Check SDK methods
40 | console.log("\n✓ Test 3: Checking SDK methods");
41 | console.log(" - ai() exists:", typeof client.ai);
42 | console.log(" - auth() exists:", typeof client.auth);
43 | console.log(" - connect() exists:", typeof client.connect);
44 | console.log(" - disconnect() exists:", typeof client.disconnect);
45 |
46 | console.log("\n✅ All basic tests passed!");
47 | }
48 |
49 | test().catch((error) => {
50 | console.error("\n❌ Test failed:", error.message);
51 | console.error(error.stack);
52 | process.exit(1);
53 | });
54 |
--------------------------------------------------------------------------------
/test/testdriver/exec-output.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Exec Output Test (Vitest)
3 | * Converted from: testdriver/acceptance/exec-output.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe.skip("Exec Output Test", () => {
10 | it(
11 | "should set date using PowerShell and navigate to calendar",
12 | async (context) => {
13 | const testdriver = TestDriver(context, { headless: true });
14 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
15 |
16 | //
17 | // Generate date in query string format
18 | const queryString = await testdriver.exec(
19 | "pwsh",
20 | `
21 | $date = (Get-Date).AddMonths(1)
22 | Write-Output $date.ToString("yyyy-MM-dd")
23 | `,
24 | 10000,
25 | );
26 |
27 | // Assert that the date is valid
28 | const dateValidResult = await testdriver.assert(
29 | `${queryString} is a valid date`,
30 | );
31 | expect(dateValidResult).toBeTruthy();
32 |
33 | // Generate date in display format
34 | const expectedDate = await testdriver.exec(
35 | "pwsh",
36 | `
37 | $date = (Get-Date).AddMonths(1)
38 | Write-Output $date.ToString("ddd MMM d yyyy")
39 | `,
40 | 10000,
41 | );
42 |
43 | // Navigate to calendar with date parameter
44 | await testdriver.focusApplication("Google Chrome");
45 | await testdriver.pressKeys(["ctrl", "l"]);
46 | await testdriver.type(
47 | `https://teamup.com/ks48cf2135e7e080bc?view=d&date=${queryString}`,
48 | );
49 | await testdriver.pressKeys(["enter"]);
50 |
51 | // Assert that the expected date shows
52 | await testdriver.focusApplication("Google Chrome");
53 | const result = await testdriver.assert(
54 | `the text ${expectedDate} is visible on screen`,
55 | );
56 | expect(result).toBeTruthy();
57 | },
58 | );
59 | });
60 |
--------------------------------------------------------------------------------
/docs/v6/apps/static-websites.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Testing Static Websites with TestDriver"
3 | sidebarTitle: "Static Websites"
4 | description: "Test static websites with TestDriver"
5 | icon: "browser"
6 | ---
7 |
8 |
17 |
18 | ---
19 |
20 | # Testing Static Websites with TestDriver
21 |
22 | TestDriver provides a powerful solution for testing static websites. With our platform, you can easily create and run tests to ensure your static website functions as expected across different browsers and devices.
23 |
24 | ## Getting started
25 |
26 | To get started with testing static websites using TestDriver, follow these steps:
27 |
28 | 1. [**Follow our quick start guide to set up your TestDriver account.**](/getting-started/setup)
29 | 2. **Create a new test project** in your TestDriver dashboard.
30 | 3. **Add your static website URL** to the test settings.
31 | It looks like this in the `provision.yaml` file:
32 |
33 | ```yaml testdriver/lifecycle/provision.yaml
34 | version: 6.0.0
35 | steps:
36 | - prompt: launch chrome
37 | commands:
38 | - command: exec
39 | lang: pwsh
40 | code: |
41 | Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "${TD_WEBSITE}"
42 | exit
43 | - command: wait-for-text
44 | text: ${TD_WEBSITE}
45 | timeout: 30000
46 | ```
47 |
48 | Set the `TD_WEBSITE` environment variable to the URL of your static website. This will allow TestDriver to launch the browser and navigate to your website. You can do this by adding this `provision.yaml` file to your project or by simply using:
49 |
50 | ```bash
51 | npx testdriverai@latest init
52 | ```
53 |
54 | Running init will create the `provision.yaml` file for you and ask you which website to load with your test.
55 |
--------------------------------------------------------------------------------
/test/testdriver/exec-pwsh.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Exec Shell Test (Vitest)
3 | * Converted from: testdriver/acceptance/exec-shell.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | describe.skip("Exec PowerShell Test", () => {
10 | it(
11 | "should generate random email using PowerShell and enter it",
12 | async (context) => {
13 | const testdriver = TestDriver(context, { headless: true });
14 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
15 |
16 | //
17 | // Generate random email using PowerShell
18 | const randomEmail = await testdriver.exec({
19 | language: "pwsh",
20 | code: `
21 | # Random email generator in PowerShell
22 |
23 | # Arrays of possible names and domains
24 | $firstNames = @("john", "jane", "alex", "chris", "sara", "mike", "lisa", "david", "emma", "ryan")
25 | $lastNames = @("smith", "johnson", "williams", "brown", "jones", "garcia", "miller", "davis", "martin", "lee")
26 | $domains = @("example.com", "testmail.com", "mailinator.com", "demo.org", "company.net")
27 |
28 | # Random selection
29 | $first = Get-Random -InputObject $firstNames
30 | $last = Get-Random -InputObject $lastNames
31 | $domain = Get-Random -InputObject $domains
32 | $number = Get-Random -Minimum 1 -Maximum 1000
33 |
34 | # Generate the email
35 | $email = "$first.$last$number@$domain".ToLower()
36 |
37 | # Output
38 | Write-Output "$email"
39 | `,
40 | timeout: 10000,
41 | });
42 |
43 | // Enter the email in username field
44 | const usernameField = await testdriver.find(
45 | "Username, input field for username",
46 | );
47 | await usernameField.click();
48 | await testdriver.type(randomEmail);
49 |
50 | // Assert that the username field shows a valid email address
51 | const result = await testdriver.assert(
52 | `the username field contains ${randomEmail} which is a valid email address`,
53 | );
54 | expect(result).toBeTruthy();
55 | },
56 | );
57 | });
58 |
--------------------------------------------------------------------------------
/docs/v6/commands/remember.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "remember"
3 | sidebarTitle: "remember"
4 | description: "Save a variable to use later."
5 | icon: "brain"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/remember-replay.mdx";
10 | import Example from "/snippets/tests/remember-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `remember` command is used to save a variable for later use. This is useful for storing values that you want to reference in subsequent commands or steps, **especially for things that are dynamic or change frequently from run to run**.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :-----------: | :------: | :-------------------------------------------------------- |
23 | | `output` | `string` | The variable name you will use to recall the value later. |
24 | | `description` | `string` | A prompt describing the value you want to store. |
25 |
26 | ---
27 |
28 | ## Example usage
29 |
30 | ```yaml
31 | - command: remember
32 | description: The date value shown to the right of 'Order Date'
33 | output: my_variable
34 | ```
35 |
36 | ## Protips
37 |
38 | - Use the output later with this syntax `${OUTPUT.my_variable}`.
39 | - The `remember` command does not persist variables across different test runs. If you need to store values for later use in different test runs, consider using external storage solutions or environment variables.
40 | - For now, the `remember` command only supports string values. If you need to store complex data types, you may use `remember` multiple times for the values. This may change in a later version.
41 |
42 | ## Gotchas
43 |
44 | - Ensure the variable name is unique to avoid overwriting existing variables.
45 | - The variable name is case-sensitive, so `my_variable` and `My_Variable` would be treated as different variables.
46 |
47 | ## Notes
48 |
49 | - The `remember` command is ideal for storing values that are generated during the test run, such as timestamps, IDs, the text value of a link you might click later, or any other dynamic data.
50 |
--------------------------------------------------------------------------------
/docs/v7/commands/remember.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "remember"
3 | sidebarTitle: "remember"
4 | description: "Save a variable to use later."
5 | icon: "brain"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/remember-replay.mdx";
10 | import Example from "/snippets/tests/remember-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `remember` command is used to save a variable for later use. This is useful for storing values that you want to reference in subsequent commands or steps, **especially for things that are dynamic or change frequently from run to run**.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :-----------: | :------: | :-------------------------------------------------------- |
23 | | `output` | `string` | The variable name you will use to recall the value later. |
24 | | `description` | `string` | A prompt describing the value you want to store. |
25 |
26 | ---
27 |
28 | ## Example usage
29 |
30 | ```yaml
31 | - command: remember
32 | description: The date value shown to the right of 'Order Date'
33 | output: my_variable
34 | ```
35 |
36 | ## Protips
37 |
38 | - Use the output later with this syntax `${OUTPUT.my_variable}`.
39 | - The `remember` command does not persist variables across different test runs. If you need to store values for later use in different test runs, consider using external storage solutions or environment variables.
40 | - For now, the `remember` command only supports string values. If you need to store complex data types, you may use `remember` multiple times for the values. This may change in a later version.
41 |
42 | ## Gotchas
43 |
44 | - Ensure the variable name is unique to avoid overwriting existing variables.
45 | - The variable name is case-sensitive, so `my_variable` and `My_Variable` would be treated as different variables.
46 |
47 | ## Notes
48 |
49 | - The `remember` command is ideal for storing values that are generated during the test run, such as timestamps, IDs, the text value of a link you might click later, or any other dynamic data.
50 |
--------------------------------------------------------------------------------
/examples/screenshot-example.js:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Screenshot Example
3 | *
4 | * This example demonstrates how to use the screenshot() method
5 | * to capture screenshots of the sandbox environment.
6 | */
7 |
8 | const TestDriver = require("../sdk.js");
9 | const fs = require("fs");
10 | const path = require("path");
11 |
12 | async function main() {
13 | // Initialize TestDriver SDK
14 | const client = new TestDriver(process.env.TD_API_KEY);
15 |
16 | try {
17 | // Connect to sandbox
18 | console.log("Connecting to sandbox...");
19 | await client.connect();
20 |
21 | // Navigate to a website
22 | console.log("Opening Chrome and navigating to example.com...");
23 | await client.focusApplication("Google Chrome");
24 | const urlBar = await client.find("URL bar in Chrome");
25 | await urlBar.click();
26 | await client.type("https://example.com");
27 | await client.pressKeys(["enter"]);
28 |
29 | // Wait a moment for page to load
30 | await client.wait(2000);
31 |
32 | // Capture a screenshot
33 | console.log("Capturing screenshot...");
34 | const screenshot = await client.screenshot();
35 |
36 | // Save screenshot to file
37 | const outputPath = path.join(__dirname, "example-screenshot.png");
38 | fs.writeFileSync(outputPath, Buffer.from(screenshot, "base64"));
39 | console.log(`Screenshot saved to: ${outputPath}`);
40 |
41 | // You can also capture with the mouse cursor visible
42 | console.log("Capturing screenshot with mouse cursor...");
43 | await client.hover(400, 300);
44 | const screenshotWithMouse = await client.screenshot(1, false, true);
45 |
46 | const outputPathWithMouse = path.join(
47 | __dirname,
48 | "example-screenshot-with-mouse.png",
49 | );
50 | fs.writeFileSync(
51 | outputPathWithMouse,
52 | Buffer.from(screenshotWithMouse, "base64"),
53 | );
54 | console.log(`Screenshot with mouse saved to: ${outputPathWithMouse}`);
55 | } catch (error) {
56 | console.error("Error:", error);
57 | } finally {
58 | // Clean up
59 | await client.disconnect();
60 | }
61 | }
62 |
63 | main();
64 |
--------------------------------------------------------------------------------
/docs/v6/apps/chrome-extensions.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: Using TestDriver with Chrome Extensions
3 | sidebarTitle: "Chrome Extensions"
4 | description: "Test Chrome extensions with TestDriver"
5 | icon: "puzzle-piece"
6 | ---
7 |
8 | import TestPrereqs from "/snippets/test-prereqs.mdx";
9 |
10 |
19 |
20 | # Testing Chrome extensions with TestDriver
21 |
22 | TestDriver can be used to test Chrome extensions. This guide provides an overview of how to set up and run tests for your Chrome extension using TestDriver.
23 |
24 |
25 |
26 | ## How do I test Chrome extensions?
27 |
28 | TestDriver provides a powerful solution for testing Chrome extensions. With our platform, you can easily create and run tests to ensure your extension functions as expected across different browsers and operating systems.
29 |
30 | ## Getting started
31 |
32 | You can test a preinstalled extension or test by installing an extension. If the extension is in development mode, turn this on by checking the "Developer mode" checkbox in the Chrome extensions page. From there you can load the unpacked extension by selecting the folder where your extension is located. To download the files onto the VM if you are performing a test on cloud hosted runners, see the [Lifecycle](/guide/lifecycle) docs.
33 |
34 | ## Sample test steps
35 |
36 | 1. Load a the Chrome Web Store.
37 | 2. Search for the extension you want to test.
38 | 3. Click on the extension to open its details page.
39 | 4. Click on the "Add to Chrome" button to install the extension.
40 | 5. Wait for the installation to complete.
41 | 6. Click the 'puzzle piece' icon to pin the extension to the toolbar.
42 | 7. Click on the extension icon to open it.
43 |
44 | ---
45 |
46 | ## Now what?
47 |
48 | Once you have installed your extension, simply create a TestDriver test to test any functionality a user would use.
49 |
--------------------------------------------------------------------------------
/agent/lib/subimage/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | const Jimp = require("jimp");
3 | const path = require("path");
4 | const cv = require("./opencv.js");
5 | const { events, emitter } = require("../../events");
6 |
7 | async function findTemplateImage(haystack, needle, threshold) {
8 | try {
9 | const positions = [];
10 |
11 | const imageSource = await Jimp.read(path.join(haystack));
12 | const imageTemplate = await Jimp.read(path.join(needle));
13 |
14 | const templ = cv.matFromImageData(imageTemplate.bitmap);
15 | let src = cv.matFromImageData(imageSource.bitmap);
16 | let processedImage = new cv.Mat();
17 | let mask = new cv.Mat();
18 |
19 | cv.matchTemplate(src, templ, processedImage, cv.TM_CCOEFF_NORMED, mask);
20 |
21 | cv.threshold(
22 | processedImage,
23 | processedImage,
24 | threshold,
25 | 1,
26 | cv.THRESH_BINARY,
27 | );
28 | processedImage.convertTo(processedImage, cv.CV_8UC1);
29 | let contours = new cv.MatVector();
30 | let hierarchy = new cv.Mat();
31 |
32 | cv.findContours(
33 | processedImage,
34 | contours,
35 | hierarchy,
36 | cv.RETR_EXTERNAL,
37 | cv.CHAIN_APPROX_SIMPLE,
38 | );
39 |
40 | for (let i = 0; i < contours.size(); ++i) {
41 | let [x, y] = contours.get(i).data32S; // Contains the points
42 | positions.push({
43 | x,
44 | y,
45 | height: templ.rows,
46 | width: templ.cols,
47 | centerX: x + templ.cols / 2,
48 | centerY: y + templ.rows / 2,
49 | });
50 | }
51 |
52 | src.delete();
53 | mask.delete();
54 | templ.delete();
55 |
56 | return positions;
57 | } catch (err) {
58 | emitter.emit(events.subimage.error, {
59 | error: err,
60 | message: "OpenCV threw an error",
61 | haystack,
62 | needle,
63 | threshold,
64 | });
65 | }
66 | }
67 |
68 | function onRuntimeInitialized() {}
69 |
70 | // Finally, load the open.js as before. The function `onRuntimeInitialized` contains our program.
71 | Module = {
72 | onRuntimeInitialized,
73 | };
74 |
75 | module.exports = {
76 | findTemplateImage,
77 | };
78 |
--------------------------------------------------------------------------------
/examples/run-tests-with-recording.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Example script showing how to run tests with TestDriver + Dashcam integration
4 | # This script demonstrates the complete flow:
5 | # 1. Start dashcam
6 | # 2. Run tests with Vitest reporter
7 | # 3. Stop and publish dashcam recording
8 | # 4. View results in TestDriver dashboard
9 |
10 | set -e
11 |
12 | echo "======================================"
13 | echo "TestDriver + Dashcam Test Recording"
14 | echo "======================================"
15 | echo ""
16 |
17 | # Check for required environment variables
18 | if [ -z "$TD_API_KEY" ]; then
19 | echo "Error: TD_API_KEY environment variable is required"
20 | echo "Get your API key from: https://console.testdriver.ai/settings/api-keys"
21 | exit 1
22 | fi
23 |
24 | if [ -z "$DASHCAM_PROJECT_ID" ]; then
25 | echo "Warning: DASHCAM_PROJECT_ID not set, replays won't be published"
26 | fi
27 |
28 | # Start Dashcam recording
29 | echo "📹 Starting Dashcam..."
30 | dashcam start
31 |
32 | # Track test logs
33 | echo "📝 Tracking test output..."
34 | dashcam track --name="TestDriver Tests" --type=application --pattern="*.log"
35 |
36 | echo ""
37 |
38 | # Run tests with Vitest and TestDriver reporter
39 | echo "🧪 Running tests..."
40 | npx vitest run --config vitest.config.example.js
41 |
42 | # Capture exit code
43 | TEST_EXIT_CODE=$?
44 |
45 | # Stop dashcam
46 | echo ""
47 | echo "🛑 Stopping Dashcam..."
48 | dashcam stop
49 |
50 | # Publish to dashcam project if configured
51 | if [ -n "$DASHCAM_PROJECT_ID" ]; then
52 | echo "📤 Publishing replay to project: $DASHCAM_PROJECT_ID"
53 | REPLAY_URL=$(dashcam publish -p "$DASHCAM_PROJECT_ID" --json | jq -r '.replayUrl')
54 | echo "✅ Replay URL: $REPLAY_URL"
55 | echo ""
56 | echo "View your test recording at:"
57 | echo "$REPLAY_URL"
58 | else
59 | echo "⚠️ Skipping publish (DASHCAM_PROJECT_ID not set)"
60 | fi
61 |
62 | echo ""
63 | echo "======================================"
64 | echo "📊 View Results"
65 | echo "======================================"
66 | echo "Dashboard: https://console.testdriver.ai/dashboard/test-runs"
67 | echo ""
68 |
69 | # Exit with the same code as tests
70 | exit $TEST_EXIT_CODE
71 |
--------------------------------------------------------------------------------
/test/testdriver/drag-and-drop.test.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * TestDriver SDK - Drag and Drop Test (Vitest)
3 | * Converted from: testdriver/acceptance/drag-and-drop.yaml
4 | */
5 |
6 | import { describe, expect, it } from "vitest";
7 | import { TestDriver } from "../../lib/vitest/hooks.mjs";
8 |
9 | const isLinux = (process.env.TD_OS || "linux") === "linux";
10 |
11 | describe("Drag and Drop Test", () => {
12 | it.skipIf(isLinux)(
13 | 'should drag "New Text Document" to "Recycle Bin"',
14 | async (context) => {
15 | const testdriver = TestDriver(context, { headless: true });
16 | await testdriver.provision.chrome({ url: 'http://testdriver-sandbox.vercel.app/login' });
17 |
18 | //
19 | // Show the desktop
20 | await testdriver.pressKeys(["win", "d"]);
21 |
22 | // Open the context menu
23 | await testdriver.pressKeys(["shift", "f10"]);
24 |
25 | // Hover over "New" in the context menu
26 | const newOption = await testdriver.find(
27 | "New, new option in the open context menu on the desktop",
28 | );
29 | await newOption.hover();
30 |
31 | // Click "Text Document" in the context menu
32 | const textDocOption = await testdriver.find(
33 | "Text Document, text document option in the new submenu of the desktop context menu",
34 | );
35 | await textDocOption.click();
36 |
37 | // Unfocus the "Text Document" text field
38 | await testdriver.pressKeys(["esc"]);
39 |
40 | // Drag the "New Text Document" icon to the "Recycle Bin"
41 | const textDoc = await testdriver.find(
42 | "New Text Document, new text document icon in the center of the desktop",
43 | );
44 | await textDoc.mouseDown();
45 |
46 | const recycleBin = await testdriver.find(
47 | "Recycle Bin, recycle bin icon in the top left corner of the desktop",
48 | );
49 | await recycleBin.mouseUp();
50 |
51 | // Assert "New Text Document" icon is not on the Desktop
52 | const result = await testdriver.assert(
53 | 'the "New Text Document" icon is not visible on the Desktop',
54 | );
55 | expect(result).toBeTruthy();
56 | },
57 | );
58 | });
59 |
--------------------------------------------------------------------------------
/docs/v6/commands/wait-for-text.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "wait-for-text"
3 | sidebarTitle: "wait-for-text"
4 | description: "Wait until the specified text is detected on the screen."
5 | icon: "clock-nine"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/wait-for-text-replay.mdx";
10 | import Example from "/snippets/tests/wait-for-text-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `wait-for-text` command waits until the specified text is detected on the screen. This is useful for ensuring that textual elements are present before proceeding with the next steps in a test.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :-------: | :-------: | :--------------------------------------------------------------------------------------------------------------- |
23 | | `text` | `string` | The text to find on the screen. |
24 | | `timeout` | `number` | (Optional) The duration in milliseconds to wait for the text to appear. Default is `5000` (5 seconds). |
25 | | `method` | `enum` | (Optional) The matching algorithm to use. Possible values are `ai` and `turbo`. Default is `turbo` |
26 | | `invert` | `boolean` | (Optional) If set to `true`, the command will wait until the specified text is NOT detected. Default is `false`. |
27 |
28 | ## Example usage
29 |
30 | ```yaml
31 | command: wait-for-text
32 | text: Copyright 2024
33 | timeout: 5000
34 | ```
35 |
36 | ## Protips
37 |
38 | - Use unique and specific text to improve detection accuracy.
39 | - Adjust the `timeout` value based on the expected load time of the text to avoid unnecessary delays.
40 |
41 | ## Gotchas
42 |
43 | - If the text doesn't appear within the specified `timeout`, the command will fail.
44 | - Ensure the text matches exactly, as variations in font size, style, or screen resolution may affect detection accuracy.
45 |
46 | ---
47 |
48 | The `wait-for-text` command is ideal for synchronizing tests with textual elements that may take time to load.
49 |
--------------------------------------------------------------------------------
/docs/v7/commands/wait-for-text.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "wait-for-text"
3 | sidebarTitle: "wait-for-text"
4 | description: "Wait until the specified text is detected on the screen."
5 | icon: "clock-nine"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/wait-for-text-replay.mdx";
10 | import Example from "/snippets/tests/wait-for-text-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `wait-for-text` command waits until the specified text is detected on the screen. This is useful for ensuring that textual elements are present before proceeding with the next steps in a test.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :-------: | :-------: | :--------------------------------------------------------------------------------------------------------------- |
23 | | `text` | `string` | The text to find on the screen. |
24 | | `timeout` | `number` | (Optional) The duration in milliseconds to wait for the text to appear. Default is `5000` (5 seconds). |
25 | | `method` | `enum` | (Optional) The matching algorithm to use. Possible values are `ai` and `turbo`. Default is `turbo` |
26 | | `invert` | `boolean` | (Optional) If set to `true`, the command will wait until the specified text is NOT detected. Default is `false`. |
27 |
28 | ## Example usage
29 |
30 | ```yaml
31 | command: wait-for-text
32 | text: Copyright 2024
33 | timeout: 5000
34 | ```
35 |
36 | ## Protips
37 |
38 | - Use unique and specific text to improve detection accuracy.
39 | - Adjust the `timeout` value based on the expected load time of the text to avoid unnecessary delays.
40 |
41 | ## Gotchas
42 |
43 | - If the text doesn't appear within the specified `timeout`, the command will fail.
44 | - Ensure the text matches exactly, as variations in font size, style, or screen resolution may affect detection accuracy.
45 |
46 | ---
47 |
48 | The `wait-for-text` command is ideal for synchronizing tests with textual elements that may take time to load.
49 |
--------------------------------------------------------------------------------
/examples/sdk-simple-example.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * TestDriver SDK - Simple Example with AWESOME Logs 🎨
5 | *
6 | * A straightforward example showing the beautiful logging in action.
7 | *
8 | * Run: TD_API_KEY=your_key node examples/sdk-simple-example.js
9 | */
10 |
11 | const TestDriver = require("../sdk.js");
12 |
13 | (async () => {
14 | try {
15 | // Create client with logging enabled (default)
16 | const client = new TestDriver(process.env.TD_API_KEY, {
17 | os: "windows",
18 | logging: true, // This enables the awesome logs!
19 | });
20 |
21 | console.log("\n🚀 Starting TestDriver SDK Example...\n");
22 |
23 | // Connect to sandbox - you'll see: 🔌 Connected
24 | await client.connect({ headless: true });
25 |
26 | // Navigate to a test page
27 | await client.focusApplication("Google Chrome");
28 | await client.type("https://example.com");
29 | await client.pressKeys(["enter"]);
30 |
31 | // Wait for page load
32 | await new Promise((resolve) => setTimeout(resolve, 3000));
33 |
34 | // Find an element - you'll see: 🔍 Found "heading..." · 📍 (x, y) · ⏱️ XXXms · ⚡ cached
35 | const heading = await client.find("heading that says Example Domain");
36 |
37 | if (heading.found()) {
38 | // Click the element - you'll see: 👆 Click "heading..."
39 | await heading.click();
40 | }
41 |
42 | // Find another element
43 | const link = await client.find("More information link");
44 |
45 | if (link.found()) {
46 | // Hover over it - you'll see: 👉 Hover "More information link"
47 | await link.hover();
48 | }
49 |
50 | // Scroll down - you'll see: 📜 Scroll
51 | await client.scroll("down", 300);
52 |
53 | // Take a screenshot - you'll see: 📸 Screenshot
54 | const screenshot = await client.screenshot();
55 | console.log(`\n📸 Screenshot captured (${screenshot.length} bytes)\n`);
56 |
57 | // Disconnect - you'll see: 🔌 Disconnected
58 | await client.disconnect();
59 |
60 | console.log("\n✅ Example completed successfully!\n");
61 | } catch (error) {
62 | console.error(`\n❌ Error: ${error.message}\n`);
63 | process.exit(1);
64 | }
65 | })();
66 |
--------------------------------------------------------------------------------
/test/manual/verify-element-api.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Verify Element class API
5 | * Tests that all expected methods and properties are available
6 | */
7 |
8 | async function verify() {
9 | // Create a test element instance - need to access Element class directly
10 | const { Element } = require("./sdk.js");
11 | const elem = new Element("test", null, null, null);
12 |
13 | console.log("Verifying Element class API...\n");
14 |
15 | // Check methods exist
16 | const methods = [
17 | "find",
18 | "found",
19 | "click",
20 | "hover",
21 | "doubleClick",
22 | "rightClick",
23 | "mouseDown",
24 | "mouseUp",
25 | "getCoordinates",
26 | "getResponse",
27 | ];
28 |
29 | let passed = 0;
30 | let failed = 0;
31 |
32 | console.log("Testing methods:");
33 | methods.forEach((method) => {
34 | if (typeof elem[method] === "function") {
35 | console.log(` ✓ ${method}()`);
36 | passed++;
37 | } else {
38 | console.log(` ✗ ${method}() - MISSING`);
39 | failed++;
40 | }
41 | });
42 |
43 | // Test property getters (should return null when element not found)
44 | const props = [
45 | "x",
46 | "y",
47 | "centerX",
48 | "centerY",
49 | "width",
50 | "height",
51 | "confidence",
52 | "screenshot",
53 | "boundingBox",
54 | "text",
55 | "label",
56 | ];
57 |
58 | console.log("\nTesting properties (should be null when not found):");
59 | props.forEach((prop) => {
60 | if (prop in elem) {
61 | const value = elem[prop];
62 | if (value === null) {
63 | console.log(` ✓ ${prop} = null`);
64 | passed++;
65 | } else {
66 | console.log(` ✗ ${prop} = ${value} (expected null)`);
67 | failed++;
68 | }
69 | } else {
70 | console.log(` ✗ ${prop} - MISSING`);
71 | failed++;
72 | }
73 | });
74 |
75 | console.log("\n" + "=".repeat(50));
76 | console.log(`Results: ${passed} passed, ${failed} failed`);
77 | console.log("=".repeat(50));
78 |
79 | if (failed > 0) {
80 | process.exit(1);
81 | } else {
82 | console.log("\n✅ All Element API verification tests passed!");
83 | }
84 | }
85 |
86 | verify().catch((err) => {
87 | console.error("Error:", err);
88 | process.exit(1);
89 | });
90 |
--------------------------------------------------------------------------------
/.github/workflows/acceptance.yaml:
--------------------------------------------------------------------------------
1 | name: Acceptance Tests
2 | permissions:
3 | id-token: write
4 | contents: write
5 | pull-requests: write
6 | checks: write
7 | on:
8 | pull_request:
9 | branches: [ main ]
10 |
11 | concurrency:
12 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
13 | cancel-in-progress: true
14 |
15 | jobs:
16 | test-linux:
17 | runs-on: ubuntu-latest
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 |
22 | - name: Setup Node.js
23 | uses: actions/setup-node@v4
24 | with:
25 | node-version: '20'
26 | cache: 'npm'
27 |
28 | - name: Install dependencies
29 | run: npm ci
30 |
31 | - name: Run Linux tests
32 | run: npx vitest run test/testdriver/*.test.mjs
33 | env:
34 | TD_API_KEY: ${{ secrets.TD_API_KEY }}
35 | TD_OS: linux
36 |
37 | - name: Upload test results to Sentry Prevent
38 | if: ${{ !cancelled() }}
39 | uses: getsentry/prevent-action@v0
40 |
41 | - name: Publish Test Results
42 | uses: EnricoMi/publish-unit-test-result-action@v2
43 | if: always()
44 | with:
45 | files: test-report.junit.xml
46 | comment_mode: always
47 | check_name: Test Results (Linux)
48 |
49 | test-windows:
50 | runs-on: ubuntu-latest
51 | if: contains(github.event.pull_request.labels.*.name, 'test-windows')
52 |
53 | steps:
54 | - uses: actions/checkout@v4
55 |
56 | - name: Setup Node.js
57 | uses: actions/setup-node@v4
58 | with:
59 | node-version: '20'
60 | cache: 'npm'
61 |
62 | - name: Install dependencies
63 | run: npm ci
64 |
65 | - name: Run Windows tests
66 | run: npx vitest run test/testdriver/*.test.mjs
67 | env:
68 | TD_API_KEY: ${{ secrets.TD_API_KEY }}
69 | TD_OS: windows
70 |
71 | - name: Upload test results to Sentry Prevent
72 | if: ${{ !cancelled() }}
73 | uses: getsentry/prevent-action@v0
74 |
75 | - name: Publish Test Results
76 | uses: EnricoMi/publish-unit-test-result-action@v2
77 | if: always()
78 | with:
79 | files: test-report.junit.xml
80 | comment_mode: always
81 | check_name: Test Results (Windows)
82 |
--------------------------------------------------------------------------------
/docs/v6/commands/if.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "if"
3 | sidebarTitle: "if"
4 | description: "Conditionally execute commands based on a specified condition."
5 | icon: "split"
6 | mode: "wide"
7 | ---
8 |
9 | ## Description
10 |
11 | The `if` command is used to conditionally execute a set of commands based on whether a specified condition evaluates to true or false. If the condition is true, the commands in the `then` block are executed. Otherwise, the commands in the `else` block are executed.
12 |
13 | ## Arguments
14 |
15 | | Argument | Type | Description |
16 | | :---------: | :----------------: | :---------------------------------------------------------------------------- |
17 | | `condition` | `string` | The condition to evaluate. |
18 | | `then` | `list of commands` | The commands to run if the condition is true. |
19 | | `else` | `list of commands` | The commands to run if the condition is false. It is an **optional command**. |
20 |
21 | ## Example usage
22 |
23 | ```yaml
24 | - prompt: click on sign up, if cookie banner appears close it
25 | commands:
26 | - command: if
27 | condition: the text "Accept Cookies" is visible
28 | then:
29 | - command: hover-text
30 | text: Accept Cookies
31 | description: cookie banner button
32 | action: click
33 | else:
34 | - command: hover-text
35 | text: Sign Up
36 | description: link in the header
37 | action: click
38 | ```
39 |
40 | ## Protips
41 |
42 | - Ensure the `condition` is clearly defined and evaluates correctly to avoid unexpected behavior.
43 | - Use descriptive `then` and `else` blocks to handle both outcomes effectively.
44 |
45 | ## Gotchas
46 |
47 | - The else block is optional, if the `condition` fails and there is no `else` block, the execution will continue from the next available command.
48 | - If the `condition` is invalid or can't be evaluated, the command will fail.
49 | - Ensure all commands in the `then` and `else` blocks are valid and properly formatted.
50 |
51 | ---
52 |
53 | The `if` command is useful for creating conditional logic in your tests, allowing for more dynamic and flexible workflows.
54 |
--------------------------------------------------------------------------------
/docs/v7/commands/if.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "if"
3 | sidebarTitle: "if"
4 | description: "Conditionally execute commands based on a specified condition."
5 | icon: "split"
6 | mode: "wide"
7 | ---
8 |
9 | ## Description
10 |
11 | The `if` command is used to conditionally execute a set of commands based on whether a specified condition evaluates to true or false. If the condition is true, the commands in the `then` block are executed. Otherwise, the commands in the `else` block are executed.
12 |
13 | ## Arguments
14 |
15 | | Argument | Type | Description |
16 | | :---------: | :----------------: | :---------------------------------------------------------------------------- |
17 | | `condition` | `string` | The condition to evaluate. |
18 | | `then` | `list of commands` | The commands to run if the condition is true. |
19 | | `else` | `list of commands` | The commands to run if the condition is false. It is an **optional command**. |
20 |
21 | ## Example usage
22 |
23 | ```yaml
24 | - prompt: click on sign up, if cookie banner appears close it
25 | commands:
26 | - command: if
27 | condition: the text "Accept Cookies" is visible
28 | then:
29 | - command: hover-text
30 | text: Accept Cookies
31 | description: cookie banner button
32 | action: click
33 | else:
34 | - command: hover-text
35 | text: Sign Up
36 | description: link in the header
37 | action: click
38 | ```
39 |
40 | ## Protips
41 |
42 | - Ensure the `condition` is clearly defined and evaluates correctly to avoid unexpected behavior.
43 | - Use descriptive `then` and `else` blocks to handle both outcomes effectively.
44 |
45 | ## Gotchas
46 |
47 | - The else block is optional, if the `condition` fails and there is no `else` block, the execution will continue from the next available command.
48 | - If the `condition` is invalid or can't be evaluated, the command will fail.
49 | - Ensure all commands in the `then` and `else` blocks are valid and properly formatted.
50 |
51 | ---
52 |
53 | The `if` command is useful for creating conditional logic in your tests, allowing for more dynamic and flexible workflows.
54 |
--------------------------------------------------------------------------------
/docs/v6/commands/wait-for-image.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "wait-for-image"
3 | sidebarTitle: "wait-for-image"
4 | description: "Wait until an image matching the description is detected on the screen."
5 | icon: "clock-three-thirty"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/wait-for-image-replay.mdx";
10 | import Example from "/snippets/tests/wait-for-image-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `wait-for-image` command waits until the specified image is detected on the screen. This is useful for ensuring that visual elements are present before proceeding with the next steps in a test.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :-----------: | :-------: | :---------------------------------------------------------------------------------------------------------------- |
23 | | `description` | `string` | A description of the image. |
24 | | `timeout` | `number` | (Optional) The duration in milliseconds to wait for the image to appear. Default is `10000` (10 seconds). |
25 | | `invert` | `boolean` | (Optional) If set to `true`, the command will wait until the specified image is NOT detected. Default is `false`. |
26 |
27 | ## Example usage
28 |
29 | ```yaml
30 | command: wait-for-image
31 | description: trash icon
32 | timeout: 5000
33 | ```
34 |
35 | ## Protips
36 |
37 | - Use clear and concise descriptions for the image to improve detection accuracy.
38 | - Adjust the `timeout` value based on the expected load time of the image to avoid unnecessary delays.
39 |
40 |
41 | If you are unable to land on an accurate description or experiencing flaky
42 | results, try to upload the image to [chatgpt](https://chatgpt.com) and ask it
43 | to describe the image and use the generated `description` to get better
44 | accuracy and determinism.
45 |
46 |
47 | ## Gotchas
48 |
49 | - If the image doesn't appear within the specified `timeout`, the command will fail.
50 | - Ensure the description accurately represents the image to avoid detection issues.
51 |
52 | ---
53 |
54 | The `wait-for-image` command is ideal for synchronizing tests with visual elements that may take time to load.
55 |
--------------------------------------------------------------------------------
/docs/v7/commands/wait-for-image.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "wait-for-image"
3 | sidebarTitle: "wait-for-image"
4 | description: "Wait until an image matching the description is detected on the screen."
5 | icon: "clock-three-thirty"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/wait-for-image-replay.mdx";
10 | import Example from "/snippets/tests/wait-for-image-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `wait-for-image` command waits until the specified image is detected on the screen. This is useful for ensuring that visual elements are present before proceeding with the next steps in a test.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :-----------: | :-------: | :---------------------------------------------------------------------------------------------------------------- |
23 | | `description` | `string` | A description of the image. |
24 | | `timeout` | `number` | (Optional) The duration in milliseconds to wait for the image to appear. Default is `10000` (10 seconds). |
25 | | `invert` | `boolean` | (Optional) If set to `true`, the command will wait until the specified image is NOT detected. Default is `false`. |
26 |
27 | ## Example usage
28 |
29 | ```yaml
30 | command: wait-for-image
31 | description: trash icon
32 | timeout: 5000
33 | ```
34 |
35 | ## Protips
36 |
37 | - Use clear and concise descriptions for the image to improve detection accuracy.
38 | - Adjust the `timeout` value based on the expected load time of the image to avoid unnecessary delays.
39 |
40 |
41 | If you are unable to land on an accurate description or experiencing flaky
42 | results, try to upload the image to [chatgpt](https://chatgpt.com) and ask it
43 | to describe the image and use the generated `description` to get better
44 | accuracy and determinism.
45 |
46 |
47 | ## Gotchas
48 |
49 | - If the image doesn't appear within the specified `timeout`, the command will fail.
50 | - Ensure the description accurately represents the image to avoid detection issues.
51 |
52 | ---
53 |
54 | The `wait-for-image` command is ideal for synchronizing tests with visual elements that may take time to load.
55 |
--------------------------------------------------------------------------------
/docs/v7/_drafts/awesome-logs-quick-ref.mdx:
--------------------------------------------------------------------------------
1 | # AWESOME Logs - Quick Reference 🎨
2 |
3 | ## At a Glance
4 |
5 | Your TestDriver SDK now has **beautiful, emoji-rich logs** with great DX!
6 |
7 | ### What You'll See
8 |
9 | ```
10 | [2.34s] 🔍 Found "submit button" · 📍 (682, 189) · ⏱️ 167ms · ⚡ cached
11 | [2.51s] 👆 Click "submit button"
12 | [2.67s] 👉 Hover "menu item"
13 | [2.89s] ⌨️ Type → hello world
14 | [3.12s] ✅ Assert "page title correct" · ✓ PASSED · ⏱️ 45ms
15 | ```
16 |
17 | ## Quick Emoji Guide
18 |
19 | | Emoji | Meaning |
20 | |-------|---------|
21 | | 🔍 | Finding element |
22 | | 👆 | Click |
23 | | 👉 | Hover |
24 | | ⌨️ | Type |
25 | | 📜 | Scroll |
26 | | ✅ | Success/Passed |
27 | | ❌ | Error/Failed |
28 | | ⚡ | Cache hit |
29 | | 💤 | Cache miss |
30 | | 📍 | Coordinates |
31 | | ⏱️ | Duration |
32 | | 🔌 | Connect/Disconnect |
33 | | 📸 | Screenshot |
34 |
35 | ## Performance Colors
36 |
37 | Duration times are color-coded:
38 |
39 | - **🟢 < 100ms** - Fast
40 | - **🟡 100-500ms** - Acceptable
41 | - **🔴 > 500ms** - Slow
42 |
43 | ## Enable/Disable
44 |
45 | ```javascript
46 | // Enabled by default
47 | const client = new TestDriver(apiKey, {
48 | logging: true
49 | });
50 |
51 | // Disable if needed
52 | client.setLogging(false);
53 | ```
54 |
55 | ## Debug Mode
56 |
57 | For detailed cache information:
58 |
59 | ```bash
60 | VERBOSE=true node your-test.js
61 | ```
62 |
63 | You'll see:
64 | - Cache strategy (image/text)
65 | - Similarity scores
66 | - Pixel diff percentages
67 | - Cache age
68 |
69 | ## Examples
70 |
71 | Try these:
72 |
73 | ```bash
74 | # Simple example
75 | TD_API_KEY=your_key node examples/sdk-simple-example.js
76 |
77 | # Full demo
78 | TD_API_KEY=your_key node examples/sdk-awesome-logs-demo.js
79 |
80 | # Cache demo with debug info
81 | TD_API_KEY=your_key VERBOSE=true node examples/sdk-cache-thresholds.js
82 | ```
83 |
84 | ## Custom Formatting
85 |
86 | ```javascript
87 | const { formatter } = require('testdriverai/sdk-log-formatter');
88 |
89 | console.log(formatter.formatHeader('My Test', '🧪'));
90 | console.log(formatter.formatAction('click', 'button'));
91 | console.log(formatter.formatProgress(3, 10, 'Processing'));
92 | ```
93 |
94 | ## Full Documentation
95 |
96 | See [SDK_AWESOME_LOGS.md](./SDK_AWESOME_LOGS.md) for complete documentation.
97 |
98 | ---
99 |
100 | **Enjoy your AWESOME logs! 🎨✨**
101 |
--------------------------------------------------------------------------------
/docs/v6/guide/code.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Custom Code in TestDriver"
3 | sidebarTitle: "Custom Code"
4 | description: "Learn how to integrate custom Node.js scripts into your TestDriver workflows for dynamic testing."
5 | icon: "code"
6 | ---
7 |
8 | TestDriver allows you to execute custom **Node.js** scripts and shell scripts within your test workflows using the [`exec`](/commands/exec) command. This feature, introduced in version `5.1.0`, enables you to integrate custom logic, such as generating one-time passwords (OTPs), hitting APIs, or performing other dynamic operations, directly into your tests.
9 |
10 | ## Key features
11 |
12 | 1. **Run Node.js Scripts**:
13 |
14 | - Execute custom JavaScript code within your test steps.
15 | - Use NPM modules to extend functionality.
16 |
17 | 2. **Dynamic Outputs**:
18 |
19 | - Store the result of your script in a variable for use in subsequent steps.
20 |
21 | 3. **NPM Support**:
22 |
23 | - Install and use NPM packages in your scripts.
24 |
25 | ## Updated example: One-time password (OTP) validator
26 |
27 | This example demonstrates how to generate a one-time password (OTP) using the `totp-generator` NPM package and use it in a test.
28 |
29 | ```yaml verify-otp.yaml
30 | version: 6.0.0
31 | steps:
32 | - commands:
33 | - command: exec
34 | lang: pwsh
35 | code: |
36 | npm install totp-generator
37 | - command: exec
38 | lang: js
39 | output: totp
40 | code: |
41 | const { TOTP } = require("totp-generator");
42 | let otp = TOTP.generate("JBSWY3DPEB3W64TMMQQQ").otp;
43 | console.log(otp);
44 | result = otp;
45 | - command: type
46 | text: ${OUTPUT.totp}
47 | ```
48 |
49 | ## Additional details
50 |
51 | - The [`exec`](/commands/exec) command now takes a `lang` argument with supported values `js` or `pwsh`.
52 | - `js` code is executed in a Node.js [VM](https://nodejs.org/api/vm.html) module internally.
53 | - `pwsh` code is executed in the PowerShell on the runner.
54 |
55 |
56 | The `result` variable is already available in your script, overwrite it to store the output as shown in the example.
57 |
58 | The `output`argument is assigned automatically by setting `result = somestringvalue` in the script you run.
59 |
60 |
61 |
62 | ## Protips
63 |
64 | - Always assign the output of your script to the `result` variable.
65 | - Ensure all required NPM packages are installed locally and in the `prerun` script when using GitHub Actions.
66 |
--------------------------------------------------------------------------------
/agent/lib/generator.js:
--------------------------------------------------------------------------------
1 | // parses markdown content to find code blocks, and then extracts yaml from those code blocks
2 | const yaml = require("js-yaml");
3 | const pkg = require("../../package.json");
4 | const session = require("./session");
5 | const theme = require("./theme");
6 | // do the actual parsing
7 | // this library is very strict
8 | // note that errors are sent to the AI will it may self-heal
9 | const manualToYml = async function (inputArgs) {
10 | // input is like `command=click x=100 y=200 z='this is a string'`
11 | // convert this to json
12 |
13 | const pattern = /(\w+)=('[^']*'|[^\s]+)/g;
14 |
15 | let match;
16 | let json = {};
17 |
18 | while ((match = pattern.exec(inputArgs)) !== null) {
19 | const key = match[1];
20 | const value = match[2].replace(/'/g, ""); // Remove single quotes if present
21 | json[key] = value;
22 | }
23 |
24 | json = {
25 | commands: [json],
26 | };
27 |
28 | // use yml dump to convert json to yml
29 | let yml = await yaml.dump(json);
30 |
31 | return yml;
32 | };
33 |
34 | const jsonToManual = function (json, colors = true) {
35 | // Convert the JSON object to key-value pairs
36 | const params = Object.keys(json)
37 | .map((key) => {
38 | let value = json[key];
39 |
40 | // If the value contains spaces, wrap it in single quotes
41 | if (typeof value === "string") {
42 | value = `'${value}'`;
43 | }
44 |
45 | if (colors) {
46 | return `${theme.cyan(key)}=${theme.green(value)}`;
47 | } else {
48 | return `${key}=${value}`;
49 | }
50 | })
51 | .join(" ");
52 |
53 | return params;
54 | };
55 |
56 | const dumpToYML = async function (inputArray, sessionInstance = null) {
57 | // use yml dump to convert json to yml
58 | let yml = await yaml.dump({
59 | version: pkg.version,
60 | session: sessionInstance ? sessionInstance.get() : session.get(),
61 | steps: inputArray,
62 | });
63 |
64 | return yml;
65 | };
66 |
67 | const hydrateFromYML = async function (yml, sessionInstance = null) {
68 | // use yml load to convert yml to json
69 | let json = await yaml.load(yml);
70 |
71 | if (!json) {
72 | json = {};
73 | }
74 |
75 | const sessionToUse = sessionInstance || session;
76 |
77 | if (!json?.session) {
78 | json.session = sessionToUse.get();
79 | }
80 |
81 | sessionToUse.set(json.session);
82 |
83 | return json;
84 | };
85 |
86 | module.exports = {
87 | manualToYml,
88 | dumpToYML,
89 | hydrateFromYML,
90 | jsonToManual,
91 | };
92 |
--------------------------------------------------------------------------------
/docs/v6/tutorials/advanced-test.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Tutorial: Advanced test"
3 | description: "Explore advanced testing techniques and features in TestDriver."
4 | sidebarTitle: "Advanced test"
5 | icon: "flask-round"
6 | ---
7 |
8 | # Advanced test tutorial
9 |
10 | Welcome to the Advanced Test Tutorial. In this guide, we will explore advanced testing techniques and features available in TestDriver. This tutorial assumes you have a basic understanding of TestDriver and its functionalities.
11 |
12 | ## Prerequisites
13 |
14 | Before starting, ensure you have completed the basic TestDriver tutorial and have the following:
15 |
16 | - TestDriver installed and configured.
17 | - A sample project to test.
18 |
19 | ## Advanced features overview
20 |
21 | TestDriver provides several advanced features to enhance your testing capabilities:
22 |
23 | 1. **Custom Assertions**: Create custom assertions to validate specific conditions.
24 | 2. **Parameterized Tests**: Run tests with different sets of input data.
25 | 3. **Test Hooks**: Use hooks to execute code before or after tests.
26 | 4. **Parallel Execution**: Speed up testing by running tests in parallel.
27 |
28 | ## Step-by-step guide
29 |
30 | ### Step 1: Setting up custom assertions
31 |
32 | Custom assertions allow you to define specific conditions for your tests. Here's an example:
33 |
34 | ```python
35 | def assert_custom_condition(value):
36 | assert value > 0, "Value must be greater than 0"
37 | ```
38 |
39 | ### Step 2: Using parameterized tests
40 |
41 | Parameterized tests enable you to test multiple scenarios with different inputs. Example:
42 |
43 | ```python
44 | import pytest
45 |
46 | @pytest.mark.parametrize("input,expected", [(1, True), (0, False)])
47 | def test_is_positive(input, expected):
48 | assert (input > 0) == expected
49 | ```
50 |
51 | ### Step 3: Implementing test hooks
52 |
53 | Test hooks let you execute code before or after tests. Example:
54 |
55 | ```python
56 | def setup_function():
57 | print("Setting up test environment")
58 |
59 | def teardown_function():
60 | print("Cleaning up test environment")
61 | ```
62 |
63 | ### Step 4: Running tests in parallel
64 |
65 | Parallel execution can be achieved using pytest-xdist. Install it with:
66 |
67 | ```bash
68 | pip install pytest-xdist
69 | ```
70 |
71 | Run tests in parallel using:
72 |
73 | ```bash
74 | pytest -n 4
75 | ```
76 |
77 | ## Conclusion
78 |
79 | By leveraging these advanced features, you can create more robust and efficient tests for your projects. Experiment with these techniques to find the best fit for your testing needs.
80 |
81 | Happy testing!
82 |
--------------------------------------------------------------------------------
/agent/lib/censorship.js:
--------------------------------------------------------------------------------
1 | // Shared censorship functionality for sensitive data
2 | // Uses @npmcli/redact for common patterns (URLs, auth headers, etc.)
3 | // Plus custom logic for environment variables and interpolation vars
4 | const { redactLog } = require("@npmcli/redact");
5 |
6 | let interpolationVars = JSON.parse(process.env.TD_INTERPOLATION_VARS || "{}");
7 |
8 | // Handle local `TD_*` variables
9 | for (const [key, value] of Object.entries(process.env)) {
10 | if (key.startsWith("TD_") && key !== "TD_INTERPOLATION_VARS") {
11 | interpolationVars[key] = value;
12 | }
13 | }
14 |
15 | // Function to censor sensitive data in strings using both npmcli/redact and custom logic
16 | const censorSensitiveData = (message) => {
17 | if (typeof message !== "string") {
18 | return message;
19 | }
20 |
21 | // First, use npmcli/redact for common patterns:
22 | // - URLs with credentials (https://user:pass@host)
23 | // - Authorization headers
24 | // - Common secret patterns in logs
25 | let result = redactLog(message);
26 |
27 | // Then apply our custom interpolation variable redaction
28 | // This catches application-specific secrets from TD_* env vars
29 | for (let value of Object.values(interpolationVars)) {
30 | // Avoid replacing vars that are 0 or 1 characters
31 | if (value && value.length >= 2) {
32 | result = result.replaceAll(value, "****");
33 | }
34 | }
35 |
36 | return result;
37 | };
38 |
39 | // Function to censor sensitive data in any value (recursive for objects/arrays)
40 | const censorSensitiveDataDeep = (value) => {
41 | try {
42 | if (typeof value === "string") {
43 | return censorSensitiveData(value);
44 | } else if (Array.isArray(value)) {
45 | return value.map(censorSensitiveDataDeep);
46 | } else if (value && typeof value === "object") {
47 | const result = {};
48 | for (const [key, val] of Object.entries(value)) {
49 | result[key] = censorSensitiveDataDeep(val);
50 | }
51 | return result;
52 | }
53 | return value;
54 | } catch {
55 | // If we hit any error (like circular reference), just return a safe placeholder
56 | return "[Object]";
57 | }
58 | };
59 |
60 | // Function to update interpolation variables (for runtime updates)
61 | const updateInterpolationVars = (newVars) => {
62 | interpolationVars = { ...interpolationVars, ...newVars };
63 | };
64 |
65 | // Function to get current interpolation variables (for debugging)
66 | const getInterpolationVars = () => {
67 | return { ...interpolationVars };
68 | };
69 |
70 | module.exports = {
71 | censorSensitiveData,
72 | censorSensitiveDataDeep,
73 | updateInterpolationVars,
74 | getInterpolationVars,
75 | };
76 |
--------------------------------------------------------------------------------
/interfaces/cli/utils/factory.js:
--------------------------------------------------------------------------------
1 | const BaseCommand = require("../lib/base.js");
2 | const { createCommandDefinitions } = require("../../../agent/interface.js");
3 |
4 | /**
5 | * Creates an oclif command class from a unified command definition
6 | */
7 | function createOclifCommand(commandName) {
8 | // Get the command definition once during class creation
9 | const tempAgent = { workingDir: process.cwd() };
10 | const definitions = createCommandDefinitions(tempAgent);
11 | const commandDef = definitions[commandName];
12 |
13 | const DynamicCommand = class extends BaseCommand {
14 | async run() {
15 | try {
16 | const { args, flags } = await this.parse(this.constructor);
17 |
18 | // Special handling for edit mode
19 | if (commandName === "edit") {
20 | await this.setupAgent(args.file, flags);
21 |
22 | // Build environment for edit mode
23 | await this.agent.buildEnv(flags);
24 |
25 | // Start interactive mode
26 | const ReadlineInterface = require("../../readline.js");
27 | const readlineInterface = new ReadlineInterface(this.agent);
28 | this.agent.readlineInterface = readlineInterface;
29 | await readlineInterface.start();
30 | } else {
31 | // For run and generate commands, use the unified command system
32 | let commandArgs;
33 | if (commandName === "generate") {
34 | // Generate command: pass prompt as first argument
35 | await this.setupAgent(args.prompt, flags);
36 | commandArgs = [args.prompt];
37 | } else {
38 | // Run and other commands use file argument
39 | const fileArg = args.file || args.action || null;
40 | await this.setupAgent(fileArg, flags);
41 | commandArgs = [fileArg];
42 | }
43 |
44 | if (commandName === "run") {
45 | // Set error limit higher for run command
46 | this.agent.errorLimit = 100;
47 | }
48 |
49 | // Execute through unified command system
50 | await this.agent.executeUnifiedCommand(
51 | commandName,
52 | commandArgs,
53 | flags,
54 | );
55 | }
56 | } catch (error) {
57 | console.error(`Error executing ${commandName} command:`, error);
58 | process.exit(1);
59 | }
60 | }
61 | };
62 |
63 | // Set static properties directly on the class
64 | DynamicCommand.description = commandDef?.description || "";
65 | DynamicCommand.args = commandDef?.args || {};
66 | DynamicCommand.flags = commandDef?.flags || {};
67 |
68 | return DynamicCommand;
69 | }
70 |
71 | module.exports = { createOclifCommand };
72 |
--------------------------------------------------------------------------------
/docs/v6/cli/overview.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "TestDriver CLI Flags"
3 | sidebarTitle: "Flags"
4 | description: "Learn about the TestDriver CLI, its commands, and how to use it effectively."
5 | icon: "flag"
6 | ---
7 |
8 | The `testdriverai` CLI is the primary interface for running, managing, and debugging TestDriver scripts. It provides commands to execute tests, validate configurations, and interact with the TestDriver framework.
9 |
10 | ## Usage
11 |
12 | ```bash
13 | npx testdriverai@latest [options]
14 | ```
15 |
16 | ## Available commands
17 |
18 | | Command | Description |
19 | | :----------------------: | :----------------------------------------------------------- |
20 | | [`run`](/commands/run) | Executes a TestDriver test. |
21 | | [`edit`](/commands/edit) | Launch interactive mode. |
22 | | [`help`](/commands/help) | Displays help information for the CLI or a specific command. |
23 |
24 | ## Available Flags
25 |
26 | | Flag | Description |
27 | | :------------------ | :----------------------------------------------------------------------------------------- |
28 | | `--heal` | Launch exploratory mode and attemp to recover if an error or failing state is encountered. |
29 | | `--write` | Ovewrite test file with new commands resulting from agentic testing |
30 | | `--new` | Create a new sandbox environment for the test run. |
31 | | `--summary=` | Output file where AI summary should be saved. |
32 | | `--junit=` | Output file where junit report should be saved. |
33 |
34 | ## Example usage
35 |
36 | ### Running a test script
37 |
38 | ```bash
39 | npx testdriverai@latest run path/to/testdriver.yaml
40 | ```
41 |
42 | This command runs the specified TestDriver script, executing all steps defined in the file.
43 |
44 | ### Initializing a new project
45 |
46 | ```bash
47 | npx testdriverai@latest init
48 | ```
49 |
50 | This command sets up a new TestDriver project in the current directory, creating the necessary folder structure and configuration files.
51 |
52 | ### Getting help
53 |
54 | ```bash
55 | npx testdriverai@latest help
56 | ```
57 |
58 | This command displays general help information. To get help for a specific command, use:
59 |
60 | ```bash
61 | npx testdriverai@latest help
62 | ```
63 |
64 | ## Protips
65 |
66 | - Combine [`run`](/commands/run) and `--headless` with CI/CD pipelines to automate test execution.
67 |
--------------------------------------------------------------------------------
/docs/v7/getting-started/quickstart.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Quick Start"
3 | sidebarTitle: "Quickstart"
4 | description: "Get started with the TestDriver JavaScript SDK in minutes."
5 | icon: "rocket"
6 | mode: "wide"
7 | ---
8 |
9 | TestDriver makes it easy to write automated computer-use tests for web browsers, desktop apps, and more. In this quickstart, you'll write and run your first TestDriver test using Vitest in just a few minutes.
10 |
11 |
12 |
13 |
14 | You will need a TestDriver account to get an API key.
15 |
16 |
23 | Start for free. No credit-card required!
24 |
25 |
26 |
27 |
28 |
29 | Use `npx` to quickly set up an example project:
30 |
31 | ```bash
32 | npx testdriverai@beta init
33 | ```
34 |
35 | This will walk you through creating a new project folder, installing dependencies, and setting up your API key.
36 |
37 |
38 |
39 |
40 |
41 | TestDriver uses Vitest as the test runner. To run your test, use:
42 |
43 | ```bash
44 | npx vitest run
45 | ```
46 |
47 | This will spawn a sandbox, launch Chrome, navigate to the specified URL, and run your test commands.
48 |
49 |
50 |
51 |
52 | ## Next Steps
53 |
54 |
55 |
60 | Everything about TestDriver + Vitest integration
61 |
62 |
63 |
68 | Chrome, VS Code, Electron, and more
69 |
70 |
71 |
76 | All available methods and options
77 |
78 |
79 |
84 | Choose your complexity level
85 |
86 |
87 |
88 | ## Why TestDriver v7?
89 |
90 | The v7 SDK offers advantages over YAML-based testing:
91 |
92 | - ✅ **Type Safety** - Full TypeScript support with IntelliSense
93 | - ✅ **Programmatic Control** - Use variables, loops, and functions
94 | - ✅ **Better Debugging** - Stack traces point to your actual code
95 | - ✅ **Automatic Lifecycle** - Presets handle setup and cleanup
96 | - ✅ **Framework Integration** - Works with Vitest, Jest, Mocha, etc.
97 | - ✅ **Video Replays** - Automatic Dashcam recording included
98 |
--------------------------------------------------------------------------------
/docs/v7/_drafts/dashcam-title-feature.mdx:
--------------------------------------------------------------------------------
1 | # Dashcam Recording Titles
2 |
3 | ## Overview
4 |
5 | Dashcam recordings now automatically use meaningful titles instead of the generic "Dashcam Recording" label.
6 |
7 | ## Automatic Title Generation
8 |
9 | When using TestDriver with Vitest, the dashcam recording title is automatically generated from:
10 | 1. **Test file name** - Extracted from the test file path
11 | 2. **Test name** - The name of the test case
12 |
13 | ### Example
14 |
15 | For a test file `login.test.mjs` with test case `"should login successfully"`:
16 | ```
17 | Recording title: "login - should login successfully"
18 | ```
19 |
20 | For standalone usage without test context:
21 | ```
22 | Recording title: "Recording 2025-12-02 14:30:45"
23 | ```
24 |
25 | ## Custom Titles
26 |
27 | You can set a custom title before starting the dashcam recording:
28 |
29 | ### With Vitest Integration
30 |
31 | ```javascript
32 | import { test } from 'vitest';
33 | import { TestDriver } from 'testdriverai/vitest';
34 |
35 | test('my test', async (context) => {
36 | const testdriver = TestDriver(context, { headless: true });
37 |
38 | // Set custom title before dashcam starts
39 | testdriver.dashcam.setTitle('My Custom Recording Title');
40 |
41 | // Start recording (provision methods start dashcam automatically)
42 | await testdriver.provision.chrome({ url: 'https://example.com' });
43 |
44 | await testdriver.find('button').click();
45 | });
46 | ```
47 |
48 | ### Direct SDK Usage
49 |
50 | ```javascript
51 | const TestDriver = require('testdriverai');
52 |
53 | const client = new TestDriver(process.env.TD_API_KEY);
54 | await client.connect();
55 |
56 | // Set custom title
57 | client.dashcam.setTitle('Integration Test - Payment Flow');
58 |
59 | // Start recording
60 | await client.dashcam.start();
61 |
62 | await client.provision.chrome({ url: 'https://example.com' });
63 | await client.find('Checkout button').click();
64 |
65 | // Stop recording and get URL
66 | const dashcamUrl = await client.dashcam.stop();
67 | console.log('Recording:', dashcamUrl);
68 | ```
69 |
70 | ## Constructor Option
71 |
72 | You can also pass a title when creating the Dashcam instance:
73 |
74 | ```javascript
75 | const { Dashcam } = require('testdriverai/core');
76 |
77 | const dashcam = new Dashcam(client, {
78 | title: 'Custom Title',
79 | autoStart: true
80 | });
81 | ```
82 |
83 | ## Implementation Details
84 |
85 | - **Default title generation** uses test context (`__vitestContext`) when available
86 | - **Test file names** are cleaned (removes `.test`, `.spec`, file extensions)
87 | - **Fallback** to ISO timestamp if no test context is available
88 | - **Title escaping** handles special characters in shell commands properly
89 | - **Cross-platform** support for Windows (PowerShell) and Linux/Mac (bash/zsh)
90 |
--------------------------------------------------------------------------------
/test/manual/test-find-api.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Quick test of the new find() API
5 | * This is a simple smoke test to verify the Element class works
6 | */
7 |
8 | const TestDriver = require("../sdk");
9 |
10 | async function testFindAPI() {
11 | console.log("Testing new find() API...\n");
12 |
13 | const client = new TestDriver("test-key", {
14 | logging: false,
15 | });
16 |
17 | // Test 1: Create element without connecting
18 | console.log("✓ Test 1: Creating Element instance");
19 | const element = client.find("test element");
20 | console.log(" Element description:", element.description);
21 | console.log(" Element found():", element.found());
22 | console.log(" Element coordinates:", element.getCoordinates());
23 |
24 | // Test 2: Verify element methods exist
25 | console.log("\n✓ Test 2: Verifying Element methods");
26 | console.log(" Has find():", typeof element.find === "function");
27 | console.log(" Has click():", typeof element.click === "function");
28 | console.log(" Has hover():", typeof element.hover === "function");
29 | console.log(
30 | " Has doubleClick():",
31 | typeof element.doubleClick === "function",
32 | );
33 | console.log(" Has rightClick():", typeof element.rightClick === "function");
34 | console.log(" Has mouseDown():", typeof element.mouseDown === "function");
35 | console.log(" Has mouseUp():", typeof element.mouseUp === "function");
36 | console.log(" Has found():", typeof element.found === "function");
37 | console.log(
38 | " Has getCoordinates():",
39 | typeof element.getCoordinates === "function",
40 | );
41 |
42 | // Test 3: Verify error handling for clicking unfound element
43 | console.log("\n✓ Test 3: Error handling for unfound element");
44 | try {
45 | await element.click();
46 | console.log(" ❌ Should have thrown error");
47 | } catch (error) {
48 | console.log(" ✓ Correctly throws error:", error.message);
49 | }
50 |
51 | // Test 4: Verify TypeScript types exist (if running from TypeScript)
52 | console.log("\n✓ Test 4: SDK methods");
53 | console.log(" Has find():", typeof client.find === "function");
54 | console.log(
55 | " Has deprecated hoverText():",
56 | typeof client.hoverText === "undefined" ? "not yet connected" : "exists",
57 | );
58 | console.log(
59 | " Has deprecated waitForText():",
60 | typeof client.waitForText === "undefined" ? "not yet connected" : "exists",
61 | );
62 |
63 | console.log("\n✅ All basic tests passed!");
64 | console.log(
65 | "\nNote: Full integration tests require connection to TestDriver sandbox.",
66 | );
67 | console.log("See examples/sdk-find-example.js for complete usage examples.");
68 | }
69 |
70 | testFindAPI().catch((error) => {
71 | console.error("Test failed:", error);
72 | process.exit(1);
73 | });
74 |
--------------------------------------------------------------------------------
/docs/v6/scenarios/log-in.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Login"
3 | sidebarTitle: "Login"
4 | description: "Test login functionality with TestDriver"
5 | icon: "lock"
6 | ---
7 |
8 | import TestPrereqs from "/snippets/test-prereqs.mdx";
9 |
10 | # Testing Login Functionality with TestDriver
11 |
12 | Test login functionality with TestDriver. This scenario demonstrates how to automate testing the login process for a web application using TestDriver.
13 |
14 |
15 |
16 | ## Scenario overview
17 |
18 | 1. Visit the login page of the web application.
19 | 2. Enter the username and password into the respective fields (see [Reusable Snippets](/features/reusable-snippets#How-to Create-and-Use-Reusable-Snippets)).
20 | 3. Click the "Login" button.
21 | 4. Verify that the user is redirected to the dashboard or home page after a successful login.
22 | 5. Optionally, check if the user is logged in by verifying the presence of a logout button or user profile information.
23 |
24 | ### Setup your test environment with predefined credentials.
25 |
26 | This example uses GitHub secret management to store credentials.
27 |
28 |
29 | You can also use the [`exec`](commands/exec) command to fetch a test user
30 | dynamically from a database or API.
31 |
32 |
33 | To use GitHub secrets, create or modify an existing `.env` file in the root of your project and add the following lines:
34 |
35 | ```bash
36 | TD_TEST_USERNAME=your_test_username
37 | TD_TEST_PASSWORD=your_test_password
38 | ```
39 |
40 | 2. Create a test file and use the credentials like this:
41 |
42 | ```yaml login.yaml
43 | version: 6.0.0
44 | steps:
45 | - prompt: Log in to the application
46 | commands:
47 | - command: hover-text
48 | text: Email address
49 | description: email input field label
50 | action: click
51 | - command: type
52 | text: ${TD_USERNAME} # Use environment variable for username
53 | - command: hover-text
54 | text: Password
55 | description: password input field label
56 | action: click
57 | - command: type
58 | text: ${TD_PASSWORD} # Use environment variable for password
59 | - command: hover-text
60 | text: Log In
61 | description: log in button
62 | action: click
63 | ```
64 |
65 | 3. Run the test using the command line:
66 |
67 | ```bash
68 | npx testdriverai@latest run login.yaml
69 | ```
70 |
71 | 4. Watch replays in [Your account](https://app.testdriver.ai)
72 |
73 | ### Conclusion
74 |
75 | In this scenario, we demonstrated how to automate the login process for a web application using TestDriver. By leveraging reusable snippets and environment variables, you can create efficient and maintainable tests for your applications. This approach not only saves time but also ensures that your tests are easily adaptable to changes in the application or test data.
76 |
--------------------------------------------------------------------------------
/docs/v7/_drafts/init-command.mdx:
--------------------------------------------------------------------------------
1 | # TestDriver Init Command
2 |
3 | The `testdriverai init` command scaffolds a complete TestDriver.ai project with Vitest integration, similar to `npm init playwright`.
4 |
5 | ## What it creates
6 |
7 | When you run `testdriverai init`, it automatically sets up:
8 |
9 | ### 1. **package.json**
10 | - Creates a new package.json if one doesn't exist
11 | - Sets `"type": "module"` for ES modules
12 | - Adds test scripts:
13 | - `npm test` - Run tests once
14 | - `npm run test:watch` - Run tests in watch mode
15 | - `npm run test:ui` - Open Vitest UI
16 |
17 | ### 2. **Vitest Configuration** (`vitest.config.js`)
18 | - Configures TestDriver plugin
19 | - Sets appropriate timeouts (120s for test and hook timeouts)
20 |
21 | ### 3. **Example Test** (`tests/example.test.js`)
22 | - Sample test using the Vitest SDK
23 | - Demonstrates basic TestDriver functionality:
24 | - Navigating to a page
25 | - Finding elements
26 | - Using expect assertions
27 |
28 | ### 4. **GitHub Actions Workflow** (`.github/workflows/testdriver.yml`)
29 | - Ready-to-use CI/CD pipeline
30 | - Runs on push to main/master and pull requests
31 | - Automatically installs dependencies and runs tests
32 | - Uploads test results as artifacts
33 | - Uses `TD_API_KEY` from GitHub secrets
34 |
35 | ### 5. **npm install**
36 | - Automatically installs dependencies:
37 | - `vitest` - Test runner
38 | - `testdriverai` - TestDriver SDK
39 |
40 | ## Usage
41 |
42 | ```bash
43 | # Initialize a new project
44 | testdriverai init
45 |
46 | # The command will:
47 | # 1. Create package.json (if needed)
48 | # 2. Create test files and config
49 | # 3. Create GitHub Actions workflow
50 | # 4. Install dependencies
51 | ```
52 |
53 | ## Next Steps After Init
54 |
55 | 1. **Set your API key:**
56 | ```bash
57 | export TD_API_KEY=your_api_key
58 | ```
59 |
60 | 2. **Run your tests:**
61 | ```bash
62 | npm test
63 | ```
64 |
65 | 3. **For CI/CD:**
66 | - Go to your GitHub repository settings
67 | - Navigate to: Settings → Secrets → Actions
68 | - Add a new repository secret named `TD_API_KEY`
69 |
70 | ## Testing
71 |
72 | Run the test script to verify the init command:
73 |
74 | ```bash
75 | ./test-init.sh
76 | ```
77 |
78 | This will:
79 | - Create a test project
80 | - Verify all files are created correctly
81 | - Verify dependencies are installed
82 | - Clean up afterward
83 |
84 | ## Implementation Details
85 |
86 | ### Files
87 | - [agent/interface.js](agent/interface.js#L258-L267) - Command definition
88 | - [interfaces/cli/commands/init.js](interfaces/cli/commands/init.js) - Command implementation
89 | - [test-init.sh](test-init.sh) - Test script
90 |
91 | ### Key Features
92 | - Idempotent: Safe to run multiple times, skips existing files
93 | - Automatic npm install with error handling
94 | - Creates proper directory structure
95 | - Ready-to-use CI/CD configuration
96 |
--------------------------------------------------------------------------------
/docs/v6/commands/scroll.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "scroll"
3 | sidebarTitle: "scroll"
4 | description: "Scroll the screen in a specified direction using the mouse wheel."
5 | icon: "computer-mouse"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/scroll-replay.mdx";
10 | import Example from "/snippets/tests/scroll-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `scroll` command is used to scroll the screen in a specified direction using the mouse wheel. This is useful for navigating through content that extends beyond the visible area.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :---------: | :------: | :------------------------------------------------------------------------------------ |
23 | | `direction` | `string` | The direction to scroll. Available directions are: `up`, `down`, `left`, `right`. |
24 | | `method` | `string` | The method to scroll. Available methods are: `mouse`, `keyboard`. Defaults to `mouse` |
25 | | `amount` | `number` | (Optional) Pixels to scroll. If not specified, a default amount of 300 is used. |
26 |
27 |
28 | The `amount` should only be used when the `method` is mouse. If the `method`
29 | is keyboard, it is redundant as the action performed is pressing the `pageup`
30 | or `pagedown` keys.
31 |
32 |
33 | ## Example usage
34 |
35 | ```yaml
36 | - command: scroll
37 | direction: down
38 | method: mouse
39 | amount: 100
40 | ```
41 |
42 | ## Protips
43 |
44 | - Be sure to focus the application and frame you are trying to scroll before using the command.
45 | - Use precise `amount` values to control the scrolling distance for better accuracy in your tests.
46 | - Combine the `scroll` command with other commands like `hover-text` or `match-image` to interact with elements that are initially off-screen.
47 | - If you wish to scroll dynamically till a specific text or image appears, use [scroll-until-text](/commands/scroll-until-text) or [scroll-until-image](/commands/scroll-until-image) commands.
48 | - The most reliable way to scroll to the top or bottom of the page is to use the `home` and `end` keys:
49 |
50 | ```yaml
51 | # Scroll to the top of the page
52 | - command: press-keys
53 | keys:
54 | - home
55 | # Scroll to the bottom of the page
56 | - command: press-keys
57 | keys:
58 | - end
59 | ```
60 |
61 | ## Gotchas
62 |
63 | - Scrolling too far may cause elements to move out of view. Adjust the `amount` value as needed.
64 | - Ensure the application or browser window is in focus when using this command to avoid unexpected behavior.
65 |
66 | ## Notes
67 |
68 | - The `scroll` command is ideal for navigating through long pages or lists during automated tests.
69 | - This command supports both vertical and horizontal scrolling for flexible navigation.
70 |
--------------------------------------------------------------------------------
/docs/v7/commands/scroll.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "scroll"
3 | sidebarTitle: "scroll"
4 | description: "Scroll the screen in a specified direction using the mouse wheel."
5 | icon: "computer-mouse"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/scroll-replay.mdx";
10 | import Example from "/snippets/tests/scroll-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `scroll` command is used to scroll the screen in a specified direction using the mouse wheel. This is useful for navigating through content that extends beyond the visible area.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :---------: | :------: | :------------------------------------------------------------------------------------ |
23 | | `direction` | `string` | The direction to scroll. Available directions are: `up`, `down`, `left`, `right`. |
24 | | `method` | `string` | The method to scroll. Available methods are: `mouse`, `keyboard`. Defaults to `mouse` |
25 | | `amount` | `number` | (Optional) Pixels to scroll. If not specified, a default amount of 300 is used. |
26 |
27 |
28 | The `amount` should only be used when the `method` is mouse. If the `method`
29 | is keyboard, it is redundant as the action performed is pressing the `pageup`
30 | or `pagedown` keys.
31 |
32 |
33 | ## Example usage
34 |
35 | ```yaml
36 | - command: scroll
37 | direction: down
38 | method: mouse
39 | amount: 100
40 | ```
41 |
42 | ## Protips
43 |
44 | - Be sure to focus the application and frame you are trying to scroll before using the command.
45 | - Use precise `amount` values to control the scrolling distance for better accuracy in your tests.
46 | - Combine the `scroll` command with other commands like `hover-text` or `match-image` to interact with elements that are initially off-screen.
47 | - If you wish to scroll dynamically till a specific text or image appears, use [scroll-until-text](/commands/scroll-until-text) or [scroll-until-image](/commands/scroll-until-image) commands.
48 | - The most reliable way to scroll to the top or bottom of the page is to use the `home` and `end` keys:
49 |
50 | ```yaml
51 | # Scroll to the top of the page
52 | - command: press-keys
53 | keys:
54 | - home
55 | # Scroll to the bottom of the page
56 | - command: press-keys
57 | keys:
58 | - end
59 | ```
60 |
61 | ## Gotchas
62 |
63 | - Scrolling too far may cause elements to move out of view. Adjust the `amount` value as needed.
64 | - Ensure the application or browser window is in focus when using this command to avoid unexpected behavior.
65 |
66 | ## Notes
67 |
68 | - The `scroll` command is ideal for navigating through long pages or lists during automated tests.
69 | - This command supports both vertical and horizontal scrolling for flexible navigation.
70 |
--------------------------------------------------------------------------------
/test/manual/debug-locate-response.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Debug script to inspect the full locate API response
5 | * Run this with: TD_API_KEY=your_key node debug-locate-response.js
6 | */
7 |
8 | const TestDriverSDK = require("./sdk.js");
9 |
10 | async function debugLocateResponse() {
11 | const client = new TestDriverSDK(process.env.TD_API_KEY);
12 |
13 | try {
14 | console.log("Connecting to sandbox (Linux)...");
15 | await client.connect({ headless: true });
16 |
17 | console.log("Opening a test page...");
18 | await client.focusApplication("Google Chrome");
19 | await client.type("https://example.com");
20 | await client.pressKeys(["enter"]);
21 |
22 | // Wait for page to load
23 | await new Promise((resolve) => setTimeout(resolve, 3000));
24 |
25 | console.log("\nFinding an element to inspect the response...");
26 | const element = await client.find("the heading that says Example Domain");
27 |
28 | console.log("\n=".repeat(60));
29 | console.log("FULL LOCATE API RESPONSE:");
30 | console.log("=".repeat(60));
31 |
32 | const response = element.getResponse();
33 | console.log(JSON.stringify(response, null, 2));
34 |
35 | console.log("\n=".repeat(60));
36 | console.log("RESPONSE KEYS:");
37 | console.log("=".repeat(60));
38 |
39 | if (response) {
40 | Object.keys(response).forEach((key) => {
41 | const value = response[key];
42 | const type = Array.isArray(value) ? "array" : typeof value;
43 | const preview =
44 | typeof value === "string" && value.length > 100
45 | ? `${value.substring(0, 100)}... (${value.length} chars)`
46 | : typeof value === "object"
47 | ? JSON.stringify(value)
48 | : value;
49 |
50 | console.log(` ${key} (${type}): ${preview}`);
51 | });
52 | }
53 |
54 | console.log("\n=".repeat(60));
55 | console.log("ELEMENT PROPERTIES:");
56 | console.log("=".repeat(60));
57 | console.log(" found:", element.found());
58 | console.log(" x:", element.x);
59 | console.log(" y:", element.y);
60 | console.log(" centerX:", element.centerX);
61 | console.log(" centerY:", element.centerY);
62 | console.log(" width:", element.width);
63 | console.log(" height:", element.height);
64 | console.log(" confidence:", element.confidence);
65 | console.log(" text:", element.text);
66 | console.log(" label:", element.label);
67 | console.log(
68 | " screenshot:",
69 | element.screenshot ? `${element.screenshot.length} chars` : null,
70 | );
71 | console.log(" boundingBox:", element.boundingBox);
72 |
73 | await client.disconnect();
74 | } catch (error) {
75 | console.error("Error:", error.message);
76 | console.error(error.stack);
77 | await client.disconnect();
78 | process.exit(1);
79 | }
80 | }
81 |
82 | debugLocateResponse();
83 |
--------------------------------------------------------------------------------
/docs/v6/commands/scroll-until-text.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "scroll-until-text"
3 | sidebarTitle: "scroll-until-text"
4 | description: "Scroll the screen until the specified text is found."
5 | icon: "scroll"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/scroll-until-text-replay.mdx";
10 | import Example from "/snippets/tests/scroll-until-text-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `scroll-until-text` command is used to scroll the screen in a specified direction until the specified text is found. This is useful for navigating to elements that aren't initially visible on the screen.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :---------: | :-------: | :---------------------------------------------------------------------------------------------------------------------- |
23 | | `text` | `string` | The text to find on the screen. Longer and unique are better. Note this is **case sensitive** |
24 | | `direction` | `string` | (Optional) The direction to scroll. Available directions are: `up`, `down`, `left`, `right`. Defaults to `down`. |
25 | | `method` | `string` | (Optional) The method to use to scroll the page. Available methods are: `mouse` and `keyboard`. Defaults to `keyboard`. |
26 | | `distance` | `number` | (Optional) The maximum number of pixels to scroll before giving up. Default is `10000`. |
27 | | `invert` | `boolean` | (Optional) If set to `true`, the command will scroll until the specified text is NOT detected. Default is `false`. |
28 |
29 |
30 | If the method is `keyboard` it just searches for the string by doing `ctrl + f`.
31 | This is helpful if there is a single string match that you want to operate with.
32 |
33 | If you are using `mouse` method, the command will manually scroll the page (based on the `distance`) until the text is found.
34 |
35 |
36 |
37 | ## Example usage
38 |
39 | ```yaml
40 | command: scroll-until-text
41 | text: Sign Up
42 | direction: down
43 | distance: 1000
44 | ```
45 |
46 | ## Protips
47 |
48 | - Use unique and specific text to improve detection accuracy.
49 | - Adjust the `distance` value to control how far the command scrolls before giving up.
50 | - If you don't specify a `distance`, the command will scroll 300 pixels at a time, giving up after 5 attempts.
51 |
52 | ## Gotchas
53 |
54 | - If the text can't be located within the specified `distance`, the command will fail.
55 | - Ensure the text is visible and matches exactly, as variations in font size, style, or screen resolution may affect detection accuracy.
56 | - Note that `text` is case-sensitive, so **Sign Up** and **sign up** would be treated as different strings.
57 |
58 | ---
59 |
60 | The `scroll-until-text` command is ideal for navigating to elements that are off-screen and can't be located using other commands.
61 |
--------------------------------------------------------------------------------
/docs/v7/commands/scroll-until-text.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "scroll-until-text"
3 | sidebarTitle: "scroll-until-text"
4 | description: "Scroll the screen until the specified text is found."
5 | icon: "scroll"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/scroll-until-text-replay.mdx";
10 | import Example from "/snippets/tests/scroll-until-text-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `scroll-until-text` command is used to scroll the screen in a specified direction until the specified text is found. This is useful for navigating to elements that aren't initially visible on the screen.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :---------: | :-------: | :---------------------------------------------------------------------------------------------------------------------- |
23 | | `text` | `string` | The text to find on the screen. Longer and unique are better. Note this is **case sensitive** |
24 | | `direction` | `string` | (Optional) The direction to scroll. Available directions are: `up`, `down`, `left`, `right`. Defaults to `down`. |
25 | | `method` | `string` | (Optional) The method to use to scroll the page. Available methods are: `mouse` and `keyboard`. Defaults to `keyboard`. |
26 | | `distance` | `number` | (Optional) The maximum number of pixels to scroll before giving up. Default is `10000`. |
27 | | `invert` | `boolean` | (Optional) If set to `true`, the command will scroll until the specified text is NOT detected. Default is `false`. |
28 |
29 |
30 | If the method is `keyboard` it just searches for the string by doing `ctrl + f`.
31 | This is helpful if there is a single string match that you want to operate with.
32 |
33 | If you are using `mouse` method, the command will manually scroll the page (based on the `distance`) until the text is found.
34 |
35 |
36 |
37 | ## Example usage
38 |
39 | ```yaml
40 | command: scroll-until-text
41 | text: Sign Up
42 | direction: down
43 | distance: 1000
44 | ```
45 |
46 | ## Protips
47 |
48 | - Use unique and specific text to improve detection accuracy.
49 | - Adjust the `distance` value to control how far the command scrolls before giving up.
50 | - If you don't specify a `distance`, the command will scroll 300 pixels at a time, giving up after 5 attempts.
51 |
52 | ## Gotchas
53 |
54 | - If the text can't be located within the specified `distance`, the command will fail.
55 | - Ensure the text is visible and matches exactly, as variations in font size, style, or screen resolution may affect detection accuracy.
56 | - Note that `text` is case-sensitive, so **Sign Up** and **sign up** would be treated as different strings.
57 |
58 | ---
59 |
60 | The `scroll-until-text` command is ideal for navigating to elements that are off-screen and can't be located using other commands.
61 |
--------------------------------------------------------------------------------
/docs/v6/security/platform.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "Security Features of the Dashboard"
3 | sidebarTitle: "TestDriver Platform"
4 | description: "Learn about the security features of the TestDriver web dashboard, including SSL, OAuth, RBAC, and more."
5 | icon: layer-group
6 | ---
7 |
8 | import GitignoreWarning from "/snippets/gitignore-warning.mdx";
9 |
10 | ## Overview
11 |
12 | The TestDriver web dashboard provides a secure interface for managing and reviewing your tests. Tests executed via the GitHub Action are recorded and reported through Dashcam, another application developed by TestDriver. For more details, refer to the [Dashcam documentation](https://dashcam.docs.testdriver.ai).
13 |
14 | Dashcam and TestDriver share the same API and web application back end, which includes robust privacy and security features.
15 |
16 | ## Security features
17 |
18 | ### SSL
19 |
20 | - All data transmitted between your browser and the TestDriver web application is encrypted using HTTPS.
21 |
22 | ### Authentication
23 |
24 | - Users can only authenticate via OAuth, provided by Auth0, ensuring secure and reliable user authentication.
25 |
26 | ### Team management
27 |
28 | - Administrators can add or remove individual team members.
29 | - Only administrators have the ability to manage team settings.
30 | - Everyone in the team gets their own API key
31 | - The team also gets a shared API key which can be used for CI/CD workflows.
32 |
33 | ### Role-based access control (RBAC)
34 |
35 | - The first user to create a team is designated as the administrator.
36 | - Administrators:
37 | - Can manage team settings.
38 | - Can't be removed from the team.
39 | - All other users are normal members with limited access.
40 |
41 | ### API key rotation
42 |
43 | - Teams can rotate their API key for enhanced security.
44 | - It's recommended to rotate the API key every 90 days to minimize risk.
45 | - Only the administrators can rotate the shared API key.
46 |
47 |
48 | For more details on **Team Management** see the [Team
49 | documentation](/account/team).
50 |
51 |
52 |
53 |
54 | ### Secret masking
55 |
56 | - Test replay logs and network requests are automatically scanned for sensitive information, such as:
57 | - Credit card numbers
58 | - Emails
59 | - Passwords
60 | - API keys
61 | - Detected secrets are masked with asterisks (`****`) to prevent exposure.
62 |
63 | ### Encrypted at rest
64 |
65 | - Test replays and logs are securely stored on Amazon S3 and encrypted at rest.
66 | - Test results are only accessible via temporary signed URLs.
67 | - Signed URLs are generated exclusively for team users and expire after a set duration.
68 |
69 | ## Notes
70 |
71 | - The TestDriver web dashboard is designed with privacy and security as top priorities.
72 | - For additional security, ensure your team rotates API keys regularly and reviews team member access permissions.
73 | - If you have specific security concerns or questions, please contact TestDriver support.
74 |
75 |
76 | See complete docs about the dashboard features [here](/account/dashboard)
77 |
78 |
--------------------------------------------------------------------------------
/docs/v6/commands/hover-text.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "hover-text"
3 | sidebarTitle: "hover-text"
4 | description: "Hover or click on text elements based on a description."
5 | icon: "text"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/hover-text-replay.mdx";
10 | import Example from "/snippets/tests/hover-text-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `hover-text` command is used to locate text on the screen based on a description and perform an action on it. This can include hovering, clicking, right-clicking, or double-clicking the text.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :-----------: | :------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23 | | `text` | `string` | The text to find on the screen. Longer and unique is better. |
24 | | `description` | `string` | A description of the text and what it represents. The actual text itself shouldn't be included here. |
25 | | `action` | `string` | The action to take when the text is found. Available actions are: `click`, `right-click`, `double-click`, `hover`. Also supports `drag-start` and `drag-end` for [dragging text](/commands/hover-image#drag-functionality). |
26 | | `method` | `enum` | The matching algorithm to use. Possible values are `turbo` (default) and `ai`. |
27 | | `timeout` | `number` | **(Optional)** The duration in milliseconds to wait for the text to appear. Default is `5000` (5 seconds). |
28 |
29 | ## Example usage
30 |
31 | ```yaml
32 | command: hover-text
33 | text: Sign Up
34 | description: link in the header
35 | action: click
36 | timeout: 10000 # 10 seconds
37 | ```
38 |
39 | ## Gotchas
40 |
41 | - If the text can't be located, it will internally call the [wait-for-text](/commands/wait-for-text) command and wait for the text to appear. The wait `timeout` is 5 seconds by default, and the command fails if the text is not found.
42 | - Variations in font size, style, or screen resolution may affect detection accuracy.
43 |
44 | ## Notes
45 |
46 | - The `hover-text` command is ideal for interacting with textual elements that can't be identified using other selectors.
47 | - Supported actions allow flexibility in how the text is interacted with during the test.
48 |
--------------------------------------------------------------------------------
/docs/v7/commands/hover-text.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | title: "hover-text"
3 | sidebarTitle: "hover-text"
4 | description: "Hover or click on text elements based on a description."
5 | icon: "text"
6 | mode: "wide"
7 | ---
8 |
9 | import Replay from "/snippets/tests/hover-text-replay.mdx";
10 | import Example from "/snippets/tests/hover-text-yaml.mdx";
11 |
12 |
13 |
14 |
15 | ## Description
16 |
17 | The `hover-text` command is used to locate text on the screen based on a description and perform an action on it. This can include hovering, clicking, right-clicking, or double-clicking the text.
18 |
19 | ## Arguments
20 |
21 | | Argument | Type | Description |
22 | | :-----------: | :------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
23 | | `text` | `string` | The text to find on the screen. Longer and unique is better. |
24 | | `description` | `string` | A description of the text and what it represents. The actual text itself shouldn't be included here. |
25 | | `action` | `string` | The action to take when the text is found. Available actions are: `click`, `right-click`, `double-click`, `hover`. Also supports `drag-start` and `drag-end` for [dragging text](/commands/hover-image#drag-functionality). |
26 | | `method` | `enum` | The matching algorithm to use. Possible values are `turbo` (default) and `ai`. |
27 | | `timeout` | `number` | **(Optional)** The duration in milliseconds to wait for the text to appear. Default is `5000` (5 seconds). |
28 |
29 | ## Example usage
30 |
31 | ```yaml
32 | command: hover-text
33 | text: Sign Up
34 | description: link in the header
35 | action: click
36 | timeout: 10000 # 10 seconds
37 | ```
38 |
39 | ## Gotchas
40 |
41 | - If the text can't be located, it will internally call the [wait-for-text](/commands/wait-for-text) command and wait for the text to appear. The wait `timeout` is 5 seconds by default, and the command fails if the text is not found.
42 | - Variations in font size, style, or screen resolution may affect detection accuracy.
43 |
44 | ## Notes
45 |
46 | - The `hover-text` command is ideal for interacting with textual elements that can't be identified using other selectors.
47 | - Supported actions allow flexibility in how the text is interacted with during the test.
48 |
--------------------------------------------------------------------------------