testdriverai 6.2.2 → 7.1.0
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/.github/workflows/acceptance-linux.yml +75 -0
- package/.github/workflows/acceptance-sdk-tests.yml +133 -0
- package/.vscode/settings.json +5 -1
- package/AGENTS.md +550 -0
- package/CODEOWNERS +0 -1
- package/README.md +126 -0
- package/{testdriver → _testdriver}/acceptance/drag-and-drop.yaml +2 -2
- package/{testdriver → _testdriver}/acceptance/snippets/login.yaml +1 -1
- package/_testdriver/examples/desktop/lifecycle/prerun.yaml +0 -0
- package/{testdriver → _testdriver}/examples/web/lifecycle/prerun.yaml +6 -1
- package/{testdriver → _testdriver}/lifecycle/postrun.yaml +3 -2
- package/_testdriver/lifecycle/prerun.yaml +15 -0
- package/{testdriver → _testdriver}/lifecycle/provision.yaml +7 -2
- package/agent/index.js +300 -85
- package/agent/interface.js +15 -0
- package/agent/lib/cache.js +142 -0
- package/agent/lib/commander.js +1 -39
- package/agent/lib/commands.js +910 -296
- package/agent/lib/redraw.js +129 -41
- package/agent/lib/sandbox.js +29 -6
- package/agent/lib/sdk.js +22 -0
- package/agent/lib/system.js +0 -3
- package/agent/lib/validation.js +1 -7
- package/debug-locate-response.js +82 -0
- package/debugger/index.html +15 -4
- package/docs/ARCHITECTURE.md +424 -0
- package/docs/AWESOME_LOGS_QUICK_REF.md +100 -0
- package/docs/MIGRATION.md +425 -0
- package/docs/PRESETS.md +210 -0
- package/docs/QUICK_START_TEST_RECORDING.md +215 -0
- package/docs/SDK_AWESOME_LOGS.md +468 -0
- package/docs/TEST_RECORDING.md +388 -0
- package/docs/docs.json +286 -152
- package/docs/guide/best-practices-polling.mdx +154 -0
- package/docs/sdk-browser-rendering.md +167 -0
- package/docs/v6/getting-started/self-hosting.mdx +407 -0
- package/docs/{guide → v6/guide}/dashcam.mdx +1 -1
- package/docs/{guide → v6/guide}/environment-variables.mdx +4 -5
- package/docs/{guide → v6/guide}/lifecycle.mdx +1 -1
- package/docs/v6/overview/comparison.mdx +101 -0
- package/docs/v7/README.md +135 -0
- package/docs/v7/api/ai.mdx +205 -0
- package/docs/v7/api/assert.mdx +285 -0
- package/docs/v7/api/assertions.mdx +403 -0
- package/docs/v7/api/click.mdx +287 -0
- package/docs/v7/api/client.mdx +322 -0
- package/docs/v7/api/dashcam.mdx +497 -0
- package/docs/v7/api/doubleClick.mdx +102 -0
- package/docs/v7/api/elements.mdx +479 -0
- package/docs/v7/api/exec.mdx +346 -0
- package/docs/v7/api/find.mdx +316 -0
- package/docs/v7/api/focusApplication.mdx +294 -0
- package/docs/v7/api/hover.mdx +279 -0
- package/docs/v7/api/mouseDown.mdx +161 -0
- package/docs/v7/api/mouseUp.mdx +164 -0
- package/docs/v7/api/pressKeys.mdx +349 -0
- package/docs/v7/api/rightClick.mdx +123 -0
- package/docs/v7/api/sandbox.mdx +404 -0
- package/docs/v7/api/scroll.mdx +300 -0
- package/docs/v7/api/type.mdx +314 -0
- package/docs/v7/commands/assert.mdx +45 -0
- package/docs/v7/commands/exec.mdx +282 -0
- package/docs/v7/commands/focus-application.mdx +44 -0
- package/docs/v7/commands/hover-image.mdx +69 -0
- package/docs/v7/commands/hover-text.mdx +47 -0
- package/docs/v7/commands/if.mdx +53 -0
- package/docs/v7/commands/match-image.mdx +67 -0
- package/docs/v7/commands/press-keys.mdx +87 -0
- package/docs/v7/commands/remember.mdx +49 -0
- package/docs/v7/commands/run.mdx +44 -0
- package/docs/v7/commands/scroll-until-image.mdx +66 -0
- package/docs/v7/commands/scroll-until-text.mdx +60 -0
- package/docs/v7/commands/scroll.mdx +69 -0
- package/docs/v7/commands/type.mdx +45 -0
- package/docs/v7/commands/wait-for-image.mdx +54 -0
- package/docs/v7/commands/wait-for-text.mdx +48 -0
- package/docs/v7/commands/wait.mdx +45 -0
- package/docs/v7/getting-started/configuration.mdx +380 -0
- package/docs/v7/getting-started/quickstart.mdx +332 -0
- package/docs/v7/guides/best-practices.mdx +486 -0
- package/docs/v7/guides/caching-ai.mdx +215 -0
- package/docs/v7/guides/caching-selectors.mdx +292 -0
- package/docs/v7/guides/caching.mdx +366 -0
- package/docs/v7/guides/ci-cd/azure.mdx +587 -0
- package/docs/v7/guides/ci-cd/circleci.mdx +523 -0
- package/docs/v7/guides/ci-cd/github-actions.mdx +457 -0
- package/docs/v7/guides/ci-cd/gitlab.mdx +498 -0
- package/docs/v7/guides/ci-cd/jenkins.mdx +664 -0
- package/docs/v7/guides/ci-cd/travis.mdx +438 -0
- package/docs/v7/guides/debugging.mdx +349 -0
- package/docs/v7/guides/faq.mdx +393 -0
- package/docs/v7/guides/migration.mdx +562 -0
- package/docs/v7/guides/performance.mdx +517 -0
- package/docs/{getting-started → v7/guides}/self-hosting.mdx +11 -12
- package/docs/v7/guides/troubleshooting.mdx +526 -0
- package/docs/v7/guides/vitest-plugin.mdx +477 -0
- package/docs/v7/guides/vitest.mdx +535 -0
- package/docs/v7/platforms/linux.mdx +308 -0
- package/docs/v7/platforms/macos.mdx +433 -0
- package/docs/v7/platforms/windows.mdx +430 -0
- package/docs/v7/playwright.mdx +342 -0
- package/docs/v7/presets/chrome-extension.mdx +223 -0
- package/docs/v7/presets/chrome.mdx +287 -0
- package/docs/v7/presets/electron.mdx +435 -0
- package/docs/v7/presets/vscode.mdx +398 -0
- package/docs/v7/presets/webapp.mdx +396 -0
- package/docs/v7/progressive-apis/CORE.md +459 -0
- package/docs/v7/progressive-apis/HOOKS.md +360 -0
- package/docs/v7/progressive-apis/PROGRESSIVE_DISCLOSURE.md +230 -0
- package/docs/v7/progressive-apis/PROVISION.md +266 -0
- package/eslint.config.js +19 -1
- package/interfaces/cli/lib/base.js +10 -4
- package/interfaces/logger.js +2 -1
- package/interfaces/shared-test-state.mjs +69 -0
- package/interfaces/vitest-plugin.mjs +830 -0
- package/package.json +29 -5
- package/schema.json +8 -29
- package/scripts/view-test-results.mjs +96 -0
- package/sdk-log-formatter.js +714 -0
- package/sdk.d.ts +1028 -0
- package/sdk.js +2567 -0
- package/{.github/workflows/self-hosted.yml → self-hosted.yml} +13 -4
- package/setup/aws/cloudformation.yaml +9 -2
- package/src/core/Dashcam.js +469 -0
- package/src/core/index.d.ts +150 -0
- package/src/core/index.js +12 -0
- package/src/presets/index.mjs +331 -0
- package/src/vitest/extended.mjs +108 -0
- package/src/vitest/hooks.d.ts +119 -0
- package/src/vitest/hooks.mjs +298 -0
- package/src/vitest/index.mjs +64 -0
- package/src/vitest/lifecycle.mjs +277 -0
- package/src/vitest/utils.mjs +150 -0
- package/test/dashcam.test.js +137 -0
- package/test/mcp-example-test.yaml +27 -0
- package/testdriver/acceptance-sdk/QUICK_REFERENCE.md +61 -0
- package/testdriver/acceptance-sdk/README.md +128 -0
- package/testdriver/acceptance-sdk/TEST_REPORTING.md +245 -0
- package/testdriver/acceptance-sdk/assert.test.mjs +26 -0
- package/testdriver/acceptance-sdk/auto-cache-key-demo.test.mjs +56 -0
- package/testdriver/acceptance-sdk/chrome-extension.test.mjs +89 -0
- package/testdriver/acceptance-sdk/drag-and-drop.test.mjs +58 -0
- package/testdriver/acceptance-sdk/element-not-found.test.mjs +25 -0
- package/testdriver/acceptance-sdk/exec-js.test.mjs +43 -0
- package/testdriver/acceptance-sdk/exec-output.test.mjs +59 -0
- package/testdriver/acceptance-sdk/exec-pwsh.test.mjs +57 -0
- package/testdriver/acceptance-sdk/focus-window.test.mjs +36 -0
- package/testdriver/acceptance-sdk/formatted-logging.test.mjs +26 -0
- package/testdriver/acceptance-sdk/hooks-example.test.mjs +38 -0
- package/testdriver/acceptance-sdk/hover-image.test.mjs +34 -0
- package/testdriver/acceptance-sdk/hover-text-with-description.test.mjs +38 -0
- package/testdriver/acceptance-sdk/hover-text.test.mjs +27 -0
- package/testdriver/acceptance-sdk/match-image.test.mjs +36 -0
- package/testdriver/acceptance-sdk/presets-example.test.mjs +87 -0
- package/testdriver/acceptance-sdk/press-keys.test.mjs +50 -0
- package/testdriver/acceptance-sdk/prompt.test.mjs +33 -0
- package/testdriver/acceptance-sdk/scroll-keyboard.test.mjs +38 -0
- package/testdriver/acceptance-sdk/scroll-until-image.test.mjs +39 -0
- package/testdriver/acceptance-sdk/scroll-until-text.test.mjs +28 -0
- package/testdriver/acceptance-sdk/scroll.test.mjs +41 -0
- package/testdriver/acceptance-sdk/setup/globalTeardown.mjs +11 -0
- package/testdriver/acceptance-sdk/setup/testHelpers.mjs +420 -0
- package/testdriver/acceptance-sdk/setup/vitestSetup.mjs +40 -0
- package/testdriver/acceptance-sdk/sully-ai.test.mjs +234 -0
- package/testdriver/acceptance-sdk/test-console-logs.test.mjs +42 -0
- package/testdriver/acceptance-sdk/type-checking-demo.js +49 -0
- package/testdriver/acceptance-sdk/type.test.mjs +45 -0
- package/verify-element-api.js +89 -0
- package/verify-types.js +0 -0
- package/vitest.config.example.js +19 -0
- package/vitest.config.mjs +66 -0
- package/vitest.config.mjs.bak +44 -0
- package/.github/workflows/acceptance-v6.yml +0 -169
- package/.vscode/mcp.json +0 -9
- package/docs/overview/comparison.mdx +0 -82
- package/testdriver/lifecycle/prerun.yaml +0 -17
- /package/{testdriver/examples/desktop/lifecycle/prerun.yaml → .env.example} +0 -0
- /package/{testdriver → _testdriver}/acceptance/assert.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/dashcam.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/embed.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-js.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-output.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/exec-shell.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/focus-window.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text-with-description.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/if-else.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/match-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/press-keys.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/prompt.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/remember.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/screenshots/cart.png +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-keyboard.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-image.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll-until-text.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/scroll.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/snippets/match-cart.yaml +0 -0
- /package/{testdriver → _testdriver}/acceptance/type.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/failure.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/hover-text.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/prerun.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/behavior/secrets.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/dashcam-chrome.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/exec-pwsh-multiline.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-exception.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/js-promise.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-in-middle.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/prompt-nested.yaml +0 -0
- /package/{testdriver → _testdriver}/edge-cases/success-test.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/example.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/postrun.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/android/readme.md +0 -0
- /package/{testdriver → _testdriver}/examples/chrome-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/desktop/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/vscode-extension/lifecycle/provision.yaml +0 -0
- /package/{testdriver → _testdriver}/examples/web/lifecycle/postrun.yaml +0 -0
- /package/docs/{account → v6/account}/dashboard.mdx +0 -0
- /package/docs/{account → v6/account}/enterprise.mdx +0 -0
- /package/docs/{account → v6/account}/pricing.mdx +0 -0
- /package/docs/{account → v6/account}/projects.mdx +0 -0
- /package/docs/{account → v6/account}/team.mdx +0 -0
- /package/docs/{action → v6/action}/ami.mdx +0 -0
- /package/docs/{action → v6/action}/performance.mdx +0 -0
- /package/docs/{action → v6/action}/secrets.mdx +0 -0
- /package/docs/{apps → v6/apps}/chrome-extensions.mdx +0 -0
- /package/docs/{apps → v6/apps}/desktop-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/mobile-apps.mdx +0 -0
- /package/docs/{apps → v6/apps}/static-websites.mdx +0 -0
- /package/docs/{apps → v6/apps}/tauri-apps.mdx +0 -0
- /package/docs/{bugs → v6/bugs}/jira.mdx +0 -0
- /package/docs/{cli → v6/cli}/overview.mdx +0 -0
- /package/docs/{commands → v6/commands}/assert.mdx +0 -0
- /package/docs/{commands → v6/commands}/exec.mdx +0 -0
- /package/docs/{commands → v6/commands}/focus-application.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/hover-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/if.mdx +0 -0
- /package/docs/{commands → v6/commands}/match-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/press-keys.mdx +0 -0
- /package/docs/{commands → v6/commands}/remember.mdx +0 -0
- /package/docs/{commands → v6/commands}/run.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll-until-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/scroll.mdx +0 -0
- /package/docs/{commands → v6/commands}/type.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-image.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait-for-text.mdx +0 -0
- /package/docs/{commands → v6/commands}/wait.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/junit.mdx +0 -0
- /package/docs/{exporting → v6/exporting}/playwright.mdx +0 -0
- /package/docs/{features → v6/features}/auto-healing.mdx +0 -0
- /package/docs/{features → v6/features}/generation.mdx +0 -0
- /package/docs/{features → v6/features}/parallel-testing.mdx +0 -0
- /package/docs/{features → v6/features}/reusable-snippets.mdx +0 -0
- /package/docs/{features → v6/features}/selectorless.mdx +0 -0
- /package/docs/{features → v6/features}/visual-assertions.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/ci.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/cli.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/editing.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/playwright.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/running.mdx +0 -0
- /package/docs/{getting-started → v6/getting-started}/vscode.mdx +0 -0
- /package/docs/{guide → v6/guide}/assertions.mdx +0 -0
- /package/docs/{guide → v6/guide}/authentication.mdx +0 -0
- /package/docs/{guide → v6/guide}/code.mdx +0 -0
- /package/docs/{guide → v6/guide}/locating.mdx +0 -0
- /package/docs/{guide → v6/guide}/protips.mdx +0 -0
- /package/docs/{guide → v6/guide}/variables.mdx +0 -0
- /package/docs/{guide → v6/guide}/waiting.mdx +0 -0
- /package/docs/{importing → v6/importing}/csv.mdx +0 -0
- /package/docs/{importing → v6/importing}/gherkin.mdx +0 -0
- /package/docs/{importing → v6/importing}/jira.mdx +0 -0
- /package/docs/{importing → v6/importing}/testrail.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/electron.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/netlify.mdx +0 -0
- /package/docs/{integrations → v6/integrations}/vercel.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/explore.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/run.mdx +0 -0
- /package/docs/{interactive → v6/interactive}/save.mdx +0 -0
- /package/docs/{overview → v6/overview}/faq.mdx +0 -0
- /package/docs/{overview → v6/overview}/performance.mdx +0 -0
- /package/docs/{overview → v6/overview}/quickstart.mdx +0 -0
- /package/docs/{overview → v6/overview}/what-is-testdriver.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/ai-chatbot.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/cookie-banner.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/file-upload.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/form-filling.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/log-in.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/pdf-generation.mdx +0 -0
- /package/docs/{scenarios → v6/scenarios}/spell-check.mdx +0 -0
- /package/docs/{security → v6/security}/action.mdx +0 -0
- /package/docs/{security → v6/security}/agent.mdx +0 -0
- /package/docs/{security → v6/security}/platform.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/advanced-test.mdx +0 -0
- /package/docs/{tutorials → v6/tutorials}/basic-test.mdx +0 -0
|
@@ -4,7 +4,12 @@ steps:
|
|
|
4
4
|
commands:
|
|
5
5
|
- command: exec
|
|
6
6
|
lang: pwsh
|
|
7
|
-
code:
|
|
7
|
+
code: |
|
|
8
|
+
npm install dashcam@beta -g
|
|
9
|
+
npm list -g dashcam
|
|
10
|
+
- command: exec
|
|
11
|
+
lang: pwsh
|
|
12
|
+
code: dashcam track --name=TestDriver --type=app --pattern="C:\Users\testdriver\Documents\testdriver.log"
|
|
8
13
|
- command: exec
|
|
9
14
|
lang: pwsh
|
|
10
15
|
code: dashcam start
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
version: 6.0.0
|
|
2
|
+
steps:
|
|
3
|
+
- prompt: dashcam
|
|
4
|
+
commands:
|
|
5
|
+
- command: exec
|
|
6
|
+
lang: sh
|
|
7
|
+
code: |
|
|
8
|
+
dashcam auth 4e93d8bf-3886-4d26-a144-116c4063522d
|
|
9
|
+
dashcam logs --add --name="TestDriver" --type=file --file="/tmp/testdriver.log"
|
|
10
|
+
- command: exec
|
|
11
|
+
lang: sh
|
|
12
|
+
code: dashcam record > /dev/null 2>&1 &
|
|
13
|
+
- command: exec
|
|
14
|
+
lang: sh
|
|
15
|
+
code: dashcam logs --add --name=TestDriver --type=file --file="/tmp/testdriver.log"
|
|
@@ -3,6 +3,11 @@ session: 67f00511acbd9ccac373edf7
|
|
|
3
3
|
steps:
|
|
4
4
|
- prompt: launch chrome
|
|
5
5
|
commands:
|
|
6
|
+
- command: exec
|
|
7
|
+
lang: sh
|
|
8
|
+
code: |
|
|
9
|
+
npm install dashcam@beta -g
|
|
10
|
+
npm list -g dashcam
|
|
6
11
|
- command: exec
|
|
7
12
|
lang: sh
|
|
8
13
|
code: |
|
|
@@ -13,8 +18,8 @@ steps:
|
|
|
13
18
|
--no-default-browser-check \
|
|
14
19
|
--no-first-run \
|
|
15
20
|
--guest \
|
|
16
|
-
"
|
|
21
|
+
"https://testdriver-sandbox.vercel.app/" \
|
|
17
22
|
>/dev/null 2>&1 &
|
|
18
23
|
- command: wait-for-text
|
|
19
|
-
text:
|
|
24
|
+
text: testdriver-sandbox
|
|
20
25
|
timeout: 60000
|
package/agent/index.js
CHANGED
|
@@ -19,6 +19,7 @@ const diff = require("diff");
|
|
|
19
19
|
|
|
20
20
|
// global utilities
|
|
21
21
|
const generator = require("./lib/generator.js");
|
|
22
|
+
const promptCache = require("./lib/cache.js");
|
|
22
23
|
const theme = require("./lib/theme.js");
|
|
23
24
|
const SourceMapper = require("./lib/source-mapper.js");
|
|
24
25
|
|
|
@@ -74,6 +75,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
74
75
|
this.sandboxId = flags["sandbox-id"] || null;
|
|
75
76
|
this.sandboxAmi = flags["sandbox-ami"] || null;
|
|
76
77
|
this.sandboxInstance = flags["sandbox-instance"] || null;
|
|
78
|
+
this.sandboxOs = flags.os || "linux";
|
|
77
79
|
this.ip = flags.ip || null;
|
|
78
80
|
this.workingDir = flags.workingDir || process.cwd();
|
|
79
81
|
|
|
@@ -101,14 +103,17 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
101
103
|
// Create outputs instance for this agent
|
|
102
104
|
this.outputs = createOutputs();
|
|
103
105
|
|
|
104
|
-
// Create SDK instance with this agent's emitter, config, and
|
|
106
|
+
// Create SDK instance with this agent's emitter, config, session, and abort signal
|
|
105
107
|
this.sdk = createSDK(this.emitter, this.config, this.session);
|
|
106
108
|
|
|
107
109
|
// Create analytics instance with this agent's emitter, config, and session
|
|
108
110
|
this.analytics = createAnalytics(this.emitter, this.config, this.session);
|
|
109
111
|
|
|
110
|
-
// Create sandbox instance with this agent's emitter and
|
|
111
|
-
this.sandbox = createSandbox(this.emitter, this.analytics);
|
|
112
|
+
// Create sandbox instance with this agent's emitter, analytics, and session
|
|
113
|
+
this.sandbox = createSandbox(this.emitter, this.analytics, this.session);
|
|
114
|
+
|
|
115
|
+
// Set the OS for the sandbox to use
|
|
116
|
+
this.sandbox.os = this.sandboxOs;
|
|
112
117
|
|
|
113
118
|
// Create system instance with emitter, sandbox and config
|
|
114
119
|
this.system = createSystem(this.emitter, this.sandbox, this.config);
|
|
@@ -121,6 +126,8 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
121
126
|
this.config,
|
|
122
127
|
this.session,
|
|
123
128
|
() => this.sourceMapper.currentFilePath || this.thisFile,
|
|
129
|
+
this.cliArgs.options.redrawThreshold,
|
|
130
|
+
null, // getDashcamElapsedTime - will be set by SDK when dashcam is available
|
|
124
131
|
);
|
|
125
132
|
this.commands = commandsResult.commands;
|
|
126
133
|
this.redraw = commandsResult.redraw;
|
|
@@ -272,6 +279,15 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
272
279
|
// Get error message
|
|
273
280
|
let eMessage = error.message ? error.message : error;
|
|
274
281
|
|
|
282
|
+
// Truncate error message if too long to prevent 400 errors from API
|
|
283
|
+
// Keep first 5000 characters as a reasonable limit for API payloads
|
|
284
|
+
const MAX_ERROR_LENGTH = 5000;
|
|
285
|
+
if (typeof eMessage === "string" && eMessage.length > MAX_ERROR_LENGTH) {
|
|
286
|
+
eMessage =
|
|
287
|
+
eMessage.substring(0, MAX_ERROR_LENGTH) +
|
|
288
|
+
"\n\n[Error message truncated - message was too long]";
|
|
289
|
+
}
|
|
290
|
+
|
|
275
291
|
// we sanitize the error message to use it as a key in the errorCounts object
|
|
276
292
|
let safeKey = JSON.stringify(error.message ? error.message : error);
|
|
277
293
|
this.errorCounts[safeKey] = this.errorCounts[safeKey]
|
|
@@ -323,19 +339,40 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
323
339
|
const streamId = `error-${Date.now()}`;
|
|
324
340
|
this.emitter.emit(events.log.markdown.start, streamId);
|
|
325
341
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
342
|
+
// Truncate markdown if too long to prevent 400 errors
|
|
343
|
+
const MAX_MARKDOWN_LENGTH = 10000;
|
|
344
|
+
let truncatedMarkdown = markdown;
|
|
345
|
+
if (typeof markdown === "string" && markdown.length > MAX_MARKDOWN_LENGTH) {
|
|
346
|
+
truncatedMarkdown =
|
|
347
|
+
markdown.substring(0, MAX_MARKDOWN_LENGTH) +
|
|
348
|
+
"\n\n[Markdown truncated - content was too long]";
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let response;
|
|
352
|
+
try {
|
|
353
|
+
response = await this.sdk.req(
|
|
354
|
+
"error",
|
|
355
|
+
{
|
|
356
|
+
description: eMessage,
|
|
357
|
+
markdown: truncatedMarkdown,
|
|
358
|
+
image,
|
|
359
|
+
},
|
|
360
|
+
(chunk) => {
|
|
361
|
+
if (chunk.type === "data") {
|
|
362
|
+
this.emitter.emit(events.log.markdown.chunk, streamId, chunk.data);
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
);
|
|
366
|
+
} catch (apiError) {
|
|
367
|
+
// If the error API call itself fails, prevent infinite loop
|
|
368
|
+
// by not retrying and instead treating as fatal
|
|
369
|
+
this.emitter.emit(
|
|
370
|
+
events.log.error,
|
|
371
|
+
theme.red(`Failed to get AI error resolution: ${apiError.message}`),
|
|
372
|
+
);
|
|
373
|
+
this.emitter.emit(events.log.log, "Original error: " + eMessage);
|
|
374
|
+
return await this.dieOnFatal(error);
|
|
375
|
+
}
|
|
339
376
|
|
|
340
377
|
this.emitter.emit(events.log.markdown.end, streamId);
|
|
341
378
|
|
|
@@ -835,6 +872,7 @@ commands:
|
|
|
835
872
|
dry = false,
|
|
836
873
|
validateAndLoop = false,
|
|
837
874
|
shouldSave = true,
|
|
875
|
+
useCache = true,
|
|
838
876
|
) {
|
|
839
877
|
// Check if execution has been stopped
|
|
840
878
|
if (this.stopped) {
|
|
@@ -853,6 +891,49 @@ commands:
|
|
|
853
891
|
|
|
854
892
|
this.tasks.push(currentTask);
|
|
855
893
|
|
|
894
|
+
// Check cache first (if enabled via parameter)
|
|
895
|
+
const cachedYaml = useCache ? promptCache.readCache(currentTask) : null;
|
|
896
|
+
|
|
897
|
+
if (cachedYaml) {
|
|
898
|
+
// Cache hit - load and execute the cached YAML file
|
|
899
|
+
this.emitter.emit(
|
|
900
|
+
events.log.debug,
|
|
901
|
+
`Using cached response for prompt: "${currentTask}"`,
|
|
902
|
+
);
|
|
903
|
+
this.emitter.emit(events.log.log, theme.dim("(using cached response)"));
|
|
904
|
+
|
|
905
|
+
try {
|
|
906
|
+
// Load the YAML using hydrateFromYML
|
|
907
|
+
const parsed = await generator.hydrateFromYML(
|
|
908
|
+
cachedYaml,
|
|
909
|
+
this.sessionInstance,
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
// Execute the commands from the first step
|
|
913
|
+
if (parsed.steps && parsed.steps.length > 0) {
|
|
914
|
+
const step = parsed.steps[0];
|
|
915
|
+
if (step.commands) {
|
|
916
|
+
await this.executeCommands(
|
|
917
|
+
step.commands,
|
|
918
|
+
0,
|
|
919
|
+
false,
|
|
920
|
+
dry,
|
|
921
|
+
shouldSave,
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
} catch (err) {
|
|
926
|
+
this.emitter.emit(
|
|
927
|
+
events.log.debug,
|
|
928
|
+
`Error loading cached YAML: ${err.message}, falling back to API`,
|
|
929
|
+
);
|
|
930
|
+
// Fall through to make API call if cache is invalid
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// Cache miss - call the API
|
|
856
937
|
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
857
938
|
|
|
858
939
|
this.lastScreenshot = await this.system.captureScreenBase64();
|
|
@@ -877,7 +958,49 @@ commands:
|
|
|
877
958
|
|
|
878
959
|
this.emitter.emit(events.log.markdown.end, streamId);
|
|
879
960
|
|
|
880
|
-
if (message) {
|
|
961
|
+
if (message && message.data) {
|
|
962
|
+
// Save the YAML to cache (if enabled)
|
|
963
|
+
if (useCache) {
|
|
964
|
+
try {
|
|
965
|
+
// Extract YAML code blocks from the markdown response
|
|
966
|
+
const codeblocks = await this.parser.findCodeBlocks(message.data);
|
|
967
|
+
if (codeblocks && codeblocks.length > 0) {
|
|
968
|
+
// Parse commands from all code blocks
|
|
969
|
+
const allCommands = [];
|
|
970
|
+
for (const block of codeblocks) {
|
|
971
|
+
const commands = await this.parser.getCommands(block);
|
|
972
|
+
allCommands.push(...commands);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Create a proper step with prompt
|
|
976
|
+
const step = {
|
|
977
|
+
prompt: currentTask,
|
|
978
|
+
commands: allCommands,
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
// Use dumpToYML to create a valid testdriver yaml file
|
|
982
|
+
const yamlContent = await generator.dumpToYML(
|
|
983
|
+
[step],
|
|
984
|
+
this.sessionInstance,
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
const cachePath = promptCache.writeCache(currentTask, yamlContent);
|
|
988
|
+
if (cachePath) {
|
|
989
|
+
this.emitter.emit(
|
|
990
|
+
events.log.debug,
|
|
991
|
+
`Cached YAML saved to: ${cachePath}`,
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
} catch (err) {
|
|
996
|
+
// If we can't extract YAML, just skip caching
|
|
997
|
+
this.emitter.emit(
|
|
998
|
+
events.log.debug,
|
|
999
|
+
`Could not cache response: ${err.message}`,
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
881
1004
|
await this.aiExecute(message.data, validateAndLoop, dry, shouldSave);
|
|
882
1005
|
this.emitter.emit(
|
|
883
1006
|
events.log.debug,
|
|
@@ -1588,12 +1711,28 @@ ${regression}
|
|
|
1588
1711
|
this.emitter.emit(events.log.log, `${inputFile} (end)`);
|
|
1589
1712
|
}
|
|
1590
1713
|
|
|
1714
|
+
// Returns the path to the sandbox file (project-local first, then global)
|
|
1715
|
+
getSandboxFilePath(preferProjectLocal = true) {
|
|
1716
|
+
const projectLocalFile = path.join(process.cwd(), ".testdriver-sandbox.json");
|
|
1717
|
+
const globalFile = path.join(os.homedir(), ".testdriverai-last-sandbox");
|
|
1718
|
+
|
|
1719
|
+
if (preferProjectLocal) {
|
|
1720
|
+
// For reading: check project-local first, then global
|
|
1721
|
+
if (fs.existsSync(projectLocalFile)) {
|
|
1722
|
+
return projectLocalFile;
|
|
1723
|
+
}
|
|
1724
|
+
if (fs.existsSync(globalFile)) {
|
|
1725
|
+
return globalFile;
|
|
1726
|
+
}
|
|
1727
|
+
// For writing: default to project-local
|
|
1728
|
+
return projectLocalFile;
|
|
1729
|
+
}
|
|
1730
|
+
return globalFile;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1591
1733
|
// Returns sandboxId to use (either from file if recent, or null)
|
|
1592
1734
|
getRecentSandboxId() {
|
|
1593
|
-
const lastSandboxFile =
|
|
1594
|
-
os.homedir(),
|
|
1595
|
-
".testdriverai-last-sandbox",
|
|
1596
|
-
);
|
|
1735
|
+
const lastSandboxFile = this.getSandboxFilePath(true);
|
|
1597
1736
|
|
|
1598
1737
|
if (fs.existsSync(lastSandboxFile)) {
|
|
1599
1738
|
try {
|
|
@@ -1619,7 +1758,8 @@ ${regression}
|
|
|
1619
1758
|
const storedInstance = sandboxInfo.instanceType || null;
|
|
1620
1759
|
|
|
1621
1760
|
if (currentAmi === storedAmi && currentInstance === storedInstance) {
|
|
1622
|
-
|
|
1761
|
+
// Return sandboxId (new format) or instanceId (old format for backwards compatibility)
|
|
1762
|
+
return sandboxInfo.sandboxId || sandboxInfo.instanceId;
|
|
1623
1763
|
} else {
|
|
1624
1764
|
this.emitter.emit(
|
|
1625
1765
|
events.log.log,
|
|
@@ -1637,19 +1777,18 @@ ${regression}
|
|
|
1637
1777
|
return null;
|
|
1638
1778
|
}
|
|
1639
1779
|
|
|
1640
|
-
saveLastSandboxId(
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
".testdriverai-last-sandbox",
|
|
1644
|
-
);
|
|
1780
|
+
saveLastSandboxId(sandboxId, osType = "linux") {
|
|
1781
|
+
// Save to project-local .testdriver-sandbox.json
|
|
1782
|
+
const projectLocalFile = path.join(process.cwd(), ".testdriver-sandbox.json");
|
|
1645
1783
|
try {
|
|
1646
1784
|
const sandboxInfo = {
|
|
1647
|
-
|
|
1785
|
+
sandboxId: sandboxId,
|
|
1786
|
+
os: osType,
|
|
1648
1787
|
ami: this.sandboxAmi || null,
|
|
1649
1788
|
instanceType: this.sandboxInstance || null,
|
|
1650
1789
|
timestamp: new Date().toISOString(),
|
|
1651
1790
|
};
|
|
1652
|
-
fs.writeFileSync(
|
|
1791
|
+
fs.writeFileSync(projectLocalFile, JSON.stringify(sandboxInfo, null, 2), {
|
|
1653
1792
|
encoding: "utf-8",
|
|
1654
1793
|
});
|
|
1655
1794
|
} catch {
|
|
@@ -1658,13 +1797,16 @@ ${regression}
|
|
|
1658
1797
|
}
|
|
1659
1798
|
|
|
1660
1799
|
clearRecentSandboxId() {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1800
|
+
// Clear project-local file first, then global
|
|
1801
|
+
const projectLocalFile = path.join(process.cwd(), ".testdriver-sandbox.json");
|
|
1802
|
+
const globalFile = path.join(os.homedir(), ".testdriverai-last-sandbox");
|
|
1803
|
+
|
|
1665
1804
|
try {
|
|
1666
|
-
if (fs.existsSync(
|
|
1667
|
-
fs.unlinkSync(
|
|
1805
|
+
if (fs.existsSync(projectLocalFile)) {
|
|
1806
|
+
fs.unlinkSync(projectLocalFile);
|
|
1807
|
+
}
|
|
1808
|
+
if (fs.existsSync(globalFile)) {
|
|
1809
|
+
fs.unlinkSync(globalFile);
|
|
1668
1810
|
}
|
|
1669
1811
|
} catch {
|
|
1670
1812
|
// ignore errors
|
|
@@ -1682,6 +1824,11 @@ ${regression}
|
|
|
1682
1824
|
|
|
1683
1825
|
let { headless = false, heal, new: createNew = false } = options;
|
|
1684
1826
|
|
|
1827
|
+
// Prioritize this.newSandbox flag if it's set
|
|
1828
|
+
if (this.newSandbox) {
|
|
1829
|
+
createNew = true;
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1685
1832
|
// If CI environment variable is true, always create a new sandbox
|
|
1686
1833
|
if (this.config.CI) {
|
|
1687
1834
|
createNew = true;
|
|
@@ -1696,11 +1843,18 @@ ${regression}
|
|
|
1696
1843
|
// If createNew flag is set, clear the recent sandbox file to force creating a new sandbox
|
|
1697
1844
|
if (createNew) {
|
|
1698
1845
|
this.clearRecentSandboxId();
|
|
1699
|
-
|
|
1846
|
+
// Also clear this.sandboxId to prevent reconnection attempts
|
|
1847
|
+
this.sandboxId = null;
|
|
1848
|
+
if (!this.config.CI && !this.newSandbox) {
|
|
1700
1849
|
this.emitter.emit(
|
|
1701
1850
|
events.log.log,
|
|
1702
1851
|
theme.dim("--`new` flag detected, will create a new sandbox"),
|
|
1703
1852
|
);
|
|
1853
|
+
} else if (this.newSandbox) {
|
|
1854
|
+
this.emitter.emit(
|
|
1855
|
+
events.log.log,
|
|
1856
|
+
theme.dim("--new-sandbox flag detected, will create a new sandbox"),
|
|
1857
|
+
);
|
|
1704
1858
|
}
|
|
1705
1859
|
}
|
|
1706
1860
|
|
|
@@ -1708,6 +1862,9 @@ ${regression}
|
|
|
1708
1862
|
await this.connectToSandboxService();
|
|
1709
1863
|
|
|
1710
1864
|
const recentId = createNew ? null : this.getRecentSandboxId();
|
|
1865
|
+
|
|
1866
|
+
// Track whether we reconnected to an existing sandbox or created a new one
|
|
1867
|
+
this.isReconnected = false;
|
|
1711
1868
|
|
|
1712
1869
|
// Set sandbox ID for reconnection (only if not creating new and recent ID exists)
|
|
1713
1870
|
if (this.ip) {
|
|
@@ -1718,23 +1875,49 @@ ${regression}
|
|
|
1718
1875
|
ip: this.ip,
|
|
1719
1876
|
});
|
|
1720
1877
|
|
|
1721
|
-
|
|
1878
|
+
this.emitter.emit(events.sandbox.connected);
|
|
1879
|
+
|
|
1880
|
+
this.instance = instance.instance;
|
|
1881
|
+
this.isReconnected = false; // Direct IP is considered new
|
|
1882
|
+
await this.renderSandbox(this.instance, headless);
|
|
1722
1883
|
await this.newSession();
|
|
1723
1884
|
await this.runLifecycle("provision");
|
|
1724
1885
|
|
|
1725
1886
|
return;
|
|
1726
1887
|
} else if (!createNew && recentId) {
|
|
1888
|
+
// Only attempt to connect to existing sandbox if not in CI mode and not creating new
|
|
1727
1889
|
this.emitter.emit(
|
|
1728
1890
|
events.log.narration,
|
|
1729
1891
|
theme.dim(`using recent sandbox: ${recentId}`),
|
|
1730
1892
|
);
|
|
1731
1893
|
this.sandboxId = recentId;
|
|
1732
|
-
|
|
1894
|
+
|
|
1895
|
+
try {
|
|
1896
|
+
let instance = await this.connectToSandboxDirect(
|
|
1897
|
+
this.sandboxId,
|
|
1898
|
+
true, // always persist by default
|
|
1899
|
+
);
|
|
1900
|
+
|
|
1901
|
+
this.instance = instance;
|
|
1902
|
+
this.isReconnected = true; // Successfully reconnected to existing sandbox
|
|
1903
|
+
|
|
1904
|
+
await this.renderSandbox(instance, headless);
|
|
1905
|
+
await this.newSession();
|
|
1906
|
+
return;
|
|
1907
|
+
} catch (error) {
|
|
1908
|
+
// If connection fails, fall through to creating a new sandbox
|
|
1909
|
+
this.emitter.emit(
|
|
1910
|
+
events.log.narration,
|
|
1911
|
+
theme.dim(`failed to connect to recent sandbox, creating new one...`),
|
|
1912
|
+
);
|
|
1913
|
+
console.error("Failed to reconnect to sandbox:", error);
|
|
1914
|
+
}
|
|
1915
|
+
} else if (!createNew && !recentId) {
|
|
1733
1916
|
this.emitter.emit(
|
|
1734
1917
|
events.log.narration,
|
|
1735
1918
|
theme.dim(`no recent sandbox found, creating a new one.`),
|
|
1736
1919
|
);
|
|
1737
|
-
} else if (this.sandboxId && !this.config.CI) {
|
|
1920
|
+
} else if (!createNew && this.sandboxId && !this.config.CI) {
|
|
1738
1921
|
// Only attempt to connect to existing sandbox if not in CI mode and not creating new
|
|
1739
1922
|
// Attempt to connect to known instance
|
|
1740
1923
|
this.emitter.emit(
|
|
@@ -1749,51 +1932,55 @@ ${regression}
|
|
|
1749
1932
|
);
|
|
1750
1933
|
|
|
1751
1934
|
this.instance = instance;
|
|
1935
|
+
this.isReconnected = true; // Successfully reconnected to existing sandbox
|
|
1752
1936
|
|
|
1753
1937
|
await this.renderSandbox(instance, headless);
|
|
1754
1938
|
await this.newSession();
|
|
1755
1939
|
return;
|
|
1756
1940
|
} catch (error) {
|
|
1757
|
-
//
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1941
|
+
// If connection fails, fall through to creating a new sandbox
|
|
1942
|
+
this.emitter.emit(
|
|
1943
|
+
events.log.narration,
|
|
1944
|
+
theme.dim(`failed to connect to recent sandbox, creating new one...`),
|
|
1945
|
+
);
|
|
1946
|
+
console.error("Failed to reconnect to sandbox:", error);
|
|
1761
1947
|
}
|
|
1762
1948
|
}
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
);
|
|
1768
|
-
// We don't have resiliency/retries baked in, so let's at least give it 1 attempt
|
|
1769
|
-
// to see if that fixes the issue.
|
|
1770
|
-
let newSandbox = await this.createNewSandbox().catch(() => {
|
|
1949
|
+
|
|
1950
|
+
// Create new sandbox (either because createNew is true, or no existing sandbox to connect to)
|
|
1951
|
+
if (!this.instance) {
|
|
1952
|
+
this.isReconnected = false; // Creating new sandbox
|
|
1771
1953
|
this.emitter.emit(
|
|
1772
1954
|
events.log.narration,
|
|
1773
|
-
theme.dim(`
|
|
1955
|
+
theme.dim(`creating new sandbox...`),
|
|
1774
1956
|
);
|
|
1957
|
+
// We don't have resiliency/retries baked in, so let's at least give it 1 attempt
|
|
1958
|
+
// to see if that fixes the issue.
|
|
1959
|
+
let newSandbox = await this.createNewSandbox().catch(() => {
|
|
1960
|
+
this.emitter.emit(
|
|
1961
|
+
events.log.narration,
|
|
1962
|
+
theme.dim(`double-checking sandbox availability`),
|
|
1963
|
+
);
|
|
1964
|
+
return this.createNewSandbox();
|
|
1965
|
+
});
|
|
1775
1966
|
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
this.emitter.emit(events.showWindow, urlToOpen);
|
|
1792
|
-
|
|
1793
|
-
await this.newSession();
|
|
1794
|
-
await this.runLifecycle("provision");
|
|
1967
|
+
// Extract the sandbox ID from the newly created sandbox
|
|
1968
|
+
this.sandboxId = newSandbox?.sandbox?.sandboxId || newSandbox?.sandbox?.instanceId;
|
|
1969
|
+
|
|
1970
|
+
// Use the configured sandbox OS type
|
|
1971
|
+
this.saveLastSandboxId(this.sandboxId, this.sandboxOs);
|
|
1972
|
+
|
|
1973
|
+
let instance = await this.connectToSandboxDirect(
|
|
1974
|
+
this.sandboxId,
|
|
1975
|
+
true, // always persist by default
|
|
1976
|
+
);
|
|
1977
|
+
this.instance = instance;
|
|
1978
|
+
await this.renderSandbox(instance, headless);
|
|
1979
|
+
await this.newSession();
|
|
1980
|
+
await this.runLifecycle("provision");
|
|
1795
1981
|
|
|
1796
|
-
|
|
1982
|
+
console.log("provision run");
|
|
1983
|
+
}
|
|
1797
1984
|
}
|
|
1798
1985
|
|
|
1799
1986
|
async start() {
|
|
@@ -1913,13 +2100,27 @@ ${regression}
|
|
|
1913
2100
|
}
|
|
1914
2101
|
|
|
1915
2102
|
async renderSandbox(instance, headless = false) {
|
|
2103
|
+
console.log("renderSandbox", instance);
|
|
2104
|
+
|
|
1916
2105
|
if (!headless) {
|
|
1917
|
-
let url
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
instance.
|
|
1922
|
-
|
|
2106
|
+
let url;
|
|
2107
|
+
|
|
2108
|
+
// If the instance already has a URL (from reconnection), use it
|
|
2109
|
+
if (instance.url) {
|
|
2110
|
+
url = instance.url;
|
|
2111
|
+
} else if (instance.ip || instance.publicIp) {
|
|
2112
|
+
// Otherwise construct it from IP and port
|
|
2113
|
+
url =
|
|
2114
|
+
"http://" +
|
|
2115
|
+
(instance.ip || instance.publicIp) +
|
|
2116
|
+
":" +
|
|
2117
|
+
(instance.vncPort || "5800") +
|
|
2118
|
+
"/vnc_lite.html?token=V3b8wG9";
|
|
2119
|
+
} else {
|
|
2120
|
+
// If we don't have URL or IP, we can't render
|
|
2121
|
+
console.warn("renderSandbox: Missing URL and IP in instance", instance);
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
1923
2124
|
|
|
1924
2125
|
let data = {
|
|
1925
2126
|
resolution: this.config.TD_RESOLUTION,
|
|
@@ -1927,7 +2128,8 @@ ${regression}
|
|
|
1927
2128
|
token: "V3b8wG9",
|
|
1928
2129
|
};
|
|
1929
2130
|
|
|
1930
|
-
|
|
2131
|
+
// Base64 encode the data (the debugger expects base64, not URL encoding)
|
|
2132
|
+
const encodedData = Buffer.from(JSON.stringify(data)).toString("base64");
|
|
1931
2133
|
|
|
1932
2134
|
// Use the debugger URL instead of the VNC URL
|
|
1933
2135
|
const urlToOpen = `${this.debuggerUrl}?data=${encodedData}`;
|
|
@@ -1965,16 +2167,26 @@ Please check your network connection, TD_API_KEY, or the service status.`,
|
|
|
1965
2167
|
|
|
1966
2168
|
async connectToSandboxDirect(sandboxId, persist = false) {
|
|
1967
2169
|
this.emitter.emit(events.log.narration, theme.dim(`connecting...`));
|
|
1968
|
-
let
|
|
1969
|
-
|
|
2170
|
+
let reply = await this.sandbox.connect(sandboxId, persist);
|
|
2171
|
+
|
|
2172
|
+
// reply includes { success, url, sandbox: {...} }
|
|
2173
|
+
// For renderSandbox, we need the sandbox object with url merged in
|
|
2174
|
+
const sandbox = reply.sandbox || {};
|
|
2175
|
+
|
|
2176
|
+
// If reply has a URL at top level, merge it into the sandbox object
|
|
2177
|
+
if (reply.url && !sandbox.url) {
|
|
2178
|
+
sandbox.url = reply.url;
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
return sandbox;
|
|
1970
2182
|
}
|
|
1971
2183
|
|
|
1972
2184
|
async createNewSandbox() {
|
|
1973
2185
|
const sandboxConfig = {
|
|
1974
2186
|
type: "create",
|
|
1975
|
-
os: "linux",
|
|
1976
2187
|
resolution: this.config.TD_RESOLUTION,
|
|
1977
2188
|
ci: this.config.CI,
|
|
2189
|
+
os: this.sandboxOs || "windows",
|
|
1978
2190
|
};
|
|
1979
2191
|
|
|
1980
2192
|
// Add AMI and instance type if specified
|
|
@@ -1985,11 +2197,14 @@ Please check your network connection, TD_API_KEY, or the service status.`,
|
|
|
1985
2197
|
sandboxConfig.instanceType = this.sandboxInstance;
|
|
1986
2198
|
}
|
|
1987
2199
|
|
|
1988
|
-
console.log("sending create");
|
|
1989
|
-
|
|
1990
2200
|
let instance = await this.sandbox.send(sandboxConfig);
|
|
1991
2201
|
|
|
1992
|
-
|
|
2202
|
+
// Save the sandbox ID for reconnection with the correct OS type
|
|
2203
|
+
if (instance.sandbox && instance.sandbox.sandboxId) {
|
|
2204
|
+
this.saveLastSandboxId(instance.sandbox.sandboxId, this.sandboxOs);
|
|
2205
|
+
} else if (instance.sandbox && instance.sandbox.instanceId) {
|
|
2206
|
+
this.saveLastSandboxId(instance.sandbox.instanceId, this.sandboxOs);
|
|
2207
|
+
}
|
|
1993
2208
|
|
|
1994
2209
|
return instance;
|
|
1995
2210
|
}
|
package/agent/interface.js
CHANGED
|
@@ -66,6 +66,11 @@ function createCommandDefinitions(agent) {
|
|
|
66
66
|
description: "Generate JUnit XML test report to specified file",
|
|
67
67
|
default: false,
|
|
68
68
|
}),
|
|
69
|
+
os: Flags.string({
|
|
70
|
+
description: "Operating system for the sandbox (windows or linux)",
|
|
71
|
+
options: ["windows", "linux"],
|
|
72
|
+
default: "windows",
|
|
73
|
+
}),
|
|
69
74
|
},
|
|
70
75
|
handler: async (args, flags) => {
|
|
71
76
|
// Use --path flag if provided, otherwise fall back to args.file
|
|
@@ -140,6 +145,11 @@ function createCommandDefinitions(agent) {
|
|
|
140
145
|
summary: Flags.string({
|
|
141
146
|
description: "Specify output file for summarize results",
|
|
142
147
|
}),
|
|
148
|
+
os: Flags.string({
|
|
149
|
+
description: "Operating system for the sandbox (windows or linux)",
|
|
150
|
+
options: ["windows", "linux"],
|
|
151
|
+
default: "windows",
|
|
152
|
+
}),
|
|
143
153
|
},
|
|
144
154
|
handler: async () => {
|
|
145
155
|
// Edit mode is handled by the CLI interface via factory.js
|
|
@@ -233,6 +243,11 @@ function createCommandDefinitions(agent) {
|
|
|
233
243
|
"sandbox-instance": Flags.string({
|
|
234
244
|
description: "Specify EC2 instance type for sandbox (e.g., i3.metal)",
|
|
235
245
|
}),
|
|
246
|
+
os: Flags.string({
|
|
247
|
+
description: "Operating system for the sandbox (windows or linux)",
|
|
248
|
+
options: ["windows", "linux"],
|
|
249
|
+
default: "windows",
|
|
250
|
+
}),
|
|
236
251
|
},
|
|
237
252
|
handler: async (args, flags) => {
|
|
238
253
|
// Call generate with the count and prompt
|