@testdriverai/agent 7.9.96-test → 7.9.97-canary
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/docs/_data/examples-manifest.json +52 -52
- package/docs/v7/client.mdx +5 -1
- package/docs/v7/examples/ai.mdx +1 -1
- package/docs/v7/examples/assert.mdx +1 -1
- package/docs/v7/examples/chrome-extension.mdx +1 -1
- package/docs/v7/examples/element-not-found.mdx +1 -1
- package/docs/v7/examples/findall-coffee-icons.mdx +1 -1
- package/docs/v7/examples/hover-image.mdx +1 -1
- package/docs/v7/examples/hover-text-with-description.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/parse.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.mdx +1 -1
- package/docs/v7/examples/type.mdx +1 -1
- package/docs/v7/machine-setup.mdx +75 -8
- package/examples/config.mjs +1 -0
- package/lib/core/Dashcam.js +33 -17
- package/package.json +1 -1
- package/sdk.d.ts +9 -7
- package/sdk.js +78 -0
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
"$schema": "./examples-manifest.schema.json",
|
|
3
3
|
"examples": {
|
|
4
4
|
"assert.test.mjs": {
|
|
5
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
6
|
-
"replayUrl": "https://console-test.testdriver.ai/replay/
|
|
7
|
-
"embedUrl": "https://console-test.testdriver.ai/replay/
|
|
8
|
-
"lastUpdated": "2026-05-
|
|
5
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a17917a7d423e81b390fab1",
|
|
6
|
+
"replayUrl": "https://console-test.testdriver.ai/replay/6a1798e07d423e81b390fb43?share=UqiUSmkhK2HQemcBtpVI3w",
|
|
7
|
+
"embedUrl": "https://console-test.testdriver.ai/replay/6a1798e07d423e81b390fb43?share=UqiUSmkhK2HQemcBtpVI3w&embed=true",
|
|
8
|
+
"lastUpdated": "2026-05-28T01:22:47.847Z"
|
|
9
9
|
},
|
|
10
10
|
"drag-and-drop.test.mjs": {
|
|
11
11
|
"url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b42fc0ac3cc632a918b",
|
|
12
12
|
"lastUpdated": "2026-03-03T00:32:25.275Z"
|
|
13
13
|
},
|
|
14
14
|
"exec-pwsh.test.mjs": {
|
|
15
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
16
|
-
"lastUpdated": "2026-05-
|
|
15
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a17908b7d423e81b390fa99",
|
|
16
|
+
"lastUpdated": "2026-05-28T01:01:50.685Z"
|
|
17
17
|
},
|
|
18
18
|
"match-image.test.mjs": {
|
|
19
19
|
"url": "https://console-test.testdriver.ai/runs/69c8738614b73310c7839412/69c8738c14b73310c783941d",
|
|
@@ -24,92 +24,92 @@
|
|
|
24
24
|
"lastUpdated": "2026-03-03T00:32:25.282Z"
|
|
25
25
|
},
|
|
26
26
|
"hover-text-with-description.test.mjs": {
|
|
27
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
28
|
-
"lastUpdated": "2026-05-
|
|
27
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1790a37d423e81b390fa9e",
|
|
28
|
+
"lastUpdated": "2026-05-28T00:47:30.860Z"
|
|
29
29
|
},
|
|
30
30
|
"windows-installer.test.mjs": {
|
|
31
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
32
|
-
"lastUpdated": "2026-05-
|
|
31
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1790d37d423e81b390faa7",
|
|
32
|
+
"lastUpdated": "2026-05-28T01:03:02.727Z"
|
|
33
33
|
},
|
|
34
34
|
"exec-output.test.mjs": {
|
|
35
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
36
|
-
"lastUpdated": "2026-05-
|
|
35
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1790d57d423e81b390faa8",
|
|
36
|
+
"lastUpdated": "2026-05-28T01:03:04.886Z"
|
|
37
37
|
},
|
|
38
38
|
"chrome-extension.test.mjs": {
|
|
39
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
40
|
-
"lastUpdated": "2026-05-
|
|
39
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1790897d423e81b390fa98",
|
|
40
|
+
"lastUpdated": "2026-05-28T00:54:24.116Z"
|
|
41
41
|
},
|
|
42
42
|
"launch-vscode-linux.test.mjs": {
|
|
43
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
44
|
-
"replayUrl": "https://console-test.testdriver.ai/replay/
|
|
45
|
-
"embedUrl": "https://console-test.testdriver.ai/replay/
|
|
46
|
-
"lastUpdated": "2026-05-
|
|
43
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1790c67d423e81b390faa5",
|
|
44
|
+
"replayUrl": "https://console-test.testdriver.ai/replay/6a1796cd7d423e81b390faed?share=vC6J7mIobNeQqdUdiZog",
|
|
45
|
+
"embedUrl": "https://console-test.testdriver.ai/replay/6a1796cd7d423e81b390faed?share=vC6J7mIobNeQqdUdiZog&embed=true",
|
|
46
|
+
"lastUpdated": "2026-05-28T01:13:57.213Z"
|
|
47
47
|
},
|
|
48
48
|
"hover-image.test.mjs": {
|
|
49
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
50
|
-
"lastUpdated": "2026-05-
|
|
49
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1790ee7d423e81b390faab",
|
|
50
|
+
"lastUpdated": "2026-05-28T00:56:05.446Z"
|
|
51
51
|
},
|
|
52
52
|
"installer.test.mjs": {
|
|
53
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
54
|
-
"lastUpdated": "2026-05-
|
|
53
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791107d423e81b390faac",
|
|
54
|
+
"lastUpdated": "2026-05-28T00:49:09.295Z"
|
|
55
55
|
},
|
|
56
56
|
"type.test.mjs": {
|
|
57
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
58
|
-
"lastUpdated": "2026-05-
|
|
57
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791497d423e81b390faaf",
|
|
58
|
+
"lastUpdated": "2026-05-28T00:57:35.980Z"
|
|
59
59
|
},
|
|
60
60
|
"press-keys.test.mjs": {
|
|
61
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
62
|
-
"replayUrl": "https://console-test.testdriver.ai/replay/
|
|
63
|
-
"embedUrl": "https://console-test.testdriver.ai/replay/
|
|
64
|
-
"lastUpdated": "2026-05-
|
|
61
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791627d423e81b390fab0",
|
|
62
|
+
"replayUrl": "https://console-test.testdriver.ai/replay/6a1798a77d423e81b390fb3c?share=HWIAWhrv9Umwbyi641bgg",
|
|
63
|
+
"embedUrl": "https://console-test.testdriver.ai/replay/6a1798a77d423e81b390fb3c?share=HWIAWhrv9Umwbyi641bgg&embed=true",
|
|
64
|
+
"lastUpdated": "2026-05-28T01:21:51.018Z"
|
|
65
65
|
},
|
|
66
66
|
"scroll-keyboard.test.mjs": {
|
|
67
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
68
|
-
"lastUpdated": "2026-05-
|
|
67
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791aa7d423e81b390fab3",
|
|
68
|
+
"lastUpdated": "2026-05-28T01:06:34.229Z"
|
|
69
69
|
},
|
|
70
70
|
"scroll.test.mjs": {
|
|
71
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
72
|
-
"replayUrl": "https://console-test.testdriver.ai/replay/
|
|
73
|
-
"embedUrl": "https://console-test.testdriver.ai/replay/
|
|
74
|
-
"lastUpdated": "2026-05-
|
|
71
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791c77d423e81b390fab7",
|
|
72
|
+
"replayUrl": "https://console-test.testdriver.ai/replay/6a179a1b7d423e81b390fb6c?share=LaHVFvjLtWSJbRzmb9pQ",
|
|
73
|
+
"embedUrl": "https://console-test.testdriver.ai/replay/6a179a1b7d423e81b390fb6c?share=LaHVFvjLtWSJbRzmb9pQ&embed=true",
|
|
74
|
+
"lastUpdated": "2026-05-28T01:28:03.366Z"
|
|
75
75
|
},
|
|
76
76
|
"scroll-until-image.test.mjs": {
|
|
77
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
78
|
-
"lastUpdated": "2026-05-
|
|
77
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791ac7d423e81b390fab4",
|
|
78
|
+
"lastUpdated": "2026-05-28T00:51:57.166Z"
|
|
79
79
|
},
|
|
80
80
|
"prompt.test.mjs": {
|
|
81
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
82
|
-
"lastUpdated": "2026-05-
|
|
81
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791af7d423e81b390fab5",
|
|
82
|
+
"lastUpdated": "2026-05-28T01:06:39.358Z"
|
|
83
83
|
},
|
|
84
84
|
"focus-window.test.mjs": {
|
|
85
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
86
|
-
"lastUpdated": "2026-05-
|
|
85
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791b17d423e81b390fab6",
|
|
86
|
+
"lastUpdated": "2026-05-28T00:52:01.734Z"
|
|
87
87
|
},
|
|
88
88
|
"captcha-api.test.mjs": {
|
|
89
89
|
"url": "https://console.testdriver.ai/runs/698f7df69e27ce1528d7d087/698f7fb0d3b320ad547d9d44",
|
|
90
90
|
"lastUpdated": "2026-02-13T19:55:05.951Z"
|
|
91
91
|
},
|
|
92
92
|
"element-not-found.test.mjs": {
|
|
93
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
94
|
-
"lastUpdated": "2026-05-
|
|
93
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791e07d423e81b390fab8",
|
|
94
|
+
"lastUpdated": "2026-05-28T01:07:28.823Z"
|
|
95
95
|
},
|
|
96
96
|
"formatted-logging.test.mjs": {
|
|
97
97
|
"url": "https://console-test.testdriver.ai/runs/69c8738614b73310c7839412/69c873a714b73310c7839450",
|
|
98
98
|
"lastUpdated": "2026-03-29T00:36:10.628Z"
|
|
99
99
|
},
|
|
100
100
|
"hover-text.test.mjs": {
|
|
101
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
102
|
-
"lastUpdated": "2026-05-
|
|
101
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1792107d423e81b390faba",
|
|
102
|
+
"lastUpdated": "2026-05-28T00:53:35.603Z"
|
|
103
103
|
},
|
|
104
104
|
"no-provision.test.mjs": {
|
|
105
105
|
"url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b7706a177a05bccd1cf",
|
|
106
106
|
"lastUpdated": "2026-03-03T00:32:25.279Z"
|
|
107
107
|
},
|
|
108
108
|
"ai.test.mjs": {
|
|
109
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
110
|
-
"replayUrl": "https://console-test.testdriver.ai/replay/
|
|
111
|
-
"embedUrl": "https://console-test.testdriver.ai/replay/
|
|
112
|
-
"lastUpdated": "2026-05-
|
|
109
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791f87d423e81b390fab9",
|
|
110
|
+
"replayUrl": "https://console-test.testdriver.ai/replay/6a179ae57d423e81b390fb82?share=keImOYhUmGpWQQFuhIZSaw",
|
|
111
|
+
"embedUrl": "https://console-test.testdriver.ai/replay/6a179ae57d423e81b390fb82?share=keImOYhUmGpWQQFuhIZSaw&embed=true",
|
|
112
|
+
"lastUpdated": "2026-05-28T01:31:25.753Z"
|
|
113
113
|
},
|
|
114
114
|
"popup-loading.test.mjs": {
|
|
115
115
|
"url": "https://console.testdriver.ai/runs/698bc89f7140c3fa7daaca8d/698bca7f7140c3fa7daacbf7",
|
|
@@ -144,12 +144,12 @@
|
|
|
144
144
|
"lastUpdated": "2026-02-13T19:55:05.953Z"
|
|
145
145
|
},
|
|
146
146
|
"findall-coffee-icons.test.mjs": {
|
|
147
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
148
|
-
"lastUpdated": "2026-05-
|
|
147
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1791927d423e81b390fab2",
|
|
148
|
+
"lastUpdated": "2026-05-28T00:58:50.726Z"
|
|
149
149
|
},
|
|
150
150
|
"parse.test.mjs": {
|
|
151
|
-
"url": "https://console-test.testdriver.ai/runs/
|
|
152
|
-
"lastUpdated": "2026-05-
|
|
151
|
+
"url": "https://console-test.testdriver.ai/runs/6a1790707d423e81b390fa89/6a1792297d423e81b390fabb",
|
|
152
|
+
"lastUpdated": "2026-05-28T01:08:39.338Z"
|
|
153
153
|
},
|
|
154
154
|
"flake-diffthreshold-001.test.mjs": {
|
|
155
155
|
"url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62bcafc0ac3cc632a91aa",
|
package/docs/v7/client.mdx
CHANGED
|
@@ -54,7 +54,11 @@ const testdriver = new TestDriver(apiKey, options)
|
|
|
54
54
|
</ParamField>
|
|
55
55
|
|
|
56
56
|
<ParamField path="reconnect" type="boolean" default="false">
|
|
57
|
-
|
|
57
|
+
Reattach to the last used sandbox instead of creating a new one. When `true`, the SDK reads the sandbox id from `.testdriver/last-sandbox` (written automatically on every successful connect) and rejoins that VM. Provision methods (`chrome`, `vscode`, `installer`, etc.) are skipped because the application is already running. The previous sandbox must still be alive — see [`keepAlive`](#keepalive) and the [Machine Setup guide](/v7/machine-setup#keeping-machines-alive-between-runs).
|
|
58
|
+
</ParamField>
|
|
59
|
+
|
|
60
|
+
<ParamField path="sandboxId" type="string">
|
|
61
|
+
Reattach to a specific sandbox id instead of the one recorded in `.testdriver/last-sandbox`. Use this for CI matrices or to pin a chain of tests to a known VM. Implies `reconnect: true` behavior (provision calls are skipped).
|
|
58
62
|
</ParamField>
|
|
59
63
|
|
|
60
64
|
<ParamField path="preview" type="string" default="browser">
|
package/docs/v7/examples/ai.mdx
CHANGED
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* ai.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://console-test.testdriver.ai/replay/
|
|
15
|
+
src="https://console-test.testdriver.ai/replay/6a179ae57d423e81b390fb82?share=keImOYhUmGpWQQFuhIZSaw&embed=true"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* assert.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://console-test.testdriver.ai/replay/
|
|
15
|
+
src="https://console-test.testdriver.ai/replay/6a1798e07d423e81b390fb43?share=UqiUSmkhK2HQemcBtpVI3w&embed=true"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* chrome-extension.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6a1790897d423e81b390fa98/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* element-not-found.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6a1791e07d423e81b390fab8/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -18,7 +18,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
18
18
|
|
|
19
19
|
{/* findall-coffee-icons.test.mjs output */}
|
|
20
20
|
<iframe
|
|
21
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
21
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6a1791927d423e81b390fab2/replay"
|
|
22
22
|
width="100%"
|
|
23
23
|
height="600"
|
|
24
24
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* hover-image.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6a1790ee7d423e81b390faab/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -18,7 +18,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
18
18
|
|
|
19
19
|
{/* hover-text-with-description.test.mjs output */}
|
|
20
20
|
<iframe
|
|
21
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
21
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6a1790a37d423e81b390fa9e/replay"
|
|
22
22
|
width="100%"
|
|
23
23
|
height="600"
|
|
24
24
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* hover-text.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6a1792107d423e81b390faba/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* installer.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6a1791107d423e81b390faac/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* launch-vscode-linux.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://console-test.testdriver.ai/replay/
|
|
15
|
+
src="https://console-test.testdriver.ai/replay/6a1796cd7d423e81b390faed?share=vC6J7mIobNeQqdUdiZog&embed=true"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -18,7 +18,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
18
18
|
|
|
19
19
|
{/* parse.test.mjs output */}
|
|
20
20
|
<iframe
|
|
21
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
21
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6a1792297d423e81b390fabb/replay"
|
|
22
22
|
width="100%"
|
|
23
23
|
height="600"
|
|
24
24
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* press-keys.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://console-test.testdriver.ai/replay/
|
|
15
|
+
src="https://console-test.testdriver.ai/replay/6a1798a77d423e81b390fb3c?share=HWIAWhrv9Umwbyi641bgg&embed=true"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* scroll-keyboard.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6a1791aa7d423e81b390fab3/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* scroll.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://console-test.testdriver.ai/replay/
|
|
15
|
+
src="https://console-test.testdriver.ai/replay/6a179a1b7d423e81b390fb6c?share=LaHVFvjLtWSJbRzmb9pQ&embed=true"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* type.test.mjs output */}
|
|
14
14
|
<iframe
|
|
15
|
-
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6a1791497d423e81b390faaf/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -87,6 +87,16 @@ const testdriver = TestDriver(context, {
|
|
|
87
87
|
|
|
88
88
|
Windows (and Linux) cold starts can be expensive if you're iterating quickly. Use `keepAlive` + `reconnect` to reuse the same VM across multiple test runs.
|
|
89
89
|
|
|
90
|
+
### How it works
|
|
91
|
+
|
|
92
|
+
Every time the SDK successfully connects to a sandbox, it records the sandbox id in `.testdriver/last-sandbox` inside your project directory. The next test that opts in with `reconnect: true` reads that file and reattaches automatically — no manual id tracking required.
|
|
93
|
+
|
|
94
|
+
Provision calls (`testdriver.provision.chrome(...)`, `vscode(...)`, etc.) are **skipped** when reconnecting, because the application is already running inside the sandbox from the previous run.
|
|
95
|
+
|
|
96
|
+
<Note>
|
|
97
|
+
`.testdriver/last-sandbox` is already covered by the default TestDriver `.gitignore`. Don't commit it.
|
|
98
|
+
</Note>
|
|
99
|
+
|
|
90
100
|
### Step 1 — Start the machine with a long `keepAlive`
|
|
91
101
|
|
|
92
102
|
```javascript
|
|
@@ -102,30 +112,87 @@ await testdriver.provision.chrome({ url: "https://example.com" });
|
|
|
102
112
|
|
|
103
113
|
When this test finishes, the sandbox stays running for 30 minutes instead of being terminated immediately.
|
|
104
114
|
|
|
105
|
-
### Step 2 —
|
|
115
|
+
### Step 2 — Reattach automatically with `reconnect: true`
|
|
106
116
|
|
|
107
117
|
```javascript
|
|
108
118
|
// second.test.mjs
|
|
109
119
|
const testdriver = TestDriver(context, {
|
|
110
120
|
os: "windows",
|
|
121
|
+
reconnect: true, // ← reads .testdriver/last-sandbox
|
|
111
122
|
keepAlive: 30 * 60 * 1000,
|
|
112
123
|
});
|
|
113
124
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// provision.chrome() is automatically skipped — Chrome is already open
|
|
125
|
+
// No provision call — Chrome is already open from the previous run.
|
|
117
126
|
await testdriver.find("Sign In button").click();
|
|
118
127
|
```
|
|
119
128
|
|
|
120
|
-
|
|
129
|
+
### Step 2 (alternative) — Reattach to an explicit id
|
|
130
|
+
|
|
131
|
+
If you need to pin to a specific sandbox (CI matrix, multiple chains in parallel, etc.) pass the id directly:
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
await testdriver.connect({ sandboxId: "sandbox-abc123" });
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
When reattaching to a sandbox:
|
|
121
138
|
- You reuse a specific running machine directly
|
|
122
|
-
- You
|
|
123
|
-
- You
|
|
139
|
+
- You continue from the app state created in the earlier run
|
|
140
|
+
- You must run within the previous test's `keepAlive` window
|
|
124
141
|
|
|
125
142
|
<Tip>
|
|
126
|
-
|
|
143
|
+
Use `testdriver.getLastSandboxId()` to read the recorded sandbox id (and optional metadata) for scripting purposes.
|
|
127
144
|
</Tip>
|
|
128
145
|
|
|
146
|
+
### Chaining describe blocks within one test file
|
|
147
|
+
|
|
148
|
+
A common pattern is to break a long flow into focused `describe` blocks that share one sandbox — the first block provisions and signs in, later blocks reconnect and continue:
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
import { describe, expect, it } from "vitest";
|
|
152
|
+
import { TestDriver } from "testdriverai/vitest/hooks";
|
|
153
|
+
|
|
154
|
+
const KEEP_ALIVE_MS = 5 * 60 * 1000;
|
|
155
|
+
|
|
156
|
+
describe("step 1 — log in", () => {
|
|
157
|
+
it("signs in and lands on the dashboard", async (context) => {
|
|
158
|
+
const testdriver = TestDriver(context, { keepAlive: KEEP_ALIVE_MS });
|
|
159
|
+
await testdriver.provision.chrome({ url: "https://example.com/login" });
|
|
160
|
+
await testdriver.find("username input").click();
|
|
161
|
+
await testdriver.type("standard_user");
|
|
162
|
+
await testdriver.pressKeys(["tab"]);
|
|
163
|
+
await testdriver.type("secret_sauce", { secret: true });
|
|
164
|
+
await testdriver.pressKeys(["enter"]);
|
|
165
|
+
expect(await testdriver.assert("the dashboard is visible")).toBeTruthy();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("step 2 — add to cart", () => {
|
|
170
|
+
it("reuses the logged-in sandbox", async (context) => {
|
|
171
|
+
const testdriver = TestDriver(context, {
|
|
172
|
+
reconnect: true, // ← skip provisioning, reattach
|
|
173
|
+
keepAlive: KEEP_ALIVE_MS,
|
|
174
|
+
});
|
|
175
|
+
await testdriver.find("Add to cart").click();
|
|
176
|
+
await testdriver.find("cart icon").click();
|
|
177
|
+
expect(await testdriver.assert("the cart has an item")).toBeTruthy();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe("step 3 — check out", () => {
|
|
182
|
+
it("continues from the cart state", async (context) => {
|
|
183
|
+
const testdriver = TestDriver(context, { reconnect: true, keepAlive: 30_000 });
|
|
184
|
+
await testdriver.find("Checkout").click();
|
|
185
|
+
expect(await testdriver.assert("the checkout form is visible")).toBeTruthy();
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
A runnable copy of this pattern lives at [`examples/reconnect-sequential.test.mjs`](https://github.com/testdriverai/mono/blob/main/sdk/examples/reconnect-sequential.test.mjs).
|
|
191
|
+
|
|
192
|
+
<Warning>
|
|
193
|
+
Vitest runs **test files** in parallel by default. Within a single file, `describe`/`it` blocks run in source order, so reconnect chaining works as written. To chain across multiple files, run them sequentially (e.g. `vitest run --sequence.concurrent=false` or place them in a single project pool with workers set to 1).
|
|
194
|
+
</Warning>
|
|
195
|
+
|
|
129
196
|
### How `keepAlive` works
|
|
130
197
|
|
|
131
198
|
`keepAlive` is a duration in milliseconds. After the SDK disconnects, the server keeps the VM running for that long before terminating it. The default is `60000` (1 minute). Note: `keepAlive: 0` currently falls back to the default disconnect grace period rather than terminating immediately, so use a positive duration when you want to control the grace window explicitly.
|
package/examples/config.mjs
CHANGED
package/lib/core/Dashcam.js
CHANGED
|
@@ -150,6 +150,34 @@ class Dashcam {
|
|
|
150
150
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Workaround for dashcam <1.5.0: webLogsDaemon.js writes its state to a
|
|
155
|
+
* `.dashcam` directory relative to process.cwd(). When launched by the
|
|
156
|
+
* runner, cwd is /usr/lib/node_modules/@testdriverai/runner (root-owned),
|
|
157
|
+
* so the daemon can't mkdir its state dir as user `user` and silently
|
|
158
|
+
* dies — recordings never start (or `dashcam logs --add` fails with EACCES).
|
|
159
|
+
* Pre-create world-writable .dashcam dirs at both the current cwd and the
|
|
160
|
+
* known global-install path so the daemon survives regardless of which path
|
|
161
|
+
* dashcam ends up using. Must run before ANY dashcam invocation, not just
|
|
162
|
+
* auth(), because addWebLog() is called before start()/auth() in provision.
|
|
163
|
+
* TODO: remove once dashcam >=1.5.0 (which uses $HOME) is bundled.
|
|
164
|
+
* @private
|
|
165
|
+
*/
|
|
166
|
+
async _ensureDashcamStateDirs() {
|
|
167
|
+
if (this.client.os === "windows") return;
|
|
168
|
+
if (!this._dashcamDirsReady) {
|
|
169
|
+
this._dashcamDirsReady = this.client.exec(
|
|
170
|
+
this._getShell(),
|
|
171
|
+
`sudo mkdir -p "$(pwd)/.dashcam" /usr/lib/node_modules/@testdriverai/runner/.dashcam 2>/dev/null; ` +
|
|
172
|
+
`sudo chmod 0777 "$(pwd)/.dashcam" /usr/lib/node_modules/@testdriverai/runner/.dashcam 2>/dev/null; ` +
|
|
173
|
+
`true`,
|
|
174
|
+
10000,
|
|
175
|
+
process.env.TD_DEBUG == "true" ? false : true,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
await this._dashcamDirsReady;
|
|
179
|
+
}
|
|
180
|
+
|
|
153
181
|
/**
|
|
154
182
|
* Authenticate dashcam with API key
|
|
155
183
|
* @param {string} [apiKey] - Override API key
|
|
@@ -174,23 +202,7 @@ class Dashcam {
|
|
|
174
202
|
this._log("debug", "Auth output:", authOutput);
|
|
175
203
|
} else {
|
|
176
204
|
// Linux/Mac authentication with TD_API_ROOT
|
|
177
|
-
|
|
178
|
-
// Workaround for dashcam <1.5.0: webLogsDaemon.js writes its state to a
|
|
179
|
-
// `.dashcam` directory relative to process.cwd(). When launched by the
|
|
180
|
-
// runner, cwd is /usr/lib/node_modules/@testdriverai/runner (root-owned),
|
|
181
|
-
// so the daemon can't mkdir its state dir as user `user` and silently
|
|
182
|
-
// dies — recordings never start. Pre-create world-writable .dashcam dirs
|
|
183
|
-
// at both the current cwd and the known global-install path so the
|
|
184
|
-
// daemon survives regardless of which path dashcam ends up using.
|
|
185
|
-
// TODO: remove once dashcam >=1.5.0 (which uses $HOME) is bundled.
|
|
186
|
-
await this.client.exec(
|
|
187
|
-
shell,
|
|
188
|
-
`sudo mkdir -p "$(pwd)/.dashcam" /usr/lib/node_modules/@testdriverai/runner/.dashcam 2>/dev/null; ` +
|
|
189
|
-
`sudo chmod 0777 "$(pwd)/.dashcam" /usr/lib/node_modules/@testdriverai/runner/.dashcam 2>/dev/null; ` +
|
|
190
|
-
`true`,
|
|
191
|
-
10000,
|
|
192
|
-
process.env.TD_DEBUG == "true" ? false : true,
|
|
193
|
-
);
|
|
205
|
+
await this._ensureDashcamStateDirs();
|
|
194
206
|
|
|
195
207
|
const authOutput = await this.client.exec(
|
|
196
208
|
shell,
|
|
@@ -233,6 +245,7 @@ class Dashcam {
|
|
|
233
245
|
);
|
|
234
246
|
this._log("debug", "Add log tracking output:", addLogOutput);
|
|
235
247
|
} else {
|
|
248
|
+
await this._ensureDashcamStateDirs();
|
|
236
249
|
// Create log file
|
|
237
250
|
await this.client.exec(
|
|
238
251
|
shell,
|
|
@@ -272,6 +285,7 @@ class Dashcam {
|
|
|
272
285
|
);
|
|
273
286
|
this._log("debug", "Add application log tracking output:", addLogOutput);
|
|
274
287
|
} else {
|
|
288
|
+
await this._ensureDashcamStateDirs();
|
|
275
289
|
const addLogOutput = await this.client.exec(
|
|
276
290
|
shell,
|
|
277
291
|
`TD_API_ROOT="${apiRoot}" dashcam logs --add --type=application --application="${application}" --name="${name}"`,
|
|
@@ -306,6 +320,7 @@ class Dashcam {
|
|
|
306
320
|
this._log("warn", "Add web log tracking failed:", err.message);
|
|
307
321
|
}
|
|
308
322
|
} else {
|
|
323
|
+
await this._ensureDashcamStateDirs();
|
|
309
324
|
const addLogOutput = await this.client.exec(
|
|
310
325
|
shell,
|
|
311
326
|
`TD_API_ROOT="${apiRoot}" dashcam logs --add --type=web --pattern="${pattern}" --name="${name}"`,
|
|
@@ -386,6 +401,7 @@ class Dashcam {
|
|
|
386
401
|
this._log("debug", "Dashcam recording started");
|
|
387
402
|
} else {
|
|
388
403
|
// Linux/Mac with TD_API_ROOT
|
|
404
|
+
await this._ensureDashcamStateDirs();
|
|
389
405
|
this._log("debug", "Starting dashcam recording on Linux/Mac...");
|
|
390
406
|
const dashcamPath = await this._getDashcamPath();
|
|
391
407
|
const titleArg = this.title
|
package/package.json
CHANGED
package/sdk.d.ts
CHANGED
|
@@ -277,8 +277,10 @@ export interface TestDriverOptions {
|
|
|
277
277
|
e2bTemplateId?: string;
|
|
278
278
|
/** Cache key for element finding operations. If provided, enables caching tied to this key */
|
|
279
279
|
cacheKey?: string;
|
|
280
|
-
/** Reconnect to the last used sandbox instead of creating a new one. When true, provision methods (chrome, vscode, installer, etc.) will be skipped since the application is already running.
|
|
280
|
+
/** Reconnect to the last used sandbox instead of creating a new one. When true, provision methods (chrome, vscode, installer, etc.) will be skipped since the application is already running. Looks up the sandbox id from `.testdriver/last-sandbox` if `sandboxId` is not also provided. */
|
|
281
281
|
reconnect?: boolean;
|
|
282
|
+
/** Explicit sandbox id to reconnect to. Implies `newSandbox: false` unless explicitly overridden. */
|
|
283
|
+
sandboxId?: string;
|
|
282
284
|
/** Enable/disable Dashcam video recording (default: true) */
|
|
283
285
|
dashcam?: boolean;
|
|
284
286
|
/**
|
|
@@ -1042,15 +1044,15 @@ export default class TestDriverSDK {
|
|
|
1042
1044
|
disconnect(): Promise<void>;
|
|
1043
1045
|
|
|
1044
1046
|
/**
|
|
1045
|
-
* Get the
|
|
1047
|
+
* Get info about the most recently provisioned sandbox (from this SDK instance
|
|
1048
|
+
* if connected, otherwise read from `.testdriver/last-sandbox`).
|
|
1046
1049
|
* @returns Last sandbox info or null if not found
|
|
1047
1050
|
*/
|
|
1048
1051
|
getLastSandboxId(): {
|
|
1049
|
-
sandboxId: string
|
|
1050
|
-
os
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
timestamp: string | null;
|
|
1052
|
+
sandboxId: string;
|
|
1053
|
+
os?: "windows" | "linux";
|
|
1054
|
+
e2bTemplateId?: string | null;
|
|
1055
|
+
createdAt?: number;
|
|
1054
1056
|
} | null;
|
|
1055
1057
|
|
|
1056
1058
|
// Element Finding API
|
package/sdk.js
CHANGED
|
@@ -1510,6 +1510,21 @@ class TestDriverSDK {
|
|
|
1510
1510
|
this.reconnect =
|
|
1511
1511
|
options.reconnect !== undefined ? options.reconnect : false;
|
|
1512
1512
|
|
|
1513
|
+
// Explicit sandbox id to reconnect to (overrides last-sandbox file)
|
|
1514
|
+
this.sandboxId = options.sandboxId || null;
|
|
1515
|
+
|
|
1516
|
+
// When reconnect is requested, an explicit sandboxId implies newSandbox=false.
|
|
1517
|
+
// If reconnect:true but no sandboxId given, try to load from .testdriver/last-sandbox.
|
|
1518
|
+
if (this.reconnect && !this.sandboxId) {
|
|
1519
|
+
const last = TestDriverSDK._readLastSandbox();
|
|
1520
|
+
if (last && last.sandboxId) {
|
|
1521
|
+
this.sandboxId = last.sandboxId;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
if (this.sandboxId && options.newSandbox === undefined) {
|
|
1525
|
+
this.newSandbox = false;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1513
1528
|
// Store keepAlive preference from options
|
|
1514
1529
|
this.keepAlive =
|
|
1515
1530
|
options.keepAlive !== undefined ? options.keepAlive : undefined;
|
|
@@ -2070,6 +2085,9 @@ CAPTCHA_SOLVER_EOF`,
|
|
|
2070
2085
|
// Set agent properties for buildEnv to use
|
|
2071
2086
|
if (connectOptions.sandboxId) {
|
|
2072
2087
|
this.agent.sandboxId = connectOptions.sandboxId;
|
|
2088
|
+
} else if (this.sandboxId) {
|
|
2089
|
+
// Constructor-provided sandboxId (explicit or loaded from .testdriver/last-sandbox)
|
|
2090
|
+
this.agent.sandboxId = this.sandboxId;
|
|
2073
2091
|
}
|
|
2074
2092
|
// Use IP from connectOptions if provided, otherwise fall back to constructor IP
|
|
2075
2093
|
if (connectOptions.ip !== undefined) {
|
|
@@ -2168,6 +2186,20 @@ CAPTCHA_SOLVER_EOF`,
|
|
|
2168
2186
|
sandboxId: this.instance?.instanceId,
|
|
2169
2187
|
});
|
|
2170
2188
|
|
|
2189
|
+
// Persist the active sandbox id so a later run with `reconnect: true`
|
|
2190
|
+
// can reattach without the caller having to thread the id through.
|
|
2191
|
+
const activeSandboxId =
|
|
2192
|
+
this.instance?.sandboxId || this.instance?.instanceId || null;
|
|
2193
|
+
if (activeSandboxId) {
|
|
2194
|
+
this.sandboxId = activeSandboxId;
|
|
2195
|
+
TestDriverSDK._writeLastSandbox({
|
|
2196
|
+
sandboxId: activeSandboxId,
|
|
2197
|
+
os: this.os,
|
|
2198
|
+
e2bTemplateId: this.e2bTemplateId || null,
|
|
2199
|
+
createdAt: Date.now(),
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2171
2203
|
return this.instance;
|
|
2172
2204
|
}
|
|
2173
2205
|
|
|
@@ -3645,6 +3677,52 @@ CAPTCHA_SOLVER_EOF`,
|
|
|
3645
3677
|
clearLogs() {
|
|
3646
3678
|
this._logBuffer = [];
|
|
3647
3679
|
}
|
|
3680
|
+
|
|
3681
|
+
/**
|
|
3682
|
+
* Return info about the most recently provisioned sandbox (from this process
|
|
3683
|
+
* or persisted from a previous run via .testdriver/last-sandbox).
|
|
3684
|
+
* @returns {{ sandboxId: string, os?: string, e2bTemplateId?: string|null, createdAt?: number } | null}
|
|
3685
|
+
*/
|
|
3686
|
+
getLastSandboxId() {
|
|
3687
|
+
if (this.sandboxId) {
|
|
3688
|
+
return {
|
|
3689
|
+
sandboxId: this.sandboxId,
|
|
3690
|
+
os: this.os,
|
|
3691
|
+
e2bTemplateId: this.e2bTemplateId || null,
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3694
|
+
return TestDriverSDK._readLastSandbox();
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
/** @private */
|
|
3698
|
+
static _lastSandboxPath() {
|
|
3699
|
+
return path.join(process.cwd(), ".testdriver", "last-sandbox");
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
/** @private */
|
|
3703
|
+
static _readLastSandbox() {
|
|
3704
|
+
try {
|
|
3705
|
+
const p = TestDriverSDK._lastSandboxPath();
|
|
3706
|
+
if (!fs.existsSync(p)) return null;
|
|
3707
|
+
const raw = fs.readFileSync(p, "utf8");
|
|
3708
|
+
const data = JSON.parse(raw);
|
|
3709
|
+
if (!data || !data.sandboxId) return null;
|
|
3710
|
+
return data;
|
|
3711
|
+
} catch (_) {
|
|
3712
|
+
return null;
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
/** @private */
|
|
3717
|
+
static _writeLastSandbox(data) {
|
|
3718
|
+
try {
|
|
3719
|
+
const p = TestDriverSDK._lastSandboxPath();
|
|
3720
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
3721
|
+
fs.writeFileSync(p, JSON.stringify(data, null, 2));
|
|
3722
|
+
} catch (_) {
|
|
3723
|
+
// Best-effort — don't fail the test if we can't persist.
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3648
3726
|
}
|
|
3649
3727
|
|
|
3650
3728
|
// Expose SDK version as a static property for use by vitest hooks/plugins
|