testdriverai 7.1.3 → 7.2.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.
Files changed (78) hide show
  1. package/.github/workflows/acceptance.yaml +81 -0
  2. package/.github/workflows/publish.yaml +44 -0
  3. package/.github/workflows/test-init.yml +145 -0
  4. package/agent/index.js +18 -19
  5. package/agent/lib/commander.js +2 -2
  6. package/agent/lib/commands.js +324 -124
  7. package/agent/lib/redraw.js +99 -39
  8. package/agent/lib/sandbox.js +98 -6
  9. package/agent/lib/sdk.js +25 -0
  10. package/agent/lib/system.js +2 -1
  11. package/agent/lib/validation.js +6 -6
  12. package/docs/docs.json +211 -101
  13. package/docs/snippets/tests/type-repeated-replay.mdx +1 -1
  14. package/docs/v7/_drafts/caching-selectors.mdx +24 -0
  15. package/docs/v7/_drafts/migration.mdx +3 -3
  16. package/docs/v7/api/act.mdx +2 -2
  17. package/docs/v7/api/assert.mdx +2 -2
  18. package/docs/v7/api/assertions.mdx +21 -21
  19. package/docs/v7/api/elements.mdx +78 -0
  20. package/docs/v7/api/find.mdx +38 -0
  21. package/docs/v7/api/focusApplication.mdx +2 -2
  22. package/docs/v7/api/hover.mdx +2 -2
  23. package/docs/v7/features/ai-native.mdx +57 -71
  24. package/docs/v7/features/application-logs.mdx +353 -0
  25. package/docs/v7/features/browser-logs.mdx +414 -0
  26. package/docs/v7/features/cache-management.mdx +402 -0
  27. package/docs/v7/features/continuous-testing.mdx +346 -0
  28. package/docs/v7/features/coverage.mdx +508 -0
  29. package/docs/v7/features/data-driven-testing.mdx +441 -0
  30. package/docs/v7/features/easy-to-write.mdx +2 -73
  31. package/docs/v7/features/enterprise.mdx +155 -39
  32. package/docs/v7/features/fast.mdx +63 -81
  33. package/docs/v7/features/managed-sandboxes.mdx +384 -0
  34. package/docs/v7/features/network-monitoring.mdx +568 -0
  35. package/docs/v7/features/observable.mdx +3 -22
  36. package/docs/v7/features/parallel-execution.mdx +381 -0
  37. package/docs/v7/features/powerful.mdx +1 -1
  38. package/docs/v7/features/reports.mdx +414 -0
  39. package/docs/v7/features/sandbox-customization.mdx +229 -0
  40. package/docs/v7/features/scalable.mdx +217 -2
  41. package/docs/v7/features/stable.mdx +106 -147
  42. package/docs/v7/features/system-performance.mdx +616 -0
  43. package/docs/v7/features/test-analytics.mdx +373 -0
  44. package/docs/v7/features/test-cases.mdx +393 -0
  45. package/docs/v7/features/test-replays.mdx +408 -0
  46. package/docs/v7/features/test-reports.mdx +308 -0
  47. package/docs/v7/getting-started/{running-and-debugging.mdx → debugging-tests.mdx} +12 -142
  48. package/docs/v7/getting-started/quickstart.mdx +22 -305
  49. package/docs/v7/getting-started/running-tests.mdx +173 -0
  50. package/docs/v7/overview/readme.mdx +1 -1
  51. package/docs/v7/overview/what-is-testdriver.mdx +2 -14
  52. package/docs/v7/presets/chrome-extension.mdx +147 -122
  53. package/interfaces/cli/commands/init.js +78 -20
  54. package/interfaces/cli/lib/base.js +3 -2
  55. package/interfaces/logger.js +0 -2
  56. package/interfaces/shared-test-state.mjs +0 -5
  57. package/interfaces/vitest-plugin.mjs +69 -42
  58. package/lib/core/Dashcam.js +65 -66
  59. package/lib/vitest/hooks.mjs +42 -50
  60. package/manual/test-init-command.js +223 -0
  61. package/package.json +2 -2
  62. package/schema.json +5 -5
  63. package/sdk-log-formatter.js +351 -176
  64. package/sdk.d.ts +8 -8
  65. package/sdk.js +436 -121
  66. package/setup/aws/cloudformation.yaml +2 -2
  67. package/setup/aws/self-hosted.yml +1 -1
  68. package/test/testdriver/chrome-extension.test.mjs +55 -72
  69. package/test/testdriver/element-not-found.test.mjs +2 -1
  70. package/test/testdriver/hover-image.test.mjs +1 -1
  71. package/test/testdriver/hover-text-with-description.test.mjs +0 -3
  72. package/test/testdriver/scroll-until-text.test.mjs +10 -6
  73. package/test/testdriver/setup/lifecycleHelpers.mjs +19 -24
  74. package/test/testdriver/setup/testHelpers.mjs +18 -23
  75. package/vitest.config.mjs +3 -3
  76. package/.github/workflows/linux-tests.yml +0 -28
  77. package/docs/v7/getting-started/generating-tests.mdx +0 -525
  78. package/test/testdriver/auto-cache-key-demo.test.mjs +0 -56
