testdriverai 7.9.70-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.
- package/docs/_data/examples-manifest.schema.json +12 -2
- package/docs/_scripts/extract-example-urls.js +21 -3
- package/docs/_scripts/generate-examples.js +39 -15
- package/docs/v7/customizing-devices.mdx +6 -4
- package/docs/v7/machine-setup.mdx +8 -6
- package/docs/v7/provision.mdx +0 -20
- package/package.json +1 -1
|
@@ -14,8 +14,18 @@
|
|
|
14
14
|
"properties": {
|
|
15
15
|
"url": {
|
|
16
16
|
"type": "string",
|
|
17
|
-
"description": "Full URL to the test run (
|
|
18
|
-
"pattern": "^https://console
|
|
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
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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,
|
|
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
|
-
|
|
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="${
|
|
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
|
|
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 (
|
|
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="${
|
|
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
|
|
588
|
-
if (
|
|
589
|
-
const iframeUpdated = updateExistingMDX(content, testMeta.filename,
|
|
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;
|
|
@@ -283,9 +283,9 @@ const testdriver = TestDriver(context, {
|
|
|
283
283
|
});
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
-
###
|
|
286
|
+
### Connecting to an Existing Sandbox
|
|
287
287
|
|
|
288
|
-
Speed up test development by
|
|
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
|
-
|
|
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
|
-
|
|
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 —
|
|
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
|
-
|
|
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
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
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.
|
package/docs/v7/provision.mdx
CHANGED
|
@@ -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
|