testdriverai 7.9.69-test → 7.9.71-test

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.
@@ -415,6 +415,18 @@
415
415
  : "TestDriver";
416
416
  document.title = `${testFileName} - Debugger`;
417
417
 
418
+ const shouldDowngradeToHttp =
419
+ window.location.protocol === "https:" &&
420
+ parsedData?.os === "windows" &&
421
+ typeof parsedData?.url === "string" &&
422
+ parsedData.url.startsWith("http://");
423
+
424
+ if (shouldDowngradeToHttp) {
425
+ const redirectUrl = new URL(window.location.href);
426
+ redirectUrl.protocol = "http:";
427
+ window.location.replace(redirectUrl.toString());
428
+ }
429
+
418
430
  // Embed the VNC URL in the iframe with token
419
431
  iframe.style.display = "block";
420
432
  const vncUrl = new URL(parsedData.url);
@@ -14,8 +14,18 @@
14
14
  "properties": {
15
15
  "url": {
16
16
  "type": "string",
17
- "description": "Full URL to the test run (without ?embed=true)",
18
- "pattern": "^https://console\\.testdriver\\.ai/runs/.+"
17
+ "description": "Full URL to the test run (legacy source for testcase ID)",
18
+ "pattern": "^https://console(?:-(?:test|canary))?\\.testdriver\\.ai/runs/.+"
19
+ },
20
+ "replayUrl": {
21
+ "type": "string",
22
+ "description": "Public replay URL with share key",
23
+ "pattern": "^https://console(?:-(?:test|canary))?\\.testdriver\\.ai/replay/.+"
24
+ },
25
+ "embedUrl": {
26
+ "type": "string",
27
+ "description": "Public replay embed URL used by docs iframes",
28
+ "pattern": "^https://console(?:-(?:test|canary))?\\.testdriver\\.ai/replay/.+"
19
29
  },
20
30
  "lastUpdated": {
21
31
  "type": "string",
@@ -15,6 +15,19 @@ const path = require("path");
15
15
 
16
16
  const MANIFEST_PATH = path.join(__dirname, "../_data/examples-manifest.json");
17
17
 
18
+ function deriveEmbedUrlFromReplayUrl(replayUrl) {
19
+ if (!replayUrl) return null;
20
+ try {
21
+ const url = new URL(replayUrl);
22
+ if (!url.pathname.includes("/replay/")) return null;
23
+ if (!url.searchParams.get("share")) return null;
24
+ url.searchParams.set("embed", "true");
25
+ return url.toString();
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
18
31
  // Parse command line arguments
19
32
  function parseArgs() {
20
33
  const args = process.argv.slice(2);
@@ -81,12 +94,16 @@ function processResultsDir(resultsDir) {
81
94
  const result = JSON.parse(content);
82
95
  const testFileName = path.basename(result.test?.file || result.testFile || entry.name);
83
96
  const url = result.urls?.testRun || result.testRunLink;
97
+ const replayUrl = result.replay?.url || result.replayUrl || null;
98
+ const embedUrl = result.replay?.embedUrl || deriveEmbedUrlFromReplayUrl(replayUrl);
84
99
 
85
- if (!url) continue;
100
+ if (!url && !embedUrl) continue;
86
101
 
87
102
  const isNew = !manifest.examples[testFileName];
88
103
  manifest.examples[testFileName] = {
89
- url: url,
104
+ url: url || manifest.examples[testFileName]?.url || null,
105
+ ...(replayUrl ? { replayUrl } : {}),
106
+ ...(embedUrl ? { embedUrl } : {}),
90
107
  lastUpdated: result.date || new Date().toISOString(),
91
108
  };
92
109
 
@@ -96,7 +113,8 @@ function processResultsDir(resultsDir) {
96
113
  stats.updated++;
97
114
  }
98
115
 
99
- console.log(`${isNew ? "➕" : "🔄"} ${testFileName}: ${url}`);
116
+ const displayUrl = embedUrl || url || "(no URL)";
117
+ console.log(`${isNew ? "➕" : "🔄"} ${testFileName}: ${displayUrl}`);
100
118
  } catch (err) {
101
119
  console.warn(`⚠️ Failed to read ${jsonFile}: ${err.message}`);
102
120
  }
@@ -302,23 +302,48 @@ function apiRootFromConsoleUrl(sourceUrl) {
302
302
  }
303
303
  }
304
304
 
305
- // Generate replay URL from testcase ID
306
- // sourceUrl is the manifest URL used to infer the correct API environment
307
- function generateReplayUrl(testcaseId, sourceUrl) {
308
- // Use the API replay endpoint which handles the redirect with embed=true
309
- const apiRoot = apiRootFromConsoleUrl(sourceUrl) || process.env.TD_API_ROOT || 'https://api.testdriver.ai';
305
+ function normalizeEmbedUrl(rawUrl) {
306
+ if (!rawUrl) return null;
307
+ try {
308
+ const url = new URL(rawUrl);
309
+ if (!url.pathname.includes('/replay/')) return null;
310
+ if (!url.searchParams.get('share')) return null;
311
+ url.searchParams.set('embed', 'true');
312
+ return url.toString();
313
+ } catch {
314
+ return null;
315
+ }
316
+ }
317
+
318
+ // Resolve the best embed URL for docs:
319
+ // 1) explicit embedUrl from manifest (preferred)
320
+ // 2) replayUrl in manifest (converted to embed=true)
321
+ // 3) legacy run URL via testcase replay redirect endpoint
322
+ function resolveReplayEmbedUrl(manifestEntry) {
323
+ const directEmbed = normalizeEmbedUrl(manifestEntry?.embedUrl);
324
+ if (directEmbed) return directEmbed;
325
+
326
+ const replayEmbed = normalizeEmbedUrl(manifestEntry?.replayUrl);
327
+ if (replayEmbed) return replayEmbed;
328
+
329
+ const testcaseId = manifestEntry?.url ? extractTestcaseId(manifestEntry.url) : null;
330
+ if (!testcaseId) return null;
331
+
332
+ const apiRoot = apiRootFromConsoleUrl(manifestEntry?.url) || process.env.TD_API_ROOT || 'https://api.testdriver.ai';
310
333
  return `${apiRoot}/api/v1/testdriver/testcase/${testcaseId}/replay`;
311
334
  }
312
335
 
313
336
  // Update existing MDX file by finding the marker comment and replacing the iframe
314
- function updateExistingMDX(existingContent, filename, testcaseId, sourceUrl) {
337
+ function updateExistingMDX(existingContent, filename, replayEmbedUrl) {
315
338
  const marker = `{/* ${filename} output */}`;
316
339
 
317
340
  if (!existingContent.includes(marker)) {
318
341
  return null; // Marker not found, can't update
319
342
  }
320
343
 
321
- const replayUrl = generateReplayUrl(testcaseId, sourceUrl);
344
+ if (!replayEmbedUrl) {
345
+ return null;
346
+ }
322
347
 
323
348
  // Pattern to match the marker followed by the iframe tag
324
349
  const escapedFilename = filename.replace(/\./g, '\\.');
@@ -327,7 +352,7 @@ function updateExistingMDX(existingContent, filename, testcaseId, sourceUrl) {
327
352
  's'
328
353
  );
329
354
 
330
- const replacement = `$1<iframe \n src="${replayUrl}"$2/>`;
355
+ const replacement = `$1<iframe \n src="${replayEmbedUrl}"$2/>`;
331
356
  const updated = existingContent.replace(pattern, replacement);
332
357
 
333
358
  if (updated === existingContent) {
@@ -363,7 +388,7 @@ function generateMDX(testMeta, manifest, description) {
363
388
  const sidebarTitle = generateSidebarTitle(testMeta.filename);
364
389
  const shortDescription = generateShortDescription(testMeta);
365
390
  const manifestEntry = manifest.examples[testMeta.filename];
366
- const testcaseId = manifestEntry?.url ? extractTestcaseId(manifestEntry.url) : null;
391
+ const replayEmbedUrl = resolveReplayEmbedUrl(manifestEntry);
367
392
 
368
393
  let mdx = `---
369
394
  title: "${title}"
@@ -379,15 +404,14 @@ ${description}
379
404
  `;
380
405
 
381
406
  // Add Live Test Run section if URL exists
382
- if (testcaseId) {
383
- const replayUrl = generateReplayUrl(testcaseId, manifestEntry?.url);
407
+ if (replayEmbedUrl) {
384
408
  mdx += `## Live Test Run
385
409
 
386
410
  Watch this test execute in a real sandbox environment:
387
411
 
388
412
  {/* ${testMeta.filename} output */}
389
413
  <iframe
390
- src="${replayUrl}"
414
+ src="${replayEmbedUrl}"
391
415
  width="100%"
392
416
  height="600"
393
417
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -584,9 +608,9 @@ async function main() {
584
608
 
585
609
  // Update iframe URL if manifest entry exists
586
610
  const manifestEntry = manifest.examples[testMeta.filename];
587
- const testcaseId = manifestEntry?.url ? extractTestcaseId(manifestEntry.url) : null;
588
- if (testcaseId) {
589
- const iframeUpdated = updateExistingMDX(content, testMeta.filename, testcaseId, manifestEntry.url);
611
+ const replayEmbedUrl = resolveReplayEmbedUrl(manifestEntry);
612
+ if (replayEmbedUrl) {
613
+ const iframeUpdated = updateExistingMDX(content, testMeta.filename, replayEmbedUrl);
590
614
  if (iframeUpdated && iframeUpdated !== content) {
591
615
  content = iframeUpdated;
592
616
  changed = true;
package/docs/docs.json CHANGED
@@ -68,7 +68,7 @@
68
68
  "pages": [
69
69
  "/v7/generating-tests",
70
70
  "/v7/device-config",
71
- "/v7/machine-setup",
71
+ "/v7/machine-setup",
72
72
  "/v7/locating-elements",
73
73
  "/v7/waiting-for-elements",
74
74
  "/v7/performing-actions",
@@ -283,9 +283,9 @@ const testdriver = TestDriver(context, {
283
283
  });
284
284
  ```
285
285
 
286
- ### Reconnecting to Existing Sandbox
286
+ ### Connecting to an Existing Sandbox
287
287
 
288
- Speed up test development by reconnecting to an existing sandbox instead of starting fresh each time. This lets you iterate quickly on failing steps without re-running the entire test from the beginning.
288
+ Speed up test development by connecting to an existing sandbox instead of starting fresh each time. This lets you iterate quickly on failing steps without re-running the entire test from the beginning.
289
289
 
290
290
  Split your test into two files: one for known-good steps that set up the desired state, and another for work-in-progress steps you want to debug.
291
291
 
@@ -298,8 +298,10 @@ const testdriver = TestDriver(context, {
298
298
  ```javascript work-in-progress.test.mjs
299
299
  // Second test file: experiment.test.mjs (run within keepAlive window)
300
300
  const testdriver = TestDriver(context, {
301
- reconnect: true, // Reconnect to existing sandbox
301
+ keepAlive: 60000,
302
302
  });
303
+
304
+ await testdriver.connect({ sandboxId: "sandbox-abc123" });
303
305
  ```
304
306
 
305
307
  Then, you can run both tests in sequence:
@@ -315,5 +317,5 @@ vitest run work-in-progress.test.mjs
315
317
  ```
316
318
 
317
319
  <Warning>
318
- Reconnect only works if run within the `keepAlive` window of the previous test.
320
+ Connecting to the same machine only works if run within the `keepAlive` window of the previous test.
319
321
  </Warning>
@@ -102,23 +102,25 @@ await testdriver.provision.chrome({ url: "https://example.com" });
102
102
 
103
103
  When this test finishes, the sandbox stays running for 30 minutes instead of being terminated immediately.
104
104
 
105
- ### Step 2 — Reconnect in subsequent runs
105
+ ### Step 2 — Connect in subsequent runs
106
106
 
107
107
  ```javascript
108
108
  // second.test.mjs
109
109
  const testdriver = TestDriver(context, {
110
110
  os: "windows",
111
- reconnect: true, // reads last sandbox ID from disk, skips provisioning
111
+ keepAlive: 30 * 60 * 1000,
112
112
  });
113
113
 
114
+ await testdriver.connect({ sandboxId: "sandbox-abc123" });
115
+
114
116
  // provision.chrome() is automatically skipped — Chrome is already open
115
117
  await testdriver.find("Sign In button").click();
116
118
  ```
117
119
 
118
- When `reconnect: true` is set:
119
- - The SDK reads the last sandbox ID from a local file via `getLastSandboxId()`
120
- - All `provision.*` calls are silently skipped since the application is already running
121
- - An error is thrown if no previous sandbox ID is found
120
+ When connecting to an existing sandbox ID:
121
+ - You reuse a specific running machine directly
122
+ - You can continue from the app state created in an earlier test run
123
+ - You should run within the previous test's `keepAlive` window
122
124
 
123
125
  <Tip>
124
126
  You can also supply a sandbox ID directly: `await testdriver.connect({ sandboxId: "sandbox-abc123" })`. Use `testdriver.getLastSandboxId()` to retrieve the ID of the last sandbox for scripting purposes.
@@ -16,10 +16,6 @@ Access provision methods via `testdriver.provision.*`:
16
16
  await testdriver.provision.chrome({ url: 'https://example.com' });
17
17
  ```
18
18
 
19
- <Note>
20
- When `reconnect: true` is set on the client, **all provision methods are skipped** since the application is assumed to already be running.
21
- </Note>
22
-
23
19
  ## Methods
24
20
 
25
21
  ### chrome()
@@ -236,22 +232,6 @@ await testdriver.provision.dashcam({
236
232
  });
237
233
  ```
238
234
 
239
- ## Reconnect Behavior
240
-
241
- When `reconnect: true` is set on the client, all provision methods are wrapped in a Proxy that intercepts calls and skips them silently. This is because when reconnecting to an existing sandbox, the applications are already running.
242
-
243
- ```javascript
244
- const testdriver = new TestDriver({
245
- reconnect: true,
246
- });
247
-
248
- await testdriver.ready();
249
-
250
- // These calls are silently skipped:
251
- await testdriver.provision.chrome({ url: 'https://example.com' });
252
- await testdriver.provision.dashcam();
253
- ```
254
-
255
235
  ## Types
256
236
 
257
237
  ```typescript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testdriverai",
3
- "version": "7.9.69-test",
3
+ "version": "7.9.71-test",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "sdk.js",
6
6
  "types": "sdk.d.ts",
package/sdk.js CHANGED
@@ -3849,7 +3849,8 @@ CAPTCHA_SOLVER_EOF`,
3849
3849
  const apiRoot = this.config?.TD_API_ROOT || 'unknown';
3850
3850
  const apiKey = this.config?.TD_API_KEY || '';
3851
3851
  const maskedKey = apiKey.length > 4 ? '***' + apiKey.slice(-4) : '(not set)';
3852
- const env = process.env.TD_CHANNEL || process.env.TD_ENV || 'unknown';
3852
+ const channelConfig = require('./lib/resolve-channel.js');
3853
+ const env = process.env.TD_CHANNEL || process.env.TD_ENV || channelConfig.active || 'unknown';
3853
3854
  const os = this.os || this.agent?.options?.os || process.env.TD_OS || 'linux';
3854
3855
  const sdkVersion = require('./package.json').version;
3855
3856