testdriverai 7.8.0-test.7 → 7.8.0-test.71

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 (98) hide show
  1. package/agent/index.js +18 -5
  2. package/agent/lib/commands.js +3 -2
  3. package/agent/lib/http.js +162 -0
  4. package/agent/lib/logger.js +15 -0
  5. package/agent/lib/sandbox.js +554 -209
  6. package/agent/lib/sdk.js +5 -22
  7. package/agent/lib/system.js +25 -65
  8. package/ai/skills/testdriver-cache/SKILL.md +221 -0
  9. package/ai/skills/testdriver-errors/SKILL.md +246 -0
  10. package/ai/skills/testdriver-events/SKILL.md +356 -0
  11. package/ai/skills/testdriver-find/SKILL.md +14 -20
  12. package/ai/skills/testdriver-mcp/SKILL.md +7 -0
  13. package/ai/skills/testdriver-provision/SKILL.md +331 -0
  14. package/ai/skills/testdriver-redraw/SKILL.md +214 -0
  15. package/ai/skills/testdriver-running-tests/SKILL.md +1 -1
  16. package/ai/skills/testdriver-screenshots/SKILL.md +184 -0
  17. package/docs/_data/examples-manifest.json +46 -46
  18. package/docs/_scripts/extract-example-urls.js +67 -72
  19. package/docs/changelog.mdx +148 -8
  20. package/docs/docs.json +46 -38
  21. package/docs/images/content/vscode/v7-chat.png +0 -0
  22. package/docs/images/content/vscode/v7-choose-agent.png +0 -0
  23. package/docs/images/content/vscode/v7-full.png +0 -0
  24. package/docs/images/content/vscode/v7-onboarding.png +0 -0
  25. package/docs/v7/cache.mdx +223 -0
  26. package/docs/v7/copilot/auto-healing.mdx +265 -0
  27. package/docs/v7/copilot/creating-tests.mdx +156 -0
  28. package/docs/v7/copilot/github.mdx +143 -0
  29. package/docs/v7/copilot/running-tests.mdx +149 -0
  30. package/docs/v7/copilot/setup.mdx +143 -0
  31. package/docs/v7/enterprise.mdx +3 -110
  32. package/docs/v7/errors.mdx +248 -0
  33. package/docs/v7/events.mdx +358 -0
  34. package/docs/v7/examples/ai.mdx +1 -1
  35. package/docs/v7/examples/assert.mdx +1 -1
  36. package/docs/v7/examples/captcha-api.mdx +1 -1
  37. package/docs/v7/examples/chrome-extension.mdx +1 -1
  38. package/docs/v7/examples/drag-and-drop.mdx +1 -1
  39. package/docs/v7/examples/element-not-found.mdx +1 -1
  40. package/docs/v7/examples/exec-output.mdx +85 -0
  41. package/docs/v7/examples/exec-pwsh.mdx +83 -0
  42. package/docs/v7/examples/focus-window.mdx +62 -0
  43. package/docs/v7/examples/hover-image.mdx +1 -1
  44. package/docs/v7/examples/hover-text.mdx +1 -1
  45. package/docs/v7/examples/installer.mdx +1 -1
  46. package/docs/v7/examples/launch-vscode-linux.mdx +1 -1
  47. package/docs/v7/examples/match-image.mdx +1 -1
  48. package/docs/v7/examples/press-keys.mdx +1 -1
  49. package/docs/v7/examples/scroll-keyboard.mdx +1 -1
  50. package/docs/v7/examples/scroll-until-image.mdx +1 -1
  51. package/docs/v7/examples/scroll-until-text.mdx +1 -1
  52. package/docs/v7/examples/scroll.mdx +1 -1
  53. package/docs/v7/examples/type.mdx +1 -1
  54. package/docs/v7/examples/windows-installer.mdx +1 -1
  55. package/docs/v7/find.mdx +14 -20
  56. package/docs/v7/{cloud.mdx → hosted.mdx} +43 -5
  57. package/docs/v7/mcp.mdx +9 -0
  58. package/docs/v7/provision.mdx +333 -0
  59. package/docs/v7/quickstart.mdx +30 -2
  60. package/docs/v7/redraw.mdx +216 -0
  61. package/docs/v7/running-tests.mdx +1 -1
  62. package/docs/v7/screenshots.mdx +186 -0
  63. package/docs/v7/self-hosted.mdx +127 -44
  64. package/docs/v7/test-results-json.mdx +258 -0
  65. package/examples/scroll-keyboard.test.mjs +1 -1
  66. package/examples/scroll.test.mjs +1 -12
  67. package/interfaces/logger.js +0 -12
  68. package/interfaces/vitest-plugin.mjs +170 -51
  69. package/lib/core/Dashcam.js +30 -23
  70. package/lib/environments.json +22 -0
  71. package/lib/github-comment.mjs +58 -40
  72. package/lib/init-project.js +5 -67
  73. package/lib/resolve-channel.js +42 -12
  74. package/lib/sentry.js +47 -23
  75. package/lib/vitest/hooks.mjs +63 -3
  76. package/{examples → manual}/drag-and-drop.test.mjs +1 -1
  77. package/manual/exec-stream-logs.test.mjs +25 -0
  78. package/mcp-server/dist/server.mjs +28 -8
  79. package/mcp-server/src/server.ts +31 -8
  80. package/package.json +4 -3
  81. package/sdk.d.ts +4 -0
  82. package/sdk.js +45 -15
  83. package/setup/aws/install-dev-runner.sh +79 -0
  84. package/setup/aws/spawn-runner.sh +165 -0
  85. package/test-sentry-span.js +35 -0
  86. package/vitest.config.mjs +22 -34
  87. package/vitest.runner.config.mjs +33 -0
  88. /package/{examples → manual}/flake-diffthreshold-001.test.mjs +0 -0
  89. /package/{examples → manual}/flake-diffthreshold-01.test.mjs +0 -0
  90. /package/{examples → manual}/flake-diffthreshold-05.test.mjs +0 -0
  91. /package/{examples → manual}/flake-noredraw-cache.test.mjs +0 -0
  92. /package/{examples → manual}/flake-noredraw-nocache.test.mjs +0 -0
  93. /package/{examples → manual}/flake-redraw-cache.test.mjs +0 -0
  94. /package/{examples → manual}/flake-redraw-nocache.test.mjs +0 -0
  95. /package/{examples → manual}/flake-rocket-match.test.mjs +0 -0
  96. /package/{examples → manual}/flake-shared.mjs +0 -0
  97. /package/{examples → manual}/no-provision.test.mjs +0 -0
  98. /package/{examples → manual}/scroll-until-text.test.mjs +0 -0
