testdriverai 7.5.0 → 7.5.9

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.
Files changed (119) hide show
  1. package/{ai/skills/testdriver:quickstart → .github/skills/testdriver-quickstart}/SKILL.md +0 -1
  2. package/.github/workflows/acceptance.yaml +2 -1
  3. package/.github/workflows/publish.yaml +1 -1
  4. package/.github/workflows/windows-self-hosted.yaml +3 -2
  5. package/CHANGELOG.md +336 -0
  6. package/agent/index.js +22 -15
  7. package/agent/lib/commands.js +1 -1
  8. package/agent/lib/sandbox.js +115 -11
  9. package/agent/lib/sdk.js +2 -1
  10. package/agent/lib/system.js +48 -28
  11. package/{.github/skills/testdriver:quickstart → ai/skills/testdriver-quickstart}/SKILL.md +0 -2
  12. package/debugger/index.html +49 -368
  13. package/docs/_scripts/generate-skills.js +7 -2
  14. package/docs/v7/quickstart.mdx +0 -2
  15. package/interfaces/cli/lib/base.js +1 -1
  16. package/interfaces/vitest-plugin.mjs +6 -5
  17. package/lib/core/Dashcam.js +51 -24
  18. package/lib/sentry.js +1 -1
  19. package/package.json +1 -1
  20. package/sdk.js +21 -28
  21. package/vitest.config.mjs +5 -5
  22. package/vscode-extension/src/extension.ts +1 -1
  23. package/SKILLs.md +0 -17
  24. package/agent/lib/debugger-server.js +0 -236
  25. package/agent/lib/debugger.js +0 -45
  26. package/test-ide-preview.mjs +0 -17
  27. package/tests/airbnb-booking.test.mjs +0 -39
  28. package/tests/airbnb-search.test.mjs +0 -43
  29. package/tests/example.test.js +0 -33
  30. package/tests/login.js +0 -28
  31. /package/.github/skills/{testdriver:ai → testdriver-ai}/SKILL.md +0 -0
  32. /package/.github/skills/{testdriver:assert → testdriver-assert}/SKILL.md +0 -0
  33. /package/.github/skills/{testdriver:aws-setup → testdriver-aws-setup}/SKILL.md +0 -0
  34. /package/.github/skills/{testdriver:caching → testdriver-caching}/SKILL.md +0 -0
  35. /package/.github/skills/{testdriver:captcha → testdriver-captcha}/SKILL.md +0 -0
  36. /package/.github/skills/{testdriver:ci-cd → testdriver-ci-cd}/SKILL.md +0 -0
  37. /package/.github/skills/{testdriver:click → testdriver-click}/SKILL.md +0 -0
  38. /package/.github/skills/{testdriver:client → testdriver-client}/SKILL.md +0 -0
  39. /package/.github/skills/{testdriver:cloud → testdriver-cloud}/SKILL.md +0 -0
  40. /package/.github/skills/{testdriver:customizing-devices → testdriver-customizing-devices}/SKILL.md +0 -0
  41. /package/.github/skills/{testdriver:dashcam → testdriver-dashcam}/SKILL.md +0 -0
  42. /package/.github/skills/{testdriver:debugging-with-screenshots → testdriver-debugging-with-screenshots}/SKILL.md +0 -0
  43. /package/.github/skills/{testdriver:device-config → testdriver-device-config}/SKILL.md +0 -0
  44. /package/.github/skills/{testdriver:double-click → testdriver-double-click}/SKILL.md +0 -0
  45. /package/.github/skills/{testdriver:elements → testdriver-elements}/SKILL.md +0 -0
  46. /package/.github/skills/{testdriver:enterprise → testdriver-enterprise}/SKILL.md +0 -0
  47. /package/.github/skills/{testdriver:examples → testdriver-examples}/SKILL.md +0 -0
  48. /package/.github/skills/{testdriver:exec → testdriver-exec}/SKILL.md +0 -0
  49. /package/.github/skills/{testdriver:find → testdriver-find}/SKILL.md +0 -0
  50. /package/.github/skills/{testdriver:focus-application → testdriver-focus-application}/SKILL.md +0 -0
  51. /package/.github/skills/{testdriver:generating-tests → testdriver-generating-tests}/SKILL.md +0 -0
  52. /package/.github/skills/{testdriver:hover → testdriver-hover}/SKILL.md +0 -0
  53. /package/.github/skills/{testdriver:locating-elements → testdriver-locating-elements}/SKILL.md +0 -0
  54. /package/.github/skills/{testdriver:making-assertions → testdriver-making-assertions}/SKILL.md +0 -0
  55. /package/.github/skills/{testdriver:mcp-workflow → testdriver-mcp-workflow}/SKILL.md +0 -0
  56. /package/.github/skills/{testdriver:mouse-down → testdriver-mouse-down}/SKILL.md +0 -0
  57. /package/.github/skills/{testdriver:mouse-up → testdriver-mouse-up}/SKILL.md +0 -0
  58. /package/.github/skills/{testdriver:parse → testdriver-parse}/SKILL.md +0 -0
  59. /package/.github/skills/{testdriver:performing-actions → testdriver-performing-actions}/SKILL.md +0 -0
  60. /package/.github/skills/{testdriver:press-keys → testdriver-press-keys}/SKILL.md +0 -0
  61. /package/.github/skills/{testdriver:reusable-code → testdriver-reusable-code}/SKILL.md +0 -0
  62. /package/.github/skills/{testdriver:right-click → testdriver-right-click}/SKILL.md +0 -0
  63. /package/.github/skills/{testdriver:running-tests → testdriver-running-tests}/SKILL.md +0 -0
  64. /package/.github/skills/{testdriver:screenshot → testdriver-screenshot}/SKILL.md +0 -0
  65. /package/.github/skills/{testdriver:scroll → testdriver-scroll}/SKILL.md +0 -0
  66. /package/.github/skills/{testdriver:secrets → testdriver-secrets}/SKILL.md +0 -0
  67. /package/.github/skills/{testdriver:self-hosted → testdriver-self-hosted}/SKILL.md +0 -0
  68. /package/.github/skills/{testdriver:test-writer → testdriver-test-writer}/SKILL.md +0 -0
  69. /package/.github/skills/{testdriver:testdriver → testdriver-testdriver}/SKILL.md +0 -0
  70. /package/.github/skills/{testdriver:testdriver-mechanic → testdriver-testdriver-mechanic}/SKILL.md +0 -0
  71. /package/.github/skills/{testdriver:type → testdriver-type}/SKILL.md +0 -0
  72. /package/.github/skills/{testdriver:variables → testdriver-variables}/SKILL.md +0 -0
  73. /package/.github/skills/{testdriver:waiting-for-elements → testdriver-waiting-for-elements}/SKILL.md +0 -0
  74. /package/.github/skills/{testdriver:what-is-testdriver → testdriver-what-is-testdriver}/SKILL.md +0 -0
  75. /package/ai/skills/{testdriver:ai → testdriver-ai}/SKILL.md +0 -0
  76. /package/ai/skills/{testdriver:assert → testdriver-assert}/SKILL.md +0 -0
  77. /package/ai/skills/{testdriver:aws-setup → testdriver-aws-setup}/SKILL.md +0 -0
  78. /package/ai/skills/{testdriver:caching → testdriver-caching}/SKILL.md +0 -0
  79. /package/ai/skills/{testdriver:captcha → testdriver-captcha}/SKILL.md +0 -0
  80. /package/ai/skills/{testdriver:ci-cd → testdriver-ci-cd}/SKILL.md +0 -0
  81. /package/ai/skills/{testdriver:click → testdriver-click}/SKILL.md +0 -0
  82. /package/ai/skills/{testdriver:client → testdriver-client}/SKILL.md +0 -0
  83. /package/ai/skills/{testdriver:cloud → testdriver-cloud}/SKILL.md +0 -0
  84. /package/ai/skills/{testdriver:customizing-devices → testdriver-customizing-devices}/SKILL.md +0 -0
  85. /package/ai/skills/{testdriver:dashcam → testdriver-dashcam}/SKILL.md +0 -0
  86. /package/ai/skills/{testdriver:debugging-with-screenshots → testdriver-debugging-with-screenshots}/SKILL.md +0 -0
  87. /package/ai/skills/{testdriver:device-config → testdriver-device-config}/SKILL.md +0 -0
  88. /package/ai/skills/{testdriver:double-click → testdriver-double-click}/SKILL.md +0 -0
  89. /package/ai/skills/{testdriver:elements → testdriver-elements}/SKILL.md +0 -0
  90. /package/ai/skills/{testdriver:enterprise → testdriver-enterprise}/SKILL.md +0 -0
  91. /package/ai/skills/{testdriver:examples → testdriver-examples}/SKILL.md +0 -0
  92. /package/ai/skills/{testdriver:exec → testdriver-exec}/SKILL.md +0 -0
  93. /package/ai/skills/{testdriver:find → testdriver-find}/SKILL.md +0 -0
  94. /package/ai/skills/{testdriver:focus-application → testdriver-focus-application}/SKILL.md +0 -0
  95. /package/ai/skills/{testdriver:generating-tests → testdriver-generating-tests}/SKILL.md +0 -0
  96. /package/ai/skills/{testdriver:hover → testdriver-hover}/SKILL.md +0 -0
  97. /package/ai/skills/{testdriver:locating-elements → testdriver-locating-elements}/SKILL.md +0 -0
  98. /package/ai/skills/{testdriver:making-assertions → testdriver-making-assertions}/SKILL.md +0 -0
  99. /package/ai/skills/{testdriver:mcp-workflow → testdriver-mcp-workflow}/SKILL.md +0 -0
  100. /package/ai/skills/{testdriver:mouse-down → testdriver-mouse-down}/SKILL.md +0 -0
  101. /package/ai/skills/{testdriver:mouse-up → testdriver-mouse-up}/SKILL.md +0 -0
  102. /package/ai/skills/{testdriver:parse → testdriver-parse}/SKILL.md +0 -0
  103. /package/ai/skills/{testdriver:performing-actions → testdriver-performing-actions}/SKILL.md +0 -0
  104. /package/ai/skills/{testdriver:press-keys → testdriver-press-keys}/SKILL.md +0 -0
  105. /package/ai/skills/{testdriver:reusable-code → testdriver-reusable-code}/SKILL.md +0 -0
  106. /package/ai/skills/{testdriver:right-click → testdriver-right-click}/SKILL.md +0 -0
  107. /package/ai/skills/{testdriver:running-tests → testdriver-running-tests}/SKILL.md +0 -0
  108. /package/ai/skills/{testdriver:screenshot → testdriver-screenshot}/SKILL.md +0 -0
  109. /package/ai/skills/{testdriver:scroll → testdriver-scroll}/SKILL.md +0 -0
  110. /package/ai/skills/{testdriver:secrets → testdriver-secrets}/SKILL.md +0 -0
  111. /package/ai/skills/{testdriver:self-hosted → testdriver-self-hosted}/SKILL.md +0 -0
  112. /package/ai/skills/{testdriver:test-writer → testdriver-test-writer}/SKILL.md +0 -0
  113. /package/ai/skills/{testdriver:testdriver → testdriver-testdriver}/SKILL.md +0 -0
  114. /package/ai/skills/{testdriver:testdriver-mechanic → testdriver-testdriver-mechanic}/SKILL.md +0 -0
  115. /package/ai/skills/{testdriver:type → testdriver-type}/SKILL.md +0 -0
  116. /package/ai/skills/{testdriver:variables → testdriver-variables}/SKILL.md +0 -0
  117. /package/ai/skills/{testdriver:waiting-for-elements → testdriver-waiting-for-elements}/SKILL.md +0 -0
  118. /package/ai/skills/{testdriver:what-is-testdriver → testdriver-what-is-testdriver}/SKILL.md +0 -0
  119. /package/{test/api-resilience.test.mjs → lib/vitest/gate-server.mjs} +0 -0