@@ -0,0 +1,81 @@
1
+ name: Acceptance Tests
2
+ permissions:
3
+ id-token: write
4
+ contents: write
5
+ pull-requests: write
6
+ checks: write
7
+ on:
8
+ pull_request:
9
+ branches: [ main ]
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ test-linux:
17
+ runs-on: ubuntu-latest
18
+
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+
22
+ - name: Setup Node.js
23
+ uses: actions/setup-node@v4
24
+ with:
25
+ node-version: '20'
26
+ cache: 'npm'
27
+
28
+ - name: Install dependencies
29
+ run: npm ci
30
+
31
+ - name: Run Linux tests
32
+ run: npx vitest run test/testdriver/*.test.mjs
33
+ env:
34
+ TD_API_KEY: ${{ secrets.TD_API_KEY }}
35
+ TD_OS: linux
36
+
37
+ - name: Upload test results to Sentry Prevent
38
+ if: ${{ !cancelled() }}
39
+ uses: getsentry/prevent-action@v0
40
+
41
+ - name: Publish Test Results
42
+ uses: EnricoMi/publish-unit-test-result-action@v2
43
+ if: always()
44
+ with:
45
+ files: test-report.junit.xml
46
+ comment_mode: always
47
+ check_name: Test Results (Linux)
48
+
49
+ test-windows:
50
+ runs-on: ubuntu-latest
51
+ if: contains(github.event.pull_request.labels.*.name, 'test-windows')
52
+
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+
56
+ - name: Setup Node.js
57
+ uses: actions/setup-node@v4
58
+ with:
59
+ node-version: '20'
60
+ cache: 'npm'
61
+
62
+ - name: Install dependencies
63
+ run: npm ci
64
+
65
+ - name: Run Windows tests
66
+ run: npx vitest run test/testdriver/*.test.mjs
67
+ env:
68
+ TD_API_KEY: ${{ secrets.TD_API_KEY }}
69
+ TD_OS: windows
70
+
71
+ - name: Upload test results to Sentry Prevent
72
+ if: ${{ !cancelled() }}
73
+ uses: getsentry/prevent-action@v0
74
+
75
+ - name: Publish Test Results
76
+ uses: EnricoMi/publish-unit-test-result-action@v2
77
+ if: always()
78
+ with:
79
+ files: test-report.junit.xml
80
+ comment_mode: always
81
+ check_name: Test Results (Windows)
@@ -0,0 +1,44 @@
1
+ name: Publish Beta
2
+ permissions:
3
+ contents: write
4
+ on:
5
+ push:
6
+ branches: [ main ]
7
+
8
+ jobs:
9
+ publish-beta:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ with:
15
+ fetch-depth: 0
16
+ token: ${{ secrets.GITHUB_TOKEN }}
17
+
18
+ - name: Setup Node.js
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: '20'
22
+ registry-url: 'https://registry.npmjs.org/'
23
+
24
+ - name: Configure Git
25
+ run: |
26
+ git config user.name "github-actions[bot]"
27
+ git config user.email "github-actions[bot]@users.noreply.github.com"
28
+
29
+ - name: Install dependencies
30
+ run: npm ci
31
+
32
+ - name: Bump version (prerelease beta)
33
+ run: npm version prerelease --preid=beta --no-git-tag-version
34
+
35
+ - name: Commit and push version bump
36
+ run: |
37
+ git add package.json package-lock.json
38
+ git commit -m "chore: bump beta version to $(node -p "require('./package.json').version")"
39
+ git push
40
+
41
+ - name: Publish to npm under beta tag
42
+ run: npm publish --tag beta
43
+ env:
44
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -0,0 +1,145 @@
1
+ name: Test Init Command
2
+
3
+ on:
4
+ push:
5
+ branches: [ main, develop ]
6
+ pull_request:
7
+ branches: [ main, develop ]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ test-init:
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout CLI repository
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup Node.js
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: '20'
22
+ cache: 'npm'
23
+
24
+ - name: Install CLI dependencies
25
+ run: npm ci
26
+
27
+ - name: Create test directory
28
+ run: |
29
+ mkdir -p /tmp/test-init-project
30
+ cd /tmp/test-init-project
31
+
32
+ - name: Run init command (skip prompts)
33
+ working-directory: /tmp/test-init-project
34
+ run: |
35
+ # Create .env with API key first to skip the prompt
36
+ echo "TD_API_KEY=${{ secrets.TD_API_KEY }}" > .env
37
+
38
+ # Run init command using the CLI from the repo
39
+ node ${{ github.workspace }}/bin/testdriverai.js init
40
+ env:
41
+ TD_API_KEY: ${{ secrets.TD_API_KEY }}
42
+
43
+ - name: Verify project structure
44
+ working-directory: /tmp/test-init-project
45
+ run: |
46
+ echo "Checking generated files..."
47
+
48
+ # Check for package.json
49
+ if [ ! -f "package.json" ]; then
50
+ echo "❌ package.json not found"
51
+ exit 1
52
+ fi
53
+ echo "✓ package.json exists"
54
+
55
+ # Check for vitest config
56
+ if [ ! -f "vitest.config.js" ]; then
57
+ echo "❌ vitest.config.js not found"
58
+ exit 1
59
+ fi
60
+ echo "✓ vitest.config.js exists"
61
+
62
+ # Check for test file
63
+ if [ ! -f "tests/example.test.js" ]; then
64
+ echo "❌ tests/example.test.js not found"
65
+ exit 1
66
+ fi
67
+ echo "✓ tests/example.test.js exists"
68
+
69
+ # Check for .env file
70
+ if [ ! -f ".env" ]; then
71
+ echo "❌ .env not found"
72
+ exit 1
73
+ fi
74
+ echo "✓ .env exists"
75
+
76
+ # Check for .gitignore
77
+ if [ ! -f ".gitignore" ]; then
78
+ echo "❌ .gitignore not found"
79
+ exit 1
80
+ fi
81
+ echo "✓ .gitignore exists"
82
+
83
+ # Check for GitHub workflow
84
+ if [ ! -f ".github/workflows/testdriver.yml" ]; then
85
+ echo "❌ .github/workflows/testdriver.yml not found"
86
+ exit 1
87
+ fi
88
+ echo "✓ .github/workflows/testdriver.yml exists"
89
+
90
+ - name: Verify vitest config contents
91
+ working-directory: /tmp/test-init-project
92
+ run: |
93
+ echo "Checking vitest.config.js contents..."
94
+
95
+ # Check for TestDriver reporter
96
+ if ! grep -q "TestDriver()" vitest.config.js; then
97
+ echo "❌ TestDriver reporter not found in vitest.config.js"
98
+ cat vitest.config.js
99
+ exit 1
100
+ fi
101
+ echo "✓ TestDriver reporter is configured"
102
+
103
+ # Check for setupFiles
104
+ if ! grep -q "setupFiles.*testdriverai/vitest/setup" vitest.config.js; then
105
+ echo "❌ setupFiles not configured correctly"
106
+ cat vitest.config.js
107
+ exit 1
108
+ fi
109
+ echo "✓ setupFiles is configured"
110
+
111
+ - name: Verify test file contents
112
+ working-directory: /tmp/test-init-project
113
+ run: |
114
+ echo "Checking test file contents..."
115
+
116
+ # Check for .provision usage
117
+ if ! grep -q "\.provision\.chrome" tests/example.test.js; then
118
+ echo "❌ Test does not use .provision.chrome"
119
+ cat tests/example.test.js
120
+ exit 1
121
+ fi
122
+ echo "✓ Test uses .provision.chrome"
123
+
124
+ # Check for TestDriver import
125
+ if ! grep -q "from 'testdriverai/vitest/hooks'" tests/example.test.js; then
126
+ echo "❌ Test does not import from testdriverai/vitest/hooks"
127
+ cat tests/example.test.js
128
+ exit 1
129
+ fi
130
+ echo "✓ Test imports TestDriver from vitest/hooks"
131
+
132
+ - name: Run the generated test
133
+ working-directory: /tmp/test-init-project
134
+ run: npm test
135
+ env:
136
+ TD_API_KEY: ${{ secrets.TD_API_KEY }}
137
+
138
+ - name: Upload test results
139
+ if: always()
140
+ uses: actions/upload-artifact@v4
141
+ with:
142
+ name: test-init-results
143
+ path: /tmp/test-init-project/test-results/
144
+ retention-days: 7
145
+ if-no-files-found: warn
package/agent/index.js CHANGED
@@ -183,7 +183,8 @@ class TestDriverAgent extends EventEmitter2 {
183
183
  // single function to handle all program exits
184
184
  // allows us to save the current state, run lifecycle hooks, and track analytics
185
185
  async exit(failed = true, shouldSave = false, shouldRunPostrun = false) {
186
- this.emitter.emit(events.log.narration, theme.dim("exiting..."), true);
186
+ const { formatter } = require("../sdk-log-formatter.js");
187
+ this.emitter.emit(events.log.narration, formatter.getPrefix("disconnect") + " " + theme.yellow.bold("Exiting") + theme.dim("..."), true);
187
188
 
188
189
  // Clean up redraw interval
189
190
  if (this.redraw && this.redraw.cleanup) {
@@ -1838,6 +1839,9 @@ ${regression}
1838
1839
  }
1839
1840
  }
1840
1841
 
1842
+ // Create session first so session ID is available for Sentry tracing in WebSocket connection
1843
+ await this.newSession();
1844
+
1841
1845
  // order is important!
1842
1846
  await this.connectToSandboxService();
1843
1847
 
@@ -1856,7 +1860,6 @@ ${regression}
1856
1860
 
1857
1861
  this.instance = instance.instance;
1858
1862
  await this.renderSandbox(this.instance, headless);
1859
- await this.newSession();
1860
1863
  await this.runLifecycle("provision");
1861
1864
 
1862
1865
  return;
@@ -1877,7 +1880,6 @@ ${regression}
1877
1880
  this.instance = instance;
1878
1881
 
1879
1882
  await this.renderSandbox(instance, headless);
1880
- await this.newSession();
1881
1883
  return;
1882
1884
  } catch (error) {
1883
1885
  // If connection fails, fall through to creating a new sandbox
@@ -1909,7 +1911,6 @@ ${regression}
1909
1911
  this.instance = instance;
1910
1912
 
1911
1913
  await this.renderSandbox(instance, headless);
1912
- await this.newSession();
1913
1914
  return;
1914
1915
  } catch (error) {
1915
1916
  // If connection fails, fall through to creating a new sandbox
@@ -1923,9 +1924,10 @@ ${regression}
1923
1924
 
1924
1925
  // Create new sandbox (either because createNew is true, or no existing sandbox to connect to)
1925
1926
  if (!this.instance) {
1927
+ const { formatter } = require("../sdk-log-formatter.js");
1926
1928
  this.emitter.emit(
1927
1929
  events.log.narration,
1928
- theme.dim(`creating new sandbox...`),
1930
+ formatter.getPrefix("connect") + " " + theme.green.bold("Creating") + " " + theme.cyan(`new sandbox...`),
1929
1931
  );
1930
1932
  // We don't have resiliency/retries baked in, so let's at least give it 1 attempt
1931
1933
  // to see if that fixes the issue.
@@ -1949,10 +1951,7 @@ ${regression}
1949
1951
  );
1950
1952
  this.instance = instance;
1951
1953
  await this.renderSandbox(instance, headless);
1952
- await this.newSession();
1953
1954
  await this.runLifecycle("provision");
1954
-
1955
- console.log("provision run");
1956
1955
  }
1957
1956
  }
1958
1957
 
@@ -2073,7 +2072,6 @@ ${regression}
2073
2072
  }
2074
2073
 
2075
2074
  async renderSandbox(instance, headless = false) {
2076
- console.log("renderSandbox", instance);
2077
2075
 
2078
2076
  if (!headless) {
2079
2077
  let url;
@@ -2126,7 +2124,8 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2126
2124
  );
2127
2125
  }
2128
2126
 
2129
- this.emitter.emit(events.log.narration, theme.dim(`authenticating...`));
2127
+ const { formatter } = require("../sdk-log-formatter.js");
2128
+ this.emitter.emit(events.log.narration, formatter.getPrefix("connect") + " " + theme.green.bold("Authenticating") + theme.dim("..."));
2130
2129
  let ableToAuth = await this.sandbox.auth(this.config.TD_API_KEY);
2131
2130
 
2132
2131
  if (!ableToAuth) {
@@ -2139,7 +2138,8 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2139
2138
  }
2140
2139
 
2141
2140
  async connectToSandboxDirect(sandboxId, persist = false) {
2142
- this.emitter.emit(events.log.narration, theme.dim(`connecting...`));
2141
+ const { formatter } = require("../sdk-log-formatter.js");
2142
+ this.emitter.emit(events.log.narration, formatter.getPrefix("connect") + " " + theme.green.bold("Connecting") + " " + theme.cyan(`to sandbox...`));
2143
2143
  let reply = await this.sandbox.connect(sandboxId, persist);
2144
2144
 
2145
2145
  // reply includes { success, url, sandbox: {...} }
@@ -2170,7 +2170,7 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2170
2170
  sandboxConfig.instanceType = this.sandboxInstance;
2171
2171
  }
2172
2172
 
2173
- let instance = await this.sandbox.send(sandboxConfig);
2173
+ let instance = await this.sandbox.send(sandboxConfig, 60000 * 8);
2174
2174
 
2175
2175
  // Save the sandbox ID for reconnection with the correct OS type
2176
2176
  if (instance.sandbox && instance.sandbox.sandboxId) {
@@ -2184,10 +2184,13 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2184
2184
 
2185
2185
  async newSession() {
2186
2186
  // should be start of new session
2187
+ // If sandbox is connected, get system info; otherwise pass empty objects
2188
+ const isSandboxConnected = this.sandbox.apiSocketConnected;
2189
+
2187
2190
  const sessionRes = await this.sdk.req("session/start", {
2188
- systemInformationOsInfo: await this.system.getSystemInformationOsInfo(),
2189
- mousePosition: await this.system.getMousePosition(),
2190
- activeWindow: await this.system.activeWin(),
2191
+ systemInformationOsInfo: isSandboxConnected ? await this.system.getSystemInformationOsInfo() : {},
2192
+ mousePosition: isSandboxConnected ? await this.system.getMousePosition() : {},
2193
+ activeWindow: isSandboxConnected ? await this.system.activeWin() : {},
2191
2194
  });
2192
2195
 
2193
2196
  if (!sessionRes) {
@@ -2265,9 +2268,6 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2265
2268
  // If sourceMapper doesn't have a current file, use thisFile which should be the file being run
2266
2269
  let currentFilePath = this.sourceMapper.currentFilePath || this.thisFile;
2267
2270
 
2268
- this.emitter.emit(events.log.log, ``);
2269
- this.emitter.emit(events.log.log, "Running lifecycle: " + lifecycleName);
2270
-
2271
2271
  // If we still don't have a currentFilePath, fall back to the default testdriver directory
2272
2272
  if (!currentFilePath) {
2273
2273
  currentFilePath = path.join(
@@ -2275,7 +2275,6 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2275
2275
  "testdriver",
2276
2276
  "testdriver.yaml",
2277
2277
  );
2278
- console.log("No currentFilePath found, using fallback:", currentFilePath);
2279
2278
  }
2280
2279
 
2281
2280
  // Ensure we have an absolute path
@@ -178,9 +178,9 @@ commands:
178
178
  emitter.emit(events.log.log, generator.jsonToManual(object));
179
179
  response = await commands["focus-application"](object.name);
180
180
  break;
181
- case "remember": {
181
+ case "extract": {
182
182
  emitter.emit(events.log.log, generator.jsonToManual(object));
183
- let value = await commands["remember"](object.description);
183
+ let value = await commands["extract"](object.description);
184
184
  emitter.emit(events.log.log, value);
185
185
  outputsInstance.set(object.output, value);
186
186
  break;