@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,547 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TestDriver MCP App - displays screenshots with overlays for action results
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
App,
|
|
6
|
+
applyDocumentTheme,
|
|
7
|
+
applyHostFonts,
|
|
8
|
+
applyHostStyleVariables,
|
|
9
|
+
type McpUiHostContext,
|
|
10
|
+
} from "@modelcontextprotocol/ext-apps";
|
|
11
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
12
|
+
import { ReadResourceResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
import "./mcp-app.css";
|
|
14
|
+
|
|
15
|
+
// DOM elements
|
|
16
|
+
const mainEl = document.querySelector(".main") as HTMLElement;
|
|
17
|
+
const containerEl = document.getElementById("screenshot-container") as HTMLDivElement;
|
|
18
|
+
const screenshotEl = document.getElementById("screenshot") as HTMLImageElement;
|
|
19
|
+
const overlaysEl = document.getElementById("overlays") as HTMLDivElement;
|
|
20
|
+
const actionStatusEl = document.getElementById("action-status") as HTMLSpanElement;
|
|
21
|
+
const sessionInfoEl = document.getElementById("session-info") as HTMLSpanElement;
|
|
22
|
+
const loadingOverlayEl = document.getElementById("loading-overlay") as HTMLDivElement;
|
|
23
|
+
const loadingTextEl = loadingOverlayEl.querySelector(".loading-text") as HTMLSpanElement;
|
|
24
|
+
|
|
25
|
+
// Create target info element dynamically
|
|
26
|
+
const targetInfoEl = document.createElement("div");
|
|
27
|
+
targetInfoEl.id = "target-info";
|
|
28
|
+
targetInfoEl.className = "target-info hidden";
|
|
29
|
+
|
|
30
|
+
// Track screenshot natural dimensions for coordinate scaling
|
|
31
|
+
let screenshotNaturalWidth = 0;
|
|
32
|
+
let screenshotNaturalHeight = 0;
|
|
33
|
+
|
|
34
|
+
// Zoom state - disabled, always show full view
|
|
35
|
+
let isZoomed = false;
|
|
36
|
+
const ZOOM_LEVEL = 2.0; // 2x zoom (not used when zoom disabled)
|
|
37
|
+
|
|
38
|
+
// Types for tool result data
|
|
39
|
+
interface ToolResultData {
|
|
40
|
+
action?: string;
|
|
41
|
+
success?: boolean;
|
|
42
|
+
imageUrl?: string; // Data URL with cropped image from find() response
|
|
43
|
+
screenshotResourceUri?: string; // Resource URI to fetch screenshot blob
|
|
44
|
+
croppedImageResourceUri?: string; // Resource URI to fetch cropped image from find operations
|
|
45
|
+
element?: {
|
|
46
|
+
description?: string;
|
|
47
|
+
x?: number;
|
|
48
|
+
y?: number;
|
|
49
|
+
centerX?: number;
|
|
50
|
+
centerY?: number;
|
|
51
|
+
width?: number;
|
|
52
|
+
height?: number;
|
|
53
|
+
confidence?: number;
|
|
54
|
+
ref?: string;
|
|
55
|
+
};
|
|
56
|
+
clickPosition?: { x: number; y: number; centerX?: number; centerY?: number };
|
|
57
|
+
scrollDirection?: string;
|
|
58
|
+
assertion?: string;
|
|
59
|
+
text?: string;
|
|
60
|
+
execResult?: string;
|
|
61
|
+
error?: string;
|
|
62
|
+
session?: {
|
|
63
|
+
id?: string;
|
|
64
|
+
expiresIn?: number;
|
|
65
|
+
};
|
|
66
|
+
debuggerUrl?: string;
|
|
67
|
+
sessionId?: string;
|
|
68
|
+
duration?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Store session info globally for display
|
|
72
|
+
let currentDebuggerUrl: string | null = null;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extract structured data from tool result
|
|
76
|
+
*/
|
|
77
|
+
function extractData(result: CallToolResult): ToolResultData {
|
|
78
|
+
return (result.structuredContent as ToolResultData) ?? {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Show loading state with optional custom message
|
|
83
|
+
*/
|
|
84
|
+
function showLoading(message = "Waiting for screenshot...") {
|
|
85
|
+
loadingTextEl.textContent = message;
|
|
86
|
+
loadingOverlayEl.classList.remove("hidden");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Hide loading state
|
|
91
|
+
*/
|
|
92
|
+
function hideLoading() {
|
|
93
|
+
loadingOverlayEl.classList.add("hidden");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Apply host context (theme, styles, safe areas, container dimensions)
|
|
98
|
+
*/
|
|
99
|
+
function handleHostContextChanged(ctx: McpUiHostContext) {
|
|
100
|
+
if (ctx.theme) {
|
|
101
|
+
applyDocumentTheme(ctx.theme);
|
|
102
|
+
}
|
|
103
|
+
if (ctx.styles?.variables) {
|
|
104
|
+
applyHostStyleVariables(ctx.styles.variables);
|
|
105
|
+
}
|
|
106
|
+
if (ctx.styles?.css?.fonts) {
|
|
107
|
+
applyHostFonts(ctx.styles.css.fonts);
|
|
108
|
+
}
|
|
109
|
+
if (ctx.safeAreaInsets) {
|
|
110
|
+
mainEl.style.paddingTop = `${ctx.safeAreaInsets.top}px`;
|
|
111
|
+
mainEl.style.paddingRight = `${ctx.safeAreaInsets.right}px`;
|
|
112
|
+
mainEl.style.paddingBottom = `${ctx.safeAreaInsets.bottom}px`;
|
|
113
|
+
mainEl.style.paddingLeft = `${ctx.safeAreaInsets.left}px`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle container dimensions to fit the entire image in the frame
|
|
117
|
+
const containerDimensions = (ctx as any).containerDimensions;
|
|
118
|
+
if (containerDimensions) {
|
|
119
|
+
// Handle height
|
|
120
|
+
if ("height" in containerDimensions) {
|
|
121
|
+
// Fixed height: fill the container
|
|
122
|
+
document.documentElement.style.height = "100vh";
|
|
123
|
+
mainEl.style.height = "100%";
|
|
124
|
+
} else if ("maxHeight" in containerDimensions && containerDimensions.maxHeight) {
|
|
125
|
+
// Flexible with max: let content determine size, up to max
|
|
126
|
+
document.documentElement.style.maxHeight = `${containerDimensions.maxHeight}px`;
|
|
127
|
+
mainEl.style.maxHeight = "100%";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Handle width
|
|
131
|
+
if ("width" in containerDimensions) {
|
|
132
|
+
// Fixed width: fill the container
|
|
133
|
+
document.documentElement.style.width = "100vw";
|
|
134
|
+
mainEl.style.width = "100%";
|
|
135
|
+
} else if ("maxWidth" in containerDimensions && containerDimensions.maxWidth) {
|
|
136
|
+
// Flexible with max: let content determine size, up to max
|
|
137
|
+
document.documentElement.style.maxWidth = `${containerDimensions.maxWidth}px`;
|
|
138
|
+
mainEl.style.maxWidth = "100%";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Scale coordinates from screenshot natural size to displayed size
|
|
145
|
+
*/
|
|
146
|
+
function scaleCoord(value: number, naturalSize: number, displayedSize: number): number {
|
|
147
|
+
if (naturalSize === 0) return value;
|
|
148
|
+
return (value / naturalSize) * displayedSize;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Apply zoom transform to center on a point
|
|
153
|
+
*/
|
|
154
|
+
function applyZoom(centerX: number, centerY: number, zoom: boolean) {
|
|
155
|
+
if (!zoom) {
|
|
156
|
+
containerEl.style.transform = "none";
|
|
157
|
+
containerEl.classList.remove("zoomed");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const displayedWidth = screenshotEl.clientWidth;
|
|
162
|
+
const displayedHeight = screenshotEl.clientHeight;
|
|
163
|
+
|
|
164
|
+
// Scale the target coordinates to displayed size
|
|
165
|
+
const scaledX = scaleCoord(centerX, screenshotNaturalWidth, displayedWidth);
|
|
166
|
+
const scaledY = scaleCoord(centerY, screenshotNaturalHeight, displayedHeight);
|
|
167
|
+
|
|
168
|
+
// Calculate translation to center the point
|
|
169
|
+
// After zoom, we need to translate so the point ends up in the center of the container
|
|
170
|
+
const containerWidth = containerEl.parentElement?.clientWidth || displayedWidth;
|
|
171
|
+
const containerHeight = containerEl.parentElement?.clientHeight || displayedHeight;
|
|
172
|
+
|
|
173
|
+
// The point's position after scaling
|
|
174
|
+
const scaledPointX = scaledX * ZOOM_LEVEL;
|
|
175
|
+
const scaledPointY = scaledY * ZOOM_LEVEL;
|
|
176
|
+
|
|
177
|
+
// Translation needed to center the point
|
|
178
|
+
const translateX = (containerWidth / 2) - scaledPointX;
|
|
179
|
+
const translateY = (containerHeight / 2) - scaledPointY;
|
|
180
|
+
|
|
181
|
+
containerEl.style.transform = `scale(${ZOOM_LEVEL}) translate(${translateX / ZOOM_LEVEL}px, ${translateY / ZOOM_LEVEL}px)`;
|
|
182
|
+
containerEl.classList.add("zoomed");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Reset zoom to show full screenshot
|
|
187
|
+
*/
|
|
188
|
+
function resetZoom() {
|
|
189
|
+
containerEl.style.transform = "none";
|
|
190
|
+
containerEl.classList.remove("zoomed");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Add overlays after screenshot loads (so we know dimensions)
|
|
195
|
+
* Uses requestAnimationFrame to ensure layout is computed
|
|
196
|
+
*/
|
|
197
|
+
function addOverlays(data: ToolResultData) {
|
|
198
|
+
// Use requestAnimationFrame to ensure the image has been laid out
|
|
199
|
+
requestAnimationFrame(() => {
|
|
200
|
+
overlaysEl.innerHTML = "";
|
|
201
|
+
|
|
202
|
+
const displayedWidth = screenshotEl.clientWidth;
|
|
203
|
+
const displayedHeight = screenshotEl.clientHeight;
|
|
204
|
+
|
|
205
|
+
console.info("addOverlays:", {
|
|
206
|
+
action: data.action,
|
|
207
|
+
hasElement: !!data.element,
|
|
208
|
+
hasClickPosition: !!data.clickPosition,
|
|
209
|
+
displayedWidth,
|
|
210
|
+
displayedHeight,
|
|
211
|
+
naturalWidth: screenshotNaturalWidth,
|
|
212
|
+
naturalHeight: screenshotNaturalHeight
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Skip if dimensions aren't ready yet
|
|
216
|
+
if (displayedWidth === 0 || displayedHeight === 0) {
|
|
217
|
+
console.warn("addOverlays: Dimensions not ready, retrying...");
|
|
218
|
+
// Retry after a short delay
|
|
219
|
+
setTimeout(() => addOverlays(data), 50);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Track the focal point for zoom
|
|
224
|
+
let focalX: number | undefined;
|
|
225
|
+
let focalY: number | undefined;
|
|
226
|
+
|
|
227
|
+
// Add element target overlay for 'find' and 'find_and_click' actions
|
|
228
|
+
// The cropped image is always centered on the found element, so position target at image center
|
|
229
|
+
const showElementTarget = (data.action === "find" || data.action === "find_and_click" || data.action === "findall") && data.element;
|
|
230
|
+
if (showElementTarget) {
|
|
231
|
+
const target = document.createElement("div");
|
|
232
|
+
target.className = "element-target";
|
|
233
|
+
|
|
234
|
+
// Position at center of displayed image (cropped image is already centered on element)
|
|
235
|
+
target.style.left = `${displayedWidth / 2}px`;
|
|
236
|
+
target.style.top = `${displayedHeight / 2}px`;
|
|
237
|
+
|
|
238
|
+
// Add crosshair lines
|
|
239
|
+
const crosshairH = document.createElement("div");
|
|
240
|
+
crosshairH.className = "crosshair-h";
|
|
241
|
+
target.appendChild(crosshairH);
|
|
242
|
+
|
|
243
|
+
const crosshairV = document.createElement("div");
|
|
244
|
+
crosshairV.className = "crosshair-v";
|
|
245
|
+
target.appendChild(crosshairV);
|
|
246
|
+
|
|
247
|
+
// Add label
|
|
248
|
+
const label = document.createElement("div");
|
|
249
|
+
label.className = "element-label";
|
|
250
|
+
label.textContent = data.element?.description || "Element";
|
|
251
|
+
if (data.element?.confidence) {
|
|
252
|
+
label.textContent += ` (${Math.round(data.element.confidence * 100)}%)`;
|
|
253
|
+
}
|
|
254
|
+
target.appendChild(label);
|
|
255
|
+
|
|
256
|
+
overlaysEl.appendChild(target);
|
|
257
|
+
|
|
258
|
+
// Set focal point for zoom at image center
|
|
259
|
+
focalX = screenshotNaturalWidth / 2;
|
|
260
|
+
focalY = screenshotNaturalHeight / 2;
|
|
261
|
+
|
|
262
|
+
console.info("addOverlays: Added element target at center");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Add click marker overlay for click actions (uses full screenshot, not cropped)
|
|
266
|
+
// Use centerX/centerY if available (where the click actually happens), fallback to x/y
|
|
267
|
+
if (data.clickPosition) {
|
|
268
|
+
const clickX = data.clickPosition.centerX ?? data.clickPosition.x;
|
|
269
|
+
const clickY = data.clickPosition.centerY ?? data.clickPosition.y;
|
|
270
|
+
|
|
271
|
+
if (clickX !== undefined && clickY !== undefined && screenshotNaturalWidth > 0) {
|
|
272
|
+
const marker = document.createElement("div");
|
|
273
|
+
marker.className = "click-marker";
|
|
274
|
+
|
|
275
|
+
const scaledX = scaleCoord(clickX, screenshotNaturalWidth, displayedWidth);
|
|
276
|
+
const scaledY = scaleCoord(clickY, screenshotNaturalHeight, displayedHeight);
|
|
277
|
+
|
|
278
|
+
marker.style.left = `${scaledX}px`;
|
|
279
|
+
marker.style.top = `${scaledY}px`;
|
|
280
|
+
|
|
281
|
+
// Add ripple effect
|
|
282
|
+
const ripple = document.createElement("div");
|
|
283
|
+
ripple.className = "click-ripple";
|
|
284
|
+
marker.appendChild(ripple);
|
|
285
|
+
|
|
286
|
+
overlaysEl.appendChild(marker);
|
|
287
|
+
|
|
288
|
+
console.info("addOverlays: Added click marker at", { clickX, clickY, scaledX, scaledY });
|
|
289
|
+
|
|
290
|
+
// Set focal point for zoom (click position takes priority if no element)
|
|
291
|
+
if (focalX === undefined) {
|
|
292
|
+
focalX = clickX;
|
|
293
|
+
focalY = clickY;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Add scroll indicator (doesn't need scaling - centered)
|
|
299
|
+
if (data.scrollDirection) {
|
|
300
|
+
const arrow = document.createElement("div");
|
|
301
|
+
arrow.className = `scroll-indicator scroll-${data.scrollDirection}`;
|
|
302
|
+
arrow.textContent = data.scrollDirection === "up" ? "↑" :
|
|
303
|
+
data.scrollDirection === "down" ? "↓" :
|
|
304
|
+
data.scrollDirection === "left" ? "←" : "→";
|
|
305
|
+
overlaysEl.appendChild(arrow);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Always show full view (zoom disabled)
|
|
309
|
+
resetZoom();
|
|
310
|
+
delete containerEl.dataset.focalX;
|
|
311
|
+
delete containerEl.dataset.focalY;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Render screenshot and overlays
|
|
317
|
+
* Fetches screenshot via HTTP from localhost server (enabled by CSP connectDomains)
|
|
318
|
+
* This keeps base64 data out of AI context - only a small URL is passed
|
|
319
|
+
*/
|
|
320
|
+
function renderResult(data: ToolResultData) {
|
|
321
|
+
// Clear previous overlays
|
|
322
|
+
overlaysEl.innerHTML = "";
|
|
323
|
+
|
|
324
|
+
// Update action status immediately
|
|
325
|
+
const actionName = data.action || "unknown";
|
|
326
|
+
const statusIcon = data.success ? "✓" : "✗";
|
|
327
|
+
const statusClass = data.success ? "success" : "error";
|
|
328
|
+
let statusText = `${statusIcon} ${actionName}`;
|
|
329
|
+
|
|
330
|
+
if (data.duration) {
|
|
331
|
+
statusText += ` (${data.duration}ms)`;
|
|
332
|
+
}
|
|
333
|
+
if (data.assertion) {
|
|
334
|
+
statusText += `: "${data.assertion}"`;
|
|
335
|
+
}
|
|
336
|
+
if (data.text && data.action === "type") {
|
|
337
|
+
statusText += `: "${data.text}"`;
|
|
338
|
+
}
|
|
339
|
+
if (data.error) {
|
|
340
|
+
statusText += ` - ${data.error}`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
actionStatusEl.textContent = statusText;
|
|
344
|
+
actionStatusEl.className = statusClass;
|
|
345
|
+
|
|
346
|
+
// Store debugger URL from session_start
|
|
347
|
+
if (data.debuggerUrl) {
|
|
348
|
+
currentDebuggerUrl = data.debuggerUrl;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Update session info with debugger link
|
|
352
|
+
if (data.session) {
|
|
353
|
+
const expiresIn = data.session.expiresIn ? Math.round(data.session.expiresIn / 1000) : 0;
|
|
354
|
+
sessionInfoEl.innerHTML = "";
|
|
355
|
+
|
|
356
|
+
if (currentDebuggerUrl) {
|
|
357
|
+
const link = document.createElement("a");
|
|
358
|
+
link.href = currentDebuggerUrl;
|
|
359
|
+
link.target = "_blank";
|
|
360
|
+
link.rel = "noopener noreferrer";
|
|
361
|
+
link.textContent = `${expiresIn}s remaining`;
|
|
362
|
+
link.className = "debugger-link";
|
|
363
|
+
link.title = `Open debugger: ${currentDebuggerUrl}`;
|
|
364
|
+
sessionInfoEl.appendChild(link);
|
|
365
|
+
} else {
|
|
366
|
+
sessionInfoEl.textContent = `${expiresIn}s remaining`;
|
|
367
|
+
}
|
|
368
|
+
sessionInfoEl.className = expiresIn < 30 ? "warning" : "";
|
|
369
|
+
} else if (currentDebuggerUrl) {
|
|
370
|
+
// No session data but we have a debugger URL - show it
|
|
371
|
+
sessionInfoEl.innerHTML = "";
|
|
372
|
+
const link = document.createElement("a");
|
|
373
|
+
link.href = currentDebuggerUrl;
|
|
374
|
+
link.target = "_blank";
|
|
375
|
+
link.rel = "noopener noreferrer";
|
|
376
|
+
link.textContent = "Open Debugger";
|
|
377
|
+
link.className = "debugger-link";
|
|
378
|
+
link.title = currentDebuggerUrl;
|
|
379
|
+
sessionInfoEl.appendChild(link);
|
|
380
|
+
} else if (data.action === "session_start") {
|
|
381
|
+
sessionInfoEl.textContent = "Session started";
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Update target info for find/find_and_click actions
|
|
385
|
+
if (data.element && (data.action === "find" || data.action === "find_and_click")) {
|
|
386
|
+
const el = data.element;
|
|
387
|
+
let targetHtml = `<strong>Target:</strong> "${el.description || "Element"}"`;
|
|
388
|
+
if (el.centerX !== undefined && el.centerY !== undefined) {
|
|
389
|
+
targetHtml += ` <span class="target-coords">(${Math.round(el.centerX)}, ${Math.round(el.centerY)})</span>`;
|
|
390
|
+
}
|
|
391
|
+
if (el.confidence !== undefined) {
|
|
392
|
+
const confidencePercent = Math.round(el.confidence * 100);
|
|
393
|
+
targetHtml += ` <span class="target-confidence ${confidencePercent >= 70 ? 'high' : confidencePercent >= 40 ? 'medium' : 'low'}">${confidencePercent}%</span>`;
|
|
394
|
+
}
|
|
395
|
+
targetInfoEl.innerHTML = targetHtml;
|
|
396
|
+
targetInfoEl.classList.remove("hidden");
|
|
397
|
+
} else {
|
|
398
|
+
targetInfoEl.classList.add("hidden");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Load cropped image from find() response (data URL)
|
|
402
|
+
if (data.imageUrl) {
|
|
403
|
+
showLoading("Loading image...");
|
|
404
|
+
screenshotEl.onerror = () => {
|
|
405
|
+
console.error("Image failed to load");
|
|
406
|
+
screenshotEl.alt = "Image failed to load";
|
|
407
|
+
containerEl.style.display = "none";
|
|
408
|
+
hideLoading();
|
|
409
|
+
};
|
|
410
|
+
screenshotEl.onload = () => {
|
|
411
|
+
console.info("Image loaded:", screenshotEl.naturalWidth, "x", screenshotEl.naturalHeight);
|
|
412
|
+
// Store natural dimensions
|
|
413
|
+
screenshotNaturalWidth = screenshotEl.naturalWidth;
|
|
414
|
+
screenshotNaturalHeight = screenshotEl.naturalHeight;
|
|
415
|
+
// Show the container and add overlays now that we know dimensions
|
|
416
|
+
containerEl.style.display = "block";
|
|
417
|
+
addOverlays(data);
|
|
418
|
+
// Hide loading state
|
|
419
|
+
hideLoading();
|
|
420
|
+
};
|
|
421
|
+
screenshotEl.src = data.imageUrl;
|
|
422
|
+
screenshotEl.style.display = "block";
|
|
423
|
+
} else {
|
|
424
|
+
// No image available - just show status without visual
|
|
425
|
+
screenshotEl.style.display = "none";
|
|
426
|
+
containerEl.style.display = "none";
|
|
427
|
+
hideLoading();
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// 1. Create app instance
|
|
432
|
+
const app = new App({ name: "TestDriver Screenshot", version: "1.0.0" });
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Fetch screenshot blob from resource URI and convert to data URL
|
|
436
|
+
*/
|
|
437
|
+
async function fetchScreenshotFromResource(resourceUri: string): Promise<string | null> {
|
|
438
|
+
try {
|
|
439
|
+
console.info("Fetching screenshot from resource:", resourceUri);
|
|
440
|
+
const result = await app.request(
|
|
441
|
+
{ method: "resources/read", params: { uri: resourceUri } },
|
|
442
|
+
ReadResourceResultSchema,
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
const content = result.contents[0];
|
|
446
|
+
if (!content || !("blob" in content)) {
|
|
447
|
+
console.error("Resource did not contain blob data");
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Convert base64 blob to data URL
|
|
452
|
+
const dataUrl = `data:${content.mimeType || "image/png"};base64,${content.blob}`;
|
|
453
|
+
console.info("Screenshot fetched successfully, blob length:", content.blob.length);
|
|
454
|
+
return dataUrl;
|
|
455
|
+
} catch (error) {
|
|
456
|
+
console.error("Failed to fetch screenshot resource:", error);
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// 2. Register handlers BEFORE connecting
|
|
462
|
+
app.onteardown = async () => {
|
|
463
|
+
console.info("TestDriver app being torn down");
|
|
464
|
+
return {};
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
app.ontoolinput = (params) => {
|
|
468
|
+
console.info("Received tool input:", params);
|
|
469
|
+
|
|
470
|
+
const toolArgs = params.arguments;
|
|
471
|
+
|
|
472
|
+
// Build a readable summary from the arguments
|
|
473
|
+
const summaryParts: string[] = [];
|
|
474
|
+
if (toolArgs) {
|
|
475
|
+
// Show key params based on what's present
|
|
476
|
+
if (toolArgs.description) summaryParts.push(`"${toolArgs.description}"`);
|
|
477
|
+
if (toolArgs.text) summaryParts.push(`"${toolArgs.text}"`);
|
|
478
|
+
if (toolArgs.url) summaryParts.push(`${toolArgs.url}`);
|
|
479
|
+
if (toolArgs.direction) summaryParts.push(`${toolArgs.direction}`);
|
|
480
|
+
if (toolArgs.assertion) summaryParts.push(`"${toolArgs.assertion}"`);
|
|
481
|
+
if (toolArgs.task) summaryParts.push(`"${toolArgs.task}"`);
|
|
482
|
+
if (toolArgs.keys) summaryParts.push(`[${(toolArgs.keys as string[]).join("+")}]`);
|
|
483
|
+
if (toolArgs.type) summaryParts.push(`${toolArgs.type}`);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const actionSummary = summaryParts.length > 0 ? summaryParts.join(" ") : "action";
|
|
487
|
+
|
|
488
|
+
// Show loading state and hide screenshot (to avoid broken image during load)
|
|
489
|
+
actionStatusEl.textContent = `Running ${actionSummary}...`;
|
|
490
|
+
actionStatusEl.className = "loading";
|
|
491
|
+
containerEl.style.display = "none";
|
|
492
|
+
showLoading(`Running ${actionSummary}...`);
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
app.ontoolresult = async (result) => {
|
|
496
|
+
console.info("Received tool result:", result);
|
|
497
|
+
console.info("structuredContent:", result.structuredContent);
|
|
498
|
+
const data = extractData(result);
|
|
499
|
+
console.info("Extracted data keys:", Object.keys(data));
|
|
500
|
+
console.info("Has imageUrl:", !!data.imageUrl);
|
|
501
|
+
console.info("Has screenshotResourceUri:", !!data.screenshotResourceUri);
|
|
502
|
+
console.info("Has croppedImageResourceUri:", !!data.croppedImageResourceUri);
|
|
503
|
+
|
|
504
|
+
// If a screenshot or cropped image resource URI is provided, fetch the image from it
|
|
505
|
+
const resourceUri = data.screenshotResourceUri || data.croppedImageResourceUri;
|
|
506
|
+
if (resourceUri && !data.imageUrl) {
|
|
507
|
+
showLoading("Fetching image...");
|
|
508
|
+
const imageUrl = await fetchScreenshotFromResource(resourceUri);
|
|
509
|
+
if (imageUrl) {
|
|
510
|
+
data.imageUrl = imageUrl;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
renderResult(data);
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
app.ontoolcancelled = (params) => {
|
|
518
|
+
console.info("Tool cancelled:", params.reason);
|
|
519
|
+
actionStatusEl.textContent = `Cancelled: ${params.reason}`;
|
|
520
|
+
actionStatusEl.className = "error";
|
|
521
|
+
hideLoading();
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
app.onerror = (error) => {
|
|
525
|
+
console.error("App error:", error);
|
|
526
|
+
actionStatusEl.textContent = `Error: ${error}`;
|
|
527
|
+
actionStatusEl.className = "error";
|
|
528
|
+
hideLoading();
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
app.onhostcontextchanged = handleHostContextChanged;
|
|
532
|
+
|
|
533
|
+
// 3. Connect to host
|
|
534
|
+
app.connect().then(() => {
|
|
535
|
+
const ctx = app.getHostContext();
|
|
536
|
+
if (ctx) {
|
|
537
|
+
handleHostContextChanged(ctx);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// Insert target info element after screenshot wrapper
|
|
542
|
+
const screenshotWrapper = document.querySelector(".screenshot-wrapper");
|
|
543
|
+
if (screenshotWrapper && screenshotWrapper.parentNode) {
|
|
544
|
+
screenshotWrapper.parentNode.insertBefore(targetInfoEl, screenshotWrapper.nextSibling);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Zoom toggle disabled - always show full view
|