@@ -330,9 +330,6 @@
330
330
 
331
331
  <div class="overlay" id="overlay">
332
332
  <div class="effects" id="effects">
333
- <div class="mouse" id="mouse"></div>
334
- <div class="screenshot" id="screenshot"></div>
335
- <div class="status" id="status"></div>
336
333
  <div class="interaction-overlay" id="interaction-overlay">
337
334
  <svg class="lock-icon" viewBox="0 0 24 24">
338
335
  <path
@@ -346,219 +343,29 @@
346
343
  </div>
347
344
 
348
345
  <script>
349
- // get data from URL parameters
346
+ // Parse session data from URL
350
347
  const urlParams = new URLSearchParams(window.location.search);
351
348
  const data = urlParams.get("data");
352
349
  let parsedData;
353
350
  if (data) {
354
351
  try {
355
352
  parsedData = JSON.parse(atob(data));
356
- console.log("Data from URL:", parsedData);
357
- // You can use parsedData here if needed
358
- } catch (error) {
359
- console.error("Error parsing data from URL:", error);
353
+ } catch (e) {
354
+ console.error("Error parsing data:", e);
360
355
  }
361
- } else {
362
- alert(
363
- "Improperly formatted URL. Please ensure the data parameter is present.",
364
- );
365
356
  }
