@testdriverai/agent 7.8.0-test.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +7 -0
- package/.env.example +4 -0
- package/.prettierignore +4 -0
- package/.prettierrc +1 -0
- package/CHANGELOG.md +953 -0
- package/README.md +81 -0
- package/agent/events.js +135 -0
- package/agent/index.js +2450 -0
- package/agent/interface.js +35 -0
- package/agent/lib/analytics.js +22 -0
- package/agent/lib/censorship.js +75 -0
- package/agent/lib/commander.js +246 -0
- package/agent/lib/commands.js +1684 -0
- package/agent/lib/config.js +60 -0
- package/agent/lib/generator.js +91 -0
- package/agent/lib/http.js +144 -0
- package/agent/lib/logger.js +56 -0
- package/agent/lib/outputs.js +29 -0
- package/agent/lib/parser.js +209 -0
- package/agent/lib/redraw.js +386 -0
- package/agent/lib/resources/cursor-2.png +0 -0
- package/agent/lib/sandbox.js +1104 -0
- package/agent/lib/sdk.js +633 -0
- package/agent/lib/session.js +25 -0
- package/agent/lib/source-mapper.js +342 -0
- package/agent/lib/subimage/index.js +77 -0
- package/agent/lib/subimage/opencv.js +69 -0
- package/agent/lib/system.js +204 -0
- package/agent/lib/theme.js +14 -0
- package/agent/lib/valid-version.js +21 -0
- package/agent/lib/validation.js +169 -0
- package/ai/.claude-plugin/plugin.json +9 -0
- package/ai/agents/testdriver.md +638 -0
- package/ai/skills/testdriver-ai/SKILL.md +204 -0
- package/ai/skills/testdriver-assert/SKILL.md +315 -0
- package/ai/skills/testdriver-aws-setup/SKILL.md +448 -0
- package/ai/skills/testdriver-cache/SKILL.md +221 -0
- package/ai/skills/testdriver-caching/SKILL.md +124 -0
- package/ai/skills/testdriver-captcha/SKILL.md +158 -0
- package/ai/skills/testdriver-ci-cd/SKILL.md +602 -0
- package/ai/skills/testdriver-click/SKILL.md +286 -0
- package/ai/skills/testdriver-client/SKILL.md +477 -0
- package/ai/skills/testdriver-cloud/SKILL.md +119 -0
- package/ai/skills/testdriver-customizing-devices/SKILL.md +319 -0
- package/ai/skills/testdriver-dashcam/SKILL.md +418 -0
- package/ai/skills/testdriver-debugging-with-screenshots/SKILL.md +401 -0
- package/ai/skills/testdriver-device-config/SKILL.md +317 -0
- package/ai/skills/testdriver-double-click/SKILL.md +102 -0
- package/ai/skills/testdriver-elements/SKILL.md +605 -0
- package/ai/skills/testdriver-enterprise/SKILL.md +114 -0
- package/ai/skills/testdriver-errors/SKILL.md +246 -0
- package/ai/skills/testdriver-events/SKILL.md +356 -0
- package/ai/skills/testdriver-examples/SKILL.md +7 -0
- package/ai/skills/testdriver-exec/SKILL.md +317 -0
- package/ai/skills/testdriver-find/SKILL.md +829 -0
- package/ai/skills/testdriver-focus-application/SKILL.md +293 -0
- package/ai/skills/testdriver-generating-tests/SKILL.md +36 -0
- package/ai/skills/testdriver-hover/SKILL.md +278 -0
- package/ai/skills/testdriver-locating-elements/SKILL.md +71 -0
- package/ai/skills/testdriver-making-assertions/SKILL.md +32 -0
- package/ai/skills/testdriver-mcp/SKILL.md +7 -0
- package/ai/skills/testdriver-mcp-workflow/SKILL.md +410 -0
- package/ai/skills/testdriver-mouse-down/SKILL.md +161 -0
- package/ai/skills/testdriver-mouse-up/SKILL.md +164 -0
- package/ai/skills/testdriver-parse/SKILL.md +236 -0
- package/ai/skills/testdriver-performing-actions/SKILL.md +54 -0
- package/ai/skills/testdriver-press-keys/SKILL.md +348 -0
- package/ai/skills/testdriver-provision/SKILL.md +331 -0
- package/ai/skills/testdriver-quickstart/SKILL.md +144 -0
- package/ai/skills/testdriver-redraw/SKILL.md +214 -0
- package/ai/skills/testdriver-reusable-code/SKILL.md +249 -0
- package/ai/skills/testdriver-right-click/SKILL.md +123 -0
- package/ai/skills/testdriver-running-tests/SKILL.md +185 -0
- package/ai/skills/testdriver-screenshot/SKILL.md +248 -0
- package/ai/skills/testdriver-screenshots/SKILL.md +184 -0
- package/ai/skills/testdriver-scroll/SKILL.md +335 -0
- package/ai/skills/testdriver-secrets/SKILL.md +115 -0
- package/ai/skills/testdriver-self-hosted/SKILL.md +65 -0
- package/ai/skills/testdriver-test-writer/SKILL.md +448 -0
- package/ai/skills/testdriver-testdriver/SKILL.md +628 -0
- package/ai/skills/testdriver-testdriver-mechanic/SKILL.md +165 -0
- package/ai/skills/testdriver-type/SKILL.md +357 -0
- package/ai/skills/testdriver-variables/SKILL.md +111 -0
- package/ai/skills/testdriver-wait/SKILL.md +50 -0
- package/ai/skills/testdriver-waiting-for-elements/SKILL.md +90 -0
- package/ai/skills/testdriver-what-is-testdriver/SKILL.md +54 -0
- package/bin/testdriverai.js +22 -0
- package/debugger/bg.png +0 -0
- package/debugger/icon.png +0 -0
- package/debugger/index.html +469 -0
- package/debugger/td.png +0 -0
- package/debugger/tray-buffered.png +0 -0
- package/debugger/tray.png +0 -0
- package/docs/GITHUB_COMMENTS.md +330 -0
- package/docs/GITHUB_COMMENTS_ANNOUNCEMENT.md +167 -0
- package/docs/QUICK-START-GITHUB-COMMENTS.md +84 -0
- package/docs/TEST-GITHUB-COMMENTS.md +129 -0
- package/docs/_data/examples-manifest.json +177 -0
- package/docs/_data/examples-manifest.schema.json +41 -0
- package/docs/_scripts/extract-example-urls.js +165 -0
- package/docs/_scripts/generate-examples.js +560 -0
- package/docs/_scripts/generate-skills.js +154 -0
- package/docs/_scripts/link-replacer.js +164 -0
- package/docs/_scripts/upload-docs-to-openai.js +284 -0
- package/docs/changelog.mdx +161 -0
- package/docs/claude-mcp-plugin.mdx +160 -0
- package/docs/docs.json +442 -0
- package/docs/github-integration-setup.md +266 -0
- package/docs/guide/best-practices-polling.mdx +174 -0
- package/docs/images/content/account/newprojectsettings.png +0 -0
- package/docs/images/content/account/projectpage.png +0 -0
- package/docs/images/content/account/projectreplays.png +0 -0
- package/docs/images/content/account/team-manage.png +0 -0
- package/docs/images/content/account/teampage.png +0 -0
- package/docs/images/content/extension/cursor.svg +1 -0
- package/docs/images/content/extension/vscode.svg +57 -0
- package/docs/images/content/extension/windsurf.svg +3 -0
- package/docs/images/content/parse/output.png +0 -0
- package/docs/images/content/self-hosted/launchtemplateid.png +0 -0
- package/docs/images/content/side-by-side.png +0 -0
- package/docs/images/content/vscode/ide-full.png +0 -0
- package/docs/images/content/vscode/running.png +0 -0
- package/docs/images/content/vscode/v7-chat.png +0 -0
- package/docs/images/content/vscode/v7-choose-agent.png +0 -0
- package/docs/images/content/vscode/v7-full.png +0 -0
- package/docs/images/content/vscode/v7-onboarding.png +0 -0
- package/docs/images/content/vscode/vscode-2-assert.png +0 -0
- package/docs/images/content/vscode/vscode-agent-preview.png +0 -0
- package/docs/images/content/vscode/vscode-copilot-ask.png +0 -0
- package/docs/images/content/vscode/vscode-file-creation.png +0 -0
- package/docs/images/content/vscode/vscode-install.png +0 -0
- package/docs/images/content/vscode/vscode-overview.png +0 -0
- package/docs/images/content/vscode/vscode-setup-walkthrough.png +0 -0
- package/docs/images/content/vscode/vscode-stopchat.png +0 -0
- package/docs/images/content/vscode/vscode-stoptest.png +0 -0
- package/docs/images/content/vscode/vscode-tdservice.png +0 -0
- package/docs/images/content/vscode/vscode-test-output.png +0 -0
- package/docs/images/content/vscode/vscode-testhistory.png +0 -0
- package/docs/images/content/vscode/vscode-testpane-runtests.png +0 -0
- package/docs/images/content/vscode/vscode-testpane.png +0 -0
- package/docs/images/template/dark.png +0 -0
- package/docs/images/template/icon.png +0 -0
- package/docs/images/template/light.png +0 -0
- package/docs/snippets/calendar-link.mdx +4 -0
- package/docs/snippets/gitignore-warning.mdx +7 -0
- package/docs/snippets/lifecycle-warning.mdx +6 -0
- package/docs/snippets/test-prereqs.mdx +12 -0
- package/docs/snippets/tests/assert-replay.mdx +7 -0
- package/docs/snippets/tests/assert-yaml.mdx +8 -0
- package/docs/snippets/tests/exec-js-replay.mdx +7 -0
- package/docs/snippets/tests/exec-js-yaml.mdx +32 -0
- package/docs/snippets/tests/exec-shell-replay.mdx +7 -0
- package/docs/snippets/tests/exec-shell-yaml.mdx +15 -0
- package/docs/snippets/tests/hover-image-replay.mdx +7 -0
- package/docs/snippets/tests/hover-image-yaml.mdx +17 -0
- package/docs/snippets/tests/hover-text-replay.mdx +7 -0
- package/docs/snippets/tests/hover-text-with-description-replay.mdx +7 -0
- package/docs/snippets/tests/hover-text-with-description-yaml.mdx +24 -0
- package/docs/snippets/tests/hover-text-yaml.mdx +14 -0
- package/docs/snippets/tests/match-image-replay.mdx +7 -0
- package/docs/snippets/tests/match-image-yaml.mdx +17 -0
- package/docs/snippets/tests/press-keys-replay.mdx +7 -0
- package/docs/snippets/tests/press-keys-yaml.mdx +36 -0
- package/docs/snippets/tests/remember-replay.mdx +7 -0
- package/docs/snippets/tests/remember-yaml.mdx +28 -0
- package/docs/snippets/tests/scroll-replay.mdx +7 -0
- package/docs/snippets/tests/scroll-until-image-replay.mdx +7 -0
- package/docs/snippets/tests/scroll-until-image-yaml.mdx +14 -0
- package/docs/snippets/tests/scroll-until-text-replay.mdx +7 -0
- package/docs/snippets/tests/scroll-until-text-yaml.mdx +17 -0
- package/docs/snippets/tests/scroll-yaml.mdx +30 -0
- package/docs/snippets/tests/type-repeated-replay.mdx +7 -0
- package/docs/snippets/tests/type-repeated-yaml.mdx +22 -0
- package/docs/snippets/tests/type-replay.mdx +7 -0
- package/docs/snippets/tests/type-yaml.mdx +28 -0
- package/docs/snippets/tests/wait-for-image-replay.mdx +7 -0
- package/docs/snippets/tests/wait-for-image-yaml.mdx +18 -0
- package/docs/snippets/tests/wait-for-text-replay.mdx +7 -0
- package/docs/snippets/tests/wait-for-text-yaml.mdx +18 -0
- package/docs/snippets/tests/wait-replay.mdx +7 -0
- package/docs/snippets/tests/wait-yaml.mdx +13 -0
- package/docs/styles.css +65 -0
- package/docs/v6/account/dashboard.mdx +16 -0
- package/docs/v6/account/enterprise.mdx +110 -0
- package/docs/v6/account/pricing.mdx +33 -0
- package/docs/v6/account/projects.mdx +33 -0
- package/docs/v6/account/team.mdx +35 -0
- package/docs/v6/action/ami.mdx +109 -0
- package/docs/v6/action/performance.mdx +105 -0
- package/docs/v6/action/secrets.mdx +93 -0
- package/docs/v6/apps/chrome-extensions.mdx +48 -0
- package/docs/v6/apps/desktop-apps.mdx +93 -0
- package/docs/v6/apps/mobile-apps.mdx +26 -0
- package/docs/v6/apps/static-websites.mdx +54 -0
- package/docs/v6/apps/tauri-apps.mdx +361 -0
- package/docs/v6/bugs/jira.mdx +232 -0
- package/docs/v6/cli/overview.mdx +66 -0
- package/docs/v6/commands/assert.mdx +45 -0
- package/docs/v6/commands/exec.mdx +276 -0
- package/docs/v6/commands/focus-application.mdx +44 -0
- package/docs/v6/commands/hover-image.mdx +69 -0
- package/docs/v6/commands/hover-text.mdx +47 -0
- package/docs/v6/commands/if.mdx +53 -0
- package/docs/v6/commands/match-image.mdx +67 -0
- package/docs/v6/commands/press-keys.mdx +87 -0
- package/docs/v6/commands/remember.mdx +49 -0
- package/docs/v6/commands/run.mdx +44 -0
- package/docs/v6/commands/scroll-until-image.mdx +66 -0
- package/docs/v6/commands/scroll-until-text.mdx +60 -0
- package/docs/v6/commands/scroll.mdx +69 -0
- package/docs/v6/commands/type.mdx +45 -0
- package/docs/v6/commands/wait-for-image.mdx +54 -0
- package/docs/v6/commands/wait-for-text.mdx +48 -0
- package/docs/v6/commands/wait.mdx +45 -0
- package/docs/v6/exporting/junit.mdx +218 -0
- package/docs/v6/exporting/playwright.mdx +197 -0
- package/docs/v6/features/auto-healing.mdx +144 -0
- package/docs/v6/features/generation.mdx +116 -0
- package/docs/v6/features/parallel-testing.mdx +151 -0
- package/docs/v6/features/reusable-snippets.mdx +131 -0
- package/docs/v6/features/selectorless.mdx +80 -0
- package/docs/v6/features/visual-assertions.mdx +139 -0
- package/docs/v6/getting-started/ci.mdx +146 -0
- package/docs/v6/getting-started/cli.mdx +91 -0
- package/docs/v6/getting-started/editing.mdx +100 -0
- package/docs/v6/getting-started/playwright.mdx +342 -0
- package/docs/v6/getting-started/running.mdx +48 -0
- package/docs/v6/getting-started/self-hosting.mdx +408 -0
- package/docs/v6/getting-started/vscode.mdx +88 -0
- package/docs/v6/guide/assertions.mdx +189 -0
- package/docs/v6/guide/authentication.mdx +136 -0
- package/docs/v6/guide/code.mdx +65 -0
- package/docs/v6/guide/dashcam.mdx +118 -0
- package/docs/v6/guide/environment-variables.mdx +26 -0
- package/docs/v6/guide/lifecycle.mdx +242 -0
- package/docs/v6/guide/locating.mdx +141 -0
- package/docs/v6/guide/protips.mdx +43 -0
- package/docs/v6/guide/variables.mdx +143 -0
- package/docs/v6/guide/waiting.mdx +130 -0
- package/docs/v6/importing/csv.mdx +196 -0
- package/docs/v6/importing/gherkin.mdx +143 -0
- package/docs/v6/importing/jira.mdx +164 -0
- package/docs/v6/importing/testrail.mdx +162 -0
- package/docs/v6/integrations/electron.mdx +146 -0
- package/docs/v6/integrations/netlify.mdx +100 -0
- package/docs/v6/integrations/vercel.mdx +125 -0
- package/docs/v6/interactive/explore.mdx +99 -0
- package/docs/v6/interactive/run.mdx +52 -0
- package/docs/v6/interactive/save.mdx +63 -0
- package/docs/v6/overview/comparison.mdx +101 -0
- package/docs/v6/overview/faq.mdx +162 -0
- package/docs/v6/overview/performance.mdx +52 -0
- package/docs/v6/overview/quickstart.mdx +137 -0
- package/docs/v6/overview/what-is-testdriver.mdx +85 -0
- package/docs/v6/scenarios/ai-chatbot.mdx +28 -0
- package/docs/v6/scenarios/cookie-banner.mdx +32 -0
- package/docs/v6/scenarios/file-upload.mdx +33 -0
- package/docs/v6/scenarios/form-filling.mdx +32 -0
- package/docs/v6/scenarios/log-in.mdx +75 -0
- package/docs/v6/scenarios/pdf-generation.mdx +25 -0
- package/docs/v6/scenarios/spell-check.mdx +22 -0
- package/docs/v6/security/action.mdx +84 -0
- package/docs/v6/security/agent.mdx +73 -0
- package/docs/v6/security/platform.mdx +77 -0
- package/docs/v6/tutorials/advanced-test.mdx +81 -0
- package/docs/v6/tutorials/basic-test.mdx +45 -0
- package/docs/v7/_drafts/agents.mdx +843 -0
- package/docs/v7/_drafts/architecture.mdx +399 -0
- package/docs/v7/_drafts/auto-cache-key.mdx +167 -0
- package/docs/v7/_drafts/awesome-logs-quick-ref.mdx +100 -0
- package/docs/v7/_drafts/best-practices.mdx +486 -0
- package/docs/v7/_drafts/caching-ai.mdx +215 -0
- package/docs/v7/_drafts/caching-selectors.mdx +424 -0
- package/docs/v7/_drafts/caching.mdx +366 -0
- package/docs/v7/_drafts/cli-to-sdk-migration.mdx +425 -0
- package/docs/v7/_drafts/commands/assert.mdx +45 -0
- package/docs/v7/_drafts/commands/exec.mdx +276 -0
- package/docs/v7/_drafts/commands/focus-application.mdx +44 -0
- package/docs/v7/_drafts/commands/hover-image.mdx +69 -0
- package/docs/v7/_drafts/commands/hover-text.mdx +47 -0
- package/docs/v7/_drafts/commands/if.mdx +53 -0
- package/docs/v7/_drafts/commands/match-image.mdx +67 -0
- package/docs/v7/_drafts/commands/press-keys.mdx +87 -0
- package/docs/v7/_drafts/commands/remember.mdx +49 -0
- package/docs/v7/_drafts/commands/run.mdx +44 -0
- package/docs/v7/_drafts/commands/scroll-until-image.mdx +66 -0
- package/docs/v7/_drafts/commands/scroll-until-text.mdx +60 -0
- package/docs/v7/_drafts/commands/scroll.mdx +69 -0
- package/docs/v7/_drafts/commands/type.mdx +45 -0
- package/docs/v7/_drafts/commands/wait-for-image.mdx +54 -0
- package/docs/v7/_drafts/commands/wait-for-text.mdx +48 -0
- package/docs/v7/_drafts/commands/wait.mdx +45 -0
- package/docs/v7/_drafts/configuration.mdx +378 -0
- package/docs/v7/_drafts/contributing.mdx +174 -0
- package/docs/v7/_drafts/core.mdx +458 -0
- package/docs/v7/_drafts/dashcam-title-feature.mdx +89 -0
- package/docs/v7/_drafts/debugging.mdx +349 -0
- package/docs/v7/_drafts/error-handling.mdx +501 -0
- package/docs/v7/_drafts/faq.mdx +393 -0
- package/docs/v7/_drafts/hooks.mdx +360 -0
- package/docs/v7/_drafts/init-command.mdx +95 -0
- package/docs/v7/_drafts/installation.mdx +420 -0
- package/docs/v7/_drafts/migration.mdx +562 -0
- package/docs/v7/_drafts/observable.mdx +604 -0
- package/docs/v7/_drafts/playwright.mdx +342 -0
- package/docs/v7/_drafts/plugin-migration.mdx +220 -0
- package/docs/v7/_drafts/powerful.mdx +419 -0
- package/docs/v7/_drafts/presets.mdx +210 -0
- package/docs/v7/_drafts/progressive-disclosure.mdx +230 -0
- package/docs/v7/_drafts/prompt-cache.mdx +200 -0
- package/docs/v7/_drafts/provision.mdx +390 -0
- package/docs/v7/_drafts/quick-start-test-recording.mdx +214 -0
- package/docs/v7/_drafts/readme.mdx +135 -0
- package/docs/v7/_drafts/reports.mdx +414 -0
- package/docs/v7/_drafts/scalable.mdx +763 -0
- package/docs/v7/_drafts/screenshot.mdx +155 -0
- package/docs/v7/_drafts/sdk-awesome-logs.mdx +468 -0
- package/docs/v7/_drafts/sdk-browser-rendering.mdx +167 -0
- package/docs/v7/_drafts/sdk-migration.mdx +474 -0
- package/docs/v7/_drafts/sdk-v7-complete.mdx +345 -0
- package/docs/v7/_drafts/self-hosting.mdx +369 -0
- package/docs/v7/_drafts/test-recording.mdx +382 -0
- package/docs/v7/_drafts/troubleshooting.mdx +526 -0
- package/docs/v7/_drafts/vitest-plugin.mdx +477 -0
- package/docs/v7/_drafts/vitest.mdx +535 -0
- package/docs/v7/_drafts/writing-tests.mdx +25 -0
- package/docs/v7/ai.mdx +205 -0
- package/docs/v7/assert.mdx +316 -0
- package/docs/v7/aws-setup.mdx +449 -0
- package/docs/v7/cache.mdx +223 -0
- package/docs/v7/caching.mdx +128 -0
- package/docs/v7/captcha.mdx +159 -0
- package/docs/v7/ci-cd.mdx +603 -0
- package/docs/v7/click.mdx +287 -0
- package/docs/v7/client.mdx +478 -0
- package/docs/v7/copilot/auto-healing.mdx +265 -0
- package/docs/v7/copilot/creating-tests.mdx +156 -0
- package/docs/v7/copilot/github.mdx +143 -0
- package/docs/v7/copilot/running-tests.mdx +149 -0
- package/docs/v7/copilot/setup.mdx +143 -0
- package/docs/v7/customizing-devices.mdx +319 -0
- package/docs/v7/dashcam.mdx +419 -0
- package/docs/v7/debugging-with-screenshots.mdx +402 -0
- package/docs/v7/device-config.mdx +317 -0
- package/docs/v7/double-click.mdx +102 -0
- package/docs/v7/elements.mdx +606 -0
- package/docs/v7/enterprise.mdx +9 -0
- package/docs/v7/errors.mdx +248 -0
- package/docs/v7/events.mdx +358 -0
- package/docs/v7/examples/ai.mdx +72 -0
- package/docs/v7/examples/assert.mdx +72 -0
- package/docs/v7/examples/captcha-api.mdx +92 -0
- package/docs/v7/examples/chrome-extension.mdx +132 -0
- package/docs/v7/examples/drag-and-drop.mdx +100 -0
- package/docs/v7/examples/element-not-found.mdx +67 -0
- package/docs/v7/examples/exec-output.mdx +85 -0
- package/docs/v7/examples/exec-pwsh.mdx +83 -0
- package/docs/v7/examples/focus-window.mdx +62 -0
- package/docs/v7/examples/hover-image.mdx +94 -0
- package/docs/v7/examples/hover-text.mdx +69 -0
- package/docs/v7/examples/installer.mdx +91 -0
- package/docs/v7/examples/launch-vscode-linux.mdx +101 -0
- package/docs/v7/examples/match-image.mdx +96 -0
- package/docs/v7/examples/press-keys.mdx +92 -0
- package/docs/v7/examples/scroll-keyboard.mdx +79 -0
- package/docs/v7/examples/scroll-until-image.mdx +81 -0
- package/docs/v7/examples/scroll-until-text.mdx +109 -0
- package/docs/v7/examples/scroll.mdx +81 -0
- package/docs/v7/examples/type.mdx +92 -0
- package/docs/v7/examples/windows-installer.mdx +89 -0
- package/docs/v7/exec.mdx +318 -0
- package/docs/v7/find.mdx +830 -0
- package/docs/v7/focus-application.mdx +294 -0
- package/docs/v7/generating-tests.mdx +36 -0
- package/docs/v7/hosted.mdx +158 -0
- package/docs/v7/hover.mdx +279 -0
- package/docs/v7/locating-elements.mdx +71 -0
- package/docs/v7/making-assertions.mdx +32 -0
- package/docs/v7/mcp.mdx +9 -0
- package/docs/v7/mouse-down.mdx +161 -0
- package/docs/v7/mouse-up.mdx +164 -0
- package/docs/v7/parse.mdx +237 -0
- package/docs/v7/performing-actions.mdx +54 -0
- package/docs/v7/press-keys.mdx +349 -0
- package/docs/v7/provision.mdx +333 -0
- package/docs/v7/quickstart.mdx +173 -0
- package/docs/v7/redraw.mdx +216 -0
- package/docs/v7/reusable-code.mdx +249 -0
- package/docs/v7/right-click.mdx +123 -0
- package/docs/v7/running-tests.mdx +185 -0
- package/docs/v7/screenshot.mdx +249 -0
- package/docs/v7/screenshots.mdx +186 -0
- package/docs/v7/scroll.mdx +336 -0
- package/docs/v7/secrets.mdx +115 -0
- package/docs/v7/self-hosted.mdx +149 -0
- package/docs/v7/type.mdx +358 -0
- package/docs/v7/variables.mdx +111 -0
- package/docs/v7/wait.mdx +52 -0
- package/docs/v7/waiting-for-elements.mdx +90 -0
- package/docs/v7/what-is-testdriver.mdx +54 -0
- package/eslint.config.js +67 -0
- package/examples/ai.test.mjs +31 -0
- package/examples/assert.test.mjs +47 -0
- package/examples/chrome-extension.test.mjs +97 -0
- package/examples/config.mjs +5 -0
- package/examples/element-not-found.test.mjs +27 -0
- package/examples/exec-output.test.mjs +60 -0
- package/examples/exec-pwsh.test.mjs +58 -0
- package/examples/findall-coffee-icons.test.mjs +42 -0
- package/examples/focus-window.test.mjs +37 -0
- package/examples/formatted-logging.test.mjs +27 -0
- package/examples/hover-image.test.mjs +53 -0
- package/examples/hover-text-with-description.test.mjs +57 -0
- package/examples/hover-text.test.mjs +28 -0
- package/examples/installer.test.mjs +50 -0
- package/examples/launch-vscode-linux.test.mjs +55 -0
- package/examples/match-image.test.mjs +55 -0
- package/examples/parse.test.mjs +19 -0
- package/examples/press-keys.test.mjs +44 -0
- package/examples/prompt.test.mjs +34 -0
- package/examples/scroll-keyboard.test.mjs +38 -0
- package/examples/scroll-until-image.test.mjs +40 -0
- package/examples/scroll.test.mjs +42 -0
- package/examples/type.test.mjs +46 -0
- package/examples/windows-installer.test.mjs +54 -0
- package/index.js +2 -0
- package/interfaces/cli/commands/init.js +438 -0
- package/interfaces/cli/commands/setup.js +382 -0
- package/interfaces/cli/lib/base.js +285 -0
- package/interfaces/cli.js +20 -0
- package/interfaces/junit-reporter.js +290 -0
- package/interfaces/logger.js +388 -0
- package/interfaces/readline.js +234 -0
- package/interfaces/shared-test-state.mjs +64 -0
- package/interfaces/vitest-plugin.d.ts +115 -0
- package/interfaces/vitest-plugin.mjs +1698 -0
- package/lib/captcha/solver.js +358 -0
- package/lib/core/Dashcam.js +533 -0
- package/lib/core/index.d.ts +172 -0
- package/lib/core/index.js +12 -0
- package/lib/environments.json +18 -0
- package/lib/github-comment-formatter.js +263 -0
- package/lib/github-comment.mjs +452 -0
- package/lib/init-project.js +575 -0
- package/lib/presets/index.mjs +331 -0
- package/lib/resolve-channel.js +46 -0
- package/lib/sentry.js +417 -0
- package/lib/vitest/hooks.d.ts +57 -0
- package/lib/vitest/hooks.mjs +674 -0
- package/lib/vitest/setup-aws.mjs +247 -0
- package/lib/vitest/setup-self-hosted.mjs +151 -0
- package/lib/vitest/setup.mjs +46 -0
- package/manual/captcha-api.test.mjs +51 -0
- package/manual/drag-and-drop.test.mjs +59 -0
- package/manual/flake-diffthreshold-001.test.mjs +9 -0
- package/manual/flake-diffthreshold-01.test.mjs +9 -0
- package/manual/flake-diffthreshold-05.test.mjs +9 -0
- package/manual/flake-noredraw-cache.test.mjs +9 -0
- package/manual/flake-noredraw-nocache.test.mjs +9 -0
- package/manual/flake-redraw-cache.test.mjs +9 -0
- package/manual/flake-redraw-nocache.test.mjs +9 -0
- package/manual/flake-rocket-match.test.mjs +30 -0
- package/manual/flake-shared.mjs +51 -0
- package/manual/no-provision.test.mjs +31 -0
- package/manual/packer-hover-image.test.mjs +176 -0
- package/manual/scroll-until-text.test.mjs +68 -0
- package/manual/test-init-command.js +223 -0
- package/mcp-server/README.md +322 -0
- package/mcp-server/dist/codegen.d.ts +9 -0
- package/mcp-server/dist/codegen.js +165 -0
- package/mcp-server/dist/mcp-app.html +114 -0
- package/mcp-server/dist/package.json +1 -0
- package/mcp-server/dist/provision-types.d.ts +290 -0
- package/mcp-server/dist/provision-types.js +174 -0
- package/mcp-server/dist/server.d.ts +6 -0
- package/mcp-server/dist/server.mjs +1925 -0
- package/mcp-server/dist/session.d.ts +85 -0
- package/mcp-server/dist/session.js +152 -0
- package/mcp-server/mcp-app.html +28 -0
- package/mcp-server/mcp-config.example.json +19 -0
- package/mcp-server/package-lock.json +4027 -0
- package/mcp-server/package.json +31 -0
- package/mcp-server/src/codegen.ts +189 -0
- package/mcp-server/src/mcp-app.css +360 -0
- package/mcp-server/src/mcp-app.ts +547 -0
- package/mcp-server/src/provision-types.ts +209 -0
- package/mcp-server/src/server.ts +2391 -0
- package/mcp-server/src/session.ts +194 -0
- package/mcp-server/tsconfig.json +16 -0
- package/mcp-server/vite.config.ts +23 -0
- package/package.json +158 -0
- package/schema.json +1046 -0
- package/scripts/generate-skills.js +94 -0
- package/sdk-log-formatter.js +1157 -0
- package/sdk.d.ts +1486 -0
- package/sdk.js +4336 -0
- package/setup/aws/cloudformation.yaml +463 -0
- package/setup/aws/disable-defender.sh +42 -0
- package/setup/aws/install-dev-runner.sh +79 -0
- package/setup/aws/spawn-runner.sh +289 -0
- package/test/captcha-solver.test.mjs +152 -0
- package/test/chrome-remote-debugging.test.mjs +66 -0
- package/test/duckduckgo/experiment.test.mjs +28 -0
- package/test/duckduckgo/setup.test.mjs +29 -0
- package/test/manual/debug-locate-response.js +82 -0
- package/test/manual/reconnect-provision.test.mjs +49 -0
- package/test/manual/test-console-logs.test.mjs +42 -0
- package/test/manual/test-find-api.js +73 -0
- package/test/manual/test-init.sh +54 -0
- package/test/manual/test-prompt-cache.js +97 -0
- package/test/manual/test-provision-auth.mjs +22 -0
- package/test/manual/test-sandbox-render.js +29 -0
- package/test/manual/test-sdk-methods.js +15 -0
- package/test/manual/test-sdk-refactor.js +53 -0
- package/test/manual/test-stack-trace.mjs +57 -0
- package/test/manual/verify-element-api.js +89 -0
- package/test/manual/verify-types.js +0 -0
- package/test/manual-unawaited-promise.test.mjs +31 -0
- package/vitest.config.mjs +58 -0
- package/vitest.runner.config.mjs +33 -0
- package/vscode-extension/.vscodeignore +12 -0
- package/vscode-extension/README.md +94 -0
- package/vscode-extension/media/icon.png +0 -0
- package/vscode-extension/package-lock.json +4126 -0
- package/vscode-extension/package.json +86 -0
- package/vscode-extension/src/extension.ts +829 -0
- package/vscode-extension/testdriverai-0.1.0.vsix +0 -0
- package/vscode-extension/tsconfig.json +16 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
const { Command } = require("@oclif/core");
|
|
2
|
+
const { createCommandDefinitions } = require("../../../agent/interface.js");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const chalk = require("chalk");
|
|
7
|
+
const { execSync } = require("child_process");
|
|
8
|
+
const readline = require("readline");
|
|
9
|
+
|
|
10
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..");
|
|
11
|
+
const CLAUDE_HOME = path.join(os.homedir(), ".claude");
|
|
12
|
+
const CLAUDE_MCP_FILE = path.join(os.homedir(), ".claude.json");
|
|
13
|
+
const CURSOR_MCP_FILE = path.join(os.homedir(), ".cursor", "mcp.json");
|
|
14
|
+
|
|
15
|
+
const MCP_SERVER_CONFIG = {
|
|
16
|
+
"testdriver-cloud": {
|
|
17
|
+
type: "sse",
|
|
18
|
+
url: "https://replayable-dev-ian-mac-m1-16.ngrok.io/api/v1/mcp",
|
|
19
|
+
headers: {
|
|
20
|
+
"x-api-key": "${TD_API_KEY}",
|
|
21
|
+
},
|
|
22
|
+
description:
|
|
23
|
+
"Query TestDriver test runs, test cases, and filters for your team using an API key.",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const CURSOR_MCP_SERVER_CONFIG = {
|
|
28
|
+
"testdriver-cloud": {
|
|
29
|
+
type: "sse",
|
|
30
|
+
url: "https://replayable-dev-ian-mac-m1-16.ngrok.io/api/v1/mcp",
|
|
31
|
+
headers: {
|
|
32
|
+
"x-api-key": "${TD_API_KEY}",
|
|
33
|
+
},
|
|
34
|
+
description:
|
|
35
|
+
"Query TestDriver test runs, test cases, and filters for your team using an API key.",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
class SetupCommand extends Command {
|
|
40
|
+
async run() {
|
|
41
|
+
await this.parse(SetupCommand);
|
|
42
|
+
|
|
43
|
+
console.log(chalk.cyan("\nSetting up TestDriver for Claude Code...\n"));
|
|
44
|
+
|
|
45
|
+
const sourceSkills = path.join(PACKAGE_ROOT, "ai", "skills");
|
|
46
|
+
const sourceAgents = path.join(PACKAGE_ROOT, "ai", "agents");
|
|
47
|
+
|
|
48
|
+
this.installSkills(sourceSkills, path.join(CLAUDE_HOME, "skills"));
|
|
49
|
+
this.installAgents(sourceAgents, path.join(CLAUDE_HOME, "agents"));
|
|
50
|
+
this.installClaudeMcp();
|
|
51
|
+
this.installCursorMcp();
|
|
52
|
+
await this.promptForApiKey();
|
|
53
|
+
|
|
54
|
+
console.log(chalk.green("\nSetup complete!\n"));
|
|
55
|
+
this.printNextSteps();
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Recursively copy a directory's contents
|
|
61
|
+
*/
|
|
62
|
+
copyDirSync(src, dest) {
|
|
63
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
64
|
+
|
|
65
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
66
|
+
const srcPath = path.join(src, entry.name);
|
|
67
|
+
const destPath = path.join(dest, entry.name);
|
|
68
|
+
|
|
69
|
+
if (entry.isDirectory()) {
|
|
70
|
+
this.copyDirSync(srcPath, destPath);
|
|
71
|
+
} else {
|
|
72
|
+
fs.copyFileSync(srcPath, destPath);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Install skills to ~/.claude/skills
|
|
79
|
+
*/
|
|
80
|
+
installSkills(source, dest) {
|
|
81
|
+
if (!fs.existsSync(source)) {
|
|
82
|
+
console.log(
|
|
83
|
+
chalk.yellow(" Skills source not found, skipping: " + source),
|
|
84
|
+
);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const skills = fs
|
|
89
|
+
.readdirSync(source, { withFileTypes: true })
|
|
90
|
+
.filter((d) => d.isDirectory());
|
|
91
|
+
|
|
92
|
+
for (const skill of skills) {
|
|
93
|
+
const srcDir = path.join(source, skill.name);
|
|
94
|
+
const destDir = path.join(dest, skill.name);
|
|
95
|
+
this.copyDirSync(srcDir, destDir);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
console.log(chalk.green(` Installed ${skills.length} skills to ${dest}`));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Install agents to ~/.claude/agents
|
|
103
|
+
*/
|
|
104
|
+
installAgents(source, dest) {
|
|
105
|
+
if (!fs.existsSync(source)) {
|
|
106
|
+
console.log(
|
|
107
|
+
chalk.yellow(" Agents source not found, skipping: " + source),
|
|
108
|
+
);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
113
|
+
|
|
114
|
+
const agents = fs.readdirSync(source).filter((f) => f.endsWith(".md"));
|
|
115
|
+
|
|
116
|
+
for (const agent of agents) {
|
|
117
|
+
fs.copyFileSync(path.join(source, agent), path.join(dest, agent));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(
|
|
121
|
+
chalk.green(` Installed ${agents.length} agent(s) to ${dest}`),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Add testdriver MCP server to ~/.claude.json
|
|
127
|
+
*/
|
|
128
|
+
installClaudeMcp() {
|
|
129
|
+
let config = {};
|
|
130
|
+
|
|
131
|
+
if (fs.existsSync(CLAUDE_MCP_FILE)) {
|
|
132
|
+
try {
|
|
133
|
+
config = JSON.parse(fs.readFileSync(CLAUDE_MCP_FILE, "utf8"));
|
|
134
|
+
} catch {
|
|
135
|
+
// If the file is malformed, start fresh but warn
|
|
136
|
+
console.log(
|
|
137
|
+
chalk.yellow(
|
|
138
|
+
" Warning: existing ~/.claude.json was not valid JSON, overwriting",
|
|
139
|
+
),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!config.mcpServers) {
|
|
145
|
+
config.mcpServers = {};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const alreadyConfigured = config.mcpServers["testdriver-cloud"];
|
|
149
|
+
|
|
150
|
+
Object.assign(config.mcpServers, MCP_SERVER_CONFIG);
|
|
151
|
+
fs.writeFileSync(CLAUDE_MCP_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
152
|
+
|
|
153
|
+
if (alreadyConfigured) {
|
|
154
|
+
console.log(
|
|
155
|
+
chalk.green(` Updated testdriver MCP server in ${CLAUDE_MCP_FILE}`),
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
console.log(
|
|
159
|
+
chalk.green(` Added testdriver MCP server to ${CLAUDE_MCP_FILE}`),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Add testdriver MCP server to ~/.cursor/mcp.json
|
|
166
|
+
*/
|
|
167
|
+
installCursorMcp() {
|
|
168
|
+
const cursorDir = path.dirname(CURSOR_MCP_FILE);
|
|
169
|
+
fs.mkdirSync(cursorDir, { recursive: true });
|
|
170
|
+
|
|
171
|
+
let config = {};
|
|
172
|
+
|
|
173
|
+
if (fs.existsSync(CURSOR_MCP_FILE)) {
|
|
174
|
+
try {
|
|
175
|
+
config = JSON.parse(fs.readFileSync(CURSOR_MCP_FILE, "utf8"));
|
|
176
|
+
} catch {
|
|
177
|
+
console.log(
|
|
178
|
+
chalk.yellow(
|
|
179
|
+
" Warning: existing ~/.cursor/mcp.json was not valid JSON, overwriting",
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!config.servers) {
|
|
186
|
+
config.servers = {};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const alreadyConfigured = config.servers["testdriver-cloud"];
|
|
190
|
+
|
|
191
|
+
Object.assign(config.servers, CURSOR_MCP_SERVER_CONFIG);
|
|
192
|
+
fs.writeFileSync(CURSOR_MCP_FILE, JSON.stringify(config, null, 2) + "\n");
|
|
193
|
+
|
|
194
|
+
if (alreadyConfigured) {
|
|
195
|
+
console.log(
|
|
196
|
+
chalk.green(` Updated testdriver MCP server in ${CURSOR_MCP_FILE}`),
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
console.log(
|
|
200
|
+
chalk.green(` Added testdriver MCP server to ${CURSOR_MCP_FILE}`),
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Prompt user for API key and save globally to shell profile
|
|
207
|
+
*/
|
|
208
|
+
async promptForApiKey() {
|
|
209
|
+
// Check if TD_API_KEY is already set in the environment
|
|
210
|
+
if (process.env.TD_API_KEY) {
|
|
211
|
+
console.log(
|
|
212
|
+
chalk.gray("\n API key already set in environment, skipping...\n"),
|
|
213
|
+
);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(chalk.cyan("\n Setting up your TestDriver API key...\n"));
|
|
218
|
+
console.log(
|
|
219
|
+
chalk.gray(" Get your API key from: https://console.testdriver.ai/team"),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const shouldOpen = await this.askYesNo(
|
|
223
|
+
" Open API keys page in browser? (Y/n): ",
|
|
224
|
+
);
|
|
225
|
+
if (shouldOpen) {
|
|
226
|
+
try {
|
|
227
|
+
const open = (await import("open")).default;
|
|
228
|
+
await open("https://console.testdriver.ai/team");
|
|
229
|
+
console.log(chalk.gray(" Opening browser...\n"));
|
|
230
|
+
} catch (error) {
|
|
231
|
+
console.log(
|
|
232
|
+
chalk.yellow(" Could not open browser automatically\n"),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const apiKey = await this.promptHidden(
|
|
238
|
+
" Enter your API key (input will be hidden): ",
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
if (apiKey && apiKey.trim()) {
|
|
242
|
+
this.addToShellProfile("TD_API_KEY", apiKey.trim());
|
|
243
|
+
process.env.TD_API_KEY = apiKey.trim();
|
|
244
|
+
} else {
|
|
245
|
+
console.log(
|
|
246
|
+
chalk.yellow(
|
|
247
|
+
"\n No API key entered. You can set it later:\n",
|
|
248
|
+
),
|
|
249
|
+
);
|
|
250
|
+
console.log(chalk.gray(' export TD_API_KEY="your_api_key"\n'));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Prompt for hidden input (like password)
|
|
256
|
+
*/
|
|
257
|
+
async promptHidden(question) {
|
|
258
|
+
return new Promise((resolve) => {
|
|
259
|
+
process.stdout.write(question);
|
|
260
|
+
|
|
261
|
+
const stdin = process.stdin;
|
|
262
|
+
const wasRaw = stdin.isRaw;
|
|
263
|
+
stdin.setRawMode(true);
|
|
264
|
+
stdin.resume();
|
|
265
|
+
stdin.setEncoding("utf8");
|
|
266
|
+
|
|
267
|
+
let input = "";
|
|
268
|
+
|
|
269
|
+
const onData = (char) => {
|
|
270
|
+
if (char === "\u0003") {
|
|
271
|
+
stdin.setRawMode(wasRaw);
|
|
272
|
+
process.exit();
|
|
273
|
+
}
|
|
274
|
+
if (char === "\r" || char === "\n") {
|
|
275
|
+
stdin.setRawMode(wasRaw);
|
|
276
|
+
stdin.removeListener("data", onData);
|
|
277
|
+
stdin.pause();
|
|
278
|
+
console.log("");
|
|
279
|
+
resolve(input);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (char === "\u007F" || char === "\b") {
|
|
283
|
+
input = input.slice(0, -1);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
input += char;
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
stdin.on("data", onData);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Ask a yes/no question
|
|
295
|
+
*/
|
|
296
|
+
async askYesNo(question) {
|
|
297
|
+
return new Promise((resolve) => {
|
|
298
|
+
const rl = readline.createInterface({
|
|
299
|
+
input: process.stdin,
|
|
300
|
+
output: process.stdout,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
rl.question(question, (answer) => {
|
|
304
|
+
rl.close();
|
|
305
|
+
const normalized = answer.toLowerCase().trim();
|
|
306
|
+
resolve(
|
|
307
|
+
normalized === "" || normalized === "y" || normalized === "yes",
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Add an environment variable export to the user's shell profile
|
|
315
|
+
*/
|
|
316
|
+
addToShellProfile(key, value) {
|
|
317
|
+
if (process.platform === "win32") {
|
|
318
|
+
try {
|
|
319
|
+
execSync(`setx ${key} "${value}"`, { stdio: "ignore" });
|
|
320
|
+
console.log(
|
|
321
|
+
chalk.green(`\n Set ${key} as user environment variable\n`),
|
|
322
|
+
);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.log(
|
|
325
|
+
chalk.yellow(`\n Could not set ${key} via setx. You can set it manually:\n`),
|
|
326
|
+
);
|
|
327
|
+
console.log(chalk.gray(` setx ${key} "your_api_key"\n`));
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const shell = process.env.SHELL || "/bin/bash";
|
|
333
|
+
const home = os.homedir();
|
|
334
|
+
let profilePath;
|
|
335
|
+
|
|
336
|
+
if (shell.includes("zsh")) {
|
|
337
|
+
profilePath = path.join(home, ".zshrc");
|
|
338
|
+
} else {
|
|
339
|
+
profilePath = path.join(home, ".bashrc");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const exportLine = `export ${key}="${value}"`;
|
|
343
|
+
|
|
344
|
+
if (fs.existsSync(profilePath)) {
|
|
345
|
+
const content = fs.readFileSync(profilePath, "utf8");
|
|
346
|
+
if (content.includes(`export ${key}=`)) {
|
|
347
|
+
const updated = content.replace(
|
|
348
|
+
new RegExp(`^export ${key}=.*$`, "m"),
|
|
349
|
+
exportLine,
|
|
350
|
+
);
|
|
351
|
+
fs.writeFileSync(profilePath, updated);
|
|
352
|
+
console.log(
|
|
353
|
+
chalk.green(`\n Updated ${key} in ${profilePath}\n`),
|
|
354
|
+
);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
fs.appendFileSync(profilePath, `\n${exportLine}\n`);
|
|
360
|
+
console.log(
|
|
361
|
+
chalk.green(`\n Added ${key} to ${profilePath}\n`),
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
printNextSteps() {
|
|
366
|
+
console.log(chalk.cyan("Next steps:\n"));
|
|
367
|
+
console.log(" 1. Restart Claude Code to pick up the new MCP server\n");
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Get command definition from interface.js
|
|
372
|
+
const tempAgent = { workingDir: process.cwd() };
|
|
373
|
+
const definitions = createCommandDefinitions(tempAgent);
|
|
374
|
+
const commandDef = definitions["setup"];
|
|
375
|
+
|
|
376
|
+
SetupCommand.description =
|
|
377
|
+
commandDef?.description ||
|
|
378
|
+
"Set up TestDriver skills, agents, and MCP for Claude Code";
|
|
379
|
+
SetupCommand.args = commandDef?.args || {};
|
|
380
|
+
SetupCommand.flags = commandDef?.flags || {};
|
|
381
|
+
|
|
382
|
+
module.exports = SetupCommand;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This is an implementation of the TestDriver library. This file should not:
|
|
3
|
+
- modify the agent's state
|
|
4
|
+
- emit events back to the agent
|
|
5
|
+
- etc
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { Command } = require("@oclif/core");
|
|
9
|
+
const { events } = require("../../../agent/events.js");
|
|
10
|
+
const { createCommandDefinitions } = require("../../../agent/interface.js");
|
|
11
|
+
const { createJUnitReporter } = require("../../junit-reporter.js");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const os = require("os");
|
|
15
|
+
const logger = require("../../logger.js");
|
|
16
|
+
|
|
17
|
+
async function openBrowser(url) {
|
|
18
|
+
try {
|
|
19
|
+
// Use dynamic import for the 'open' package (ES module)
|
|
20
|
+
const { default: open } = await import("open");
|
|
21
|
+
|
|
22
|
+
// Open the browser
|
|
23
|
+
await open(url, {
|
|
24
|
+
// Wait for the app to open
|
|
25
|
+
wait: false,
|
|
26
|
+
background: true
|
|
27
|
+
});
|
|
28
|
+
} catch (error) {
|
|
29
|
+
console.error("Failed to open browser automatically:", error);
|
|
30
|
+
console.log(`Please manually open: ${url}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class BaseCommand extends Command {
|
|
35
|
+
constructor(argv, config) {
|
|
36
|
+
super(argv, config);
|
|
37
|
+
this.agent = null; // Initialize as null, create only when needed
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
sendToSandbox(message) {
|
|
41
|
+
if (!message) return;
|
|
42
|
+
|
|
43
|
+
// ensure message is a string
|
|
44
|
+
if (typeof message !== "string") {
|
|
45
|
+
message = JSON.stringify(message);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.agent.sandbox.send({
|
|
49
|
+
type: "output",
|
|
50
|
+
output: Buffer.from(message).toString("base64"),
|
|
51
|
+
}).catch(() => {
|
|
52
|
+
// Silently ignore output send failures to prevent infinite loops
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
setupEventListeners() {
|
|
57
|
+
if (!this.logFilePath) {
|
|
58
|
+
// Create a temp log file for this session
|
|
59
|
+
this.logFilePath = path.join(
|
|
60
|
+
os.tmpdir(),
|
|
61
|
+
`testdriverai-cli-${process.pid}.log`,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
console.log(`Log file: ${this.logFilePath}`);
|
|
65
|
+
console.log("");
|
|
66
|
+
fs.writeFileSync(this.logFilePath, ""); // Initialize the log file
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Helper to append log messages to the temp file
|
|
70
|
+
const appendLog = (level, message) => {
|
|
71
|
+
const timestamp = new Date().toISOString();
|
|
72
|
+
fs.appendFileSync(
|
|
73
|
+
this.logFilePath,
|
|
74
|
+
`[${timestamp}] [${level}] ${message}\n`,
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
let isConnected = false;
|
|
79
|
+
const debugMode = process.env.VERBOSE || process.env.TD_DEBUG;
|
|
80
|
+
|
|
81
|
+
// Use pattern matching for log events, but skip log:Debug unless debug mode is enabled
|
|
82
|
+
this.agent.emitter.on("log:*", (message) => {
|
|
83
|
+
const event = this.agent.emitter.event;
|
|
84
|
+
|
|
85
|
+
if (event === events.log.debug && !debugMode) return;
|
|
86
|
+
|
|
87
|
+
if (event === events.log.narration && isConnected) return;
|
|
88
|
+
console.log(message);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Use pattern matching for error events
|
|
92
|
+
this.agent.emitter.on("error:*", (data) => {
|
|
93
|
+
const event = this.agent.emitter.event;
|
|
94
|
+
console.error(event, ":", data);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Handle status events
|
|
98
|
+
this.agent.emitter.on("status", (message) => {
|
|
99
|
+
console.log(`- ${message}`);
|
|
100
|
+
this.sendToSandbox(`- ${message}`);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Handle sandbox connection with pattern matching for subsequent events
|
|
104
|
+
this.agent.emitter.once("sandbox:connected", () => {
|
|
105
|
+
isConnected = true;
|
|
106
|
+
// Once sandbox is connected, send all log and error events to sandbox
|
|
107
|
+
this.agent.emitter.on("log:*", (message) => {
|
|
108
|
+
this.sendToSandbox(message);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
this.agent.emitter.on("error:*", (message) => {
|
|
112
|
+
// Don't forward sandbox errors back to sandbox - this creates an infinite loop
|
|
113
|
+
// (sandbox error → error:* event → sendToSandbox → output message → sandbox error → ...)
|
|
114
|
+
const event = this.agent.emitter.event;
|
|
115
|
+
if (event === "error:sandbox") return;
|
|
116
|
+
this.sendToSandbox(message);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Handle all other events with wildcard pattern
|
|
121
|
+
this.agent.emitter.on("**", (data) => {
|
|
122
|
+
const event = this.agent.emitter.event;
|
|
123
|
+
appendLog(event, JSON.stringify(data));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
logger.createMarkdownLogger(this.agent.emitter);
|
|
127
|
+
|
|
128
|
+
// Initialize JUnit reporter if junit flag is provided
|
|
129
|
+
if (this.agent.cliArgs?.options?.junit) {
|
|
130
|
+
const junitOutputPath = this.agent.cliArgs.options.junit;
|
|
131
|
+
const mainTestFile = this.agent.thisFile; // Get the main test file from the agent
|
|
132
|
+
this.junitReporter = createJUnitReporter(
|
|
133
|
+
this.agent.emitter,
|
|
134
|
+
junitOutputPath,
|
|
135
|
+
mainTestFile,
|
|
136
|
+
);
|
|
137
|
+
console.log(`JUnit reporting enabled: ${junitOutputPath}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
this.agent.emitter.on("exit", (exitCode) => {
|
|
141
|
+
// Ensure sandbox is closed before exiting
|
|
142
|
+
if (this.agent?.sandbox) {
|
|
143
|
+
try {
|
|
144
|
+
this.agent.sandbox.close();
|
|
145
|
+
} catch (err) {
|
|
146
|
+
// Ignore close errors
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
process.exit(exitCode);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Handle process signals to ensure clean disconnection
|
|
153
|
+
let isExiting = false;
|
|
154
|
+
const cleanupAndExit = async (signal) => {
|
|
155
|
+
if (isExiting) return;
|
|
156
|
+
isExiting = true;
|
|
157
|
+
|
|
158
|
+
console.log(`\nReceived ${signal}, cleaning up...`);
|
|
159
|
+
|
|
160
|
+
// Use the agent's exit method for proper cleanup
|
|
161
|
+
if (this.agent) {
|
|
162
|
+
try {
|
|
163
|
+
await this.agent.exit(true, false, false);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.error("Error during cleanup:", err.message);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
// Fallback if no agent
|
|
169
|
+
if (this.agent?.sandbox) {
|
|
170
|
+
try {
|
|
171
|
+
this.agent.sandbox.close();
|
|
172
|
+
} catch (err) {
|
|
173
|
+
// Ignore close errors
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
process.on('SIGINT', () => cleanupAndExit('SIGINT'));
|
|
181
|
+
process.on('SIGTERM', () => cleanupAndExit('SIGTERM'));
|
|
182
|
+
|
|
183
|
+
// Handle unhandled promise rejections to prevent them from interfering with the exit flow
|
|
184
|
+
// This is particularly important when JavaScript execution in VM contexts leaves dangling promises
|
|
185
|
+
process.on("unhandledRejection", (reason) => {
|
|
186
|
+
// Log the rejection but don't let it crash the process
|
|
187
|
+
console.error("Unhandled Promise Rejection:", reason);
|
|
188
|
+
// The exit flow should continue normally
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Handle show window events
|
|
192
|
+
this.agent.emitter.on("show-window", async (url) => {
|
|
193
|
+
console.log("");
|
|
194
|
+
console.log(`Live test execution: `);
|
|
195
|
+
console.log(url);
|
|
196
|
+
if (!this.agent.config.CI) {
|
|
197
|
+
await openBrowser(url);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async init() {
|
|
203
|
+
// Only start debugger for commands that actually need it
|
|
204
|
+
// Help commands and other static commands don't need the debugger
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Extract file path from args or use default
|
|
208
|
+
normalizeFilePath(file) {
|
|
209
|
+
const path = require("path");
|
|
210
|
+
if (!file) {
|
|
211
|
+
// Use config default if agent is available, otherwise fall back to hardcoded default
|
|
212
|
+
file = "testdriver/testdriver.yaml";
|
|
213
|
+
}
|
|
214
|
+
file = path.join(this.agent.workingDir, file);
|
|
215
|
+
if (!file.endsWith(".yaml") && !file.endsWith(".yml")) {
|
|
216
|
+
file += ".yaml";
|
|
217
|
+
}
|
|
218
|
+
return file;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async setupAgent(firstArg, flags) {
|
|
222
|
+
// Load .env file into process.env for CLI usage
|
|
223
|
+
require("dotenv").config();
|
|
224
|
+
|
|
225
|
+
// Create the agent only when actually needed
|
|
226
|
+
const TestDriverAgent = require("../../../agent/index.js");
|
|
227
|
+
|
|
228
|
+
let args;
|
|
229
|
+
if (this.id === "generate") {
|
|
230
|
+
// For generate command, the first parameter is a prompt, not a file
|
|
231
|
+
args = firstArg ? [firstArg] : [];
|
|
232
|
+
} else {
|
|
233
|
+
// For run and other commands, handle file path
|
|
234
|
+
const filePath = this.id === "run" && flags.path ? flags.path : firstArg;
|
|
235
|
+
args = filePath ? [filePath] : [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Prepare CLI args for the agent with all derived options
|
|
239
|
+
const cliArgs = {
|
|
240
|
+
command: this.id,
|
|
241
|
+
args,
|
|
242
|
+
options: {
|
|
243
|
+
...flags,
|
|
244
|
+
resultFile:
|
|
245
|
+
flags.summary && typeof flags.summary === "string"
|
|
246
|
+
? path.resolve(flags.summary)
|
|
247
|
+
: null,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
// Create agent with explicit process.env and consolidated CLI args
|
|
252
|
+
this.agent = new TestDriverAgent(process.env, cliArgs);
|
|
253
|
+
this.setupEventListeners();
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
// Start the agent's initialization
|
|
257
|
+
await this.agent.start();
|
|
258
|
+
} catch (e) {
|
|
259
|
+
console.error("Failed to start agent:", e);
|
|
260
|
+
this.agent.emitter.emit(
|
|
261
|
+
events.error.fatal,
|
|
262
|
+
"Failed to start agent: " + JSON.stringify(e),
|
|
263
|
+
);
|
|
264
|
+
if (this.agent) {
|
|
265
|
+
await this.agent.exit(true);
|
|
266
|
+
} else {
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Get unified command definition for this command
|
|
273
|
+
getUnifiedDefinition() {
|
|
274
|
+
const commandName = this.id;
|
|
275
|
+
if (!this.agent) {
|
|
276
|
+
// Create a temporary agent for definition purposes with empty environment
|
|
277
|
+
const tempAgent = { workingDir: process.cwd() };
|
|
278
|
+
return createCommandDefinitions(tempAgent)[commandName];
|
|
279
|
+
}
|
|
280
|
+
const definitions = createCommandDefinitions(this.agent);
|
|
281
|
+
return definitions[commandName];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
module.exports = BaseCommand;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { run } = require("@oclif/core");
|
|
4
|
+
const sentry = require("../lib/sentry");
|
|
5
|
+
|
|
6
|
+
// Run oclif (with default command handling built-in)
|
|
7
|
+
run()
|
|
8
|
+
.then(() => {
|
|
9
|
+
// Success
|
|
10
|
+
})
|
|
11
|
+
.catch(async (error) => {
|
|
12
|
+
// Capture error in Sentry
|
|
13
|
+
sentry.captureException(error, {
|
|
14
|
+
tags: { component: "cli-init" },
|
|
15
|
+
});
|
|
16
|
+
await sentry.flush();
|
|
17
|
+
|
|
18
|
+
console.error("Failed to start TestDriver.ai agent:", error);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
});
|