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.
@@ -10,11 +10,8 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
10
10
  class Sandbox {
11
11
  constructor() {
12
12
  this._ably = null;
13
- this._cmdChannel = null;
14
- this._respChannel = null;
15
- this._ctrlChannel = null;
16
- this._filesChannel = null;
17
- this._channelNames = null;
13
+ this._sessionChannel = null;
14
+ this._channelName = null;
18
15
  this.ps = {};
19
16
  this._execBuffers = {}; // accumulate streamed exec.output chunks per requestId
20
17
  this.heartbeat = null;
@@ -51,7 +48,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
51
48
  );
52
49
  }
53
50
 
54
- async _initAbly(ablyToken, channelNames) {
51
+ async _initAbly(ablyToken, channelName) {
55
52
  if (this._ably) {
56
53
  try {
57
54
  this._ably.close();
@@ -59,7 +56,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
59
56
  /* ignore */
60
57
  }
61
58
  }
62
- this._channelNames = channelNames;
59
+ this._channelName = channelName;
63
60
  var self = this;
64
61
 
65
62
  this._ably = new Ably.Realtime({
@@ -71,6 +68,8 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
71
68
  suspendedRetryTimeout: 15000, // retry from suspended every 15s (default 30s)
72
69
  });
73
70
 
71
+ logger.log(`[ably] Connecting as sdk-${this._sandboxId}...`);
72
+
74
73
  await new Promise(function (resolve, reject) {
75
74
  self._ably.connection.on("connected", resolve);
76
75
  self._ably.connection.on("failed", function () {
@@ -81,26 +80,28 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
81
80
  }, 30000);
82
81
  });
83
82
 
84
- this._cmdChannel = this._ably.channels.get(channelNames.commands);
85
- this._respChannel = this._ably.channels.get(channelNames.responses);
86
- this._ctrlChannel = this._ably.channels.get(channelNames.control);
87
- this._filesChannel = this._ably.channels.get(channelNames.files);
83
+ this._sessionChannel = this._ably.channels.get(channelName);
84
+
85
+ logger.log(`[ably] Channel initialized: ${channelName}`);
88
86
 
89
- // Enter presence on control channel so the API can count connected SDK clients
87
+ // Enter presence on the session channel so the API can count connected SDK clients
90
88
  try {
91
- await this._ctrlChannel.presence.enter({
89
+ await this._sessionChannel.presence.enter({
92
90
  sandboxId: this._sandboxId,
93
91
  connectedAt: Date.now(),
94
92
  });
93
+ logger.log(`[ably] Entered presence on session channel (sandbox=${this._sandboxId})`);
95
94
  } catch (e) {
96
95
  // Non-fatal — presence is used for concurrency counting, not critical path
97
- logger.warn("Failed to enter presence on control channel: " + (e.message || e));
96
+ logger.warn("Failed to enter presence on session channel: " + (e.message || e));
98
97
  }
99
98
 
100
- this._respChannel.subscribe("response", function (msg) {
99
+ this._sessionChannel.subscribe("response", function (msg) {
101
100
  var message = msg.data;
102
101
  if (!message) return;
103
102
 
103
+ logger.log(`[ably] Received response: type=${message.type || 'unknown'} (requestId=${message.requestId || 'none'})`);
104
+
104
105
  if (message.type === "sandbox.progress") {
105
106
  emitter.emit(events.sandbox.progress, {
106
107
  step: message.step,
@@ -196,9 +197,10 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
196
197
  delete self.ps[message.requestId];
197
198
  });
198
199
 
199
- this._filesChannel.subscribe("response", function (msg) {
200
+ this._sessionChannel.subscribe("file", function (msg) {
200
201
  var message = msg.data;
201
202
  if (!message) return;
203
+ logger.log(`[ably] Received file: type=${message.type || 'unknown'} (requestId=${message.requestId || 'none'})`);
202
204
  if (message.requestId && self.ps[message.requestId]) {
203
205
  emitter.emit(events.sandbox.received);
204
206
  self.ps[message.requestId].resolve(message);
@@ -210,20 +212,30 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
210
212
  this.heartbeat = setInterval(function () {}, 5000);
211
213
  if (this.heartbeat.unref) this.heartbeat.unref();
212
214
 
215
+ // ─── Periodic stats logging ────────────────────────────────────────
216
+ this._statsInterval = setInterval(() => {
217
+ const connState = this._ably ? this._ably.connection.state : 'no-client';
218
+ const chState = this._sessionChannel ? this._sessionChannel.state : 'null';
219
+ const pending = Object.keys(this.ps).length;
220
+ logger.log(`[ably][stats] connection=${connState} | sandbox=${this._sandboxId} | pending=${pending} | channel=${chState}`);
221
+ }, 10000);
222
+ if (this._statsInterval.unref) this._statsInterval.unref();
223
+
213
224
  this._ably.connection.on("disconnected", function () {
214
- logger.log("Ably disconnected - will auto-reconnect");
225
+ logger.log("[ably] Connection: disconnected - will auto-reconnect");
215
226
  });
216
227
 
217
228
  this._ably.connection.on("connected", function () {
218
229
  // Log reconnection so the user knows the blip was recovered
219
- logger.log("Ably reconnected");
230
+ logger.log("[ably] Connection: reconnected");
220
231
  });
221
232
 
222
233
  this._ably.connection.on("suspended", function () {
223
- logger.warn("Ably suspended - connection lost for extended period, will keep retrying");
234
+ logger.warn("[ably] Connection: suspended - connection lost for extended period, will keep retrying");
224
235
  });
225
236
 
226
237
  this._ably.connection.on("failed", function () {
238
+ logger.error("[ably] Connection: failed");
227
239
  self.apiSocketConnected = false;
228
240
  self.instanceSocketConnected = false;
229
241
  emitter.emit(events.error.sandbox, "Ably connection failed");
@@ -362,14 +374,14 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
362
374
  this._teamId = reply.teamId;
363
375
 
364
376
  if (reply.ably && reply.ably.token) {
365
- await this._initAbly(reply.ably.token, reply.ably.channels);
377
+ await this._initAbly(reply.ably.token, reply.ably.channel);
366
378
  this.instanceSocketConnected = true;
367
379
 
368
380
  // Tell the runner to enable debug log forwarding if debug mode is on
369
381
  var debugMode =
370
382
  process.env.VERBOSE || process.env.TD_DEBUG;
371
- if (debugMode && this._ctrlChannel) {
372
- this._ctrlChannel.publish("control", {
383
+ if (debugMode && this._sessionChannel) {
384
+ this._sessionChannel.publish("control", {
373
385
  type: "debug",
374
386
  enabled: true,
375
387
  });
@@ -402,7 +414,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
402
414
  // agent to signal readiness before sending commands. Without this
403
415
  // gate, commands published before the agent subscribes are lost.
404
416
  var self = this;
405
- if (!reply.runner && this._ctrlChannel) {
417
+ if (!reply.runner && this._sessionChannel) {
406
418
  logger.log('Waiting for runner agent to signal readiness...');
407
419
  var readyTimeout = 120000; // 120s — allows for EC2 boot + agent startup
408
420
  await new Promise(function (resolve, reject) {
@@ -411,7 +423,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
411
423
  if (resolved) return;
412
424
  resolved = true;
413
425
  clearTimeout(timer);
414
- self._ctrlChannel.unsubscribe('control', onCtrl);
426
+ self._sessionChannel.unsubscribe('control', onCtrl);
415
427
  // Update runner info if provided
416
428
  if (data && data.os) reply.runner = reply.runner || {};
417
429
  if (data && data.os && reply.runner) reply.runner.os = data.os;
@@ -436,7 +448,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
436
448
  var timer = setTimeout(function () {
437
449
  if (!resolved) {
438
450
  resolved = true;
439
- self._ctrlChannel.unsubscribe('control', onCtrl);
451
+ self._sessionChannel.unsubscribe('control', onCtrl);
440
452
  var err = new Error('Runner agent did not signal readiness within ' + readyTimeout + 'ms');
441
453
  sentry.captureException(err, {
442
454
  tags: { phase: 'runner_ready', connection_type: 'create' },
@@ -455,12 +467,12 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
455
467
  finish(data);
456
468
  }
457
469
  };
458
- self._ctrlChannel.subscribe('control', onCtrl);
470
+ self._sessionChannel.subscribe('control', onCtrl);
459
471
 
460
472
  // Also check channel history in case runner.ready was published
461
473
  // before we subscribed (race condition on fast-booting agents).
462
474
  try {
463
- self._ctrlChannel.history({ limit: 50 }, function (err, page) {
475
+ self._sessionChannel.history({ limit: 50 }, function (err, page) {
464
476
  if (err) {
465
477
  logger.warn('History lookup failed (non-fatal): ' + (err.message || err));
466
478
  return;
@@ -515,17 +527,22 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
515
527
  // provision the config to the instance via SSM (client-side).
516
528
  // This runs from the user's infrastructure where AWS permissions exist,
517
529
  // rather than from the API server.
518
- if (reply.agentConfig && message.instanceId) {
519
- logger.log('Provisioning agent config to instance ' + message.instanceId + ' via SSM...');
520
- await this._provisionAgentConfig(message.instanceId, reply.agentConfig);
530
+ // NOTE: For direct connections, the user MUST provide the AWS instanceId
531
+ // because the API only knows the sandboxId, not the actual EC2 instance ID.
532
+ var instanceId = message.instanceId;
533
+ if (reply.agentConfig && instanceId) {
534
+ logger.log('Provisioning agent config to instance ' + instanceId + ' via SSM...');
535
+ await this._provisionAgentConfig(instanceId, reply.agentConfig);
521
536
  logger.log('Agent config provisioned successfully.');
537
+ } else if (reply.agentConfig && !instanceId) {
538
+ logger.log('Warning: agentConfig returned but no instanceId provided - cannot provision via SSM');
522
539
  }
523
540
 
524
541
  // If the API returned agent credentials (reply.agent present),
525
542
  // wait for the runner agent to signal readiness before sending commands.
526
543
  // Without this gate, commands published before the agent subscribes are lost.
527
544
  var self = this;
528
- if (reply.agent && this._ctrlChannel) {
545
+ if (reply.agent && this._sessionChannel) {
529
546
  logger.log('Waiting for runner agent to signal readiness (direct connection)...');
530
547
  var readyTimeout = 120000; // 120s — allows for SSM provisioning + agent startup
531
548
  await new Promise(function (resolve, reject) {
@@ -534,7 +551,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
534
551
  if (resolved) return;
535
552
  resolved = true;
536
553
  clearTimeout(timer);
537
- self._ctrlChannel.unsubscribe('control', onCtrl);
554
+ self._sessionChannel.unsubscribe('control', onCtrl);
538
555
  logger.log('Runner agent ready (direct, os=' + ((data && data.os) || 'unknown') + ', runner v' + ((data && data.runnerVersion) || 'unknown') + ')');
539
556
  if (data && data.update) {
540
557
  var u = data.update;
@@ -554,7 +571,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
554
571
  var timer = setTimeout(function () {
555
572
  if (!resolved) {
556
573
  resolved = true;
557
- self._ctrlChannel.unsubscribe('control', onCtrl);
574
+ self._sessionChannel.unsubscribe('control', onCtrl);
558
575
  var err = new Error('Runner agent did not signal readiness within ' + readyTimeout + 'ms (direct connection)');
559
576
  sentry.captureException(err, {
560
577
  tags: { phase: 'runner_ready', connection_type: 'direct' },
@@ -573,12 +590,12 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
573
590
  finish(data);
574
591
  }
575
592
  };
576
- self._ctrlChannel.subscribe('control', onCtrl);
593
+ self._sessionChannel.subscribe('control', onCtrl);
577
594
 
578
595
  // Also check channel history in case runner.ready was published
579
596
  // before we subscribed (race condition on fast-booting agents).
580
597
  try {
581
- self._ctrlChannel.history({ limit: 50 }, function (err, page) {
598
+ self._sessionChannel.history({ limit: 50 }, function (err, page) {
582
599
  if (err) {
583
600
  logger.warn('History lookup failed (non-fatal): ' + (err.message || err));
584
601
  return;
@@ -620,7 +637,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
620
637
  _sendAbly(message, timeout) {
621
638
  if (timeout === undefined) timeout = 300000;
622
639
 
623
- if (!this._cmdChannel || !this._ably) {
640
+ if (!this._sessionChannel || !this._ably) {
624
641
  return Promise.reject(
625
642
  new Error("Sandbox not connected (no Ably client)"),
626
643
  );
@@ -741,7 +758,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
741
758
  p.catch(function () {});
742
759
  }
743
760
 
744
- this._throttledPublish(this._cmdChannel, "command", message)
761
+ this._throttledPublish(this._sessionChannel, "command", message)
745
762
  .then(function () {
746
763
  emitter.emit(events.sandbox.sent, message);
747
764
  })
@@ -800,7 +817,9 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
800
817
  this._publishWindowStart = Date.now();
801
818
  }
802
819
 
803
- return channel.publish(eventName, message);
820
+ return channel.publish(eventName, message).then(function () {
821
+ logger.log(`[ably] Published: channel=${channel.name.split(':').pop()}, event=${eventName}, type=${message.type || 'unknown'} (requestId=${message.requestId || 'none'})`);
822
+ });
804
823
  }
805
824
 
806
825
  async auth(apiKey) {
@@ -872,7 +891,7 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
872
891
  this._sandboxId = reply.sandboxId;
873
892
 
874
893
  if (reply.ably && reply.ably.token) {
875
- await this._initAbly(reply.ably.token, reply.ably.channels);
894
+ await this._initAbly(reply.ably.token, reply.ably.channel);
876
895
  }
877
896
 
878
897
  this.setConnectionParams({
@@ -921,38 +940,43 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
921
940
  clearInterval(this.heartbeat);
922
941
  this.heartbeat = null;
923
942
  }
943
+ if (this._statsInterval) {
944
+ clearInterval(this._statsInterval);
945
+ this._statsInterval = null;
946
+ }
924
947
 
925
948
  // Send end-session control message to runner before disconnecting
926
- if (this._ctrlChannel && this._ably?.connection?.state === 'connected') {
949
+ if (this._sessionChannel && this._ably?.connection?.state === 'connected') {
927
950
  try {
928
- await this._ctrlChannel.publish('control', { type: 'end-session' });
951
+ logger.log('[ably] Publishing control: type=end-session');
952
+ await this._sessionChannel.publish('control', { type: 'end-session' });
929
953
  } catch (e) {
930
954
  // Ignore - best effort
931
955
  }
932
956
  }
933
957
 
934
- // Leave presence on control channel
935
- if (this._ctrlChannel) {
958
+ // Leave presence on session channel
959
+ if (this._sessionChannel) {
936
960
  try {
937
- await this._ctrlChannel.presence.leave();
961
+ logger.log('[ably] Leaving presence on session channel');
962
+ await this._sessionChannel.presence.leave();
938
963
  } catch (e) {
939
964
  // ignore - best effort, Ably will auto-leave on disconnect
940
965
  }
941
966
  }
942
967
 
943
968
  try {
944
- await Promise.allSettled([
945
- this._cmdChannel?.detach(),
946
- this._respChannel?.detach(),
947
- this._ctrlChannel?.detach(),
948
- this._filesChannel?.detach(),
949
- ].filter(Boolean));
969
+ logger.log('[ably] Detaching session channel');
970
+ if (this._sessionChannel) {
971
+ await this._sessionChannel.detach();
972
+ }
950
973
  } catch (e) {
951
974
  /* ignore */
952
975
  }
953
976
 
954
977
  if (this._ably) {
955
978
  try {
979
+ logger.log('[ably] Closing Ably connection');
956
980
  this._ably.close();
957
981
  } catch (e) {
958
982
  /* ignore */
@@ -960,11 +984,8 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
960
984
  this._ably = null;
961
985
  }
962
986
 
963
- this._cmdChannel = null;
964
- this._respChannel = null;
965
- this._ctrlChannel = null;
966
- this._filesChannel = null;
967
- this._channelNames = null;
987
+ this._sessionChannel = null;
988
+ this._channelName = null;
968
989
  this.apiSocketConnected = false;
969
990
  this.instanceSocketConnected = false;
970
991
  this.authenticated = false;
@@ -987,11 +1008,51 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
987
1008
  const region = process.env.AWS_REGION || 'us-east-2';
988
1009
 
989
1010
  // Write SSM parameters to a temp file to avoid shell quoting issues
1011
+ // Log key config details for debugging
1012
+ logger.log('Agent config being provisioned:');
1013
+ logger.log(' sandboxId: ' + agentConfig.sandboxId);
1014
+ logger.log(' apiRoot: ' + agentConfig.apiRoot);
1015
+ logger.log(' channel: ' + (agentConfig.ably?.channel || 'N/A'));
1016
+ logger.log(' token length: ' + (agentConfig.ably?.token ? JSON.stringify(agentConfig.ably.token).length : 0));
1017
+
990
1018
  const paramsJson = JSON.stringify({
991
1019
  commands: [
1020
+ // Debug: show existing state
1021
+ "Write-Host '=== Checking existing state ==='",
1022
+ "$task = Get-ScheduledTask -TaskName RunTestDriverAgent -ErrorAction SilentlyContinue",
1023
+ "if ($task) { Write-Host \"Task exists, state: $($task.State)\" } else { Write-Host 'Task does NOT exist!' }",
1024
+ "if (Test-Path 'C:\\Windows\\Temp\\testdriver-agent.json') { Write-Host 'Old config:'; Get-Content 'C:\\Windows\\Temp\\testdriver-agent.json' | Write-Host } else { Write-Host 'Config file does NOT exist yet' }",
1025
+ // Stop any running runner
1026
+ "Write-Host '=== Stopping runner ==='",
1027
+ "Stop-Process -Name node -Force -ErrorAction SilentlyContinue",
1028
+ "Stop-ScheduledTask -TaskName RunTestDriverAgent -ErrorAction SilentlyContinue",
1029
+ // Write config
1030
+ "Write-Host '=== Writing config ==='",
992
1031
  "$config = '" + configJson.replace(/'/g, "''") + "'",
993
1032
  "[System.IO.File]::WriteAllText('C:\\Windows\\Temp\\testdriver-agent.json', $config)",
994
1033
  "Write-Host 'Config written for sandbox " + agentConfig.sandboxId + "'",
1034
+ // Show what was written (redact token)
1035
+ "Write-Host '=== New config (token redacted) ==='",
1036
+ "$cfg = Get-Content 'C:\\Windows\\Temp\\testdriver-agent.json' | ConvertFrom-Json",
1037
+ "Write-Host \"sandboxId: $($cfg.sandboxId)\"",
1038
+ "Write-Host \"apiRoot: $($cfg.apiRoot)\"",
1039
+ "Write-Host \"channel: $($cfg.ably.channel)\"",
1040
+ "Write-Host \"token type: $($cfg.ably.token.GetType().Name)\"",
1041
+ // Start the runner
1042
+ "Write-Host '=== Starting runner ==='",
1043
+ "Start-Sleep -Seconds 1",
1044
+ "Start-ScheduledTask -TaskName RunTestDriverAgent -ErrorAction Stop",
1045
+ "$task = Get-ScheduledTask -TaskName RunTestDriverAgent",
1046
+ "Write-Host \"Task state after start: $($task.State)\"",
1047
+ // Check if node process started
1048
+ "Start-Sleep -Seconds 3",
1049
+ "Write-Host '=== Checking runner process ==='",
1050
+ "$procs = Get-Process -Name node -ErrorAction SilentlyContinue",
1051
+ "if ($procs) { Write-Host \"Node processes: $($procs.Count)\"; $procs | ForEach-Object { Write-Host \" PID: $($_.Id), StartTime: $($_.StartTime)\" } } else { Write-Host 'No node process found!' }",
1052
+ // Check runner logs
1053
+ "Write-Host '=== Runner log (last 30 lines) ==='",
1054
+ "if (Test-Path 'C:\\testdriver\\logs\\sandbox-agent.log') { Get-Content 'C:\\testdriver\\logs\\sandbox-agent.log' -Tail 30 | Write-Host } else { Write-Host 'No log file found' }",
1055
+ "Write-Host '=== Done ==='",
995
1056
  ],
996
1057
  });
997
1058
  const tmpFile = join(tmpdir(), 'td-provision-' + Date.now() + '.json');
@@ -1013,6 +1074,24 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
1013
1074
  '--command-id "' + cmdId + '" --instance-id "' + instanceId + '"',
1014
1075
  { encoding: 'utf-8', timeout: 60000 }
1015
1076
  );
1077
+
1078
+ // Get the command output for debugging
1079
+ try {
1080
+ const invocationOutput = execSync(
1081
+ 'aws ssm get-command-invocation --region "' + region + '" ' +
1082
+ '--command-id "' + cmdId + '" --instance-id "' + instanceId + '" --output json',
1083
+ { encoding: 'utf-8', timeout: 30000 }
1084
+ );
1085
+ const invocation = JSON.parse(invocationOutput);
1086
+ if (invocation.StandardOutputContent) {
1087
+ logger.log('SSM output:\n' + invocation.StandardOutputContent);
1088
+ }
1089
+ if (invocation.StandardErrorContent) {
1090
+ logger.warn('SSM errors:\n' + invocation.StandardErrorContent);
1091
+ }
1092
+ } catch (e) {
1093
+ logger.warn('Could not retrieve SSM command output: ' + e.message);
1094
+ }
1016
1095
  } finally {
1017
1096
  try { unlinkSync(tmpFile); } catch (e) { /* ignore */ }
1018
1097
  }
@@ -2,104 +2,104 @@
2
2
  "$schema": "./examples-manifest.schema.json",
3
3
  "examples": {
4
4
  "assert.test.mjs": {
5
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f24840cc10fb18cf6746",
6
- "lastUpdated": "2026-03-18T00:46:04.770Z"
5
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc951f36864abb362f8584",
6
+ "lastUpdated": "2026-03-20T00:46:19.602Z"
7
7
  },
8
8
  "drag-and-drop.test.mjs": {
9
9
  "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b42fc0ac3cc632a918b",
10
10
  "lastUpdated": "2026-03-03T00:32:25.275Z"
11
11
  },
12
12
  "exec-pwsh.test.mjs": {
13
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f23308cd8455316368d7",
14
- "lastUpdated": "2026-03-18T00:46:04.761Z"
13
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc95088797a8a13f4cbfa2",
14
+ "lastUpdated": "2026-03-20T00:46:19.594Z"
15
15
  },
16
16
  "match-image.test.mjs": {
17
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f23508cd8455316368d9",
18
- "lastUpdated": "2026-03-18T00:46:04.761Z"
17
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc950af14c7f71c46d6b78",
18
+ "lastUpdated": "2026-03-20T00:46:19.594Z"
19
19
  },
20
20
  "scroll-until-text.test.mjs": {
21
21
  "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b99dc33133fc0da9440",
22
22
  "lastUpdated": "2026-03-03T00:32:25.282Z"
23
23
  },
24
24
  "hover-text-with-description.test.mjs": {
25
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f23740cc10fb18cf6735",
26
- "lastUpdated": "2026-03-18T00:46:04.761Z"
25
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc950c8797a8a13f4cbfa3",
26
+ "lastUpdated": "2026-03-20T00:46:19.594Z"
27
27
  },
28
28
  "windows-installer.test.mjs": {
29
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f23acb6e450d5272d316",
30
- "lastUpdated": "2026-03-18T00:46:04.761Z"
29
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc9510f14c7f71c46d6b7a",
30
+ "lastUpdated": "2026-03-20T00:46:19.595Z"
31
31
  },
32
32
  "exec-output.test.mjs": {
33
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f23c08cd8455316368dc",
34
- "lastUpdated": "2026-03-18T00:46:04.761Z"
33
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc95118797a8a13f4cbfa5",
34
+ "lastUpdated": "2026-03-20T00:46:19.595Z"
35
35
  },
36
36
  "chrome-extension.test.mjs": {
37
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f232cb6e450d5272d311",
38
- "lastUpdated": "2026-03-18T00:46:04.761Z"
37
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc9507f14c7f71c46d6b77",
38
+ "lastUpdated": "2026-03-20T00:46:19.594Z"
39
39
  },
40
40
  "launch-vscode-linux.test.mjs": {
41
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f23940cc10fb18cf673a",
42
- "lastUpdated": "2026-03-18T00:46:04.761Z"
41
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc950e8797a8a13f4cbfa4",
42
+ "lastUpdated": "2026-03-20T00:46:19.595Z"
43
43
  },
44
44
  "hover-image.test.mjs": {
45
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f23d08cd8455316368de",
46
- "lastUpdated": "2026-03-18T00:46:04.762Z"
45
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc95138797a8a13f4cbfa6",
46
+ "lastUpdated": "2026-03-20T00:46:19.595Z"
47
47
  },
48
48
  "installer.test.mjs": {
49
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f23fcb6e450d5272d317",
50
- "lastUpdated": "2026-03-18T00:46:04.767Z"
49
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc951636864abb362f857c",
50
+ "lastUpdated": "2026-03-20T00:46:19.600Z"
51
51
  },
52
52
  "type.test.mjs": {
53
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f24108cd8455316368df",
54
- "lastUpdated": "2026-03-18T00:46:04.767Z"
53
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc9517f14c7f71c46d6b7c",
54
+ "lastUpdated": "2026-03-20T00:46:19.600Z"
55
55
  },
56
56
  "press-keys.test.mjs": {
57
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f24640cc10fb18cf6745",
58
- "lastUpdated": "2026-03-18T00:46:04.770Z"
57
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc951df14c7f71c46d6b84",
58
+ "lastUpdated": "2026-03-20T00:46:19.602Z"
59
59
  },
60
60
  "scroll-keyboard.test.mjs": {
61
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f24408cd8455316368e1",
62
- "lastUpdated": "2026-03-18T00:46:04.770Z"
61
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc951b8797a8a13f4cbfa7",
62
+ "lastUpdated": "2026-03-20T00:46:19.601Z"
63
63
  },
64
64
  "scroll.test.mjs": {
65
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f243cb6e450d5272d319",
66
- "lastUpdated": "2026-03-18T00:46:04.767Z"
65
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc9519f14c7f71c46d6b7f",
66
+ "lastUpdated": "2026-03-20T00:46:19.600Z"
67
67
  },
68
68
  "scroll-until-image.test.mjs": {
69
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f24bcb6e450d5272d31b",
70
- "lastUpdated": "2026-03-18T00:46:04.770Z"
69
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc95238797a8a13f4cbfb3",
70
+ "lastUpdated": "2026-03-20T00:46:19.602Z"
71
71
  },
72
72
  "prompt.test.mjs": {
73
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f24d08cd8455316368e2",
74
- "lastUpdated": "2026-03-18T00:46:04.770Z"
73
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc952436864abb362f8586",
74
+ "lastUpdated": "2026-03-20T00:46:19.602Z"
75
75
  },
76
76
  "focus-window.test.mjs": {
77
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f24e08cd8455316368e3",
78
- "lastUpdated": "2026-03-18T00:46:04.770Z"
77
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc9526f14c7f71c46d6b85",
78
+ "lastUpdated": "2026-03-20T00:46:19.602Z"
79
79
  },
80
80
  "captcha-api.test.mjs": {
81
81
  "url": "https://console.testdriver.ai/runs/698f7df69e27ce1528d7d087/698f7fb0d3b320ad547d9d44",
82
82
  "lastUpdated": "2026-02-13T19:55:05.951Z"
83
83
  },
84
84
  "element-not-found.test.mjs": {
85
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f252cb6e450d5272d31d",
86
- "lastUpdated": "2026-03-18T00:46:04.773Z"
85
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc952af14c7f71c46d6b89",
86
+ "lastUpdated": "2026-03-20T00:46:19.602Z"
87
87
  },
88
88
  "formatted-logging.test.mjs": {
89
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f25040cc10fb18cf6747",
90
- "lastUpdated": "2026-03-18T00:46:04.770Z"
89
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc95288797a8a13f4cbfb7",
90
+ "lastUpdated": "2026-03-20T00:46:19.602Z"
91
91
  },
92
92
  "hover-text.test.mjs": {
93
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f25508cd8455316368e7",
94
- "lastUpdated": "2026-03-18T00:46:04.773Z"
93
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc952e8797a8a13f4cbfc1",
94
+ "lastUpdated": "2026-03-20T00:46:19.603Z"
95
95
  },
96
96
  "no-provision.test.mjs": {
97
97
  "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b7706a177a05bccd1cf",
98
98
  "lastUpdated": "2026-03-03T00:32:25.279Z"
99
99
  },
100
100
  "ai.test.mjs": {
101
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f253cb6e450d5272d31e",
102
- "lastUpdated": "2026-03-18T00:46:04.773Z"
101
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc952cf14c7f71c46d6b90",
102
+ "lastUpdated": "2026-03-20T00:46:19.603Z"
103
103
  },
104
104
  "popup-loading.test.mjs": {
105
105
  "url": "https://console.testdriver.ai/runs/698bc89f7140c3fa7daaca8d/698bca7f7140c3fa7daacbf7",
@@ -134,12 +134,12 @@
134
134
  "lastUpdated": "2026-02-13T19:55:05.953Z"
135
135
  },
136
136
  "findall-coffee-icons.test.mjs": {
137
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f24acb6e450d5272d31a",
138
- "lastUpdated": "2026-03-18T00:46:04.770Z"
137
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc95218797a8a13f4cbfb2",
138
+ "lastUpdated": "2026-03-20T00:46:19.602Z"
139
139
  },
140
140
  "parse.test.mjs": {
141
- "url": "https://console-test.testdriver.ai/runs/69b9f230cb6e450d5272d30e/69b9f25740cc10fb18cf6748",
142
- "lastUpdated": "2026-03-18T00:46:04.773Z"
141
+ "url": "https://console-test.testdriver.ai/runs/69bc950536864abb362f8579/69bc953036864abb362f858e",
142
+ "lastUpdated": "2026-03-20T00:46:19.603Z"
143
143
  },
144
144
  "flake-diffthreshold-001.test.mjs": {
145
145
  "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62bcafc0ac3cc632a91aa",
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* ai.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69b9f253cb6e450d5272d31e/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69bc952cf14c7f71c46d6b90/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
  {/* assert.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69b9f24840cc10fb18cf6746/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69bc951f36864abb362f8584/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
  {/* chrome-extension.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69b9f232cb6e450d5272d311/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69bc9507f14c7f71c46d6b77/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
  {/* element-not-found.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69b9f252cb6e450d5272d31d/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69bc952af14c7f71c46d6b89/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
  {/* exec-output.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69b9f23c08cd8455316368dc/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/69bc95118797a8a13f4cbfa5/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}