testdriverai 7.1.4 → 7.2.0
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/.github/workflows/acceptance.yaml +81 -0
- package/.github/workflows/publish.yaml +44 -0
- package/agent/index.js +18 -19
- package/agent/lib/commands.js +321 -121
- package/agent/lib/redraw.js +99 -39
- package/agent/lib/sandbox.js +98 -6
- package/agent/lib/sdk.js +25 -0
- package/agent/lib/system.js +2 -1
- package/agent/lib/validation.js +6 -6
- package/docs/docs.json +211 -101
- package/docs/snippets/tests/type-repeated-replay.mdx +1 -1
- package/docs/v7/_drafts/caching-selectors.mdx +24 -0
- package/docs/v7/api/act.mdx +1 -1
- package/docs/v7/api/assert.mdx +1 -1
- package/docs/v7/api/assertions.mdx +7 -7
- package/docs/v7/api/elements.mdx +78 -0
- package/docs/v7/api/find.mdx +38 -0
- package/docs/v7/api/focusApplication.mdx +2 -2
- package/docs/v7/api/hover.mdx +2 -2
- package/docs/v7/features/ai-native.mdx +57 -71
- package/docs/v7/features/application-logs.mdx +353 -0
- package/docs/v7/features/browser-logs.mdx +414 -0
- package/docs/v7/features/cache-management.mdx +402 -0
- package/docs/v7/features/continuous-testing.mdx +346 -0
- package/docs/v7/features/coverage.mdx +508 -0
- package/docs/v7/features/data-driven-testing.mdx +441 -0
- package/docs/v7/features/easy-to-write.mdx +2 -73
- package/docs/v7/features/enterprise.mdx +155 -39
- package/docs/v7/features/fast.mdx +63 -81
- package/docs/v7/features/managed-sandboxes.mdx +384 -0
- package/docs/v7/features/network-monitoring.mdx +568 -0
- package/docs/v7/features/observable.mdx +3 -22
- package/docs/v7/features/parallel-execution.mdx +381 -0
- package/docs/v7/features/powerful.mdx +1 -1
- package/docs/v7/features/reports.mdx +414 -0
- package/docs/v7/features/sandbox-customization.mdx +229 -0
- package/docs/v7/features/scalable.mdx +217 -2
- package/docs/v7/features/stable.mdx +106 -147
- package/docs/v7/features/system-performance.mdx +616 -0
- package/docs/v7/features/test-analytics.mdx +373 -0
- package/docs/v7/features/test-cases.mdx +393 -0
- package/docs/v7/features/test-replays.mdx +408 -0
- package/docs/v7/features/test-reports.mdx +308 -0
- package/docs/v7/getting-started/{running-and-debugging.mdx → debugging-tests.mdx} +12 -142
- package/docs/v7/getting-started/quickstart.mdx +22 -305
- package/docs/v7/getting-started/running-tests.mdx +173 -0
- package/docs/v7/overview/what-is-testdriver.mdx +2 -14
- package/docs/v7/presets/chrome-extension.mdx +147 -122
- package/interfaces/cli/commands/init.js +3 -3
- package/interfaces/cli/lib/base.js +3 -2
- package/interfaces/logger.js +0 -2
- package/interfaces/shared-test-state.mjs +0 -5
- package/interfaces/vitest-plugin.mjs +69 -42
- package/lib/core/Dashcam.js +65 -66
- package/lib/vitest/hooks.mjs +42 -50
- package/package.json +1 -1
- package/sdk-log-formatter.js +350 -175
- package/sdk.js +431 -116
- package/setup/aws/cloudformation.yaml +2 -2
- package/setup/aws/self-hosted.yml +1 -1
- package/test/testdriver/chrome-extension.test.mjs +55 -72
- package/test/testdriver/element-not-found.test.mjs +2 -1
- package/test/testdriver/hover-image.test.mjs +1 -1
- package/test/testdriver/scroll-until-text.test.mjs +10 -6
- package/test/testdriver/setup/lifecycleHelpers.mjs +19 -24
- package/test/testdriver/setup/testHelpers.mjs +18 -23
- package/vitest.config.mjs +3 -3
- package/.github/workflows/linux-tests.yml +0 -28
- package/docs/v7/getting-started/generating-tests.mdx +0 -525
- package/test/testdriver/auto-cache-key-demo.test.mjs +0 -56
package/agent/lib/commands.js
CHANGED
|
@@ -114,6 +114,7 @@ const createCommands = (
|
|
|
114
114
|
const niceSeconds = (ms) => {
|
|
115
115
|
return Math.round(ms / 1000);
|
|
116
116
|
};
|
|
117
|
+
|
|
117
118
|
const delay = (t) => new Promise((resolve) => setTimeout(resolve, t));
|
|
118
119
|
|
|
119
120
|
const findImageOnScreen = async (
|
|
@@ -214,12 +215,15 @@ const createCommands = (
|
|
|
214
215
|
};
|
|
215
216
|
|
|
216
217
|
const assert = async (assertion, shouldThrow = false) => {
|
|
218
|
+
let assertStartTimeForHandler;
|
|
217
219
|
const handleAssertResponse = (response) => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
220
|
+
const { formatter } = require("../../sdk-log-formatter.js");
|
|
221
|
+
const passed = response.indexOf("The task passed") > -1;
|
|
222
|
+
const duration = assertStartTimeForHandler ? Date.now() - assertStartTimeForHandler : undefined;
|
|
223
|
+
|
|
224
|
+
emitter.emit(events.log.narration, formatter.formatAssertResult(passed, response, duration), true);
|
|
221
225
|
|
|
222
|
-
if (
|
|
226
|
+
if (passed) {
|
|
223
227
|
return true;
|
|
224
228
|
} else {
|
|
225
229
|
if (shouldThrow) {
|
|
@@ -237,9 +241,12 @@ const createCommands = (
|
|
|
237
241
|
const assertingMessage = formatter.formatAsserting(assertion);
|
|
238
242
|
emitter.emit(events.log.log, assertingMessage);
|
|
239
243
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
244
|
+
// Capture absolute timestamp at the very start of the command
|
|
245
|
+
// Frontend will calculate relative time using: timestamp - replay.clientStartDate
|
|
246
|
+
const assertTimestamp = Date.now();
|
|
247
|
+
const assertStartTime = assertTimestamp;
|
|
248
|
+
assertStartTimeForHandler = assertStartTime;
|
|
249
|
+
|
|
243
250
|
let response = await sdk.req("assert", {
|
|
244
251
|
expect: assertion,
|
|
245
252
|
image: await system.captureScreenBase64(),
|
|
@@ -258,7 +265,7 @@ const createCommands = (
|
|
|
258
265
|
interactionType: "assert",
|
|
259
266
|
session: sessionId,
|
|
260
267
|
prompt: assertion,
|
|
261
|
-
timestamp:
|
|
268
|
+
timestamp: assertTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
262
269
|
duration: assertDuration,
|
|
263
270
|
success: assertionPassed,
|
|
264
271
|
error: assertionPassed ? undefined : response.data,
|
|
@@ -283,6 +290,10 @@ const createCommands = (
|
|
|
283
290
|
* @param {number} [options.redraw.diffThreshold=0.1] - Screen diff threshold percentage
|
|
284
291
|
*/
|
|
285
292
|
const scroll = async (direction = 'down', options = {}) => {
|
|
293
|
+
// Capture absolute timestamp at the very start of the command
|
|
294
|
+
// Frontend will calculate relative time using: timestamp - replay.clientStartDate
|
|
295
|
+
const scrollTimestamp = Date.now();
|
|
296
|
+
const scrollStartTime = scrollTimestamp;
|
|
286
297
|
// Convert number to object format
|
|
287
298
|
if (typeof options === 'number') {
|
|
288
299
|
options = { amount: options };
|
|
@@ -290,50 +301,121 @@ const createCommands = (
|
|
|
290
301
|
|
|
291
302
|
let { amount = 300 } = options;
|
|
292
303
|
const redrawOptions = extractRedrawOptions(options);
|
|
293
|
-
|
|
294
|
-
emitter.emit(
|
|
295
|
-
events.log.narration,
|
|
296
|
-
theme.dim(`scrolling ${direction} ${amount}px...`),
|
|
297
|
-
);
|
|
298
304
|
|
|
299
305
|
await redraw.start(redrawOptions);
|
|
300
306
|
|
|
301
307
|
amount = parseInt(amount, 10);
|
|
302
308
|
|
|
303
309
|
const before = await system.captureScreenBase64();
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
310
|
+
let scrollSuccess = true;
|
|
311
|
+
let scrollError;
|
|
312
|
+
let actionEndTime;
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
switch (direction) {
|
|
316
|
+
case "up":
|
|
317
|
+
await sandbox.send({
|
|
318
|
+
type: "scroll",
|
|
319
|
+
amount,
|
|
320
|
+
direction,
|
|
321
|
+
});
|
|
322
|
+
actionEndTime = Date.now();
|
|
323
|
+
break;
|
|
324
|
+
case "down":
|
|
325
|
+
await sandbox.send({
|
|
326
|
+
type: "scroll",
|
|
327
|
+
amount,
|
|
328
|
+
direction,
|
|
329
|
+
});
|
|
330
|
+
actionEndTime = Date.now();
|
|
331
|
+
break;
|
|
332
|
+
case "left":
|
|
333
|
+
console.error("Not Supported");
|
|
334
|
+
scrollSuccess = false;
|
|
335
|
+
scrollError = "Left scroll not supported";
|
|
336
|
+
break;
|
|
337
|
+
case "right":
|
|
338
|
+
console.error("Not Supported");
|
|
339
|
+
scrollSuccess = false;
|
|
340
|
+
scrollError = "Right scroll not supported";
|
|
341
|
+
break;
|
|
342
|
+
default:
|
|
343
|
+
scrollSuccess = false;
|
|
344
|
+
scrollError = "Direction not found";
|
|
345
|
+
throw new CommandError("Direction not found");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const actionDuration = actionEndTime ? actionEndTime - scrollStartTime : Date.now() - scrollStartTime;
|
|
349
|
+
|
|
350
|
+
// Log nested scroll action completion
|
|
351
|
+
const { formatter } = require("../../sdk-log-formatter.js");
|
|
352
|
+
emitter.emit(
|
|
353
|
+
events.log.narration,
|
|
354
|
+
formatter.formatScrollResult(direction, amount, actionDuration),
|
|
355
|
+
true,
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
// Wait for redraw and track duration
|
|
359
|
+
const redrawStartTime = Date.now();
|
|
360
|
+
await redraw.wait(2500, redrawOptions);
|
|
361
|
+
const redrawDuration = Date.now() - redrawStartTime;
|
|
362
|
+
|
|
363
|
+
const after = await system.captureScreenBase64();
|
|
331
364
|
|
|
332
|
-
|
|
365
|
+
if (before === after) {
|
|
366
|
+
emitter.emit(
|
|
367
|
+
events.log.warn,
|
|
368
|
+
"Attempted to scroll, but the screen did not change. You may need to click a non-interactive element to focus the scrollable area first.",
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Log nested redraw completion
|
|
333
373
|
emitter.emit(
|
|
334
|
-
events.log.
|
|
335
|
-
|
|
374
|
+
events.log.narration,
|
|
375
|
+
formatter.formatRedrawComplete(redrawDuration),
|
|
376
|
+
true,
|
|
336
377
|
);
|
|
378
|
+
|
|
379
|
+
// Track interaction success
|
|
380
|
+
const sessionId = sessionInstance?.get();
|
|
381
|
+
if (sessionId) {
|
|
382
|
+
try {
|
|
383
|
+
const scrollDuration = Date.now() - scrollStartTime;
|
|
384
|
+
await sandbox.send({
|
|
385
|
+
type: "trackInteraction",
|
|
386
|
+
interactionType: "scroll",
|
|
387
|
+
session: sessionId,
|
|
388
|
+
input: { direction, amount },
|
|
389
|
+
timestamp: scrollTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
390
|
+
duration: scrollDuration,
|
|
391
|
+
success: scrollSuccess,
|
|
392
|
+
error: scrollError,
|
|
393
|
+
});
|
|
394
|
+
} catch (err) {
|
|
395
|
+
console.warn("Failed to track scroll interaction:", err.message);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} catch (error) {
|
|
399
|
+
// Track interaction failure
|
|
400
|
+
const sessionId = sessionInstance?.get();
|
|
401
|
+
if (sessionId) {
|
|
402
|
+
try {
|
|
403
|
+
const scrollDuration = Date.now() - scrollStartTime;
|
|
404
|
+
await sandbox.send({
|
|
405
|
+
type: "trackInteraction",
|
|
406
|
+
interactionType: "scroll",
|
|
407
|
+
session: sessionId,
|
|
408
|
+
input: { direction, amount },
|
|
409
|
+
timestamp: scrollTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
410
|
+
duration: scrollDuration,
|
|
411
|
+
success: false,
|
|
412
|
+
error: error.message,
|
|
413
|
+
});
|
|
414
|
+
} catch (err) {
|
|
415
|
+
console.warn("Failed to track scroll interaction:", err.message);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
throw error;
|
|
337
419
|
}
|
|
338
420
|
};
|
|
339
421
|
|
|
@@ -354,7 +436,10 @@ const createCommands = (
|
|
|
354
436
|
* @param {number} [options.redraw.diffThreshold=0.1] - Screen diff threshold percentage
|
|
355
437
|
*/
|
|
356
438
|
const click = async (...args) => {
|
|
357
|
-
|
|
439
|
+
// Capture absolute timestamp at the very start of the command
|
|
440
|
+
// Frontend will calculate relative time using: timestamp - replay.clientStartDate
|
|
441
|
+
const clickTimestamp = Date.now();
|
|
442
|
+
const clickStartTime = clickTimestamp;
|
|
358
443
|
let x, y, action, elementData, redrawOptions;
|
|
359
444
|
|
|
360
445
|
// Handle both object and positional argument styles
|
|
@@ -384,37 +469,35 @@ const createCommands = (
|
|
|
384
469
|
double = true;
|
|
385
470
|
}
|
|
386
471
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
true,
|
|
391
|
-
);
|
|
472
|
+
// Show nested action details
|
|
473
|
+
const actionText = action.split("-").join("");
|
|
474
|
+
const clickActionLogStart = Date.now();
|
|
392
475
|
|
|
393
476
|
x = parseInt(x);
|
|
394
477
|
y = parseInt(y);
|
|
395
478
|
|
|
396
|
-
// Add
|
|
397
|
-
|
|
398
|
-
const elapsed = getDashcamElapsedTime();
|
|
399
|
-
if (elapsed !== null) {
|
|
400
|
-
elementData.timestamp = elapsed;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
479
|
+
// Add absolute timestamp for sandbox events
|
|
480
|
+
elementData.timestamp = Date.now();
|
|
403
481
|
|
|
404
482
|
await sandbox.send({ type: "moveMouse", x, y, ...elementData });
|
|
405
483
|
|
|
406
484
|
emitter.emit(events.mouseMove, { x, y });
|
|
407
485
|
|
|
408
486
|
await delay(2500); // wait for the mouse to move
|
|
487
|
+
|
|
488
|
+
// Update the action log with duration
|
|
489
|
+
const clickMoveEndTime = Date.now();
|
|
490
|
+
const { formatter } = require("../../sdk-log-formatter.js");
|
|
491
|
+
emitter.emit(
|
|
492
|
+
events.log.narration,
|
|
493
|
+
formatter.formatClickResult(button, x, y, clickMoveEndTime - clickActionLogStart),
|
|
494
|
+
true,
|
|
495
|
+
);
|
|
409
496
|
|
|
410
497
|
if (action !== "hover") {
|
|
411
498
|
// Update timestamp for the actual click action
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (elapsed !== null) {
|
|
415
|
-
elementData.timestamp = elapsed;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
499
|
+
elementData.timestamp = Date.now();
|
|
500
|
+
|
|
418
501
|
|
|
419
502
|
if (action === "click" || action === "left-click") {
|
|
420
503
|
await sandbox.send({ type: "leftClick", x, y, ...elementData });
|
|
@@ -438,19 +521,22 @@ const createCommands = (
|
|
|
438
521
|
|
|
439
522
|
emitter.emit(events.mouseClick, { x, y, button, click, double });
|
|
440
523
|
|
|
524
|
+
// Track action duration (before redraw wait)
|
|
525
|
+
const actionEndTime = Date.now();
|
|
526
|
+
const actionDuration = actionEndTime - clickStartTime;
|
|
527
|
+
|
|
441
528
|
// Track interaction
|
|
442
529
|
const sessionId = sessionInstance?.get();
|
|
443
530
|
if (sessionId && elementData.prompt) {
|
|
444
531
|
try {
|
|
445
|
-
const clickDuration = Date.now() - clickStartTime;
|
|
446
532
|
await sandbox.send({
|
|
447
533
|
type: "trackInteraction",
|
|
448
534
|
interactionType: "click",
|
|
449
535
|
session: sessionId,
|
|
450
536
|
prompt: elementData.prompt,
|
|
451
537
|
input: { x, y, action },
|
|
452
|
-
timestamp:
|
|
453
|
-
duration:
|
|
538
|
+
timestamp: clickTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
539
|
+
duration: actionDuration,
|
|
454
540
|
success: true,
|
|
455
541
|
cacheHit: elementData.cacheHit,
|
|
456
542
|
selector: elementData.selector,
|
|
@@ -460,10 +546,33 @@ const createCommands = (
|
|
|
460
546
|
console.warn("Failed to track click interaction:", err.message);
|
|
461
547
|
}
|
|
462
548
|
}
|
|
549
|
+
|
|
550
|
+
// Wait for redraw and track duration
|
|
551
|
+
const redrawStartTime = Date.now();
|
|
552
|
+
await redraw.wait(5000, redrawOptions);
|
|
553
|
+
const redrawDuration = Date.now() - redrawStartTime;
|
|
554
|
+
|
|
555
|
+
// Log nested redraw completion
|
|
556
|
+
emitter.emit(
|
|
557
|
+
events.log.narration,
|
|
558
|
+
formatter.formatRedrawComplete(redrawDuration),
|
|
559
|
+
true,
|
|
560
|
+
);
|
|
561
|
+
} else {
|
|
562
|
+
// For hover action (within click function)
|
|
563
|
+
const redrawStartTime = Date.now();
|
|
564
|
+
await redraw.wait(5000, redrawOptions);
|
|
565
|
+
const redrawDuration = Date.now() - redrawStartTime;
|
|
566
|
+
const actionDuration = Date.now() - clickStartTime - redrawDuration;
|
|
567
|
+
|
|
568
|
+
// Log nested redraw completion
|
|
569
|
+
emitter.emit(
|
|
570
|
+
events.log.narration,
|
|
571
|
+
formatter.formatRedrawComplete(redrawDuration),
|
|
572
|
+
true,
|
|
573
|
+
);
|
|
463
574
|
}
|
|
464
575
|
|
|
465
|
-
await redraw.wait(5000, redrawOptions);
|
|
466
|
-
|
|
467
576
|
return;
|
|
468
577
|
} catch (error) {
|
|
469
578
|
// Track interaction failure
|
|
@@ -477,7 +586,7 @@ const createCommands = (
|
|
|
477
586
|
session: sessionId,
|
|
478
587
|
prompt: elementData.prompt,
|
|
479
588
|
input: { x, y, action },
|
|
480
|
-
timestamp:
|
|
589
|
+
timestamp: clickTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
481
590
|
duration: clickDuration,
|
|
482
591
|
success: false,
|
|
483
592
|
error: error.message,
|
|
@@ -504,7 +613,10 @@ const createCommands = (
|
|
|
504
613
|
* @param {boolean} [options.selectorUsed] - Whether selector was used
|
|
505
614
|
*/
|
|
506
615
|
const hover = async (...args) => {
|
|
507
|
-
|
|
616
|
+
// Capture absolute timestamp at the very start of the command
|
|
617
|
+
// Frontend will calculate relative time using: timestamp - replay.clientStartDate
|
|
618
|
+
const hoverTimestamp = Date.now();
|
|
619
|
+
const hoverStartTime = hoverTimestamp;
|
|
508
620
|
let x, y, elementData, redrawOptions;
|
|
509
621
|
|
|
510
622
|
// Handle both object and positional argument styles
|
|
@@ -528,29 +640,26 @@ const createCommands = (
|
|
|
528
640
|
x = parseInt(x);
|
|
529
641
|
y = parseInt(y);
|
|
530
642
|
|
|
531
|
-
// Add
|
|
532
|
-
|
|
533
|
-
const elapsed = getDashcamElapsedTime();
|
|
534
|
-
if (elapsed !== null) {
|
|
535
|
-
elementData.timestamp = elapsed;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
643
|
+
// Add absolute timestamp for sandbox events
|
|
644
|
+
elementData.timestamp = Date.now();
|
|
538
645
|
|
|
539
646
|
await sandbox.send({ type: "moveMouse", x, y, ...elementData });
|
|
540
647
|
|
|
541
648
|
// Track interaction
|
|
542
649
|
const sessionId = sessionInstance?.get();
|
|
650
|
+
const actionEndTime = Date.now();
|
|
651
|
+
const actionDuration = actionEndTime - hoverStartTime;
|
|
652
|
+
|
|
543
653
|
if (sessionId && elementData.prompt) {
|
|
544
654
|
try {
|
|
545
|
-
const hoverDuration = Date.now() - hoverStartTime;
|
|
546
655
|
await sandbox.send({
|
|
547
656
|
type: "trackInteraction",
|
|
548
657
|
interactionType: "hover",
|
|
549
658
|
session: sessionId,
|
|
550
659
|
prompt: elementData.prompt,
|
|
551
660
|
input: { x, y },
|
|
552
|
-
timestamp:
|
|
553
|
-
duration:
|
|
661
|
+
timestamp: hoverTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
662
|
+
duration: actionDuration,
|
|
554
663
|
success: true,
|
|
555
664
|
cacheHit: elementData.cacheHit,
|
|
556
665
|
selector: elementData.selector,
|
|
@@ -561,7 +670,19 @@ const createCommands = (
|
|
|
561
670
|
}
|
|
562
671
|
}
|
|
563
672
|
|
|
673
|
+
// Wait for redraw and track duration
|
|
674
|
+
const redrawStartTime = Date.now();
|
|
564
675
|
await redraw.wait(2500, redrawOptions);
|
|
676
|
+
const redrawDuration = Date.now() - redrawStartTime;
|
|
677
|
+
|
|
678
|
+
// Log action completion with separate durations
|
|
679
|
+
const { formatter } = require("../../sdk-log-formatter.js");
|
|
680
|
+
const completionMessage = formatter.formatActionComplete("hover", elementData.prompt, {
|
|
681
|
+
actionDuration,
|
|
682
|
+
redrawDuration,
|
|
683
|
+
cacheHit: elementData.cacheHit,
|
|
684
|
+
});
|
|
685
|
+
emitter.emit(events.log.log, completionMessage);
|
|
565
686
|
|
|
566
687
|
return;
|
|
567
688
|
} catch (error) {
|
|
@@ -576,7 +697,7 @@ const createCommands = (
|
|
|
576
697
|
session: sessionId,
|
|
577
698
|
prompt: elementData.prompt,
|
|
578
699
|
input: { x, y },
|
|
579
|
-
timestamp:
|
|
700
|
+
timestamp: hoverTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
580
701
|
duration: hoverDuration,
|
|
581
702
|
success: false,
|
|
582
703
|
error: error.message,
|
|
@@ -754,32 +875,35 @@ const createCommands = (
|
|
|
754
875
|
* @param {number} [options.redraw.diffThreshold=0.1] - Screen diff threshold percentage
|
|
755
876
|
*/
|
|
756
877
|
"type": async (text, options = {}) => {
|
|
757
|
-
const
|
|
878
|
+
const { formatter } = require("../../sdk-log-formatter.js");
|
|
879
|
+
// Capture absolute timestamp at the very start of the command
|
|
880
|
+
// Frontend will calculate relative time using: timestamp - replay.clientStartDate
|
|
881
|
+
const typeTimestamp = Date.now();
|
|
882
|
+
const typeStartTime = typeTimestamp;
|
|
758
883
|
const { delay = 250, secret = false, redraw: redrawOpts, ...elementData } = options;
|
|
759
884
|
const redrawOptions = extractRedrawOptions({ redraw: redrawOpts, ...options });
|
|
760
885
|
|
|
761
|
-
// Log
|
|
886
|
+
// Log parent action with text
|
|
762
887
|
if (secret) {
|
|
763
|
-
emitter.emit(events.log.narration, theme.dim(`
|
|
888
|
+
emitter.emit(events.log.narration, formatter.getPrefix("type") + " " + theme.yellow.bold("Type") + " " + theme.dim(`secret "****"`));
|
|
764
889
|
} else {
|
|
765
|
-
emitter.emit(events.log.narration, theme.
|
|
890
|
+
emitter.emit(events.log.narration, formatter.getPrefix("type") + " " + theme.yellow.bold("Type") + " " + theme.cyan(`"${text}"`));
|
|
766
891
|
}
|
|
767
892
|
|
|
768
893
|
await redraw.start(redrawOptions);
|
|
769
894
|
|
|
770
895
|
text = text.toString();
|
|
771
896
|
|
|
772
|
-
// Add
|
|
773
|
-
|
|
774
|
-
const elapsed = getDashcamElapsedTime();
|
|
775
|
-
if (elapsed !== null) {
|
|
776
|
-
elementData.timestamp = elapsed;
|
|
777
|
-
}
|
|
778
|
-
}
|
|
897
|
+
// Add absolute timestamp for sandbox events
|
|
898
|
+
elementData.timestamp = Date.now();
|
|
779
899
|
|
|
780
900
|
// Actually type the text in the sandbox
|
|
781
901
|
await sandbox.send({ type: "write", text, delay, ...elementData });
|
|
782
902
|
|
|
903
|
+
// Update the action log with duration
|
|
904
|
+
const typeActionEndTime = Date.now();
|
|
905
|
+
emitter.emit(events.log.narration, formatter.formatTypeResult(text, secret, typeActionEndTime - typeStartTime), true);
|
|
906
|
+
|
|
783
907
|
// Track interaction
|
|
784
908
|
const sessionId = sessionInstance?.get();
|
|
785
909
|
if (sessionId) {
|
|
@@ -791,7 +915,7 @@ const createCommands = (
|
|
|
791
915
|
session: sessionId,
|
|
792
916
|
// Store masked text if secret, otherwise store actual text
|
|
793
917
|
input: { text: secret ? "****" : text, delay },
|
|
794
|
-
timestamp:
|
|
918
|
+
timestamp: typeTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
795
919
|
duration: typeDuration,
|
|
796
920
|
success: true,
|
|
797
921
|
isSecret: secret, // Flag this interaction if it contains a secret
|
|
@@ -801,7 +925,17 @@ const createCommands = (
|
|
|
801
925
|
}
|
|
802
926
|
}
|
|
803
927
|
|
|
928
|
+
const redrawStartTime = Date.now();
|
|
804
929
|
await redraw.wait(5000, redrawOptions);
|
|
930
|
+
const redrawDuration = Date.now() - redrawStartTime;
|
|
931
|
+
|
|
932
|
+
// Log nested redraw completion
|
|
933
|
+
emitter.emit(
|
|
934
|
+
events.log.narration,
|
|
935
|
+
formatter.formatRedrawComplete(redrawDuration),
|
|
936
|
+
true,
|
|
937
|
+
);
|
|
938
|
+
|
|
805
939
|
return;
|
|
806
940
|
},
|
|
807
941
|
/**
|
|
@@ -815,20 +949,36 @@ const createCommands = (
|
|
|
815
949
|
* @param {number} [options.redraw.diffThreshold=0.1] - Screen diff threshold percentage
|
|
816
950
|
*/
|
|
817
951
|
"press-keys": async (keys, options = {}) => {
|
|
818
|
-
const
|
|
952
|
+
const { formatter } = require("../../sdk-log-formatter.js");
|
|
953
|
+
// Capture absolute timestamp at the very start of the command
|
|
954
|
+
// Frontend will calculate relative time using: timestamp - replay.clientStartDate
|
|
955
|
+
const pressKeysTimestamp = Date.now();
|
|
956
|
+
const pressKeysStartTime = pressKeysTimestamp;
|
|
819
957
|
const redrawOptions = extractRedrawOptions(options);
|
|
958
|
+
const keysDisplay = Array.isArray(keys) ? keys.join(", ") : keys;
|
|
959
|
+
|
|
960
|
+
// Log parent action
|
|
820
961
|
emitter.emit(
|
|
821
962
|
events.log.narration,
|
|
822
|
-
theme.
|
|
823
|
-
`pressing keys: ${Array.isArray(keys) ? keys.join(", ") : keys}...`,
|
|
824
|
-
),
|
|
963
|
+
formatter.getPrefix("pressKeys") + " " + theme.yellow.bold("PressKeys") + " " + theme.cyan(`${keysDisplay}`),
|
|
825
964
|
);
|
|
826
965
|
|
|
827
966
|
await redraw.start(redrawOptions);
|
|
828
967
|
|
|
968
|
+
// Log nested action details
|
|
969
|
+
const pressKeysActionLogStart = Date.now();
|
|
970
|
+
|
|
829
971
|
// finally, press the keys
|
|
830
972
|
await sandbox.send({ type: "press", keys });
|
|
831
973
|
|
|
974
|
+
// Update the action log with duration
|
|
975
|
+
const pressKeysActionEndTime = Date.now();
|
|
976
|
+
emitter.emit(
|
|
977
|
+
events.log.narration,
|
|
978
|
+
formatter.formatPressKeysResult(keysDisplay, pressKeysActionEndTime - pressKeysActionLogStart),
|
|
979
|
+
true,
|
|
980
|
+
);
|
|
981
|
+
|
|
832
982
|
// Track interaction
|
|
833
983
|
const sessionId = sessionInstance?.get();
|
|
834
984
|
if (sessionId) {
|
|
@@ -839,7 +989,7 @@ const createCommands = (
|
|
|
839
989
|
interactionType: "pressKeys",
|
|
840
990
|
session: sessionId,
|
|
841
991
|
input: { keys },
|
|
842
|
-
timestamp:
|
|
992
|
+
timestamp: pressKeysTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
843
993
|
duration: pressKeysDuration,
|
|
844
994
|
success: true,
|
|
845
995
|
});
|
|
@@ -848,7 +998,16 @@ const createCommands = (
|
|
|
848
998
|
}
|
|
849
999
|
}
|
|
850
1000
|
|
|
1001
|
+
const redrawStartTime = Date.now();
|
|
851
1002
|
await redraw.wait(5000, redrawOptions);
|
|
1003
|
+
const redrawDuration = Date.now() - redrawStartTime;
|
|
1004
|
+
|
|
1005
|
+
// Log nested redraw completion
|
|
1006
|
+
emitter.emit(
|
|
1007
|
+
events.log.narration,
|
|
1008
|
+
formatter.formatRedrawComplete(redrawDuration),
|
|
1009
|
+
true,
|
|
1010
|
+
);
|
|
852
1011
|
|
|
853
1012
|
return;
|
|
854
1013
|
},
|
|
@@ -858,7 +1017,10 @@ const createCommands = (
|
|
|
858
1017
|
* @param {Object} [options] - Additional options (reserved for future use)
|
|
859
1018
|
*/
|
|
860
1019
|
"wait": async (timeout = 3000, options = {}) => {
|
|
861
|
-
|
|
1020
|
+
// Capture absolute timestamp at the very start of the command
|
|
1021
|
+
// Frontend will calculate relative time using: timestamp - replay.clientStartDate
|
|
1022
|
+
const waitTimestamp = Date.now();
|
|
1023
|
+
const waitStartTime = waitTimestamp;
|
|
862
1024
|
emitter.emit(events.log.narration, theme.dim(`waiting ${timeout}ms...`));
|
|
863
1025
|
const result = await delay(timeout);
|
|
864
1026
|
|
|
@@ -872,7 +1034,7 @@ const createCommands = (
|
|
|
872
1034
|
interactionType: "wait",
|
|
873
1035
|
session: sessionId,
|
|
874
1036
|
input: { timeout },
|
|
875
|
-
timestamp:
|
|
1037
|
+
timestamp: waitTimestamp, // Use dashcam elapsed time instead of absolute time
|
|
876
1038
|
duration: waitDuration,
|
|
877
1039
|
success: true,
|
|
878
1040
|
});
|
|
@@ -890,6 +1052,9 @@ const createCommands = (
|
|
|
890
1052
|
* @param {number} [options.timeout=10000] - Timeout in milliseconds
|
|
891
1053
|
*/
|
|
892
1054
|
"wait-for-image": async (...args) => {
|
|
1055
|
+
// Capture absolute timestamp at the very start of the command
|
|
1056
|
+
// Frontend will calculate relative time using: timestamp - replay.clientStartDate
|
|
1057
|
+
const waitForImageTimestamp = Date.now();
|
|
893
1058
|
let description, timeout;
|
|
894
1059
|
|
|
895
1060
|
// Handle both object and positional argument styles
|
|
@@ -951,7 +1116,7 @@ const createCommands = (
|
|
|
951
1116
|
session: sessionId,
|
|
952
1117
|
prompt: description,
|
|
953
1118
|
input: { timeout },
|
|
954
|
-
timestamp:
|
|
1119
|
+
timestamp: waitForImageTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
955
1120
|
duration: waitForImageDuration,
|
|
956
1121
|
success: true,
|
|
957
1122
|
});
|
|
@@ -974,7 +1139,7 @@ const createCommands = (
|
|
|
974
1139
|
session: sessionId,
|
|
975
1140
|
prompt: description,
|
|
976
1141
|
input: { timeout },
|
|
977
|
-
timestamp:
|
|
1142
|
+
timestamp: waitForImageTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
978
1143
|
duration: waitForImageDuration,
|
|
979
1144
|
success: false,
|
|
980
1145
|
error: errorMsg,
|
|
@@ -999,6 +1164,9 @@ const createCommands = (
|
|
|
999
1164
|
* @param {number} [options.redraw.diffThreshold=0.1] - Screen diff threshold percentage
|
|
1000
1165
|
*/
|
|
1001
1166
|
"wait-for-text": async (...args) => {
|
|
1167
|
+
// Capture absolute timestamp at the very start of the command
|
|
1168
|
+
// Frontend will calculate relative time using: timestamp - replay.clientStartDate
|
|
1169
|
+
const waitForTextTimestamp = Date.now();
|
|
1002
1170
|
let text, timeout, redrawOptions;
|
|
1003
1171
|
|
|
1004
1172
|
// Handle both object and positional argument styles
|
|
@@ -1061,7 +1229,7 @@ const createCommands = (
|
|
|
1061
1229
|
session: sessionId,
|
|
1062
1230
|
prompt: text,
|
|
1063
1231
|
input: { timeout },
|
|
1064
|
-
timestamp:
|
|
1232
|
+
timestamp: waitForTextTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
1065
1233
|
duration: waitForTextDuration,
|
|
1066
1234
|
success: true,
|
|
1067
1235
|
});
|
|
@@ -1084,7 +1252,7 @@ const createCommands = (
|
|
|
1084
1252
|
session: sessionId,
|
|
1085
1253
|
prompt: text,
|
|
1086
1254
|
input: { timeout },
|
|
1087
|
-
timestamp:
|
|
1255
|
+
timestamp: waitForTextTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
1088
1256
|
duration: waitForTextDuration,
|
|
1089
1257
|
success: false,
|
|
1090
1258
|
error: errorMsg,
|
|
@@ -1281,7 +1449,10 @@ const createCommands = (
|
|
|
1281
1449
|
* @param {string} options.description - What to extract
|
|
1282
1450
|
*/
|
|
1283
1451
|
"extract": async (...args) => {
|
|
1284
|
-
|
|
1452
|
+
// Capture absolute timestamp at the very start of the command
|
|
1453
|
+
// Frontend will calculate relative time using: timestamp - replay.clientStartDate
|
|
1454
|
+
const rememberTimestamp = Date.now();
|
|
1455
|
+
const rememberStartTime = rememberTimestamp;
|
|
1285
1456
|
let description;
|
|
1286
1457
|
|
|
1287
1458
|
// Handle both object and positional argument styles
|
|
@@ -1305,15 +1476,15 @@ const createCommands = (
|
|
|
1305
1476
|
const rememberDuration = Date.now() - rememberStartTime;
|
|
1306
1477
|
await sandbox.send({
|
|
1307
1478
|
type: "trackInteraction",
|
|
1308
|
-
interactionType: "
|
|
1479
|
+
interactionType: "extract",
|
|
1309
1480
|
session: sessionId,
|
|
1310
1481
|
prompt: description,
|
|
1311
|
-
timestamp:
|
|
1482
|
+
timestamp: rememberTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
1312
1483
|
duration: rememberDuration,
|
|
1313
1484
|
success: true,
|
|
1314
1485
|
});
|
|
1315
1486
|
} catch (err) {
|
|
1316
|
-
console.warn("Failed to track
|
|
1487
|
+
console.warn("Failed to track extract interaction:", err.message);
|
|
1317
1488
|
}
|
|
1318
1489
|
}
|
|
1319
1490
|
|
|
@@ -1326,16 +1497,16 @@ const createCommands = (
|
|
|
1326
1497
|
const rememberDuration = Date.now() - rememberStartTime;
|
|
1327
1498
|
await sandbox.send({
|
|
1328
1499
|
type: "trackInteraction",
|
|
1329
|
-
interactionType: "
|
|
1500
|
+
interactionType: "extract",
|
|
1330
1501
|
session: sessionId,
|
|
1331
1502
|
prompt: description,
|
|
1332
|
-
timestamp:
|
|
1503
|
+
timestamp: rememberTimestamp, // Absolute epoch timestamp - frontend calculates relative using clientStartDate
|
|
1333
1504
|
duration: rememberDuration,
|
|
1334
1505
|
success: false,
|
|
1335
1506
|
error: error.message,
|
|
1336
1507
|
});
|
|
1337
1508
|
} catch (err) {
|
|
1338
|
-
console.warn("Failed to track
|
|
1509
|
+
console.warn("Failed to track extract interaction:", err.message);
|
|
1339
1510
|
}
|
|
1340
1511
|
}
|
|
1341
1512
|
throw error;
|
|
@@ -1360,6 +1531,7 @@ const createCommands = (
|
|
|
1360
1531
|
* @param {boolean} [options.silent=false] - Suppress output
|
|
1361
1532
|
*/
|
|
1362
1533
|
"exec": async (...args) => {
|
|
1534
|
+
const { formatter } = require("../../sdk-log-formatter.js");
|
|
1363
1535
|
let language, code, timeout, silent;
|
|
1364
1536
|
|
|
1365
1537
|
// Handle both object and positional argument styles
|
|
@@ -1370,9 +1542,13 @@ const createCommands = (
|
|
|
1370
1542
|
[language = 'pwsh', code, timeout, silent = false] = args;
|
|
1371
1543
|
}
|
|
1372
1544
|
|
|
1373
|
-
|
|
1545
|
+
// Log parent action
|
|
1546
|
+
emitter.emit(events.log.narration, formatter.getPrefix("action") + " " + theme.cyan.bold("Exec") + " " + theme.magenta(`[${language}]`), true);
|
|
1374
1547
|
|
|
1375
|
-
|
|
1548
|
+
// Log nested command details (truncate to first line)
|
|
1549
|
+
const firstLine = code.split('\n')[0];
|
|
1550
|
+
const codeDisplay = code.includes('\n') ? firstLine + '...' : firstLine;
|
|
1551
|
+
emitter.emit(events.log.log, formatter.formatCodeLine(codeDisplay));
|
|
1376
1552
|
|
|
1377
1553
|
let plat = system.platform();
|
|
1378
1554
|
|
|
@@ -1399,27 +1575,48 @@ const createCommands = (
|
|
|
1399
1575
|
language = "pwsh";
|
|
1400
1576
|
}
|
|
1401
1577
|
|
|
1578
|
+
const execActionLogStart = Date.now();
|
|
1579
|
+
|
|
1402
1580
|
let result = null;
|
|
1403
1581
|
|
|
1404
1582
|
result = await sandbox.send({
|
|
1405
1583
|
type: "commands.run",
|
|
1406
1584
|
command: code,
|
|
1407
1585
|
timeout,
|
|
1408
|
-
});
|
|
1586
|
+
}, timeout || 300000);
|
|
1587
|
+
|
|
1588
|
+
const execActionEndTime = Date.now();
|
|
1589
|
+
const execDuration = execActionEndTime - execActionLogStart;
|
|
1590
|
+
|
|
1591
|
+
// const debugMode = process.env.VERBOSE || process.env.DEBUG || process.env.TD_DEBUG;
|
|
1592
|
+
// if (debugMode) {
|
|
1593
|
+
// console.log(result);
|
|
1594
|
+
// }
|
|
1409
1595
|
|
|
1410
1596
|
if (result.out && result.out.returncode !== 0) {
|
|
1597
|
+
emitter.emit(
|
|
1598
|
+
events.log.narration,
|
|
1599
|
+
formatter.formatExecComplete(result.out.returncode, execDuration),
|
|
1600
|
+
true,
|
|
1601
|
+
);
|
|
1411
1602
|
throw new MatchError(
|
|
1412
1603
|
`Command failed with exit code ${result.out.returncode}: ${result.out.stderr}`,
|
|
1413
1604
|
);
|
|
1414
1605
|
} else {
|
|
1606
|
+
emitter.emit(
|
|
1607
|
+
events.log.narration,
|
|
1608
|
+
formatter.formatExecComplete(0, execDuration),
|
|
1609
|
+
true,
|
|
1610
|
+
);
|
|
1611
|
+
|
|
1415
1612
|
if (!silent && result.out?.stdout) {
|
|
1416
|
-
emitter.emit(events.log.log, theme.dim(`stdout:`), true);
|
|
1417
|
-
emitter.emit(events.log.log,
|
|
1613
|
+
emitter.emit(events.log.log, theme.dim(` stdout:`), true);
|
|
1614
|
+
emitter.emit(events.log.log, theme.dim(` ${result.out.stdout}`), true);
|
|
1418
1615
|
}
|
|
1419
1616
|
|
|
1420
1617
|
if (!silent && result.out.stderr) {
|
|
1421
|
-
emitter.emit(events.log.log, theme.dim(`stderr:`), true);
|
|
1422
|
-
emitter.emit(events.log.log,
|
|
1618
|
+
emitter.emit(events.log.log, theme.dim(` stderr:`), true);
|
|
1619
|
+
emitter.emit(events.log.log, theme.dim(` ${result.out.stderr}`), true);
|
|
1423
1620
|
}
|
|
1424
1621
|
|
|
1425
1622
|
return result.out?.stdout?.trim();
|
|
@@ -1476,10 +1673,13 @@ const createCommands = (
|
|
|
1476
1673
|
if (!stepResult) {
|
|
1477
1674
|
emitter.emit(events.log.log, `No result returned from script`, true);
|
|
1478
1675
|
} else {
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1676
|
+
/* The above JavaScript code is checking if the variable `silent` is falsy (not true) and if
|
|
1677
|
+
so, it emits log events using an emitter. The emitted log events include the
|
|
1678
|
+
theme.dim(`Result:`) and the value of the `stepResult` variable. */
|
|
1679
|
+
// if (!silent) {
|
|
1680
|
+
// emitter.emit(events.log.log, theme.dim(`Result:`), true);
|
|
1681
|
+
// emitter.emit(events.log.log, stepResult, true);
|
|
1682
|
+
// }
|
|
1483
1683
|
}
|
|
1484
1684
|
|
|
1485
1685
|
return stepResult;
|