testdriverai 7.8.0-test.6 → 7.8.0-test.61
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/CHANGELOG.md +6 -0
- package/agent/index.js +10 -5
- package/agent/lib/commands.js +3 -2
- package/agent/lib/http.js +144 -0
- package/agent/lib/logger.js +15 -0
- package/agent/lib/sandbox.js +530 -207
- package/agent/lib/sdk.js +4 -2
- package/agent/lib/system.js +25 -65
- package/ai/skills/testdriver-cache/SKILL.md +221 -0
- package/ai/skills/testdriver-errors/SKILL.md +246 -0
- package/ai/skills/testdriver-events/SKILL.md +356 -0
- package/ai/skills/testdriver-find/SKILL.md +14 -20
- package/ai/skills/testdriver-mcp/SKILL.md +7 -0
- package/ai/skills/testdriver-provision/SKILL.md +331 -0
- package/ai/skills/testdriver-redraw/SKILL.md +214 -0
- package/ai/skills/testdriver-running-tests/SKILL.md +1 -1
- package/ai/skills/testdriver-screenshots/SKILL.md +184 -0
- package/docs/_data/examples-manifest.json +46 -46
- package/docs/_scripts/extract-example-urls.js +67 -72
- package/docs/changelog.mdx +151 -5
- package/docs/docs.json +46 -38
- package/docs/images/content/vscode/v7-chat.png +0 -0
- package/docs/images/content/vscode/v7-choose-agent.png +0 -0
- package/docs/images/content/vscode/v7-full.png +0 -0
- package/docs/images/content/vscode/v7-onboarding.png +0 -0
- package/docs/v7/cache.mdx +223 -0
- package/docs/v7/copilot/auto-healing.mdx +265 -0
- package/docs/v7/copilot/creating-tests.mdx +156 -0
- package/docs/v7/copilot/github.mdx +143 -0
- package/docs/v7/copilot/running-tests.mdx +149 -0
- package/docs/v7/copilot/setup.mdx +143 -0
- package/docs/v7/enterprise.mdx +3 -110
- package/docs/v7/errors.mdx +248 -0
- package/docs/v7/events.mdx +358 -0
- package/docs/v7/examples/ai.mdx +1 -1
- package/docs/v7/examples/assert.mdx +1 -1
- package/docs/v7/examples/captcha-api.mdx +1 -1
- package/docs/v7/examples/chrome-extension.mdx +1 -1
- package/docs/v7/examples/drag-and-drop.mdx +1 -1
- package/docs/v7/examples/element-not-found.mdx +1 -1
- package/docs/v7/examples/exec-output.mdx +85 -0
- package/docs/v7/examples/exec-pwsh.mdx +83 -0
- package/docs/v7/examples/focus-window.mdx +62 -0
- package/docs/v7/examples/hover-image.mdx +1 -1
- package/docs/v7/examples/hover-text.mdx +1 -1
- package/docs/v7/examples/installer.mdx +1 -1
- package/docs/v7/examples/launch-vscode-linux.mdx +1 -1
- package/docs/v7/examples/match-image.mdx +1 -1
- package/docs/v7/examples/press-keys.mdx +1 -1
- package/docs/v7/examples/scroll-keyboard.mdx +1 -1
- package/docs/v7/examples/scroll-until-image.mdx +1 -1
- package/docs/v7/examples/scroll-until-text.mdx +1 -1
- package/docs/v7/examples/scroll.mdx +1 -1
- package/docs/v7/examples/type.mdx +1 -1
- package/docs/v7/examples/windows-installer.mdx +1 -1
- package/docs/v7/find.mdx +14 -20
- package/docs/v7/{cloud.mdx → hosted.mdx} +43 -5
- package/docs/v7/mcp.mdx +9 -0
- package/docs/v7/provision.mdx +333 -0
- package/docs/v7/quickstart.mdx +30 -2
- package/docs/v7/redraw.mdx +216 -0
- package/docs/v7/running-tests.mdx +1 -1
- package/docs/v7/screenshots.mdx +186 -0
- package/docs/v7/self-hosted.mdx +127 -44
- package/docs/v7/test-results-json.mdx +258 -0
- package/examples/scroll-keyboard.test.mjs +1 -1
- package/interfaces/logger.js +0 -12
- package/interfaces/vitest-plugin.mjs +169 -50
- package/lib/core/Dashcam.js +30 -23
- package/lib/environments.json +18 -0
- package/lib/github-comment.mjs +58 -40
- package/lib/resolve-channel.js +4 -3
- package/lib/sentry.js +5 -0
- package/lib/vitest/hooks.mjs +63 -3
- package/{examples → manual}/drag-and-drop.test.mjs +1 -1
- package/mcp-server/dist/server.mjs +4 -0
- package/mcp-server/src/server.ts +5 -0
- package/package.json +3 -3
- package/sdk.d.ts +4 -0
- package/sdk.js +44 -14
- package/setup/aws/install-dev-runner.sh +79 -0
- package/setup/aws/spawn-runner.sh +165 -0
- package/vitest.config.mjs +22 -34
- package/vitest.runner.config.mjs +33 -0
- /package/{examples → manual}/flake-diffthreshold-001.test.mjs +0 -0
- /package/{examples → manual}/flake-diffthreshold-01.test.mjs +0 -0
- /package/{examples → manual}/flake-diffthreshold-05.test.mjs +0 -0
- /package/{examples → manual}/flake-noredraw-cache.test.mjs +0 -0
- /package/{examples → manual}/flake-noredraw-nocache.test.mjs +0 -0
- /package/{examples → manual}/flake-redraw-cache.test.mjs +0 -0
- /package/{examples → manual}/flake-redraw-nocache.test.mjs +0 -0
- /package/{examples → manual}/flake-rocket-match.test.mjs +0 -0
- /package/{examples → manual}/flake-shared.mjs +0 -0
- /package/{examples → manual}/no-provision.test.mjs +0 -0
- /package/{examples → manual}/scroll-until-text.test.mjs +0 -0
package/CHANGELOG.md
CHANGED
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();
|
|
@@ -1941,18 +1942,19 @@ ${regression}
|
|
|
1941
1942
|
// Allow explicit override via env (e.g. VITE_DOMAIN from .env)
|
|
1942
1943
|
if (process.env.VITE_DOMAIN) return process.env.VITE_DOMAIN;
|
|
1943
1944
|
|
|
1945
|
+
const environments = require("../lib/environments.json");
|
|
1944
1946
|
const mapping = {
|
|
1945
|
-
"https://
|
|
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",
|
|
1947
|
+
"https://v6.testdriver.ai": environments.stable.consoleUrl,
|
|
1949
1948
|
};
|
|
1949
|
+
for (const env of Object.values(environments)) {
|
|
1950
|
+
mapping[env.apiRoot] = env.consoleUrl;
|
|
1951
|
+
}
|
|
1950
1952
|
if (mapping[apiRoot]) return mapping[apiRoot];
|
|
1951
1953
|
// Local dev: API on localhost:1337 -> Web on localhost:3001
|
|
1952
1954
|
if (apiRoot.includes("localhost:1337") || apiRoot.includes("127.0.0.1:1337")) {
|
|
1953
1955
|
return "http://localhost:3001";
|
|
1954
1956
|
}
|
|
1955
|
-
return
|
|
1957
|
+
return environments.stable.consoleUrl;
|
|
1956
1958
|
}
|
|
1957
1959
|
|
|
1958
1960
|
// Write session file for IDE preview (VSCode extension watches for these)
|
|
@@ -2187,6 +2189,9 @@ Please check your network connection, TD_API_KEY, or the service status.`,
|
|
|
2187
2189
|
if (this.sandboxInstance) {
|
|
2188
2190
|
sandboxConfig.instanceType = this.sandboxInstance;
|
|
2189
2191
|
}
|
|
2192
|
+
if (this.e2bTemplateId) {
|
|
2193
|
+
sandboxConfig.e2bTemplateId = this.e2bTemplateId;
|
|
2194
|
+
}
|
|
2190
2195
|
// Add keepAlive TTL if specified
|
|
2191
2196
|
if (this.keepAlive !== undefined && this.keepAlive !== null) {
|
|
2192
2197
|
sandboxConfig.keepAlive = this.keepAlive;
|
package/agent/lib/commands.js
CHANGED
|
@@ -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,144 @@
|
|
|
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 from a session ID.
|
|
20
|
+
* Both sandbox.js and sdk.js duplicated this — it now lives here.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} sessionId
|
|
23
|
+
* @returns {object} Headers object (empty if no sessionId)
|
|
24
|
+
*/
|
|
25
|
+
function getSentryTraceHeaders(sessionId) {
|
|
26
|
+
if (!sessionId) return {};
|
|
27
|
+
const traceId = crypto.createHash("md5").update(sessionId).digest("hex");
|
|
28
|
+
const spanId = crypto.randomBytes(8).toString("hex");
|
|
29
|
+
return {
|
|
30
|
+
"sentry-trace": traceId + "-" + spanId + "-1",
|
|
31
|
+
baggage:
|
|
32
|
+
"sentry-trace_id=" +
|
|
33
|
+
traceId +
|
|
34
|
+
",sentry-sample_rate=1.0,sentry-sampled=true",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build common request headers.
|
|
40
|
+
* @param {object} [extra] - Additional headers to merge
|
|
41
|
+
* @returns {object}
|
|
42
|
+
*/
|
|
43
|
+
function baseHeaders(extra) {
|
|
44
|
+
return {
|
|
45
|
+
"User-Agent": USER_AGENT,
|
|
46
|
+
...extra,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* POST JSON to `url` and return the parsed response body.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} url - Absolute URL
|
|
54
|
+
* @param {object} [data] - JSON body
|
|
55
|
+
* @param {object} [opts] - Extra axios config (headers, timeout, …)
|
|
56
|
+
* @returns {Promise<object>} Parsed response data
|
|
57
|
+
*/
|
|
58
|
+
async function httpPost(url, data, opts = {}) {
|
|
59
|
+
const { headers: extraHeaders, ...rest } = opts;
|
|
60
|
+
const res = await axios({
|
|
61
|
+
method: "post",
|
|
62
|
+
url,
|
|
63
|
+
headers: baseHeaders({
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
...extraHeaders,
|
|
66
|
+
}),
|
|
67
|
+
data,
|
|
68
|
+
timeout: opts.timeout || 30000,
|
|
69
|
+
...rest,
|
|
70
|
+
});
|
|
71
|
+
return res.data;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* GET `url` and return the parsed response body.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} url - Absolute URL
|
|
78
|
+
* @param {object} [opts] - Extra axios config
|
|
79
|
+
* @returns {Promise<object>} Parsed response data
|
|
80
|
+
*/
|
|
81
|
+
async function httpGet(url, opts = {}) {
|
|
82
|
+
const { headers: extraHeaders, ...rest } = opts;
|
|
83
|
+
const res = await axios({
|
|
84
|
+
method: "get",
|
|
85
|
+
url,
|
|
86
|
+
headers: baseHeaders(extraHeaders),
|
|
87
|
+
timeout: opts.timeout || 30000,
|
|
88
|
+
...rest,
|
|
89
|
+
});
|
|
90
|
+
return res.data;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* PUT data to `url` (e.g. S3 presigned upload).
|
|
95
|
+
*
|
|
96
|
+
* @param {string} url - Absolute URL
|
|
97
|
+
* @param {Buffer|string} data - Request body
|
|
98
|
+
* @param {object} [opts] - Extra axios config (headers, timeout, …)
|
|
99
|
+
* @returns {Promise<object>} Parsed response data (or empty object for 2xx with no body)
|
|
100
|
+
*/
|
|
101
|
+
async function httpPut(url, data, opts = {}) {
|
|
102
|
+
const { headers: extraHeaders, ...rest } = opts;
|
|
103
|
+
const res = await axios({
|
|
104
|
+
method: "put",
|
|
105
|
+
url,
|
|
106
|
+
headers: baseHeaders(extraHeaders),
|
|
107
|
+
data,
|
|
108
|
+
timeout: opts.timeout || 30000,
|
|
109
|
+
maxBodyLength: Infinity,
|
|
110
|
+
maxContentLength: Infinity,
|
|
111
|
+
...rest,
|
|
112
|
+
});
|
|
113
|
+
return res.data;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Download a URL as a Buffer (e.g. screenshot from S3).
|
|
118
|
+
*
|
|
119
|
+
* @param {string} url - Absolute URL
|
|
120
|
+
* @param {object} [opts] - Extra axios config
|
|
121
|
+
* @returns {Promise<Buffer>}
|
|
122
|
+
*/
|
|
123
|
+
async function downloadBuffer(url, opts = {}) {
|
|
124
|
+
const { headers: extraHeaders, ...rest } = opts;
|
|
125
|
+
const res = await axios({
|
|
126
|
+
method: "get",
|
|
127
|
+
url,
|
|
128
|
+
headers: baseHeaders(extraHeaders),
|
|
129
|
+
responseType: "arraybuffer",
|
|
130
|
+
timeout: opts.timeout || 60000,
|
|
131
|
+
...rest,
|
|
132
|
+
});
|
|
133
|
+
return Buffer.from(res.data);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
module.exports = {
|
|
137
|
+
httpPost,
|
|
138
|
+
httpGet,
|
|
139
|
+
httpPut,
|
|
140
|
+
downloadBuffer,
|
|
141
|
+
getSentryTraceHeaders,
|
|
142
|
+
USER_AGENT,
|
|
143
|
+
baseHeaders,
|
|
144
|
+
};
|
package/agent/lib/logger.js
CHANGED
|
@@ -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,
|