package/agent/index.js CHANGED
@@ -70,6 +70,7 @@ class TestDriverAgent extends EventEmitter2 {
70
70
  this.sandboxId = flags["sandbox-id"] || null;
71
71
  this.sandboxAmi = flags["sandbox-ami"] || null;
72
72
  this.sandboxInstance = flags["sandbox-instance"] || null;
73
+ this.e2bTemplateId = flags["e2b-template-id"] || null;
73
74
  this.sandboxOs = flags.os || "linux";
74
75
  this.ip = flags.ip || null;
75
76
  this.workingDir = flags.workingDir || process.cwd();
@@ -211,6 +212,14 @@ class TestDriverAgent extends EventEmitter2 {
211
212
  }
212
213
  }
213
214
 
215
+ // End the Sentry root session span so the trace is finalized
216
+ try {
217
+ const sentry = require("../lib/sentry");
218
+ sentry.clearSessionTraceContext(this.session && this.session.get());
219
+ } catch (e) {
220
+ // Sentry module may not be available, ignore
221
+ }
222
+
214
223
  shouldRunPostrun =
215
224
  !this.hasRunPostrun &&
216
225
  (shouldRunPostrun || this.cliArgs?.command == "run");
@@ -1941,18 +1950,19 @@ ${regression}
1941
1950
  // Allow explicit override via env (e.g. VITE_DOMAIN from .env)
1942
1951
  if (process.env.VITE_DOMAIN) return process.env.VITE_DOMAIN;
1943
1952
 
1953
+ const environments = require("../lib/environments.json");
1944
1954
  const mapping = {
1945
- "https://api.testdriver.ai": "https://console.testdriver.ai",
1946
- "https://v6.testdriver.ai": "https://console.testdriver.ai",
1947
- "https://api-canary.testdriver.ai": "https://console-canary.testdriver.ai",
1948
- "https://api-test.testdriver.ai": "https://console-test.testdriver.ai",
1955
+ "https://v6.testdriver.ai": environments.stable.consoleUrl,
1949
1956
  };
1957
+ for (const env of Object.values(environments)) {
1958
+ mapping[env.apiRoot] = env.consoleUrl;
1959
+ }
1950
1960
  if (mapping[apiRoot]) return mapping[apiRoot];
