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.
- package/agent/lib/sandbox.js +134 -55
- package/docs/_data/examples-manifest.json +46 -46
- package/docs/v7/examples/ai.mdx +1 -1
- package/docs/v7/examples/assert.mdx +1 -1
- package/docs/v7/examples/chrome-extension.mdx +1 -1
- package/docs/v7/examples/element-not-found.mdx +1 -1
- package/docs/v7/examples/exec-output.mdx +1 -1
- package/docs/v7/examples/exec-pwsh.mdx +1 -1
- package/docs/v7/examples/focus-window.mdx +1 -1
- package/docs/v7/examples/hover-image.mdx +1 -1
- package/docs/v7/examples/hover-text.mdx +1 -1
- package/docs/v7/examples/installer.mdx +1 -1
- package/docs/v7/examples/launch-vscode-linux.mdx +1 -1
- package/docs/v7/examples/match-image.mdx +1 -1
- package/docs/v7/examples/press-keys.mdx +1 -1
- package/docs/v7/examples/scroll-keyboard.mdx +1 -1
- package/docs/v7/examples/scroll-until-image.mdx +1 -1
- package/docs/v7/examples/scroll.mdx +1 -1
- package/docs/v7/examples/type.mdx +1 -1
- package/docs/v7/examples/windows-installer.mdx +1 -1
- package/interfaces/vitest-plugin.mjs +45 -43
- package/lib/core/Dashcam.js +17 -7
- package/lib/github-comment.mjs +58 -40
- package/package.json +1 -1
- package/setup/aws/install-dev-runner.sh +79 -0
- package/setup/aws/spawn-runner.sh +134 -0
- package/vitest.runner.config.mjs +33 -0
package/agent/lib/sandbox.js
CHANGED
|
@@ -10,11 +10,8 @@ const createSandbox = function (emitter, analytics, sessionInstance) {
|
|
|
10
10
|
class Sandbox {
|
|
11
11
|
constructor() {
|
|
12
12
|
this._ably = null;
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
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,
|
|
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.
|
|
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.
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
87
|
+
// Enter presence on the session channel so the API can count connected SDK clients
|
|
90
88
|
try {
|
|
91
|
-
await this.
|
|
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
|
|
96
|
+
logger.warn("Failed to enter presence on session channel: " + (e.message || e));
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
this.
|
|
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.
|
|
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("
|
|
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("
|
|
230
|
+
logger.log("[ably] Connection: reconnected");
|
|
220
231
|
});
|
|
221
232
|
|
|
222
233
|
this._ably.connection.on("suspended", function () {
|
|
223
|
-
logger.warn("
|
|
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.
|
|
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.
|
|
372
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
949
|
+
if (this._sessionChannel && this._ably?.connection?.state === 'connected') {
|
|
927
950
|
try {
|
|
928
|
-
|
|
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
|
|
935
|
-
if (this.
|
|
958
|
+
// Leave presence on session channel
|
|
959
|
+
if (this._sessionChannel) {
|
|
936
960
|
try {
|
|
937
|
-
|
|
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
|
-
|
|
945
|
-
|
|
946
|
-
this.
|
|
947
|
-
|
|
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.
|
|
964
|
-
this.
|
|
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/
|
|
6
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
14
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
18
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
26
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
30
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
34
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
38
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
42
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
46
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
50
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
54
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
58
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
62
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
66
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
70
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
74
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
78
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
86
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
90
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
94
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
102
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
138
|
-
"lastUpdated": "2026-03-
|
|
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/
|
|
142
|
-
"lastUpdated": "2026-03-
|
|
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",
|
package/docs/v7/examples/ai.mdx
CHANGED
|
@@ -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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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" }}
|