├── .prettierrc ├── test ├── manual │ ├── verify-types.js │ ├── test-sdk-methods.js │ ├── test-provision-auth.mjs │ ├── test-sandbox-render.js │ ├── test-init.sh │ ├── test-console-logs.test.mjs │ ├── test-stack-trace.mjs │ ├── test-sdk-refactor.js │ ├── verify-element-api.js │ ├── test-find-api.js │ └── debug-locate-response.js └── testdriver │ ├── setup │ ├── globalTeardown.mjs │ └── vitestSetup.mjs │ ├── element-not-found.test.mjs │ ├── formatted-logging.test.mjs │ ├── hover-text.test.mjs │ ├── hover-image.test.mjs │ ├── focus-window.test.mjs │ ├── prompt.test.mjs │ ├── hover-text-with-description.test.mjs │ ├── match-image.test.mjs │ ├── scroll-until-image.test.mjs │ ├── scroll-keyboard.test.mjs │ ├── assert.test.mjs │ ├── scroll.test.mjs │ ├── type.test.mjs │ ├── scroll-until-text.test.mjs │ ├── press-keys.test.mjs │ ├── exec-output.test.mjs │ ├── exec-pwsh.test.mjs │ └── drag-and-drop.test.mjs ├── .env.example ├── debugger ├── bg.png ├── td.png ├── icon.png ├── tray.png └── tray-buffered.png ├── .prettierignore ├── .vscode ├── extensions.json ├── mcp.json ├── settings.json └── launch.json ├── docs ├── images │ ├── template │ │ ├── dark.png │ │ ├── icon.png │ │ └── light.png │ └── content │ │ ├── side-by-side.png │ │ ├── vscode │ │ ├── running.png │ │ ├── ide-full.png │ │ ├── vscode-install.png │ │ ├── vscode-2-assert.png │ │ ├── vscode-overview.png │ │ ├── vscode-stopchat.png │ │ ├── vscode-stoptest.png │ │ ├── vscode-tdservice.png │ │ ├── vscode-testpane.png │ │ ├── vscode-copilot-ask.png │ │ ├── vscode-test-output.png │ │ ├── vscode-testhistory.png │ │ ├── vscode-agent-preview.png │ │ ├── vscode-file-creation.png │ │ ├── vscode-setup-walkthrough.png │ │ └── vscode-testpane-runtests.png │ │ ├── account │ │ ├── teampage.png │ │ ├── projectpage.png │ │ ├── team-manage.png │ │ ├── projectreplays.png │ │ └── newprojectsettings.png │ │ ├── self-hosted │ │ └── launchtemplateid.png │ │ └── extension │ │ └── windsurf.svg ├── snippets │ ├── calendar-link.mdx │ ├── tests │ │ ├── assert-replay.mdx │ │ ├── scroll-replay.mdx │ │ ├── type-replay.mdx │ │ ├── exec-js-replay.mdx │ │ ├── exec-shell-replay.mdx │ │ ├── hover-image-replay.mdx │ │ ├── hover-text-replay.mdx │ │ ├── match-image-replay.mdx │ │ ├── press-keys-replay.mdx │ │ ├── remember-replay.mdx │ │ ├── wait-replay.mdx │ │ ├── assert-yaml.mdx │ │ ├── scroll-until-text-replay.mdx │ │ ├── type-repeated-replay.mdx │ │ ├── wait-for-image-replay.mdx │ │ ├── wait-for-text-replay.mdx │ │ ├── scroll-until-image-replay.mdx │ │ ├── hover-text-with-description-replay.mdx │ │ ├── wait-yaml.mdx │ │ ├── hover-text-yaml.mdx │ │ ├── wait-for-text-yaml.mdx │ │ ├── wait-for-image-yaml.mdx │ │ ├── match-image-yaml.mdx │ │ ├── scroll-until-image-yaml.mdx │ │ ├── scroll-until-text-yaml.mdx │ │ ├── exec-shell-yaml.mdx │ │ ├── hover-image-yaml.mdx │ │ ├── type-repeated-yaml.mdx │ │ ├── hover-text-with-description-yaml.mdx │ │ ├── remember-yaml.mdx │ │ ├── scroll-yaml.mdx │ │ ├── exec-js-yaml.mdx │ │ ├── press-keys-yaml.mdx │ │ └── type-yaml.mdx │ ├── lifecycle-warning.mdx │ ├── gitignore-warning.mdx │ └── test-prereqs.mdx ├── v6 │ ├── account │ │ ├── dashboard.mdx │ │ ├── pricing.mdx │ │ ├── projects.mdx │ │ └── team.mdx │ ├── scenarios │ │ ├── ai-chatbot.mdx │ │ ├── pdf-generation.mdx │ │ ├── spell-check.mdx │ │ ├── cookie-banner.mdx │ │ ├── file-upload.mdx │ │ ├── form-filling.mdx │ │ └── log-in.mdx │ ├── apps │ │ ├── mobile-apps.mdx │ │ ├── static-websites.mdx │ │ └── chrome-extensions.mdx │ ├── guide │ │ ├── environment-variables.mdx │ │ └── code.mdx │ ├── tutorials │ │ ├── basic-test.mdx │ │ └── advanced-test.mdx │ ├── commands │ │ ├── type.mdx │ │ ├── wait.mdx │ │ ├── focus-application.mdx │ │ ├── assert.mdx │ │ ├── run.mdx │ │ ├── remember.mdx │ │ ├── wait-for-text.mdx │ │ ├── if.mdx │ │ ├── wait-for-image.mdx │ │ ├── scroll.mdx │ │ ├── scroll-until-text.mdx │ │ └── hover-text.mdx │ ├── getting-started │ │ └── running.mdx │ ├── interactive │ │ ├── run.mdx │ │ └── save.mdx │ ├── cli │ │ └── overview.mdx │ └── security │ │ └── platform.mdx ├── styles.css └── v7 │ ├── commands │ ├── type.mdx │ ├── wait.mdx │ ├── focus-application.mdx │ ├── assert.mdx │ ├── run.mdx │ ├── remember.mdx │ ├── wait-for-text.mdx │ ├── if.mdx │ ├── wait-for-image.mdx │ ├── scroll.mdx │ ├── scroll-until-text.mdx │ └── hover-text.mdx │ ├── _drafts │ ├── awesome-logs-quick-ref.mdx │ ├── dashcam-title-feature.mdx │ └── init-command.mdx │ └── getting-started │ └── quickstart.mdx ├── agent └── lib │ ├── resources │ └── cursor-2.png │ ├── theme.js │ ├── analytics.js │ ├── session.js │ ├── valid-version.js │ ├── outputs.js │ ├── debugger.js │ ├── config.js │ ├── subimage │ └── index.js │ ├── generator.js │ └── censorship.js ├── index.js ├── interfaces ├── cli │ ├── commands │ │ ├── edit.js │ │ ├── run.js │ │ └── generate.js │ └── utils │ │ └── factory.js ├── cli.js └── shared-test-state.mjs ├── lib ├── core │ └── index.js └── vitest │ └── setup.mjs ├── bin └── testdriverai.js ├── vitest.config.mjs ├── .github └── workflows │ ├── publish.yaml │ └── acceptance.yaml ├── eslint.config.js └── examples ├── screenshot-example.js ├── run-tests-with-recording.sh └── sdk-simple-example.js /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/manual/verify-types.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | TD_API_KEY= 2 | TD_API_ROOT= 3 | -------------------------------------------------------------------------------- /debugger/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/debugger/bg.png -------------------------------------------------------------------------------- /debugger/td.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/debugger/td.png -------------------------------------------------------------------------------- /debugger/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/debugger/icon.png -------------------------------------------------------------------------------- /debugger/tray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/debugger/tray.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | agent/lib/subimage/opencv.js 2 | node_modules 3 | schema.json 4 | docs 5 | -------------------------------------------------------------------------------- /debugger/tray-buffered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/debugger/tray-buffered.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/images/template/dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/template/dark.png -------------------------------------------------------------------------------- /docs/images/template/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/template/icon.png -------------------------------------------------------------------------------- /docs/images/template/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/template/light.png -------------------------------------------------------------------------------- /agent/lib/resources/cursor-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/agent/lib/resources/cursor-2.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Only provide the agent interface when required as a module 2 | module.exports = require("./agent"); 3 | -------------------------------------------------------------------------------- /docs/images/content/side-by-side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/side-by-side.png -------------------------------------------------------------------------------- /docs/images/content/vscode/running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/running.png -------------------------------------------------------------------------------- /docs/images/content/account/teampage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/account/teampage.png -------------------------------------------------------------------------------- /docs/images/content/vscode/ide-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/ide-full.png -------------------------------------------------------------------------------- /docs/images/content/account/projectpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/account/projectpage.png -------------------------------------------------------------------------------- /docs/images/content/account/team-manage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/account/team-manage.png -------------------------------------------------------------------------------- /docs/snippets/calendar-link.mdx: -------------------------------------------------------------------------------- 1 | export const calendar = 2 | "https://calendly.com/d/cq23-qyn-3v6/testdriver-ai-demo"; 3 | 4 | ; 5 | -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-install.png -------------------------------------------------------------------------------- /docs/images/content/account/projectreplays.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/account/projectreplays.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-2-assert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-2-assert.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-overview.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-stopchat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-stopchat.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-stoptest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-stoptest.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-tdservice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-tdservice.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-testpane.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-testpane.png -------------------------------------------------------------------------------- /docs/images/content/account/newprojectsettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/account/newprojectsettings.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-copilot-ask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-copilot-ask.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-test-output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-test-output.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-testhistory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-testhistory.png -------------------------------------------------------------------------------- /interfaces/cli/commands/edit.js: -------------------------------------------------------------------------------- 1 | const { createOclifCommand } = require("../utils/factory.js"); 2 | 3 | module.exports = createOclifCommand("edit"); 4 | -------------------------------------------------------------------------------- /interfaces/cli/commands/run.js: -------------------------------------------------------------------------------- 1 | const { createOclifCommand } = require("../utils/factory.js"); 2 | 3 | module.exports = createOclifCommand("run"); 4 | -------------------------------------------------------------------------------- /docs/images/content/self-hosted/launchtemplateid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/self-hosted/launchtemplateid.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-agent-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-agent-preview.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-file-creation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-file-creation.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-setup-walkthrough.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-setup-walkthrough.png -------------------------------------------------------------------------------- /docs/images/content/vscode/vscode-testpane-runtests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/testdriverai/cli/HEAD/docs/images/content/vscode/vscode-testpane-runtests.png -------------------------------------------------------------------------------- /interfaces/cli/commands/generate.js: -------------------------------------------------------------------------------- 1 | const { createOclifCommand } = require("../utils/factory.js"); 2 | 3 | module.exports = createOclifCommand("generate"); 4 | -------------------------------------------------------------------------------- /lib/core/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Core SDK Exports 3 | * Main exports for the TestDriver SDK 4 | */ 5 | 6 | const TestDriver = require('../../sdk.js'); 7 | const Dashcam = require('./Dashcam.js'); 8 | 9 | module.exports = { 10 | TestDriver, 11 | Dashcam, 12 | }; 13 | -------------------------------------------------------------------------------- /docs/snippets/tests/assert-replay.mdx: -------------------------------------------------------------------------------- 1 |
2 | 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 | 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 | --------------------------------------------------------------------------------