testdriverai 7.1.4 → 7.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.github/workflows/acceptance.yaml +81 -0
  2. package/.github/workflows/publish.yaml +44 -0
  3. package/agent/index.js +18 -19
  4. package/agent/interface.js +4 -0
  5. package/agent/lib/commands.js +321 -121
  6. package/agent/lib/redraw.js +99 -39
  7. package/agent/lib/sandbox.js +98 -6
  8. package/agent/lib/sdk.js +25 -0
  9. package/agent/lib/system.js +2 -1
  10. package/agent/lib/validation.js +6 -6
  11. package/docs/docs.json +211 -101
  12. package/docs/snippets/tests/type-repeated-replay.mdx +1 -1
  13. package/docs/v7/_drafts/caching-selectors.mdx +24 -0
  14. package/docs/v7/api/act.mdx +1 -1
  15. package/docs/v7/api/assert.mdx +1 -1
  16. package/docs/v7/api/assertions.mdx +7 -7
  17. package/docs/v7/api/elements.mdx +78 -0
  18. package/docs/v7/api/find.mdx +38 -0
  19. package/docs/v7/api/focusApplication.mdx +2 -2
  20. package/docs/v7/api/hover.mdx +2 -2
  21. package/docs/v7/features/ai-native.mdx +57 -71
  22. package/docs/v7/features/application-logs.mdx +353 -0
  23. package/docs/v7/features/browser-logs.mdx +414 -0
  24. package/docs/v7/features/cache-management.mdx +402 -0
  25. package/docs/v7/features/continuous-testing.mdx +346 -0
  26. package/docs/v7/features/coverage.mdx +508 -0
  27. package/docs/v7/features/data-driven-testing.mdx +441 -0
  28. package/docs/v7/features/easy-to-write.mdx +2 -73
  29. package/docs/v7/features/enterprise.mdx +155 -39
  30. package/docs/v7/features/fast.mdx +63 -81
  31. package/docs/v7/features/managed-sandboxes.mdx +384 -0
  32. package/docs/v7/features/network-monitoring.mdx +568 -0
  33. package/docs/v7/features/observable.mdx +3 -22
  34. package/docs/v7/features/parallel-execution.mdx +381 -0
  35. package/docs/v7/features/powerful.mdx +1 -1
  36. package/docs/v7/features/reports.mdx +414 -0
  37. package/docs/v7/features/sandbox-customization.mdx +229 -0
  38. package/docs/v7/features/scalable.mdx +217 -2
  39. package/docs/v7/features/stable.mdx +106 -147
  40. package/docs/v7/features/system-performance.mdx +616 -0
  41. package/docs/v7/features/test-analytics.mdx +373 -0
  42. package/docs/v7/features/test-cases.mdx +393 -0
  43. package/docs/v7/features/test-replays.mdx +408 -0
  44. package/docs/v7/features/test-reports.mdx +308 -0
  45. package/docs/v7/getting-started/{running-and-debugging.mdx → debugging-tests.mdx} +12 -142
  46. package/docs/v7/getting-started/quickstart.mdx +22 -305
  47. package/docs/v7/getting-started/running-tests.mdx +173 -0
  48. package/docs/v7/overview/what-is-testdriver.mdx +2 -14
  49. package/docs/v7/presets/chrome-extension.mdx +147 -122
  50. package/interfaces/cli/commands/init.js +3 -3
  51. package/interfaces/cli/lib/base.js +3 -2
  52. package/interfaces/logger.js +0 -2
  53. package/interfaces/shared-test-state.mjs +0 -5
  54. package/interfaces/vitest-plugin.mjs +70 -50
  55. package/lib/core/Dashcam.js +60 -85
  56. package/lib/vitest/hooks.mjs +42 -50
  57. package/package.json +1 -1
  58. package/sdk-log-formatter.js +350 -175
  59. package/sdk.d.ts +36 -3
  60. package/sdk.js +431 -116
  61. package/setup/aws/cloudformation.yaml +2 -2
  62. package/setup/aws/self-hosted.yml +1 -1
  63. package/test/testdriver/chrome-extension.test.mjs +55 -72
  64. package/test/testdriver/element-not-found.test.mjs +2 -1
  65. package/test/testdriver/hover-image.test.mjs +1 -1
  66. package/test/testdriver/scroll-until-text.test.mjs +10 -6
  67. package/test/testdriver/setup/lifecycleHelpers.mjs +19 -24
  68. package/test/testdriver/setup/testHelpers.mjs +18 -23
  69. package/vitest.config.mjs +3 -3
  70. package/.github/workflows/linux-tests.yml +0 -28
  71. package/docs/v7/getting-started/generating-tests.mdx +0 -525
  72. package/test/testdriver/auto-cache-key-demo.test.mjs +0 -56