1951
1961
  // Local dev: API on localhost:1337 -> Web on localhost:3001
1952
1962
  if (apiRoot.includes("localhost:1337") || apiRoot.includes("127.0.0.1:1337")) {
1953
1963
  return "http://localhost:3001";
1954
1964
  }
1955
- return "https://console.testdriver.ai";
1965
+ return environments.stable.consoleUrl;
1956
1966
  }
1957
1967
 
1958
1968
  // Write session file for IDE preview (VSCode extension watches for these)
@@ -2187,6 +2197,9 @@ Please check your network connection, TD_API_KEY, or the service status.`,
2187
2197
  if (this.sandboxInstance) {
2188
2198
  sandboxConfig.instanceType = this.sandboxInstance;
2189
2199
  }
2200
+ if (this.e2bTemplateId) {
2201
+ sandboxConfig.e2bTemplateId = this.e2bTemplateId;
2202
+ }
2190
2203
  // Add keepAlive TTL if specified
2191
2204
  if (this.keepAlive !== undefined && this.keepAlive !== null) {
2192
2205
  sandboxConfig.keepAlive = this.keepAlive;
@@ -580,11 +580,12 @@ const createCommands = (
580
580
  } else if (action === "mouseDown") {
581
581
  await sandbox.send({ type: "mousePress", button: "left", x, y, ...elementData });
582
582
  } else if (action === "mouseUp") {
583
+ // Move first to create drag motion, then release
584
+ // (pyautogui.mouseUp with x/y teleports instead of dragging)
585
+ await sandbox.send({ type: "moveMouse", x, y, ...elementData });
583
586
  await sandbox.send({
584
587
  type: "mouseRelease",
585
588
  button: "left",
586
- x,
587
- y,
588
589
  ...elementData
589
590
  });
590
591
  }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Shared HTTP client for the TestDriver SDK.
3
+ *
4
+ * All SDK HTTP traffic should go through these helpers so that
5
+ * User-Agent, timeouts, Sentry tracing headers, and response
6
+ * parsing are handled in one place.
7
+ *
8
+ * Uses axios under the hood — the same library the rest of the SDK
9
+ * already depends on.
10
+ */
11
+
12
+ const axios = require("axios");
13
+ const crypto = require("crypto");
14
+ const { version } = require("../../package.json");
15
+
16
+ const USER_AGENT = `TestDriverSDK/${version} (Node.js ${process.version})`;
17
+
18
+ /**
19
+ * Generate Sentry distributed-tracing headers.
20
+ *
21
+ * When Sentry is initialized and a span is active, uses Sentry.getTraceData()
22
+ * so the headers reference the real active span (proper parent-child linkage).
23
+ * Falls back to MD5(sessionId)-based headers when Sentry is not available or
24
+ * has no active span (e.g. TD_TELEMETRY=false).
25
+ *
26
+ * @param {string} sessionId
27
+ * @returns {object} Headers object (empty if no sessionId and no active span)
28
+ */
29
+ function getSentryTraceHeaders(sessionId) {
30
+ // Prefer Sentry's own trace propagation when available
31
+ try {
32
+ const Sentry = require("@sentry/node");
33
+ if (typeof Sentry.getTraceData === "function") {
34
+ const traceData = Sentry.getTraceData();
35
+ if (traceData && traceData["sentry-trace"]) {
36
+ return traceData;
37
+ }
38
+ }
39
+ } catch (e) {
40
+ // Sentry not available — fall through to manual derivation
41
+ }
42
+
43
+ // Fallback: derive deterministic trace from session ID
44
+ if (!sessionId) return {};
45
+ const traceId = crypto.createHash("md5").update(sessionId).digest("hex");
46
+ const spanId = crypto.randomBytes(8).toString("hex");
47
+ return {
48
+ "sentry-trace": traceId + "-" + spanId + "-1",
49
+ baggage:
50
+ "sentry-trace_id=" +
51
+ traceId +
52
+ ",sentry-sample_rate=1.0,sentry-sampled=true",
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Build common request headers.
58
+ * @param {object} [extra] - Additional headers to merge
59
+ * @returns {object}
60
+ */
61
+ function baseHeaders(extra) {
62
+ return {
63
+ "User-Agent": USER_AGENT,
64
+ ...extra,
65
+ };
66
+ }
67
+
68
+ /**
69
+ * POST JSON to `url` and return the parsed response body.
70
+ *
71
+ * @param {string} url - Absolute URL
72
+ * @param {object} [data] - JSON body
73
+ * @param {object} [opts] - Extra axios config (headers, timeout, …)
74
+ * @returns {Promise<object>} Parsed response data
75
+ */
76
+ async function httpPost(url, data, opts = {}) {
77
+ const { headers: extraHeaders, ...rest } = opts;
78
+ const res = await axios({
79
+ method: "post",
80
+ url,
81
+ headers: baseHeaders({
82
+ "Content-Type": "application/json",
83
+ ...extraHeaders,
84
+ }),
85
+ data,
86
+ timeout: opts.timeout || 30000,
87
+ ...rest,
88
+ });
89
+ return res.data;
90
+ }
91
+
92
+ /**
93
+ * GET `url` and return the parsed response body.
94
+ *
95
+ * @param {string} url - Absolute URL
96
+ * @param {object} [opts] - Extra axios config
97
+ * @returns {Promise<object>} Parsed response data
98
+ */
99
+ async function httpGet(url, opts = {}) {
100
+ const { headers: extraHeaders, ...rest } = opts;
101
+ const res = await axios({
102
+ method: "get",
103
+ url,
104
+ headers: baseHeaders(extraHeaders),
105
+ timeout: opts.timeout || 30000,
106
+ ...rest,
107
+ });
108
+ return res.data;
109
+ }
110
+
111
+ /**
112
+ * PUT data to `url` (e.g. S3 presigned upload).
113
+ *
114
+ * @param {string} url - Absolute URL
115
+ * @param {Buffer|string} data - Request body
116
+ * @param {object} [opts] - Extra axios config (headers, timeout, …)
117
+ * @returns {Promise<object>} Parsed response data (or empty object for 2xx with no body)
118
+ */
119
+ async function httpPut(url, data, opts = {}) {
120
+ const { headers: extraHeaders, ...rest } = opts;
121
+ const res = await axios({
122
+ method: "put",
123
+ url,
124
+ headers: baseHeaders(extraHeaders),
125
+ data,
126
+ timeout: opts.timeout || 30000,
127
+ maxBodyLength: Infinity,
128
+ maxContentLength: Infinity,
129
+ ...rest,
130
+ });
131
+ return res.data;
132
+ }
133
+
134
+ /**
135
+ * Download a URL as a Buffer (e.g. screenshot from S3).
136
+ *
137
+ * @param {string} url - Absolute URL
138
+ * @param {object} [opts] - Extra axios config
139
+ * @returns {Promise<Buffer>}
140
+ */
141
+ async function downloadBuffer(url, opts = {}) {
142
+ const { headers: extraHeaders, ...rest } = opts;
143
+ const res = await axios({
144
+ method: "get",
145
+ url,
146
+ headers: baseHeaders(extraHeaders),
147
+ responseType: "arraybuffer",
148
+ timeout: opts.timeout || 60000,
149
+ ...rest,
150
+ });
151
+ return Buffer.from(res.data);
152
+ }
153
+
154
+ module.exports = {
155
+ httpPost,
156
+ httpGet,
157
+ httpPut,
158
+ downloadBuffer,
159
+ getSentryTraceHeaders,
160
+ USER_AGENT,
161
+ baseHeaders,
162
+ };
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  const useStderr = process.env.TD_STDIO === 'stderr';
10
+ const isDebug = process.env.TD_DEBUG === 'true' || process.env.VERBOSE === 'true';
10
11
 
11
12
  /**
12
13
  * Log a message - uses stdout by default, stderr if TD_STDIO=stderr
@@ -40,6 +41,19 @@ function warn(...args) {
40
41
  }
41
42
  }
42
43
 
44
+ /**
45
+ * Log a debug message - only outputs when DEBUG=true
46
+ * @param {...any} args - Arguments to log
47
+ */
48
+ function debug(...args) {
49
+ if (!isDebug) return;
50
+ if (useStderr) {
51
+ console.error(...args);
52
+ } else {
53
+ console.log(...args);
54
+ }
55
+ }
56
+
43
57
  /**
44
58
  * Check if logger is configured to use stderr
45
59
  * @returns {boolean}
@@ -50,6 +64,7 @@ function isStderrMode() {
50
64
 
51
65
  module.exports = {
52
66
  log,
67
+ debug,
53
68
  error,
54
69
  warn,
55
70
  isStderrMode,