testdriverai 7.2.3 → 7.2.10
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/publish.yaml +15 -7
- package/.github/workflows/testdriver.yml +163 -0
- package/.testdriver/last-sandbox +7 -0
- package/agent/events.js +1 -0
- package/agent/index.js +99 -163
- package/agent/lib/sandbox.js +11 -1
- package/agents.md +393 -0
- package/bin/testdriverai.js +8 -0
- package/debug/01-table-initial.png +0 -0
- package/debug/02-after-ai-explore.png +0 -0
- package/debug/02-after-scroll.png +0 -0
- package/debugger/index.html +37 -0
- package/docs/docs.json +93 -125
- package/docs/v7/_drafts/architecture.mdx +1 -26
- package/docs/v7/_drafts/caching.mdx +2 -2
- package/docs/v7/{getting-started → _drafts}/installation.mdx +0 -66
- package/docs/v7/{features/coverage.mdx → _drafts/powerful.mdx} +1 -90
- package/docs/v7/_drafts/quick-start-test-recording.mdx +0 -1
- package/docs/v7/{features → _drafts}/scalable.mdx +126 -4
- package/docs/v7/_drafts/screenshot.mdx +155 -0
- package/docs/v7/_drafts/test-recording.mdx +0 -6
- package/docs/v7/_drafts/writing-tests.mdx +25 -0
- package/docs/v7/{api/act.mdx → ai.mdx} +28 -27
- package/docs/v7/{api/assert.mdx → assert.mdx} +3 -3
- package/docs/v7/aws-setup.mdx +338 -0
- package/docs/v7/caching.mdx +128 -0
- package/docs/v7/ci-cd.mdx +605 -0
- package/docs/v7/{api/click.mdx → click.mdx} +4 -4
- package/docs/v7/cloud.mdx +120 -0
- package/docs/v7/customizing-devices.mdx +129 -0
- package/docs/v7/{api/doubleClick.mdx → double-click.mdx} +5 -5
- package/docs/v7/enterprise.mdx +135 -0
- package/docs/v7/examples.mdx +5 -0
- package/docs/v7/{api/exec.mdx → exec.mdx} +3 -3
- package/docs/v7/{api/find.mdx → find.mdx} +17 -21
- package/docs/v7/{api/focusApplication.mdx → focus-application.mdx} +3 -3
- package/docs/v7/generating-tests.mdx +32 -0
- package/docs/v7/{api/hover.mdx → hover.mdx} +3 -3
- package/docs/v7/locating-elements.mdx +71 -0
- package/docs/v7/making-assertions.mdx +32 -0
- package/docs/v7/{api/mouseDown.mdx → mouse-down.mdx} +7 -7
- package/docs/v7/{api/mouseUp.mdx → mouse-up.mdx} +8 -8
- package/docs/v7/performing-actions.mdx +51 -0
- package/docs/v7/{api/pressKeys.mdx → press-keys.mdx} +3 -3
- package/docs/v7/quickstart.mdx +162 -0
- package/docs/v7/reusable-code.mdx +240 -0
- package/docs/v7/{api/rightClick.mdx → right-click.mdx} +5 -5
- package/docs/v7/running-tests.mdx +181 -0
- package/docs/v7/{api/scroll.mdx → scroll.mdx} +3 -3
- package/docs/v7/secrets.mdx +115 -0
- package/docs/v7/self-hosted.mdx +66 -0
- package/docs/v7/{api/type.mdx → type.mdx} +3 -3
- package/docs/v7/variables.mdx +111 -0
- package/docs/v7/waiting-for-elements.mdx +66 -0
- package/docs/v7/what-is-testdriver.mdx +54 -0
- package/interfaces/cli/commands/init.js +33 -19
- package/interfaces/cli/lib/base.js +24 -0
- package/interfaces/cli.js +8 -1
- package/interfaces/logger.js +8 -3
- package/interfaces/vitest-plugin.mjs +16 -71
- package/lib/sentry.js +343 -0
- package/lib/vitest/hooks.mjs +81 -81
- package/package.json +4 -3
- package/sdk-log-formatter.js +41 -0
- package/sdk.d.ts +22 -9
- package/sdk.js +344 -100
- package/test/manual/reconnect-provision.test.mjs +49 -0
- package/test/manual/reconnect-signin.test.mjs +41 -0
- package/test/testdriver/act.test.mjs +30 -0
- package/test/testdriver/ai.test.mjs +30 -0
- package/test/testdriver/assert.test.mjs +1 -1
- package/test/testdriver/hover-text.test.mjs +1 -1
- package/test/testdriver/setup/testHelpers.mjs +8 -119
- package/test/testdriver/windows-installer.test.mjs +61 -0
- package/tests/example.test.js +33 -0
- package/tests/login.js +28 -0
- package/tests/table-sort-enrollments.test.mjs +72 -0
- package/tests/table-sort-experiment.test.mjs +42 -0
- package/tests/table-sort-setup.test.mjs +59 -0
- package/vitest.config.mjs +3 -1
- package/agent/lib/cache.js +0 -142
- package/docs/v7/api/assertions.mdx +0 -403
- package/docs/v7/features/ai-native.mdx +0 -413
- package/docs/v7/features/application-logs.mdx +0 -353
- package/docs/v7/features/browser-logs.mdx +0 -414
- package/docs/v7/features/cache-management.mdx +0 -402
- package/docs/v7/features/continuous-testing.mdx +0 -346
- package/docs/v7/features/data-driven-testing.mdx +0 -441
- package/docs/v7/features/easy-to-write.mdx +0 -280
- package/docs/v7/features/enterprise.mdx +0 -656
- package/docs/v7/features/fast.mdx +0 -406
- package/docs/v7/features/managed-sandboxes.mdx +0 -384
- package/docs/v7/features/network-monitoring.mdx +0 -568
- package/docs/v7/features/parallel-execution.mdx +0 -381
- package/docs/v7/features/powerful.mdx +0 -531
- package/docs/v7/features/sandbox-customization.mdx +0 -229
- package/docs/v7/features/stable.mdx +0 -473
- package/docs/v7/features/system-performance.mdx +0 -616
- package/docs/v7/features/test-analytics.mdx +0 -373
- package/docs/v7/features/test-cases.mdx +0 -393
- package/docs/v7/features/test-replays.mdx +0 -408
- package/docs/v7/features/test-reports.mdx +0 -308
- package/docs/v7/getting-started/debugging-tests.mdx +0 -382
- package/docs/v7/getting-started/quickstart.mdx +0 -90
- package/docs/v7/getting-started/running-tests.mdx +0 -173
- package/docs/v7/getting-started/setting-up-in-ci.mdx +0 -612
- package/docs/v7/getting-started/writing-tests.mdx +0 -534
- package/docs/v7/overview/what-is-testdriver.mdx +0 -386
- package/docs/v7/presets/chrome-extension.mdx +0 -248
- package/docs/v7/presets/chrome.mdx +0 -300
- package/docs/v7/presets/electron.mdx +0 -460
- package/docs/v7/presets/vscode.mdx +0 -417
- package/docs/v7/presets/webapp.mdx +0 -393
- /package/docs/v7/{commands → _drafts/commands}/assert.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/exec.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/focus-application.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/hover-image.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/hover-text.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/if.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/match-image.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/press-keys.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/remember.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/run.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/scroll-until-image.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/scroll-until-text.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/scroll.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/type.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/wait-for-image.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/wait-for-text.mdx +0 -0
- /package/docs/v7/{commands → _drafts/commands}/wait.mdx +0 -0
- /package/docs/v7/{getting-started → _drafts}/configuration.mdx +0 -0
- /package/docs/v7/{features → _drafts}/observable.mdx +0 -0
- /package/docs/v7/{platforms → _drafts/platforms}/linux.mdx +0 -0
- /package/docs/v7/{platforms → _drafts/platforms}/macos.mdx +0 -0
- /package/docs/v7/{platforms → _drafts/platforms}/windows.mdx +0 -0
- /package/docs/v7/{playwright.mdx → _drafts/playwright.mdx} +0 -0
- /package/docs/v7/{overview → _drafts}/readme.mdx +0 -0
- /package/docs/v7/{features → _drafts}/reports.mdx +0 -0
- /package/docs/v7/{api/client.mdx → client.mdx} +0 -0
- /package/docs/v7/{api/dashcam.mdx → dashcam.mdx} +0 -0
- /package/docs/v7/{api/elements.mdx → elements.mdx} +0 -0
- /package/docs/v7/{api/sandbox.mdx → sandbox.mdx} +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
name: Publish
|
|
1
|
+
name: Publish
|
|
2
2
|
permissions:
|
|
3
3
|
contents: write
|
|
4
|
+
id-token: write # Required for OIDC
|
|
4
5
|
on:
|
|
5
6
|
push:
|
|
6
7
|
branches: [ main ]
|
|
7
8
|
|
|
8
9
|
jobs:
|
|
9
|
-
publish
|
|
10
|
+
publish:
|
|
10
11
|
runs-on: ubuntu-latest
|
|
11
12
|
|
|
12
13
|
steps:
|
|
@@ -29,16 +30,23 @@ jobs:
|
|
|
29
30
|
- name: Install dependencies
|
|
30
31
|
run: npm ci
|
|
31
32
|
|
|
32
|
-
- name: Bump version (
|
|
33
|
-
run: npm version
|
|
33
|
+
- name: Bump version (patch)
|
|
34
|
+
run: npm version patch --no-git-tag-version
|
|
34
35
|
|
|
35
36
|
- name: Commit and push version bump
|
|
36
37
|
run: |
|
|
37
38
|
git add package.json package-lock.json
|
|
38
|
-
git commit -m "chore: bump
|
|
39
|
+
git commit -m "chore: bump version to $(node -p "require('./package.json').version")"
|
|
39
40
|
git push
|
|
40
41
|
|
|
41
|
-
- name:
|
|
42
|
-
run:
|
|
42
|
+
- name: Debug NPM Token
|
|
43
|
+
run: |
|
|
44
|
+
echo "NPM_TOKEN is set: ${{ secrets.NPM_TOKEN != '' }}"
|
|
45
|
+
echo "NPM_TOKEN first 4 chars: ${NPM_TOKEN:0:4}..."
|
|
46
|
+
env:
|
|
47
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
48
|
+
|
|
49
|
+
- name: Publish to npm
|
|
50
|
+
run: npm publish --tag beta
|
|
43
51
|
env:
|
|
44
52
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
name: TestDriver.ai Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main, master ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main, master ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- name: Setup Node.js
|
|
17
|
+
uses: actions/setup-node@v4
|
|
18
|
+
with:
|
|
19
|
+
node-version: '20'
|
|
20
|
+
cache: 'npm'
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: npm ci
|
|
24
|
+
|
|
25
|
+
- name: Run TestDriver.ai tests
|
|
26
|
+
env:
|
|
27
|
+
TD_API_KEY: ${{ secrets.TD_API_KEY }}
|
|
28
|
+
run: npx vitest run
|
|
29
|
+
|
|
30
|
+
- name: Upload test results
|
|
31
|
+
if: always()
|
|
32
|
+
uses: actions/upload-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: test-results
|
|
35
|
+
path: test-results/
|
|
36
|
+
retention-days: 30
|
|
37
|
+
|
|
38
|
+
# Init command test - only runs on PRs, not on main/master
|
|
39
|
+
- name: Create test directory for init
|
|
40
|
+
if: github.event_name == 'pull_request'
|
|
41
|
+
run: |
|
|
42
|
+
mkdir -p /tmp/test-init-project
|
|
43
|
+
cd /tmp/test-init-project
|
|
44
|
+
|
|
45
|
+
- name: Run init command (skip prompts)
|
|
46
|
+
if: github.event_name == 'pull_request'
|
|
47
|
+
working-directory: /tmp/test-init-project
|
|
48
|
+
run: |
|
|
49
|
+
# Create .env with API key first to skip the prompt
|
|
50
|
+
echo "TD_API_KEY=${{ secrets.TD_API_KEY }}" > .env
|
|
51
|
+
|
|
52
|
+
# Run init command using the CLI from the repo
|
|
53
|
+
node ${{ github.workspace }}/bin/testdriverai.js init
|
|
54
|
+
env:
|
|
55
|
+
TD_API_KEY: ${{ secrets.TD_API_KEY }}
|
|
56
|
+
|
|
57
|
+
- name: Verify project structure
|
|
58
|
+
if: github.event_name == 'pull_request'
|
|
59
|
+
working-directory: /tmp/test-init-project
|
|
60
|
+
run: |
|
|
61
|
+
echo "Checking generated files..."
|
|
62
|
+
|
|
63
|
+
# Check for package.json
|
|
64
|
+
if [ ! -f "package.json" ]; then
|
|
65
|
+
echo "❌ package.json not found"
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
echo "✓ package.json exists"
|
|
69
|
+
|
|
70
|
+
# Check for vitest config
|
|
71
|
+
if [ ! -f "vitest.config.js" ]; then
|
|
72
|
+
echo "❌ vitest.config.js not found"
|
|
73
|
+
exit 1
|
|
74
|
+
fi
|
|
75
|
+
echo "✓ vitest.config.js exists"
|
|
76
|
+
|
|
77
|
+
# Check for test file
|
|
78
|
+
if [ ! -f "tests/example.test.js" ]; then
|
|
79
|
+
echo "❌ tests/example.test.js not found"
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
echo "✓ tests/example.test.js exists"
|
|
83
|
+
|
|
84
|
+
# Check for .env file
|
|
85
|
+
if [ ! -f ".env" ]; then
|
|
86
|
+
echo "❌ .env not found"
|
|
87
|
+
exit 1
|
|
88
|
+
fi
|
|
89
|
+
echo "✓ .env exists"
|
|
90
|
+
|
|
91
|
+
# Check for .gitignore
|
|
92
|
+
if [ ! -f ".gitignore" ]; then
|
|
93
|
+
echo "❌ .gitignore not found"
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
echo "✓ .gitignore exists"
|
|
97
|
+
|
|
98
|
+
# Check for GitHub workflow
|
|
99
|
+
if [ ! -f ".github/workflows/testdriver.yml" ]; then
|
|
100
|
+
echo "❌ .github/workflows/testdriver.yml not found"
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
echo "✓ .github/workflows/testdriver.yml exists"
|
|
104
|
+
|
|
105
|
+
- name: Verify vitest config contents
|
|
106
|
+
if: github.event_name == 'pull_request'
|
|
107
|
+
working-directory: /tmp/test-init-project
|
|
108
|
+
run: |
|
|
109
|
+
echo "Checking vitest.config.js contents..."
|
|
110
|
+
|
|
111
|
+
# Check for TestDriver reporter
|
|
112
|
+
if ! grep -q "TestDriver()" vitest.config.js; then
|
|
113
|
+
echo "❌ TestDriver reporter not found in vitest.config.js"
|
|
114
|
+
cat vitest.config.js
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
echo "✓ TestDriver reporter is configured"
|
|
118
|
+
|
|
119
|
+
# Check for setupFiles
|
|
120
|
+
if ! grep -q "setupFiles.*testdriverai/vitest/setup" vitest.config.js; then
|
|
121
|
+
echo "❌ setupFiles not configured correctly"
|
|
122
|
+
cat vitest.config.js
|
|
123
|
+
exit 1
|
|
124
|
+
fi
|
|
125
|
+
echo "✓ setupFiles is configured"
|
|
126
|
+
|
|
127
|
+
- name: Verify test file contents
|
|
128
|
+
if: github.event_name == 'pull_request'
|
|
129
|
+
working-directory: /tmp/test-init-project
|
|
130
|
+
run: |
|
|
131
|
+
echo "Checking test file contents..."
|
|
132
|
+
|
|
133
|
+
# Check for .provision usage
|
|
134
|
+
if ! grep -q "\.provision\.chrome" tests/example.test.js; then
|
|
135
|
+
echo "❌ Test does not use .provision.chrome"
|
|
136
|
+
cat tests/example.test.js
|
|
137
|
+
exit 1
|
|
138
|
+
fi
|
|
139
|
+
echo "✓ Test uses .provision.chrome"
|
|
140
|
+
|
|
141
|
+
# Check for TestDriver import
|
|
142
|
+
if ! grep -q "from 'testdriverai/vitest/hooks'" tests/example.test.js; then
|
|
143
|
+
echo "❌ Test does not import from testdriverai/vitest/hooks"
|
|
144
|
+
cat tests/example.test.js
|
|
145
|
+
exit 1
|
|
146
|
+
fi
|
|
147
|
+
echo "✓ Test imports TestDriver from vitest/hooks"
|
|
148
|
+
|
|
149
|
+
- name: Run the generated test
|
|
150
|
+
if: github.event_name == 'pull_request'
|
|
151
|
+
working-directory: /tmp/test-init-project
|
|
152
|
+
run: npm test
|
|
153
|
+
env:
|
|
154
|
+
TD_API_KEY: ${{ secrets.TD_API_KEY }}
|
|
155
|
+
|
|
156
|
+
- name: Upload init test results
|
|
157
|
+
if: always() && github.event_name == 'pull_request'
|
|
158
|
+
uses: actions/upload-artifact@v4
|
|
159
|
+
with:
|
|
160
|
+
name: test-init-results
|
|
161
|
+
path: /tmp/test-init-project/test-results/
|
|
162
|
+
retention-days: 7
|
|
163
|
+
if-no-files-found: warn
|
package/agent/events.js
CHANGED
package/agent/index.js
CHANGED
|
@@ -17,7 +17,6 @@ const diff = require("diff");
|
|
|
17
17
|
|
|
18
18
|
// global utilities
|
|
19
19
|
const generator = require("./lib/generator.js");
|
|
20
|
-
const promptCache = require("./lib/cache.js");
|
|
21
20
|
const theme = require("./lib/theme.js");
|
|
22
21
|
const SourceMapper = require("./lib/source-mapper.js");
|
|
23
22
|
|
|
@@ -110,6 +109,10 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
110
109
|
// Create sandbox instance with this agent's emitter, analytics, and session
|
|
111
110
|
this.sandbox = createSandbox(this.emitter, this.analytics, this.session);
|
|
112
111
|
|
|
112
|
+
// Attach Sentry log listeners to capture CLI logs as breadcrumbs
|
|
113
|
+
const sentry = require("../lib/sentry");
|
|
114
|
+
sentry.attachLogListeners(this.emitter);
|
|
115
|
+
|
|
113
116
|
// Set the OS for the sandbox to use
|
|
114
117
|
this.sandbox.os = this.sandboxOs;
|
|
115
118
|
|
|
@@ -191,6 +194,15 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
191
194
|
this.redraw.cleanup();
|
|
192
195
|
}
|
|
193
196
|
|
|
197
|
+
// Close sandbox connection to release the connection slot
|
|
198
|
+
if (this.sandbox) {
|
|
199
|
+
try {
|
|
200
|
+
this.sandbox.close();
|
|
201
|
+
} catch (err) {
|
|
202
|
+
// Ignore sandbox close errors during exit
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
194
206
|
shouldRunPostrun =
|
|
195
207
|
!this.hasRunPostrun &&
|
|
196
208
|
(shouldRunPostrun || this.cliArgs?.command == "run");
|
|
@@ -356,7 +368,7 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
356
368
|
image,
|
|
357
369
|
},
|
|
358
370
|
(chunk) => {
|
|
359
|
-
if (chunk.type === "data") {
|
|
371
|
+
if (chunk.type === "data" && chunk.data) {
|
|
360
372
|
this.emitter.emit(events.log.markdown.chunk, streamId, chunk.data);
|
|
361
373
|
}
|
|
362
374
|
},
|
|
@@ -420,9 +432,6 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
420
432
|
let mousePosition = await this.system.getMousePosition();
|
|
421
433
|
let activeWindow = await this.system.activeWin();
|
|
422
434
|
|
|
423
|
-
const streamId = `check-${Date.now()}`;
|
|
424
|
-
this.emitter.emit(events.log.markdown.start, streamId);
|
|
425
|
-
|
|
426
435
|
let response = await this.sdk.req(
|
|
427
436
|
"check",
|
|
428
437
|
{
|
|
@@ -430,15 +439,10 @@ class TestDriverAgent extends EventEmitter2 {
|
|
|
430
439
|
images,
|
|
431
440
|
mousePosition,
|
|
432
441
|
activeWindow,
|
|
433
|
-
}
|
|
434
|
-
(chunk) => {
|
|
435
|
-
if (chunk.type === "data") {
|
|
436
|
-
this.emitter.emit(events.log.markdown.chunk, streamId, chunk.data);
|
|
437
|
-
}
|
|
438
|
-
},
|
|
442
|
+
}
|
|
439
443
|
);
|
|
440
444
|
|
|
441
|
-
this.emitter.emit(events.log.markdown.
|
|
445
|
+
this.emitter.emit(events.log.markdown.static, response.data);
|
|
442
446
|
|
|
443
447
|
this.lastScreenshot = thisScreenshot;
|
|
444
448
|
|
|
@@ -869,8 +873,7 @@ commands:
|
|
|
869
873
|
currentTask,
|
|
870
874
|
dry = false,
|
|
871
875
|
validateAndLoop = false,
|
|
872
|
-
shouldSave = true
|
|
873
|
-
useCache = true,
|
|
876
|
+
shouldSave = true
|
|
874
877
|
) {
|
|
875
878
|
// Check if execution has been stopped
|
|
876
879
|
if (this.stopped) {
|
|
@@ -889,56 +892,10 @@ commands:
|
|
|
889
892
|
|
|
890
893
|
this.tasks.push(currentTask);
|
|
891
894
|
|
|
892
|
-
// Check cache first (if enabled via parameter)
|
|
893
|
-
const cachedYaml = useCache ? promptCache.readCache(currentTask) : null;
|
|
894
|
-
|
|
895
|
-
if (cachedYaml) {
|
|
896
|
-
// Cache hit - load and execute the cached YAML file
|
|
897
|
-
this.emitter.emit(
|
|
898
|
-
events.log.debug,
|
|
899
|
-
`Using cached response for prompt: "${currentTask}"`,
|
|
900
|
-
);
|
|
901
|
-
this.emitter.emit(events.log.log, theme.dim("(using cached response)"));
|
|
902
|
-
|
|
903
|
-
try {
|
|
904
|
-
// Load the YAML using hydrateFromYML
|
|
905
|
-
const parsed = await generator.hydrateFromYML(
|
|
906
|
-
cachedYaml,
|
|
907
|
-
this.sessionInstance,
|
|
908
|
-
);
|
|
909
|
-
|
|
910
|
-
// Execute the commands from the first step
|
|
911
|
-
if (parsed.steps && parsed.steps.length > 0) {
|
|
912
|
-
const step = parsed.steps[0];
|
|
913
|
-
if (step.commands) {
|
|
914
|
-
await this.executeCommands(
|
|
915
|
-
step.commands,
|
|
916
|
-
0,
|
|
917
|
-
false,
|
|
918
|
-
dry,
|
|
919
|
-
shouldSave,
|
|
920
|
-
);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
} catch (err) {
|
|
924
|
-
this.emitter.emit(
|
|
925
|
-
events.log.debug,
|
|
926
|
-
`Error loading cached YAML: ${err.message}, falling back to API`,
|
|
927
|
-
);
|
|
928
|
-
// Fall through to make API call if cache is invalid
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
// Cache miss - call the API
|
|
935
895
|
this.emitter.emit(events.log.narration, theme.dim("thinking..."), true);
|
|
936
896
|
|
|
937
897
|
this.lastScreenshot = await this.system.captureScreenBase64();
|
|
938
898
|
|
|
939
|
-
const streamId = `input-${Date.now()}`;
|
|
940
|
-
this.emitter.emit(events.log.markdown.start, streamId);
|
|
941
|
-
|
|
942
899
|
let message = await this.sdk.req(
|
|
943
900
|
"input",
|
|
944
901
|
{
|
|
@@ -946,59 +903,12 @@ commands:
|
|
|
946
903
|
mousePosition: await this.system.getMousePosition(),
|
|
947
904
|
activeWindow: await this.system.activeWin(),
|
|
948
905
|
image: this.lastScreenshot,
|
|
949
|
-
}
|
|
950
|
-
(chunk) => {
|
|
951
|
-
if (chunk.type === "data") {
|
|
952
|
-
this.emitter.emit(events.log.markdown.chunk, streamId, chunk.data);
|
|
953
|
-
}
|
|
954
|
-
},
|
|
906
|
+
}
|
|
955
907
|
);
|
|
956
908
|
|
|
957
|
-
this.emitter.emit(events.log.
|
|
909
|
+
this.emitter.emit(events.log.log, message.data);
|
|
958
910
|
|
|
959
911
|
if (message && message.data) {
|
|
960
|
-
// Save the YAML to cache (if enabled)
|
|
961
|
-
if (useCache) {
|
|
962
|
-
try {
|
|
963
|
-
// Extract YAML code blocks from the markdown response
|
|
964
|
-
const codeblocks = await this.parser.findCodeBlocks(message.data);
|
|
965
|
-
if (codeblocks && codeblocks.length > 0) {
|
|
966
|
-
// Parse commands from all code blocks
|
|
967
|
-
const allCommands = [];
|
|
968
|
-
for (const block of codeblocks) {
|
|
969
|
-
const commands = await this.parser.getCommands(block);
|
|
970
|
-
allCommands.push(...commands);
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
// Create a proper step with prompt
|
|
974
|
-
const step = {
|
|
975
|
-
prompt: currentTask,
|
|
976
|
-
commands: allCommands,
|
|
977
|
-
};
|
|
978
|
-
|
|
979
|
-
// Use dumpToYML to create a valid testdriver yaml file
|
|
980
|
-
const yamlContent = await generator.dumpToYML(
|
|
981
|
-
[step],
|
|
982
|
-
this.sessionInstance,
|
|
983
|
-
);
|
|
984
|
-
|
|
985
|
-
const cachePath = promptCache.writeCache(currentTask, yamlContent);
|
|
986
|
-
if (cachePath) {
|
|
987
|
-
this.emitter.emit(
|
|
988
|
-
events.log.debug,
|
|
989
|
-
`Cached YAML saved to: ${cachePath}`,
|
|
990
|
-
);
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
} catch (err) {
|
|
994
|
-
// If we can't extract YAML, just skip caching
|
|
995
|
-
this.emitter.emit(
|
|
996
|
-
events.log.debug,
|
|
997
|
-
`Could not cache response: ${err.message}`,
|
|
998
|
-
);
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
|
|
1002
912
|
await this.aiExecute(message.data, validateAndLoop, dry, shouldSave);
|
|
1003
913
|
this.emitter.emit(
|
|
1004
914
|
events.log.debug,
|
|
@@ -1709,49 +1619,35 @@ ${regression}
|
|
|
1709
1619
|
this.emitter.emit(events.log.log, `${inputFile} (end)`);
|
|
1710
1620
|
}
|
|
1711
1621
|
|
|
1712
|
-
// Returns
|
|
1713
|
-
|
|
1714
|
-
const
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1622
|
+
// Returns the path to the last sandbox file
|
|
1623
|
+
getLastSandboxFilePath() {
|
|
1624
|
+
const testdriverDir = path.join(process.cwd(), '.testdriver');
|
|
1625
|
+
return path.join(testdriverDir, 'last-sandbox');
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// Returns full sandbox info from last-sandbox file (no timeout - let API validate)
|
|
1629
|
+
getLastSandboxId() {
|
|
1630
|
+
const lastSandboxFile = this.getLastSandboxFilePath();
|
|
1718
1631
|
|
|
1719
1632
|
if (fs.existsSync(lastSandboxFile)) {
|
|
1720
1633
|
try {
|
|
1721
|
-
const
|
|
1722
|
-
const mtime = new Date(stats.mtime);
|
|
1723
|
-
const now = new Date();
|
|
1724
|
-
const diffMinutes = (now - mtime) / (1000 * 60);
|
|
1725
|
-
if (diffMinutes < 10) {
|
|
1726
|
-
const fileContent = fs.readFileSync(lastSandboxFile, "utf-8").trim();
|
|
1727
|
-
|
|
1728
|
-
// Parse sandbox info (supports both old format and new format)
|
|
1729
|
-
let sandboxInfo;
|
|
1730
|
-
try {
|
|
1731
|
-
sandboxInfo = JSON.parse(fileContent);
|
|
1732
|
-
} catch {
|
|
1733
|
-
return fileContent || null;
|
|
1734
|
-
}
|
|
1634
|
+
const fileContent = fs.readFileSync(lastSandboxFile, "utf-8").trim();
|
|
1735
1635
|
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
if (currentAmi === storedAmi && currentInstance === storedInstance) {
|
|
1743
|
-
// Return sandboxId (new format) or instanceId (old format for backwards compatibility)
|
|
1744
|
-
return sandboxInfo.sandboxId || sandboxInfo.instanceId;
|
|
1745
|
-
} else {
|
|
1746
|
-
this.emitter.emit(
|
|
1747
|
-
events.log.log,
|
|
1748
|
-
theme.dim(
|
|
1749
|
-
"Recent sandbox found but AMI/instance type doesn't match current requirements",
|
|
1750
|
-
),
|
|
1751
|
-
);
|
|
1752
|
-
return null;
|
|
1753
|
-
}
|
|
1636
|
+
// Parse sandbox info (supports both old format and new format)
|
|
1637
|
+
let sandboxInfo;
|
|
1638
|
+
try {
|
|
1639
|
+
sandboxInfo = JSON.parse(fileContent);
|
|
1640
|
+
} catch {
|
|
1641
|
+
return { sandboxId: fileContent || null };
|
|
1754
1642
|
}
|
|
1643
|
+
|
|
1644
|
+
return {
|
|
1645
|
+
sandboxId: sandboxInfo.sandboxId || sandboxInfo.instanceId || null,
|
|
1646
|
+
os: sandboxInfo.os || 'linux',
|
|
1647
|
+
ami: sandboxInfo.ami || null,
|
|
1648
|
+
instanceType: sandboxInfo.instanceType || null,
|
|
1649
|
+
timestamp: sandboxInfo.timestamp || null,
|
|
1650
|
+
};
|
|
1755
1651
|
} catch {
|
|
1756
1652
|
// ignore errors
|
|
1757
1653
|
}
|
|
@@ -1759,12 +1655,43 @@ ${regression}
|
|
|
1759
1655
|
return null;
|
|
1760
1656
|
}
|
|
1761
1657
|
|
|
1658
|
+
// Returns sandboxId to use if AMI/instance type match current requirements
|
|
1659
|
+
getRecentSandboxId() {
|
|
1660
|
+
const sandboxInfo = this.getLastSandboxId();
|
|
1661
|
+
|
|
1662
|
+
if (!sandboxInfo || !sandboxInfo.sandboxId) {
|
|
1663
|
+
return null;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// Check if AMI and instance type match current requirements
|
|
1667
|
+
const currentAmi = this.sandboxAmi || null;
|
|
1668
|
+
const currentInstance = this.sandboxInstance || null;
|
|
1669
|
+
const storedAmi = sandboxInfo.ami || null;
|
|
1670
|
+
const storedInstance = sandboxInfo.instanceType || null;
|
|
1671
|
+
|
|
1672
|
+
if (currentAmi === storedAmi && currentInstance === storedInstance) {
|
|
1673
|
+
return sandboxInfo.sandboxId;
|
|
1674
|
+
} else {
|
|
1675
|
+
this.emitter.emit(
|
|
1676
|
+
events.log.log,
|
|
1677
|
+
theme.dim(
|
|
1678
|
+
"Recent sandbox found but AMI/instance type doesn't match current requirements",
|
|
1679
|
+
),
|
|
1680
|
+
);
|
|
1681
|
+
return null;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1762
1685
|
saveLastSandboxId(sandboxId, osType = "linux") {
|
|
1763
|
-
const lastSandboxFile =
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
);
|
|
1686
|
+
const lastSandboxFile = this.getLastSandboxFilePath();
|
|
1687
|
+
const testdriverDir = path.dirname(lastSandboxFile);
|
|
1688
|
+
|
|
1767
1689
|
try {
|
|
1690
|
+
// Ensure .testdriver directory exists
|
|
1691
|
+
if (!fs.existsSync(testdriverDir)) {
|
|
1692
|
+
fs.mkdirSync(testdriverDir, { recursive: true });
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1768
1695
|
const sandboxInfo = {
|
|
1769
1696
|
sandboxId: sandboxId,
|
|
1770
1697
|
os: osType,
|
|
@@ -1772,7 +1699,7 @@ ${regression}
|
|
|
1772
1699
|
instanceType: this.sandboxInstance || null,
|
|
1773
1700
|
timestamp: new Date().toISOString(),
|
|
1774
1701
|
};
|
|
1775
|
-
fs.writeFileSync(lastSandboxFile, JSON.stringify(sandboxInfo), {
|
|
1702
|
+
fs.writeFileSync(lastSandboxFile, JSON.stringify(sandboxInfo, null, 2), {
|
|
1776
1703
|
encoding: "utf-8",
|
|
1777
1704
|
});
|
|
1778
1705
|
} catch {
|
|
@@ -1781,10 +1708,7 @@ ${regression}
|
|
|
1781
1708
|
}
|
|
1782
1709
|
|
|
1783
1710
|
clearRecentSandboxId() {
|
|
1784
|
-
const lastSandboxFile =
|
|
1785
|
-
os.homedir(),
|
|
1786
|
-
".testdriverai-last-sandbox",
|
|
1787
|
-
);
|
|
1711
|
+
const lastSandboxFile = this.getLastSandboxFilePath();
|
|
1788
1712
|
try {
|
|
1789
1713
|
if (fs.existsSync(lastSandboxFile)) {
|
|
1790
1714
|
fs.unlinkSync(lastSandboxFile);
|
|
@@ -1793,6 +1717,7 @@ ${regression}
|
|
|
1793
1717
|
// ignore errors
|
|
1794
1718
|
}
|
|
1795
1719
|
}
|
|
1720
|
+
|
|
1796
1721
|
async buildEnv(options = {}) {
|
|
1797
1722
|
// If instance already exists, do not build environment again
|
|
1798
1723
|
if (this.instance) {
|
|
@@ -1875,6 +1800,7 @@ ${regression}
|
|
|
1875
1800
|
let instance = await this.connectToSandboxDirect(
|
|
1876
1801
|
this.sandboxId,
|
|
1877
1802
|
true, // always persist by default
|
|
1803
|
+
this.keepAlive, // pass keepAlive TTL
|
|
1878
1804
|
);
|
|
1879
1805
|
|
|
1880
1806
|
this.instance = instance;
|
|
@@ -1889,11 +1815,6 @@ ${regression}
|
|
|
1889
1815
|
);
|
|
1890
1816
|
console.error("Failed to reconnect to sandbox:", error);
|
|
1891
1817
|
}
|
|
1892
|
-
} else if (!createNew && !recentId) {
|
|
1893
|
-
this.emitter.emit(
|
|
1894
|
-
events.log.narration,
|
|
1895
|
-
theme.dim(`no recent sandbox found, creating a new one.`),
|
|
1896
|
-
);
|
|
1897
1818
|
} else if (!createNew && this.sandboxId && !this.config.CI) {
|
|
1898
1819
|
// Only attempt to connect to existing sandbox if not in CI mode and not creating new
|
|
1899
1820
|
// Attempt to connect to known instance
|
|
@@ -1906,6 +1827,7 @@ ${regression}
|
|
|
1906
1827
|
let instance = await this.connectToSandboxDirect(
|
|
1907
1828
|
this.sandboxId,
|
|
1908
1829
|
true, // always persist by default
|
|
1830
|
+
this.keepAlive, // pass keepAlive TTL
|
|
1909
1831
|
);
|
|
1910
1832
|
|
|
1911
1833
|
this.instance = instance;
|
|
@@ -1948,6 +1870,7 @@ ${regression}
|
|
|
1948
1870
|
let instance = await this.connectToSandboxDirect(
|
|
1949
1871
|
this.sandboxId,
|
|
1950
1872
|
true, // always persist by default
|
|
1873
|
+
this.keepAlive, // pass keepAlive TTL
|
|
1951
1874
|
);
|
|
1952
1875
|
this.instance = instance;
|
|
1953
1876
|
await this.renderSandbox(instance, headless);
|
|
@@ -2137,10 +2060,10 @@ Please check your network connection, TD_API_KEY, or the service status.`,
|
|
|
2137
2060
|
}
|
|
2138
2061
|
}
|
|
2139
2062
|
|
|
2140
|
-
async connectToSandboxDirect(sandboxId, persist = false) {
|
|
2063
|
+
async connectToSandboxDirect(sandboxId, persist = false, keepAlive = null) {
|
|
2141
2064
|
const { formatter } = require("../sdk-log-formatter.js");
|
|
2142
2065
|
this.emitter.emit(events.log.narration, formatter.getPrefix("connect") + " " + theme.green.bold("Connecting") + " " + theme.cyan(`to sandbox...`));
|
|
2143
|
-
let reply = await this.sandbox.connect(sandboxId, persist);
|
|
2066
|
+
let reply = await this.sandbox.connect(sandboxId, persist, keepAlive);
|
|
2144
2067
|
|
|
2145
2068
|
// reply includes { success, url, sandbox: {...} }
|
|
2146
2069
|
// For renderSandbox, we need the sandbox object with url merged in
|
|
@@ -2169,6 +2092,10 @@ Please check your network connection, TD_API_KEY, or the service status.`,
|
|
|
2169
2092
|
if (this.sandboxInstance) {
|
|
2170
2093
|
sandboxConfig.instanceType = this.sandboxInstance;
|
|
2171
2094
|
}
|
|
2095
|
+
// Add keepAlive TTL if specified
|
|
2096
|
+
if (this.keepAlive !== undefined && this.keepAlive !== null) {
|
|
2097
|
+
sandboxConfig.keepAlive = this.keepAlive;
|
|
2098
|
+
}
|
|
2172
2099
|
|
|
2173
2100
|
let instance = await this.sandbox.send(sandboxConfig, 60000 * 8);
|
|
2174
2101
|
|
|
@@ -2200,6 +2127,15 @@ Please check your network connection, TD_API_KEY, or the service status.`,
|
|
|
2200
2127
|
}
|
|
2201
2128
|
|
|
2202
2129
|
this.session.set(sessionRes.data.id);
|
|
2130
|
+
|
|
2131
|
+
// Set Sentry session trace context for distributed tracing
|
|
2132
|
+
// This links CLI errors/logs to the same trace as API calls
|
|
2133
|
+
try {
|
|
2134
|
+
const sentry = require("../lib/sentry");
|
|
2135
|
+
sentry.setSessionTraceContext(sessionRes.data.id);
|
|
2136
|
+
} catch (e) {
|
|
2137
|
+
// Sentry module may not be available, ignore
|
|
2138
|
+
}
|
|
2203
2139
|
}
|
|
2204
2140
|
|
|
2205
2141
|
// Helper method to find testdriver directory by traversing up from a file path
|
package/agent/lib/sandbox.js
CHANGED
|
@@ -155,11 +155,12 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
|
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
async connect(sandboxId, persist = false) {
|
|
158
|
+
async connect(sandboxId, persist = false, keepAlive = null) {
|
|
159
159
|
let reply = await this.send({
|
|
160
160
|
type: "connect",
|
|
161
161
|
persist,
|
|
162
162
|
sandboxId,
|
|
163
|
+
keepAlive,
|
|
163
164
|
});
|
|
164
165
|
|
|
165
166
|
if (reply.success) {
|
|
@@ -227,6 +228,15 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
|
|
|
227
228
|
this.socket.on("message", async (raw) => {
|
|
228
229
|
let message = JSON.parse(raw);
|
|
229
230
|
|
|
231
|
+
// Handle progress messages (no requestId needed)
|
|
232
|
+
if (message.type === 'sandbox.progress') {
|
|
233
|
+
emitter.emit(events.sandbox.progress, {
|
|
234
|
+
step: message.step,
|
|
235
|
+
message: message.message,
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
230
240
|
if (!this.ps[message.requestId]) {
|
|
231
241
|
console.warn(
|
|
232
242
|
"No pending promise found for requestId:",
|