366
-
367
- const iframe = document.querySelector("#vm-iframe");
368
-
369
- // Detect if OS is Linux
370
- const isLinux = parsedData.os === "linux";
371
- const topBarOffset = isLinux ? 14 : 0;
372
-
373
- // set overlay width and height to match the given resolution
374
- const overlayWidth = parsedData.resolution[0];
375
- const overlayHeight = parsedData.resolution[1];
376
-
377
- iframe.style.display = "block";
378
- iframe.src = parsedData.url;
379
- iframe.style.width = overlayWidth + "px";
380
- // Increase iframe height by 14px for Linux to account for top bar
381
- iframe.style.height = overlayHeight + topBarOffset + "px";
382
-
383
- // Calculate scale factor to fit within window if needed
384
- const windowWidth = window.innerWidth;
385
- const windowHeight = window.innerHeight;
386
- const padding = 20; // Add some padding to prevent cropping
387
- const scaleX = (windowWidth - padding * 2) / overlayWidth;
388
- const scaleY = (windowHeight - padding * 2) / overlayHeight;
389
- const scale = Math.min(scaleX, scaleY, 1); // Don't scale up, only down
390
-
391
- console.log("window:", {
392
- width: windowWidth,
393
- height: windowHeight,
394
- });
395
- console.log("overlay:", {
396
- width: overlayWidth,
397
- height: overlayHeight,
398
- });
399
-
400
- const overlay = document.getElementById("overlay");
401
- overlay.style.width = overlayWidth + "px";
402
- overlay.style.height = overlayHeight + "px";
403
-
404
- console.log("scale", scale);
405
- // Apply scaling if needed
406
- if (scale < 1) {
407
- overlay.style.transform = `scale(${scale})`;
357
+ if (!parsedData || !parsedData.url) {
358
+ alert("Missing or invalid data parameter.");
408
359
  }
409
360
 
410
- // WebSocket connection
411
- const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
412
- const ws = new WebSocket(`${protocol}//${window.location.host}`);
413
-
414
- // WebSocket event handlers
415
- const eventHandlers = new Map();
416
-
417
- const addEventHandler = (event, callback) => {
418
- if (!eventHandlers.has(event)) {
419
- eventHandlers.set(event, []);
420
- }
421
- eventHandlers.get(event).push(callback);
422
- };
423
-
424
- // Activity tracking for title updates
425
- let idleTimeout = null;
426
- const IDLE_THRESHOLD = 5000; // 5 seconds
427
-
428
- // Get test file name for title (use just filename, not full path)
429
- const testFileName = parsedData?.testFile
430
- ? parsedData.testFile.split('/').pop().split('\\\\').pop()
431
- : 'TestDriver';
432
-
433
- const setTitle = (status) => {
434
- document.title = `[${status}] ${testFileName}`;
435
- };
436
-
437
- const resetIdleTimer = () => {
438
- setTitle("Running");
439
- if (idleTimeout) clearTimeout(idleTimeout);
440
- idleTimeout = setTimeout(() => {
441
- setTitle("Idle");
442
- }, IDLE_THRESHOLD);
443
- };
444
-
445
- ws.addEventListener("message", (message) => {
446
- resetIdleTimer();
447
- try {
448
- const data = JSON.parse(message.data);
449
- console.log("WebSocket message received:", data);
450
- const handlers = eventHandlers.get(data.event);
451
- if (handlers) {
452
- handlers.forEach((callback) => callback(null, data.data));
453
- }
454
- } catch (error) {
455
- console.error("Error parsing WebSocket message:", error);
456
- }
457
- });
458
-
459
- ws.addEventListener("open", () => {
460
- console.log("WebSocket connected to TestDriver.ai overlay");
461
-
462
- // Hide loading screen
463
- const loadingScreen = document.getElementById("loading-screen");
464
- loadingScreen.classList.add("hidden");
465
-
466
- // Show connection status briefly
467
- document.getElementById("status").textContent = "Connected";
468
- document.getElementById("status").classList.add("visible");
469
- setTimeout(() => {
470
- document.getElementById("status").classList.remove("visible");
471
- }, 2000);
472
- });
473
-
474
- ws.addEventListener("error", (error) => {
475
- console.error("WebSocket error:", error);
476
- document.getElementById("status").textContent = "Connection Error";
477
- document.getElementById("status").classList.add("visible");
478
- });
479
-
480
- ws.addEventListener("close", () => {
481
- console.log("WebSocket disconnected");
482
- if (idleTimeout) clearTimeout(idleTimeout);
483
- setTitle("Done");
484
- document.getElementById("status").textContent = "Disconnected";
485
- document.getElementById("status").classList.add("visible");
486
- });
487
-
488
- // Event handling (same as original)
489
- const events = {
490
- mouseClick: "mouse-click",
491
- mouseMove: "mouse-move",
492
- screenCapture: {
493
- start: "screen-capture:start",
494
- end: "screen-capture:end",
495
- error: "screen-capture:error",
496
- },
497
- interactive: "interactive",
498
- terminal: {
499
- stdout: "terminal:stdout",
500
- stderr: "terminal:stderr",
501
- },
502
- matches: {
503
- show: "matches:show",
504
- },
505
- vm: {
506
- show: "vm:show",
507
- },
508
- test: {
509
- start: "test:start",
510
- stop: "test:stop",
511
- success: "test:success",
512
- error: "test:error",
513
- },
514
- error: {
515
- fatal: "error:fatal",
516
- general: "error:general",
517
- sdk: "error:sdk",
518
- },
519
- };
520
-
521
- // Title state handlers for immediate feedback
522
- addEventHandler(events.test.start, () => {
523
- if (idleTimeout) clearTimeout(idleTimeout);
524
- setTitle("Running");
525
- });
526
-
527
- addEventHandler(events.test.stop, () => {
528
- if (idleTimeout) clearTimeout(idleTimeout);
529
- setTitle("Stopped");
530
- });
531
-
532
- addEventHandler(events.test.success, () => {
533
- if (idleTimeout) clearTimeout(idleTimeout);
534
- setTitle("Passed");
535
- });
536
-
537
- addEventHandler(events.test.error, () => {
538
- if (idleTimeout) clearTimeout(idleTimeout);
539
- setTitle("Failed");
540
- });
541
-
542
- addEventHandler(events.error.fatal, () => {
543
- if (idleTimeout) clearTimeout(idleTimeout);
544
- setTitle("Error");
545
- });
546
-
547
- addEventHandler(events.error.sdk, () => {
548
- if (idleTimeout) clearTimeout(idleTimeout);
549
- setTitle("Error");
550
- });
551
-
552
- const effects = document.getElementById("effects");
553
- const mouse = document.getElementById("mouse");
554
- const screenshotElement = document.getElementById("screenshot");
361
+ // Elements
362
+ const iframe = document.getElementById("vm-iframe");
363
+ const overlay = document.getElementById("overlay");
364
+ const loadingScreen = document.getElementById("loading-screen");
555
365
  const interactionOverlay = document.getElementById("interaction-overlay");
556
366
 
557
- let boundingBoxesTimeout;
558
- let interactionTimeout;
559
367
  let isInteractionEnabled = false;
560
368
 
561
- // Show interaction overlay on hover, hide on click, and bring back after 30 seconds
562
369
  const showInteractionOverlay = () => {
563
370
  if (!isInteractionEnabled) {
564
371
  interactionOverlay.classList.add("visible");
@@ -568,204 +375,78 @@
568
375
  const hideInteractionOverlay = () => {
569
376
  interactionOverlay.classList.remove("visible");
570
377
  isInteractionEnabled = true;
571
- iframe.classList.add("interactive"); // Enable pointer events on iframe
572
-
573
- // Clear any existing timeout
574
- if (interactionTimeout) {
575
- clearTimeout(interactionTimeout);
576
- }
378
+ iframe.classList.add("interactive");
577
379
  };
578
380
 
579
381
  const disableInteraction = () => {
580
382
  isInteractionEnabled = false;
581
- iframe.classList.remove("interactive"); // Disable pointer events on iframe
383
+ iframe.classList.remove("interactive");
582
384
  };
583
385
 
584
- // Event listeners for interaction overlay
585
386
  overlay.addEventListener("mouseenter", showInteractionOverlay);
586
387
  overlay.addEventListener("mouseleave", () => {
587
388
  if (!isInteractionEnabled) {
588
389
  interactionOverlay.classList.remove("visible");
589
390
  }
590
391
  });
591
-
592
392
  interactionOverlay.addEventListener("click", hideInteractionOverlay);
593
393
 
594
- const drawBoxes = (boxes) => {
595
- // Remove existing boxes
596
- document.querySelectorAll(".bounding-box").forEach((box) => {
597
- box.remove();
598
- });
599
-
600
- // Add new boxes
601
- boxes.forEach((box) => {
602
- const boxElement = document.createElement("div");
603
- boxElement.className = "bounding-box";
604
- boxElement.style.left = toCss(box.x);
605
- boxElement.style.top = toCss(box.y + topBarOffset);
606
- boxElement.style.width = toCss(box.width);
607
- boxElement.style.height = toCss(box.height);
608
- effects.appendChild(boxElement);
609
- });
610
- };
611
- // Screen capture event handlers
612
- addEventHandler(events.screenCapture.start, (event, data) => {
613
- if (data?.silent) return;
614
- screenshotElement.style.opacity = 0;
615
- });
616
-
617
- addEventHandler(events.screenCapture.error, (event, data) => {
618
- if (data?.silent) return;
619
- screenshotElement.style.opacity = 1;
620
- });
621
-
622
- addEventHandler(events.screenCapture.end, (event, data) => {
623
- if (data?.silent) return;
624
- screenshotElement.classList.remove("screenshot");
625
- // Force reflow
626
- void screenshotElement.offsetWidth;
627
- screenshotElement.classList.add("screenshot");
394
+ window.addEventListener("blur", () => {
395
+ showInteractionOverlay();
396
+ disableInteraction();
628
397
  });
629
398
 
630
- // Mouse event handlers
631
- addEventHandler(events.mouseMove, (event, { x, y } = {}) => {
632
- mouse.style.marginLeft = toCss(x);
633
- mouse.style.marginTop = toCss(y + topBarOffset);
634
- });
399
+ // Set title from test file name
400
+ const testFileName = parsedData?.testFile
401
+ ? parsedData.testFile.split("/").pop().split("\\").pop()
402
+ : "TestDriver";
403
+ document.title = `${testFileName} - Debugger`;
635
404
 
636
- addEventHandler(
637
- events.mouseClick,
638
- (event, { x, y, click = "single" } = {}) => {
639
- mouse.style.marginLeft = toCss(x);
640
- mouse.style.marginTop = toCss(y + topBarOffset);
641
- // Reset class so animation can restart
642
- mouse.setAttribute("class", "mouse");
643
- // Force reflow
644
- void mouse.offsetWidth;
645
- mouse.classList.add(`${click}-click`);
646
- },
647
- );
648
-
649
- // Matches and VM event handlers
650
- addEventHandler(events.matches.show, (event, closeMatches = []) => {
651
- if (boundingBoxesTimeout) clearTimeout(boundingBoxesTimeout);
652
- drawBoxes(closeMatches);
653
- boundingBoxesTimeout = setTimeout(() => drawBoxes([]), 10000);
654
- });
405
+ // Embed the VNC URL in the iframe
406
+ iframe.style.display = "block";
407
+ iframe.src = parsedData.url;
655
408
 
656
- addEventHandler(events.terminal.stdout, (event, data) => {
657
- console.log("Terminal stdout:", data);
658
- // Could be used to display terminal output in UI
409
+ // Hide loading screen once iframe loads
410
+ iframe.addEventListener("load", () => {
411
+ loadingScreen.classList.add("hidden");
659
412
  });
413
+ // Fallback: hide loading screen after 5s even if load event doesn't fire
414
+ setTimeout(() => loadingScreen.classList.add("hidden"), 5000);
660
415
 
661
- addEventHandler(events.terminal.stderr, (event, data) => {
662
- console.log("Terminal stderr:", data);
663
- // Could be used to display terminal errors in UI
664
- });
416
+ // Resolution from session data
417
+ const overlayWidth = parsedData.resolution?.[0] || 1366;
418
+ const overlayHeight = parsedData.resolution?.[1] || 768;
665
419
 
666
- const toCss = (size) => {
667
- if (typeof size === "number") {
668
- return `${size}px`;
669
- }
670
- return size;
671
- };
420
+ iframe.style.width = overlayWidth + "px";
421
+ iframe.style.height = overlayHeight + "px";
672
422
 
673
- // Throttle function to limit how often a function can run
423
+ // Throttle helper
674
424
  function throttle(fn, wait) {
675
- let lastTime = 0;
425
+ let last = 0;
676
426
  return function (...args) {
677
427
  const now = Date.now();
678
- if (now - lastTime >= wait) {
679
- lastTime = now;
680
- fn.apply(this, args);
681
- }
428
+ if (now - last >= wait) { last = now; fn.apply(this, args); }
682
429
  };
683
430
  }
684
431
 
432
+ // Responsive: scale and center the overlay to fit the window
685
433
  const resizeOverlay = () => {
686
- if (parsedData && parsedData.resolution) {
687
- const overlayWidth = parsedData.resolution[0];
688
- const overlayHeight = parsedData.resolution[1];
689
- const windowWidth = window.innerWidth;
690
- const windowHeight = window.innerHeight;
691
- const scaleX = windowWidth / overlayWidth;
692
- const scaleY = windowHeight / overlayHeight;
693
- const scale = Math.min(scaleX, scaleY);
694
-
695
- console.log("scale", scale);
696
-
697
- console.log(
698
- "resolution:",
699
- JSON.stringify({
700
- width: overlayWidth,
701
- height: overlayHeight,
702
- }),
703
- );
704
-
705
- console.log(
706
- "window:",
707
- JSON.stringify({
708
- width: windowWidth,
709
- height: windowHeight,
710
- }),
711
- );
712
- const overlay = document.getElementById("overlay");
713
- if (scale < 1) {
714
- overlay.style.transform = `scale(${scale})`;
715
- } else {
716
- overlay.style.transform = "none";
717
- }
718
-
719
- // get the new width and height after scaling
720
- const scaledWidth = overlayWidth * scale;
721
- const scaledHeight = overlayHeight * scale;
722
-
723
- console.log("scaledWidth", JSON.stringify(scaledWidth));
724
- console.log("scaledHeight", JSON.stringify(scaledHeight));
725
-
726
- // use the scaled width and height to center the overlay
727
- overlay.style.position = "absolute";
728
- if (scale < 1) {
729
- // When scaling down, use the original dimensions and let CSS transform handle the scaling
730
- overlay.style.width = `${overlayWidth}px`;
731
- overlay.style.height = `${overlayHeight}px`;
732
- overlay.style.left = `${(windowWidth - scaledWidth) / 2}px`;
733
- overlay.style.top = `${(windowHeight - scaledHeight) / 2}px`;
734
- } else {
735
- // When no scaling is needed, just center the overlay at its original size
736
- overlay.style.width = `${overlayWidth}px`;
737
- overlay.style.height = `${overlayHeight}px`;
738
- overlay.style.left = `${(windowWidth - overlayWidth) / 2}px`;
739
- overlay.style.top = `${(windowHeight - overlayHeight) / 2}px`;
740
- }
741
-
742
- console.log(
743
- "left,top",
744
- JSON.stringify({
745
- left:
746
- scale < 1
747
- ? (windowWidth - scaledWidth) / 2
748
- : (windowWidth - overlayWidth) / 2,
749
- top:
750
- scale < 1
751
- ? (windowHeight - scaledHeight) / 2
752
- : (windowHeight - overlayHeight) / 2,
753
- }),
754
- );
755
- }
434
+ const ww = window.innerWidth;
435
+ const wh = window.innerHeight;
436
+ const scale = Math.min(ww / overlayWidth, wh / overlayHeight, 1);
437
+ const sw = overlayWidth * scale;
438
+ const sh = overlayHeight * scale;
439
+
440
+ overlay.style.width = overlayWidth + "px";
441
+ overlay.style.height = overlayHeight + "px";
442
+ overlay.style.position = "absolute";
443
+ overlay.style.transform = scale < 1 ? `scale(${scale})` : "none";
444
+ overlay.style.left = `${(ww - sw) / 2}px`;
445
+ overlay.style.top = `${(wh - sh) / 2}px`;
756
446
  };
757
- resizeOverlay();
758
447
 
759
- // Handle window resize to recalculate scale (throttled)
448
+ resizeOverlay();
760
449
  window.addEventListener("resize", throttle(resizeOverlay, 100));
761
-
762
- setInterval(resizeOverlay, 1000);
763
-
764
- // Handle window blur/focus for screen locking
765
- window.addEventListener("blur", () => {
766
- showInteractionOverlay();
767
- disableInteraction();
768
- });
769
450
  </script>
770
451
  </body>
771
452
  </html>
@@ -36,11 +36,16 @@ function parseFrontmatter(content) {
36
36
  return { frontmatter, body };
37
37
  }
38
38
 
39
- // Get skill name from filename
39
+ // Get skill name from filename (used in SKILL.md frontmatter)
40
40
  function getSkillName(filename) {
41
41
  return `testdriver:${filename.replace(".mdx", "")}`;
42
42
  }
43
43
 
44
+ // Get directory name from filename (Windows-compatible, uses hyphen instead of colon)
45
+ function getDirName(filename) {
46
+ return `testdriver-${filename.replace(".mdx", "")}`;
47
+ }
48
+
44
49
  // Generate SKILL.md content
45
50
  function generateSkillContent(filename, frontmatter, body) {
46
51
  const skillName = getSkillName(filename);
@@ -76,7 +81,7 @@ function processFiles() {
76
81
  for (const file of mdxFiles) {
77
82
  const filePath = path.join(DOCS_DIR, file);
78
83
  const skillName = getSkillName(file);
79
- const outputDir = path.join(OUTPUT_DIR, skillName);
84
+ const outputDir = path.join(OUTPUT_DIR, getDirName(file));
80
85
  const outputPath = path.join(outputDir, "SKILL.md");
81
86
 
82
87
  try {
@@ -81,8 +81,6 @@ TestDriver makes it easy to write automated computer-use tests for web browsers,
81
81
  test: {
82
82
  testTimeout: 900000,
83
83
  hookTimeout: 900000,
84
- disableConsoleIntercept: true,
85
- maxConcurrency: 1, // this should match your plan's concurrency limit
86
84
  reporters: [
87
85
  'default',
88
86
  TestDriver()
@@ -76,7 +76,7 @@ class BaseCommand extends Command {
76
76
  };
77
77
 
78
78
  let isConnected = false;
79
- const debugMode = process.env.VERBOSE || process.env.DEBUG || process.env.TD_DEBUG;
79
+ const debugMode = process.env.VERBOSE || process.env.TD_DEBUG;
80
80
 
81
81
  // Use pattern matching for log events, but skip log:Debug unless debug mode is enabled
82
82
  this.agent.emitter.on("log:*", (message) => {
@@ -586,7 +586,7 @@ export async function cleanupTestDriver(testdriver) {
586
586
  try {
587
587
  const dashcamUrl = await testdriver.dashcam.stop();
588
588
  const debugMode =
589
- process.env.VERBOSE || process.env.DEBUG || process.env.TD_DEBUG;
589
+ process.env.VERBOSE || process.env.TD_DEBUG;
590
590
  if (debugMode) {
591
591
  console.log("🎥 Dashcam URL:", dashcamUrl);
592
592
  }
@@ -1243,6 +1243,9 @@ class TestDriverReporter {
1243
1243
  * @returns {string} The corresponding web console URL
1244
1244
  */
1245
1245
  function getConsoleUrl(apiRoot) {
1246
+ // Allow explicit override via env (e.g. VITE_DOMAIN from .env)
1247
+ if (process.env.VITE_DOMAIN) return process.env.VITE_DOMAIN;
1248
+
1246
1249
  if (!apiRoot) return "https://console.testdriver.ai";
1247
1250
 
1248
1251
  // Production: API on render.com -> Console on testdriver.ai
@@ -1251,13 +1254,11 @@ function getConsoleUrl(apiRoot) {
1251
1254
  }
1252
1255
 
1253
1256
  // Local development: API on localhost:1337 -> Web on localhost:3001
1254
- if (apiRoot.includes("ngrok.io")) {
1257
+ if (apiRoot.includes("ngrok.io") || apiRoot.includes("trycloudflare.com") || apiRoot.includes("localhost")) {
1255
1258
  return `http://localhost:3001`;
1256
1259
  }
1257
1260
 
1258
- // Ngrok or other tunnels: assume same host, different path structure
1259
- // For ngrok, the API and web might be on same domain or user needs to configure
1260
- // Return as-is since we can't reliably determine the mapping
1261
+ // Other tunnels or unknown hosts: return as-is
1261
1262
  return apiRoot;
1262
1263
  }
1263
1264