testdriverai 7.8.0-test.32 → 7.8.0-test.39
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/agent/lib/sandbox.js +134 -55
- package/docs/_data/examples-manifest.json +46 -46
- 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/exec-output.mdx +1 -1
- package/docs/v7/examples/exec-pwsh.mdx +1 -1
- package/docs/v7/examples/focus-window.mdx +1 -1
- 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.mdx +1 -1
- package/docs/v7/examples/type.mdx +1 -1
- package/docs/v7/examples/windows-installer.mdx +1 -1
- package/interfaces/vitest-plugin.mjs +45 -43
- package/lib/core/Dashcam.js +17 -7
- package/lib/github-comment.mjs +58 -40
- package/package.json +1 -1
- package/setup/aws/install-dev-runner.sh +79 -0
- package/setup/aws/spawn-runner.sh +134 -0
- package/vitest.runner.config.mjs +33 -0
|
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
|
|
|
12
12
|
|
|
13
13
|
{/* exec-pwsh.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/69bc95088797a8a13f4cbfa2/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
|
{/* focus-window.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/69bc9526f14c7f71c46d6b85/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
|
{/* 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/69bc95138797a8a13f4cbfa6/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
|
{/* 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/69bc952e8797a8a13f4cbfc1/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/69bc951636864abb362f857c/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://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69bc950e8797a8a13f4cbfa4/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
|
{/* match-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/69bc950af14c7f71c46d6b78/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
|
{/* press-keys.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/69bc951df14c7f71c46d6b84/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-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/69bc951b8797a8a13f4cbfa7/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-until-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/69bc95238797a8a13f4cbfb3/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://api-test.testdriver.ai/api/v1/testdriver/testcase/
|
|
15
|
+
src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69bc9519f14c7f71c46d6b7f/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
|
{/* 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/69bc9517f14c7f71c46d6b7c/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
|
{/* windows-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/69bc9510f14c7f71c46d6b7a/replay"
|
|
16
16
|
width="100%"
|
|
17
17
|
height="390"
|
|
18
18
|
style={{ border: "1px solid #333", borderRadius: "8px" }}
|
|
@@ -13,6 +13,7 @@ const channelConfig = require("../lib/resolve-channel.js");
|
|
|
13
13
|
|
|
14
14
|
// Import Sentry for error reporting
|
|
15
15
|
const Sentry = require("@sentry/node");
|
|
16
|
+
const chalk = require("chalk");
|
|
16
17
|
|
|
17
18
|
// Track if Sentry has been initialized
|
|
18
19
|
let sentryInitialized = false;
|
|
@@ -23,7 +24,7 @@ let sentryInitialized = false;
|
|
|
23
24
|
*/
|
|
24
25
|
function initializeSentry() {
|
|
25
26
|
if (sentryInitialized) return;
|
|
26
|
-
|
|
27
|
+
|
|
27
28
|
// Respect telemetry opt-out
|
|
28
29
|
if (process.env.TD_TELEMETRY === "false") {
|
|
29
30
|
return;
|
|
@@ -31,7 +32,7 @@ function initializeSentry() {
|
|
|
31
32
|
|
|
32
33
|
try {
|
|
33
34
|
const version = resolveTestDriverVersion() || "unknown";
|
|
34
|
-
|
|
35
|
+
|
|
35
36
|
Sentry.init({
|
|
36
37
|
dsn:
|
|
37
38
|
process.env.SENTRY_DSN ||
|
|
@@ -53,12 +54,12 @@ function initializeSentry() {
|
|
|
53
54
|
// Filter out events that should not be reported to Sentry
|
|
54
55
|
beforeSend(event, hint) {
|
|
55
56
|
const error = hint.originalException;
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
// Don't send user-cancelled errors
|
|
58
59
|
if (error && error.message && error.message.includes("User cancelled")) {
|
|
59
60
|
return null;
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
+
|
|
62
63
|
// Don't send test failures - these are expected behavior, not bugs in the SDK
|
|
63
64
|
// Test failures indicate the test found a problem, which is the intended use case
|
|
64
65
|
if (event.exception?.values) {
|
|
@@ -67,18 +68,18 @@ function initializeSentry() {
|
|
|
67
68
|
if (exception.type === "TestFailure") {
|
|
68
69
|
return null;
|
|
69
70
|
}
|
|
70
|
-
|
|
71
|
+
|
|
71
72
|
// Filter out common user code errors (ReferenceError, TypeError from user tests)
|
|
72
73
|
// Only report if the error originates from TestDriver SDK code, not user test code
|
|
73
74
|
const isUserCodeError = exception.stacktrace?.frames?.some(frame => {
|
|
74
75
|
const filename = frame.filename || frame.abs_path || "";
|
|
75
76
|
// Check if error is from user test files (not from SDK internals)
|
|
76
|
-
return filename.includes("/tests/") ||
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
return filename.includes("/tests/") ||
|
|
78
|
+
filename.includes("/test/") ||
|
|
79
|
+
filename.includes(".test.") ||
|
|
80
|
+
filename.includes(".spec.");
|
|
80
81
|
});
|
|
81
|
-
|
|
82
|
+
|
|
82
83
|
if (isUserCodeError && (exception.type === "ReferenceError" || exception.type === "TypeError")) {
|
|
83
84
|
return null;
|
|
84
85
|
}
|
|
@@ -89,11 +90,11 @@ function initializeSentry() {
|
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
+
|
|
93
94
|
return event;
|
|
94
95
|
},
|
|
95
96
|
});
|
|
96
|
-
|
|
97
|
+
|
|
97
98
|
sentryInitialized = true;
|
|
98
99
|
logger.debug("Sentry initialized for vitest");
|
|
99
100
|
} catch (err) {
|
|
@@ -123,17 +124,17 @@ async function flushSentry(timeout = 2000) {
|
|
|
123
124
|
function resolveTestDriverVersion() {
|
|
124
125
|
try {
|
|
125
126
|
return require("../package.json").version;
|
|
126
|
-
} catch {}
|
|
127
|
+
} catch { }
|
|
127
128
|
|
|
128
129
|
try {
|
|
129
130
|
const cwdRequire = createRequire(path.join(process.cwd(), "package.json"));
|
|
130
131
|
return cwdRequire("testdriverai/package.json").version;
|
|
131
|
-
} catch {}
|
|
132
|
+
} catch { }
|
|
132
133
|
|
|
133
134
|
try {
|
|
134
135
|
const pkgPath = path.join(process.cwd(), "node_modules", "testdriverai", "package.json");
|
|
135
136
|
return JSON.parse(fs.readFileSync(pkgPath, "utf8")).version;
|
|
136
|
-
} catch {}
|
|
137
|
+
} catch { }
|
|
137
138
|
|
|
138
139
|
return null;
|
|
139
140
|
}
|
|
@@ -154,19 +155,19 @@ function resolveVitestVersion() {
|
|
|
154
155
|
// Strategy 1: createRequire from import.meta.url (standard CJS interop)
|
|
155
156
|
try {
|
|
156
157
|
return require("vitest/package.json").version;
|
|
157
|
-
} catch {}
|
|
158
|
+
} catch { }
|
|
158
159
|
|
|
159
160
|
// Strategy 2: createRequire from process.cwd() (works when import.meta.url is rewritten)
|
|
160
161
|
try {
|
|
161
162
|
const cwdRequire = createRequire(path.join(process.cwd(), "package.json"));
|
|
162
163
|
return cwdRequire("vitest/package.json").version;
|
|
163
|
-
} catch {}
|
|
164
|
+
} catch { }
|
|
164
165
|
|
|
165
166
|
// Strategy 3: read directly from node_modules on disk
|
|
166
167
|
try {
|
|
167
168
|
const vitestPkgPath = path.join(process.cwd(), "node_modules", "vitest", "package.json");
|
|
168
169
|
return JSON.parse(fs.readFileSync(vitestPkgPath, "utf8")).version;
|
|
169
|
-
} catch {}
|
|
170
|
+
} catch { }
|
|
170
171
|
|
|
171
172
|
return null;
|
|
172
173
|
}
|
|
@@ -181,7 +182,7 @@ function checkVitestVersion() {
|
|
|
181
182
|
if (!version) {
|
|
182
183
|
throw new Error(
|
|
183
184
|
"TestDriver requires Vitest to be installed. " +
|
|
184
|
-
|
|
185
|
+
"Please install it: npm install vitest@latest",
|
|
185
186
|
);
|
|
186
187
|
}
|
|
187
188
|
|
|
@@ -189,7 +190,7 @@ function checkVitestVersion() {
|
|
|
189
190
|
if (major < MINIMUM_VITEST_VERSION) {
|
|
190
191
|
throw new Error(
|
|
191
192
|
`TestDriver requires Vitest >= ${MINIMUM_VITEST_VERSION}.0.0, but found ${version}. ` +
|
|
192
|
-
|
|
193
|
+
`Please upgrade Vitest: npm install vitest@latest`,
|
|
193
194
|
);
|
|
194
195
|
}
|
|
195
196
|
}
|
|
@@ -379,7 +380,7 @@ export async function authenticateWithApiKey(apiKey, apiRoot) {
|
|
|
379
380
|
// Network-level error (fetch failed entirely)
|
|
380
381
|
const networkError = new Error(
|
|
381
382
|
`Unable to reach TestDriver API at ${apiRoot}. ` +
|
|
382
|
-
|
|
383
|
+
"Check your internet connection and try again.",
|
|
383
384
|
);
|
|
384
385
|
networkError.code = "NETWORK_ERROR";
|
|
385
386
|
networkError.isNetworkError = true;
|
|
@@ -399,8 +400,8 @@ export async function authenticateWithApiKey(apiKey, apiRoot) {
|
|
|
399
400
|
if (response.status === 401) {
|
|
400
401
|
const authError = new Error(
|
|
401
402
|
data.message ||
|
|
402
|
-
|
|
403
|
-
|
|
403
|
+
"Invalid API key. Please check your TD_API_KEY and try again. " +
|
|
404
|
+
"Get your API key at https://console.testdriver.ai/team",
|
|
404
405
|
);
|
|
405
406
|
authError.code = data.error || "INVALID_API_KEY";
|
|
406
407
|
authError.isAuthError = true;
|
|
@@ -411,7 +412,7 @@ export async function authenticateWithApiKey(apiKey, apiRoot) {
|
|
|
411
412
|
if (response.status >= 500) {
|
|
412
413
|
const serverError = new Error(
|
|
413
414
|
data.message ||
|
|
414
|
-
|
|
415
|
+
`TestDriver API is currently unavailable (HTTP ${response.status}). Please try again later.`,
|
|
415
416
|
);
|
|
416
417
|
serverError.code = data.error || "API_UNAVAILABLE";
|
|
417
418
|
serverError.isServerError = true;
|
|
@@ -431,7 +432,7 @@ export async function authenticateWithApiKey(apiKey, apiRoot) {
|
|
|
431
432
|
// Other HTTP errors
|
|
432
433
|
throw new Error(
|
|
433
434
|
`Authentication failed: ${response.status} ${response.statusText}` +
|
|
434
|
-
|
|
435
|
+
(data.message ? ` - ${data.message}` : ""),
|
|
435
436
|
);
|
|
436
437
|
}
|
|
437
438
|
|
|
@@ -1143,7 +1144,7 @@ class TestDriverReporter {
|
|
|
1143
1144
|
const error = result.errors[0];
|
|
1144
1145
|
errorMessage = error.message;
|
|
1145
1146
|
errorStack = error.stack;
|
|
1146
|
-
|
|
1147
|
+
|
|
1147
1148
|
// Note: We do NOT report test failures to Sentry.
|
|
1148
1149
|
// Test failures are expected behavior (they indicate a test found a bug).
|
|
1149
1150
|
// We only want actual SDK crashes and exceptions reported to Sentry.
|
|
@@ -1213,8 +1214,9 @@ class TestDriverReporter {
|
|
|
1213
1214
|
|
|
1214
1215
|
console.log("");
|
|
1215
1216
|
console.log(
|
|
1216
|
-
`🔗 Test Report: ${consoleUrl}/runs/${testRunDbId}/${testCaseDbId}
|
|
1217
|
+
chalk.cyan(`🔗 Test Report: ${consoleUrl}/runs/${testRunDbId}/${testCaseDbId}`),
|
|
1217
1218
|
);
|
|
1219
|
+
console.log("");
|
|
1218
1220
|
|
|
1219
1221
|
// If there were retries, list all per-attempt dashcam URLs for debugging
|
|
1220
1222
|
if (hasRetries) {
|
|
@@ -1226,7 +1228,7 @@ class TestDriverReporter {
|
|
|
1226
1228
|
}
|
|
1227
1229
|
}
|
|
1228
1230
|
}
|
|
1229
|
-
|
|
1231
|
+
|
|
1230
1232
|
// Output parseable format for docs generation (examples only)
|
|
1231
1233
|
if (testFile.startsWith("examples/")) {
|
|
1232
1234
|
const testFileName = path.basename(testFile);
|
|
@@ -1252,12 +1254,20 @@ class TestDriverReporter {
|
|
|
1252
1254
|
* @returns {string} The corresponding web console URL
|
|
1253
1255
|
*/
|
|
1254
1256
|
function getConsoleUrl(apiRoot) {
|
|
1255
|
-
//
|
|
1256
|
-
if (process.env.
|
|
1257
|
+
// Explicit override — use TD_CONSOLE_URL when deliberately set
|
|
1258
|
+
if (process.env.TD_CONSOLE_URL) return process.env.TD_CONSOLE_URL;
|
|
1257
1259
|
|
|
1258
1260
|
if (!apiRoot) return "https://console.testdriver.ai";
|
|
1259
1261
|
|
|
1260
|
-
//
|
|
1262
|
+
// Fly.io: swap "-api" for "-web" in the hostname
|
|
1263
|
+
// e.g. preview-138-api.fly.dev -> preview-138-web.fly.dev
|
|
1264
|
+
// td-test-api.fly.dev -> td-test-web.fly.dev
|
|
1265
|
+
const flyMatch = apiRoot.match(/https:\/\/([\w-]+)-api\.fly\.dev/);
|
|
1266
|
+
if (flyMatch) {
|
|
1267
|
+
return `https://${flyMatch[1]}-web.fly.dev`;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Known channel API URLs -> console equivalents
|
|
1261
1271
|
// e.g. https://api-canary.testdriver.ai -> https://console-canary.testdriver.ai
|
|
1262
1272
|
for (const url of Object.values(channelConfig.channels)) {
|
|
1263
1273
|
if (url === apiRoot) {
|
|
@@ -1265,27 +1275,19 @@ function getConsoleUrl(apiRoot) {
|
|
|
1265
1275
|
}
|
|
1266
1276
|
}
|
|
1267
1277
|
|
|
1268
|
-
// Local development
|
|
1278
|
+
// Local development
|
|
1269
1279
|
if (apiRoot.includes("ngrok.io") || apiRoot.includes("trycloudflare.com") || apiRoot.includes("localhost")) {
|
|
1270
|
-
return
|
|
1280
|
+
return "http://localhost:3001";
|
|
1271
1281
|
}
|
|
1272
1282
|
|
|
1273
|
-
// Render PR previews
|
|
1274
|
-
// canary-api-pr-123.onrender.com -> canary-web-pr-123.onrender.com
|
|
1275
|
-
// testdriver-api-i4m4-pr-123.onrender.com -> web-i4m4-pr-123.onrender.com
|
|
1283
|
+
// Render PR previews (legacy)
|
|
1276
1284
|
const renderPrMatch = apiRoot.match(/https:\/\/([\w-]+)-api(-[\w]+)?(-pr-\d+)?\.onrender\.com/);
|
|
1277
1285
|
if (renderPrMatch) {
|
|
1278
1286
|
const [, prefix, suffix, prSuffix] = renderPrMatch;
|
|
1279
|
-
|
|
1280
|
-
if (prefix === 'testdriver' && suffix) {
|
|
1281
|
-
webPrefix = 'web' + suffix;
|
|
1282
|
-
} else {
|
|
1283
|
-
webPrefix = prefix + '-web';
|
|
1284
|
-
}
|
|
1287
|
+
const webPrefix = (prefix === 'testdriver' && suffix) ? 'web' + suffix : prefix + '-web';
|
|
1285
1288
|
return `https://${webPrefix}${prSuffix || ''}.onrender.com`;
|
|
1286
1289
|
}
|
|
1287
1290
|
|
|
1288
|
-
// Other tunnels or unknown hosts: return as-is
|
|
1289
1291
|
return apiRoot;
|
|
1290
1292
|
}
|
|
1291
1293
|
|
package/lib/core/Dashcam.js
CHANGED
|
@@ -104,7 +104,7 @@ class Dashcam {
|
|
|
104
104
|
return `https://console-${envMatch[1]}.testdriver.ai`;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
// Production: API on
|
|
107
|
+
// Production: API on custom domain or v6 -> Console on testdriver.ai
|
|
108
108
|
if (
|
|
109
109
|
apiRoot.includes("api.testdriver.ai") ||
|
|
110
110
|
apiRoot.includes("v6.testdriver.ai")
|
|
@@ -117,21 +117,31 @@ class Dashcam {
|
|
|
117
117
|
return "http://localhost:3001";
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
// Fly.io PR previews: map API app to Web app
|
|
121
|
+
// pr-123-api.fly.dev -> pr-123-web.fly.dev
|
|
122
|
+
const flyPrMatch = apiRoot.match(/https:\/\/(pr-\d+)-api\.fly\.dev/);
|
|
123
|
+
if (flyPrMatch) {
|
|
124
|
+
const [, prPrefix] = flyPrMatch;
|
|
125
|
+
return `https://${prPrefix}-web.fly.dev`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Fly.io environment apps: test-api.fly.dev -> test-web.fly.dev
|
|
129
|
+
const flyEnvMatch = apiRoot.match(/https:\/\/([\w-]+)-api\.fly\.dev/);
|
|
130
|
+
if (flyEnvMatch) {
|
|
131
|
+
const [, prefix] = flyEnvMatch;
|
|
132
|
+
return `https://${prefix}-web.fly.dev`;
|
|
133
|
+
}
|
|
134
|
+
|
|
120
135
|
// Render PR previews: map API service to Web service
|
|
121
136
|
// canary-api-pr-123.onrender.com -> canary-web-pr-123.onrender.com
|
|
122
137
|
// testdriver-api-i4m4-pr-123.onrender.com -> web-i4m4-pr-123.onrender.com
|
|
123
138
|
const renderPrMatch = apiRoot.match(/https:\/\/([\w-]+)-api(-[\w]+)?(-pr-\d+)?\.onrender\.com/);
|
|
124
139
|
if (renderPrMatch) {
|
|
125
140
|
const [, prefix, suffix, prSuffix] = renderPrMatch;
|
|
126
|
-
// Map API naming to Web naming:
|
|
127
|
-
// canary-api -> canary-web
|
|
128
|
-
// testdriver-api-i4m4 -> web-i4m4
|
|
129
141
|
let webPrefix;
|
|
130
142
|
if (prefix === 'testdriver' && suffix) {
|
|
131
|
-
// testdriver-api-i4m4 -> web-i4m4
|
|
132
143
|
webPrefix = 'web' + suffix;
|
|
133
144
|
} else {
|
|
134
|
-
// canary-api -> canary-web
|
|
135
145
|
webPrefix = prefix + '-web';
|
|
136
146
|
}
|
|
137
147
|
return `https://${webPrefix}${prSuffix || ''}.onrender.com`;
|
|
@@ -446,7 +456,7 @@ class Dashcam {
|
|
|
446
456
|
this.recording = false;
|
|
447
457
|
|
|
448
458
|
// Extract the /replay/... path from CLI output and reconstruct the URL
|
|
449
|
-
// using getConsoleUrl(). The CLI may return a wrong domain
|
|
459
|
+
// using getConsoleUrl(). The CLI may return a wrong domain
|
|
450
460
|
// so we always rewrite the base URL to match the current environment.
|
|
451
461
|
if (output) {
|
|
452
462
|
// Match /replay/{id} with optional query params from any URL or broken prefix
|