@@ -249,6 +249,12 @@ export async function createTestDriver(options = {}) {
249
249
  // Merge options: plugin global options < test-specific options
250
250
  const mergedOptions = { ...pluginOptions, ...options };
251
251
 
252
+ // Support TD_OS environment variable for specifying target OS (linux, mac, windows)
253
+ // Priority: test options > plugin options > environment variable > default (linux)
254
+ if (!mergedOptions.os && process.env.TD_OS) {
255
+ mergedOptions.os = process.env.TD_OS;
256
+ }
257
+
252
258
  // Extract TestDriver-specific options
253
259
  const apiKey = mergedOptions.apiKey || process.env.TD_API_KEY;
254
260
 
@@ -264,10 +270,8 @@ export async function createTestDriver(options = {}) {
264
270
  const testdriver = new TestDriverSDK(apiKey, config);
265
271
 
266
272
  // Connect to sandbox
267
- console.log('[testdriver] Connecting to sandbox...');
268
273
  await testdriver.auth();
269
274
  await testdriver.connect();
270
- console.log('[testdriver] ✅ Connected to sandbox');
271
275
 
272
276
  return testdriver;
273
277
  }
@@ -312,9 +316,7 @@ export async function cleanupTestDriver(testdriver) {
312
316
  if (!testdriver) {
313
317
  return;
314
318
  }
315
-
316
- console.log('[testdriver] Cleaning up TestDriver client...');
317
-
319
+
318
320
  try {
319
321
  // Stop dashcam if it was started
320
322
  if (testdriver._dashcam && testdriver._dashcam.recording) {
@@ -337,7 +339,6 @@ export async function cleanupTestDriver(testdriver) {
337
339
  }
338
340
 
339
341
  await testdriver.disconnect();
340
- console.log('✅ Client disconnected');
341
342
  } catch (error) {
342
343
  console.error('Error disconnecting client:', error);
343
344
  }
@@ -351,7 +352,7 @@ async function handleProcessExit() {
351
352
  return;
352
353
  }
353
354
 
354
- logger.info("Process interrupted, marking test run as cancelled...");
355
+ logger.debug("Process interrupted, marking test run as cancelled...");
355
356
 
356
357
  try {
357
358
  const stats = {
@@ -378,7 +379,7 @@ async function handleProcessExit() {
378
379
  }
379
380
 
380
381
  await completeTestRun(completeData);
381
- logger.info("✅ Test run marked as cancelled");
382
+ logger.debug("✅ Test run marked as cancelled");
382
383
  } catch (error) {
383
384
  logger.error("Failed to mark test run as cancelled:", error.message);
384
385
  }
@@ -402,14 +403,7 @@ function registerExitHandlers() {
402
403
  await handleProcessExit();
403
404
  process.exit(143); // Standard exit code for SIGTERM
404
405
  });
405
-
406
- // Handle unexpected exits
407
- process.on("beforeExit", async () => {
408
- // Only handle if test run is still running (hasn't been completed normally)
409
- if (pluginState.testRun && !pluginState.testRunCompleted) {
410
- await handleProcessExit();
411
- }
412
- });
406
+
413
407
  }
414
408
 
415
409
  /**
@@ -494,8 +488,6 @@ class TestDriverReporter {
494
488
  return;
495
489
  }
496
490
 
497
- logger.info("Starting test run initialization with API key...");
498
-
499
491
  try {
500
492
  // Exchange API key for JWT token
501
493
  logger.debug("Authenticating with API...");
@@ -544,7 +536,7 @@ class TestDriverReporter {
544
536
  startTime: pluginState.startTime,
545
537
  });
546
538
 
547
- logger.info(`Test run created: ${pluginState.testRunId}`);
539
+ logger.debug(`Test run created: ${pluginState.testRunId}`);
548
540
  } catch (error) {
549
541
  logger.error("Failed to initialize:", error.message);
550
542
  pluginState.apiKey = null;
@@ -566,7 +558,7 @@ class TestDriverReporter {
566
558
  return;
567
559
  }
568
560
 
569
- logger.info("Completing test run...");
561
+ logger.debug("Completing test run...");
570
562
 
571
563
  try {
572
564
  // Calculate statistics from testModules
@@ -574,14 +566,17 @@ class TestDriverReporter {
574
566
 
575
567
  logger.debug("Stats:", stats);
576
568
 
577
- // Determine overall status based on reason and stats
569
+ // Determine overall status based on stats (not reason, which is unreliable in parallel runs)
578
570
  let status = "passed";
579
- if (reason === "failed" || stats.failedTests > 0) {
571
+ if (stats.failedTests > 0) {
580
572
  status = "failed";
581
573
  } else if (reason === "interrupted") {
582
574
  status = "cancelled";
583
575
  } else if (stats.totalTests === 0) {
584
576
  status = "cancelled";
577
+ } else if (stats.passedTests === 0 && stats.skippedTests === 0) {
578
+ // No tests actually ran (all were filtered/excluded)
579
+ status = "cancelled";
585
580
  }
586
581
 
587
582
  // Complete test run via API
@@ -599,9 +594,12 @@ class TestDriverReporter {
599
594
 
600
595
  // Update platform if detected from test results
601
596
  const platform = getPlatform();
597
+ logger.debug(`Platform detection result: ${platform}, detectedPlatform in state: ${pluginState.detectedPlatform}`);
602
598
  if (platform) {
603
599
  completeData.platform = platform;
604
600
  logger.debug(`Updating test run with platform: ${platform}`);
601
+ } else {
602
+ logger.warn(`No platform detected, test run will keep default platform`);
605
603
  }
606
604
 
607
605
  // Wait for any pending operations (shouldn't be any, but just in case)
@@ -619,7 +617,17 @@ class TestDriverReporter {
619
617
  // Mark test run as completed to prevent duplicate completion
620
618
  pluginState.testRunCompleted = true;
621
619
 
622
- logger.info(`✅ Test run completed: ${stats.passedTests}/${stats.totalTests} passed`);
620
+ // Output the test run URL for CI to capture
621
+ const testRunDbId = process.env.TD_TEST_RUN_DB_ID;
622
+ const consoleUrl = getConsoleUrl(pluginState.apiRoot);
623
+ if (testRunDbId) {
624
+ const testRunUrl = `${consoleUrl}/runs/${testRunDbId}`;
625
+ logger.debug(`🔗 View test run: ${testRunUrl}`);
626
+ // Output in a parseable format for CI
627
+ console.log(`TESTDRIVER_RUN_URL=${testRunUrl}`);
628
+ }
629
+
630
+ logger.debug(`✅ Test run completed: ${stats.passedTests}/${stats.totalTests} passed`);
623
631
  } catch (error) {
624
632
  logger.error("Failed to complete test run:", error.message);
625
633
  logger.debug("Error stack:", error.stack);
@@ -633,9 +641,6 @@ class TestDriverReporter {
633
641
  test,
634
642
  startTime: Date.now(),
635
643
  });
636
-
637
- // Try to detect platform from test context
638
- detectPlatformFromTest(test);
639
644
  }
640
645
 
641
646
  async onTestCaseResult(test) {
@@ -649,7 +654,7 @@ class TestDriverReporter {
649
654
  ? "skipped"
650
655
  : "failed";
651
656
 
652
- logger.info(`Test case completed: ${test.name} (${status})`);
657
+ logger.debug(`Test case completed: ${test.name} (${status})`);
653
658
 
654
659
  // Calculate duration from tracked start time
655
660
  const testCase = pluginState.testCases.get(test.id);
@@ -692,12 +697,9 @@ class TestDriverReporter {
692
697
  // Don't override duration from file - use Vitest's result.duration
693
698
  // duration is already set above from result.duration
694
699
 
695
- logger.debug(`Read from file - dashcam: ${dashcamUrl}, platform: ${platform}, sessionId: ${sessionId}, testFile: ${testFile}, testOrder: ${testOrder}, duration: ${duration}ms`);
696
-
697
700
  // Update test run platform from first test that reports it
698
701
  if (platform && !pluginState.detectedPlatform) {
699
702
  pluginState.detectedPlatform = platform;
700
- logger.debug(`Detected platform from test: ${platform}`);
701
703
  }
702
704
 
703
705
  // Clean up the file after reading
@@ -811,8 +813,8 @@ class TestDriverReporter {
811
813
  const testCaseDbId = testCaseResponse.data?.id;
812
814
  const testRunDbId = process.env.TD_TEST_RUN_DB_ID;
813
815
 
814
- logger.debug(`Reported test case to API${dashcamUrl ? " with dashcam URL" : ""}`);
815
- logger.info(`🔗 View test: ${pluginState.apiRoot.replace("testdriver-api.onrender.com", "console.testdriver.ai")}/runs/${testRunDbId}/${testCaseDbId}`);
816
+ console.log('');
817
+ console.log(`🔗 Test Report: ${getConsoleUrl(pluginState.apiRoot)}/runs/${testRunDbId}/${testCaseDbId}`);
816
818
  } catch (error) {
817
819
  logger.error("Failed to report test case:", error.message);
818
820
  }
@@ -823,6 +825,33 @@ class TestDriverReporter {
823
825
  // Helper Functions
824
826
  // ============================================================================
825
827
 
828
+ /**
829
+ * Maps an API root URL to its corresponding web console URL.
830
+ * The API and web console are served from different domains/ports.
831
+ *
832
+ * @param {string} apiRoot - The API root URL (e.g., https://testdriver-api.onrender.com)
833
+ * @returns {string} The corresponding web console URL
834
+ */
835
+ function getConsoleUrl(apiRoot) {
836
+
837
+ if (!apiRoot) return 'https://console.testdriver.ai';
838
+
839
+ // Production: API on render.com -> Console on testdriver.ai
840
+ if (apiRoot.includes('testdriver-api.onrender.com')) {
841
+ return 'https://console.testdriver.ai';
842
+ }
843
+
844
+ // Local development: API on localhost:1337 -> Web on localhost:3001
845
+ if (apiRoot.includes('ngrok.io')) {
846
+ return `http://localhost:3001`;
847
+ }
848
+
849
+ // Ngrok or other tunnels: assume same host, different path structure
850
+ // For ngrok, the API and web might be on same domain or user needs to configure
851
+ // Return as-is since we can't reliably determine the mapping
852
+ return apiRoot;
853
+ }
854
+
826
855
  function generateRunId() {
827
856
  return `${Date.now()}-${crypto.randomBytes(4).toString("hex")}`;
828
857
  }
@@ -838,27 +867,18 @@ function getPlatform() {
838
867
  return pluginState.detectedPlatform;
839
868
  }
840
869
 
870
+ // Try to get platform from dashcam URLs (registered during test cleanup)
871
+ for (const [, data] of pluginState.dashcamUrls) {
872
+ if (data.platform) {
873
+ logger.debug(`Using platform from dashcam URL registration: ${data.platform}`);
874
+ return data.platform;
875
+ }
876
+ }
877
+
841
878
  logger.debug("Platform not yet detected from client");
842
879
  return null;
843
880
  }
844
881
 
845
- function detectPlatformFromTest(test) {
846
- // Check if testdriver client is accessible via test context
847
- const client = test.context?.testdriver || test.meta?.testdriver;
848
-
849
- if (client && client.os) {
850
- // Normalize platform value
851
- let platform = client.os.toLowerCase();
852
- if (platform === "darwin" || platform === "mac") platform = "mac";
853
- else if (platform === "win32" || platform === "windows")
854
- platform = "windows";
855
- else if (platform === "linux") platform = "linux";
856
-
857
- pluginState.detectedPlatform = platform;
858
- logger.debug(`Detected platform from test context: ${platform}`);
859
- }
860
- }
861
-
862
882
  function calculateStatsFromModules(testModules) {
863
883
  let totalTests = 0;
864
884
  let passedTests = 0;
@@ -973,7 +993,7 @@ function getGitInfo() {
973
993
  }
974
994
  }
975
995
 
976
- logger.info("Collected git info:", info);
996
+ logger.debug("Collected git info:", info);
977
997
  return info;
978
998
  }
