testdriverai 7.8.0-test.32 → 7.8.0-test.38

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.
@@ -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/69b9f23308cd8455316368d7/replay"
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/69b9f24e08cd8455316368e3/replay"
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/69b9f23d08cd8455316368de/replay"
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/69b9f25508cd8455316368e7/replay"
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/69b9f23fcb6e450d5272d317/replay"
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/69b9f23940cc10fb18cf673a/replay"
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/69b9f23508cd8455316368d9/replay"
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/69b9f24640cc10fb18cf6745/replay"
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/69b9f24408cd8455316368e1/replay"
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/69b9f24bcb6e450d5272d31b/replay"
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/69b9f243cb6e450d5272d319/replay"
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/69b9f24108cd8455316368df/replay"
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/69b9f23acb6e450d5272d316/replay"
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
- filename.includes("/test/") ||
78
- filename.includes(".test.") ||
79
- filename.includes(".spec.");
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
- "Please install it: npm install vitest@latest",
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
- `Please upgrade Vitest: npm install vitest@latest`,
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
- "Check your internet connection and try again.",
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
- "Invalid API key. Please check your TD_API_KEY and try again. " +
403
- "Get your API key at https://console.testdriver.ai/team",
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
- `TestDriver API is currently unavailable (HTTP ${response.status}). Please try again later.`,
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
- (data.message ? ` - ${data.message}` : ""),
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
- // Allow explicit override via env (e.g. VITE_DOMAIN from .env)
1256
- if (process.env.VITE_DOMAIN) return process.env.VITE_DOMAIN;
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
- // Map known channel API URLs to their console equivalents
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: ngrok/cloudflare tunnels -> localhost:3001
1278
+ // Local development
1269
1279
  if (apiRoot.includes("ngrok.io") || apiRoot.includes("trycloudflare.com") || apiRoot.includes("localhost")) {
1270
- return `http://localhost:3001`;
1280
+ return "http://localhost:3001";
1271
1281
  }
1272
1282
 
1273
- // Render PR previews: map API service to Web service
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
- let webPrefix;
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
 
@@ -104,7 +104,7 @@ class Dashcam {
104
104
  return `https://console-${envMatch[1]}.testdriver.ai`;
105
105
  }
106
106
 
107
- // Production: API on render.com or v6 -> Console on testdriver.ai
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 (e.g. canary-web.onrender.com)
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