979
999
 
@@ -9,6 +9,8 @@
9
9
  * - Retrieving replay URLs
10
10
  */
11
11
 
12
+ const { logger } = require('../../interfaces/logger');
13
+
12
14
  class Dashcam {
13
15
  /**
14
16
  * Create a Dashcam instance
@@ -74,6 +76,23 @@ class Dashcam {
74
76
  return this.client.config?.TD_API_ROOT || 'https://testdriver-api.onrender.com';
75
77
  }
76
78
 
79
+ /**
80
+ * Get console URL based on API root
81
+ * Maps API endpoints to their corresponding web console URLs
82
+ * @param {string} apiRoot - The API root URL
83
+ * @returns {string} The corresponding console URL
84
+ */
85
+ static getConsoleUrl(apiRoot = 'https://testdriver-api.onrender.com') {
86
+ // Map API roots to console URLs
87
+ const apiToConsoleMap = {
88
+ 'https://testdriver-api.onrender.com': 'https://console.testdriver.ai',
89
+ 'https://v6.testdriver.ai': 'https://console.testdriver.ai',
90
+ 'https://replayable-dev-ian-mac-m1-16.ngrok.io': 'http://localhost:3001',
91
+ };
92
+
93
+ return apiToConsoleMap[apiRoot] || 'https://console.testdriver.ai';
94
+ }
95
+
77
96
  /**
78
97
  * Get dashcam executable path
79
98
  * @private
@@ -83,7 +102,7 @@ class Dashcam {
83
102
  const npmPrefix = await this.client.exec(shell, 'npm prefix -g', 40000, true);
84
103
 
85
104
  if (this.client.os === 'windows') {
86
- return npmPrefix.trim() + '\\dashcam.cmd';
105
+ return 'dashcam';
87
106
  }
88
107
  return npmPrefix.trim() + '/bin/dashcam';
89
108
  }
@@ -98,33 +117,16 @@ class Dashcam {
98
117
  const shell = this._getShell();
99
118
  const apiRoot = this._getApiRoot();
100
119
 
101
- if (this.client.os === 'windows') {
102
- // Debug session info
103
- const debug = await this.client.exec(shell, 'query session', 40000, true);
104
- this._log('debug', 'Debug version output:', debug);
120
+ let install = await this.client.exec(
121
+ shell,
122
+ 'npm ls dashcam -g || echo "not installed"',
123
+ 40000,
124
+ true
125
+ );
126
+ this._log('debug', 'Dashcam install check:', install);
105
127
 
106
- // Uninstall and clear cache for fresh install
107
- await this.client.exec(shell, 'npm uninstall dashcam -g', 40000, true);
108
- await this.client.exec(shell, 'npm cache clean --force', 40000, true);
109
-
110
- // Install dashcam with TD_API_ROOT environment variable
111
- const installOutput = await this.client.exec(
112
- shell,
113
- `$env:TD_API_ROOT="${apiRoot}"; npm install dashcam@beta -g`,
114
- 120000,
115
- true
116
- );
117
- this._log('debug', 'Install dashcam output:', installOutput);
128
+ if (this.client.os === 'windows') {
118
129
 
119
- // Verify version
120
- const latestVersion = await this.client.exec(
121
- shell,
122
- 'npm view dashcam@beta version',
123
- 40000,
124
- true
125
- );
126
- this._log('debug', 'Latest beta version available:', latestVersion);
127
-
128
130
  const dashcamPath = await this._getDashcamPath();
129
131
  this._log('debug', 'Dashcam executable path:', dashcamPath);
130
132
 
@@ -145,16 +147,6 @@ class Dashcam {
145
147
  );
146
148
  this._log('debug', 'Dashcam version test:', versionTest);
147
149
 
148
- // Verify installation
149
- if (!installedVersion) {
150
- this._log('error', 'Dashcam version command returned null/empty');
151
- this._log('debug', 'Install output was:', installOutput);
152
- } else if (!installedVersion.includes('1.3.')) {
153
- this._log('warn', 'Dashcam version may be outdated. Expected 1.3.x, got:', installedVersion);
154
- } else {
155
- this._log('debug', 'Dashcam version verified:', installedVersion);
156
- }
157
-
158
150
  // Authenticate with TD_API_ROOT
159
151
  const authOutput = await this.client.exec(
160
152
  shell,
@@ -292,7 +284,6 @@ class Dashcam {
292
284
 
293
285
  // Auto-authenticate if not already done
294
286
  if (!this._authenticated) {
295
- this._log('info', 'Auto-authenticating dashcam...');
296
287
  await this.auth();
297
288
  }
298
289
 
@@ -300,7 +291,6 @@ class Dashcam {
300
291
  const apiRoot = this._getApiRoot();
301
292
 
302
293
  if (this.client.os === 'windows') {
303
- this._log('info', 'Starting dashcam recording on Windows...');
304
294
 
305
295
  const dashcamPath = await this._getDashcamPath();
306
296
  this._log('debug', 'Dashcam path:', dashcamPath);
@@ -316,11 +306,12 @@ class Dashcam {
316
306
 
317
307
  // Start dashcam record and redirect output with TD_API_ROOT
318
308
  const outputFile = 'C:\\Users\\testdriver\\.dashcam-cli\\dashcam-start.log';
319
- const titleArg = this.title ? ` --title="${this.title.replace(/"/g, '\"')}"` : '';
309
+ // const titleArg = this.title ? ` --title=\`"${this.title.replace(/"/g, '`"')}\`"` : '';
310
+ let titleArg = '';
320
311
  const startScript = `
321
312
  try {
322
313
  $env:TD_API_ROOT="${apiRoot}"
323
- $process = Start-Process "cmd.exe" -ArgumentList "/c", "${dashcamPath} record${titleArg} > ${outputFile} 2>&1" -PassThru
314
+ $process = Start-Process "cmd.exe" -ArgumentList "/c", "\`"${dashcamPath}\`" record${titleArg}"
324
315
  Write-Output "Process started with PID: $($process.Id)"
325
316
  Start-Sleep -Seconds 2
326
317
  if ($process.HasExited) {
@@ -332,7 +323,11 @@ class Dashcam {
332
323
  Write-Output "ERROR: $_"
333
324
  }
334
325
  `;
326
+
327
+ // add 2>&1" -PassThru
335
328
 
329
+ // Capture startTime right before issuing the dashcam command to sync with actual recording start
330
+ this.startTime = Date.now();
336
331
  const startOutput = await this.client.exec(shell, startScript, 10000, true);
337
332
  this._log('debug', 'Start-Process output:', startOutput);
338
333
 
@@ -349,40 +344,18 @@ class Dashcam {
349
344
  // Give process time to initialize
350
345
  await new Promise(resolve => setTimeout(resolve, 5000));
351
346
 
352
- this._log('info', 'Dashcam recording started');
347
+ this._log('debug', 'Dashcam recording started');
353
348
  } else {
354
349
  // Linux/Mac with TD_API_ROOT
355
- this._log('info', 'Starting dashcam recording on Linux/Mac...');
350
+ this._log('debug', 'Starting dashcam recording on Linux/Mac...');
356
351
  const titleArg = this.title ? ` --title="${this.title.replace(/"/g, '\"')}"` : '';
352
+ // Capture startTime right before issuing the dashcam command to sync with actual recording start
353
+ this.startTime = Date.now();
357
354
  await this.client.exec(shell, `TD_API_ROOT="${apiRoot}" dashcam record${titleArg} >/dev/null 2>&1 &`);
358
- this._log('info', 'Dashcam recording started');
355
+ this._log('debug', 'Dashcam recording started');
359
356
  }
360
357
 
361
358
  this.recording = true;
362
- this.startTime = Date.now(); // Record the timestamp when dashcam started
363
-
364
- // Update the session with dashcam start time for interaction timestamp synchronization
365
- if (this.client && this.client.agent && this.client.agent.session) {
366
- try {
367
- const apiRoot = this.apiRoot || process.env.TD_API_ROOT || 'https://console.testdriver.ai';
368
- const response = await fetch(`${apiRoot}/api/v7.0.0/testdriver/session/${this.client.agent.session}/update-dashcam-time`, {
369
- method: 'POST',
370
- headers: {
371
- 'Content-Type': 'application/json',
372
- 'Authorization': `Bearer ${this.apiKey}`
373
- },
374
- body: JSON.stringify({ dashcamStartTime: this.startTime })
375
- });
376
-
377
- if (response.ok) {
378
- this._log('info', `Updated session ${this.client.agent.session} with dashcam start time: ${this.startTime}`);
379
- } else {
380
- this._log('warn', 'Failed to update session with dashcam start time:', response.statusText);
381
- }
382
- } catch (err) {
383
- this._log('warn', 'Error updating session with dashcam start time:', err.message);
384
- }
385
- }
386
359
  }
387
360
 
388
361
  /**
@@ -406,7 +379,7 @@ class Dashcam {
406
379
  return null;
407
380
  }
408
381
 
409
- this._log('info', 'Stopping dashcam and retrieving URL...');
382
+ this._log('debug', 'Stopping dashcam and retrieving URL...');
410
383
  const shell = this._getShell();
411
384
  const apiRoot = this._getApiRoot();
412
385
  let output;
@@ -417,12 +390,12 @@ class Dashcam {
417
390
  const dashcamPath = await this._getDashcamPath();
418
391
 
419
392
  // Stop and get output with TD_API_ROOT
420
- output = await this.client.exec(shell, `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" stop`, 120000);
393
+ output = await this.client.exec(shell, `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" stop`, 300000, true);
421
394
  this._log('debug', 'Dashcam stop command output:', output);
422
395
  } else {
423
396
  // Linux/Mac with TD_API_ROOT
424
397
  const dashcamPath = await this._getDashcamPath();
425
- output = await this.client.exec(shell, `TD_API_ROOT="${apiRoot}" "${dashcamPath}" stop`, 60000, false);
398
+ output = await this.client.exec(shell, `TD_API_ROOT="${apiRoot}" "${dashcamPath}" stop`, 300000, true);
426
399
  this._log('debug', 'Dashcam command output:', output);
427
400
  }
428
401
 
@@ -437,7 +410,6 @@ class Dashcam {
437
410
  let url = replayUrlMatch[0];
438
411
  // Remove trailing punctuation but keep query params
439
412
  url = url.replace(/[.,;:!\)\]]+$/, '').trim();
440
- this._log('info', 'Found dashcam URL:', url);
441
413
  return url;
442
414
  }
443
415
 
@@ -446,7 +418,6 @@ class Dashcam {
446
418
  if (dashcamUrlMatch) {
447
419
  let url = dashcamUrlMatch[0];
448
420
  url = url.replace(/[.,;:!\?\)\]]+$/, '').trim();
449
- this._log('info', 'Found dashcam URL:', url);
450
421
  return url;
451
422
  }
452
423
 
@@ -459,7 +430,7 @@ class Dashcam {
459
430
  }
460
431
 
461
432
  /**
462
- * Internal logging - writes to testdriver log file but not user console
433
+ * Internal logging - uses TestDriver logger
463
434
  * @private
464
435
  */
465
436
  _log(level, ...args) {
@@ -467,19 +438,23 @@ class Dashcam {
467
438
  typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
468
439
  ).join(' ');
469
440
 
470
- const timestamp = new Date().toISOString();
471
- const logLine = `[${timestamp}] [DASHCAM:${level.toUpperCase()}] ${message}`;
441
+ const logMessage = `[DASHCAM] ${message}`;
472
442
 
473
- // Send to sandbox log file via output command (same as console interceptor)
474
- if (this.client?.sandbox?.instanceSocketConnected) {
475
- try {
476
- this.client.sandbox.send({
477
- type: "output",
478
- output: Buffer.from(logLine, "utf8").toString("base64"),
479
- });
480
- } catch {
481
- // Silently fail
482
- }
443
+ // Use the TestDriver logger based on level
444
+ switch (level) {
445
+ case 'error':
446
+ logger.error(logMessage);
447
+ break;
448
+ case 'warn':
449
+ logger.warn(logMessage);
450
+ break;
451
+ case 'debug':
452
+ logger.debug(logMessage);
453
+ break;
454
+ case 'info':
455
+ default:
456
+ logger.info(logMessage);
457
+ break;
483
458
  }
484
459
  }
485
460