testdriverai 7.2.56 → 7.2.57

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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Dashcam Class
3
3
  * Manages Dashcam CLI recording lifecycle
4
- *
4
+ *
5
5
  * Provides a clean interface for:
6
6
  * - Authentication
7
7
  * - Log tracking
@@ -9,7 +9,7 @@
9
9
  * - Retrieving replay URLs
10
10
  */
11
11
 
12
- const { logger } = require('../../interfaces/logger');
12
+ const { logger } = require("../../interfaces/logger");
13
13
 
14
14
  class Dashcam {
15
15
  /**
@@ -23,12 +23,16 @@ class Dashcam {
23
23
  */
24
24
  constructor(client, options = {}) {
25
25
  if (!client) {
26
- throw new Error('Dashcam requires a TestDriver client instance');
26
+ throw new Error("Dashcam requires a TestDriver client instance");
27
27
  }
28
-
28
+
29
29
  this.client = client;
30
30
  // Use provided apiKey, or client's apiKey, or fallback to a default
31
- this.apiKey = options.apiKey || client.apiKey || client.config?.TD_API_KEY || '4e93d8bf-3886-4d26-a144-116c4063522d';
31
+ this.apiKey =
32
+ options.apiKey ||
33
+ client.apiKey ||
34
+ client.config?.TD_API_KEY ||
35
+ "4e93d8bf-3886-4d26-a144-116c4063522d";
32
36
  this.autoStart = options.autoStart ?? false;
33
37
  this.logs = options.logs || [];
34
38
  this.recording = false;
@@ -46,18 +50,21 @@ class Dashcam {
46
50
  // Check for Vitest context
47
51
  if (this.client.__vitestContext) {
48
52
  const task = this.client.__vitestContext;
49
- const testName = task.name || 'Test';
53
+ const testName = task.name || "Test";
50
54
  const fileName = task.file?.name || task.file?.filepath;
51
55
  if (fileName) {
52
- const baseName = fileName.split('/').pop().replace(/\.(test|spec)\.(js|mjs|ts|tsx)$/, '');
56
+ const baseName = fileName
57
+ .split("/")
58
+ .pop()
59
+ .replace(/\.(test|spec)\.(js|mjs|ts|tsx)$/, "");
53
60
  return `${baseName} - ${testName}`;
54
61
  }
55
62
  return testName;
56
63
  }
57
-
64
+
58
65
  // Fallback to timestamp
59
66
  const now = new Date();
60
- return `Recording ${now.toISOString().replace(/T/, ' ').replace(/\..+/, '')}`;
67
+ return `Recording ${now.toISOString().replace(/T/, " ").replace(/\..+/, "")}`;
61
68
  }
62
69
 
63
70
  /**
@@ -65,7 +72,7 @@ class Dashcam {
65
72
  * @private
66
73
  */
67
74
  _getShell() {
68
- return this.client.os === 'windows' ? 'pwsh' : 'sh';
75
+ return this.client.os === "windows" ? "pwsh" : "sh";
69
76
  }
70
77
 
71
78
  /**
@@ -73,7 +80,9 @@ class Dashcam {
73
80
  * @private
74
81
  */
75
82
  _getApiRoot() {
76
- return this.client.config?.TD_API_ROOT || 'https://testdriver-api.onrender.com';
83
+ return (
84
+ this.client.config?.TD_API_ROOT || "https://testdriver-api.onrender.com"
85
+ );
77
86
  }
78
87
 
79
88
  /**
@@ -82,15 +91,15 @@ class Dashcam {
82
91
  * @param {string} apiRoot - The API root URL
83
92
  * @returns {string} The corresponding console URL
84
93
  */
85
- static getConsoleUrl(apiRoot = 'https://testdriver-api.onrender.com') {
94
+ static getConsoleUrl(apiRoot = "https://testdriver-api.onrender.com") {
86
95
  // Map API roots to console URLs
87
96
  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',
97
+ "https://testdriver-api.onrender.com": "https://console.testdriver.ai",
98
+ "https://v6.testdriver.ai": "https://console.testdriver.ai",
99
+ "https://replayable-dev-ian-mac-m1-16.ngrok.io": "http://localhost:3001",
91
100
  };
92
-
93
- return apiToConsoleMap[apiRoot] || 'https://console.testdriver.ai';
101
+
102
+ return apiToConsoleMap[apiRoot] || "https://console.testdriver.ai";
94
103
  }
95
104
 
96
105
  /**
@@ -99,12 +108,17 @@ class Dashcam {
99
108
  */
100
109
  async _getDashcamPath() {
101
110
  const shell = this._getShell();
102
- const npmPrefix = await this.client.exec(shell, 'npm prefix -g', 40000, false);
103
-
104
- if (this.client.os === 'windows') {
105
- return 'C:\\Program Files\\nodejs\\dashcam.cmd';
111
+ const npmPrefix = await this.client.exec(
112
+ shell,
113
+ "npm prefix -g",
114
+ 40000,
115
+ false,
116
+ );
117
+
118
+ if (this.client.os === "windows") {
119
+ return "C:\\Program Files\\nodejs\\dashcam.cmd";
106
120
  }
107
- return npmPrefix.trim() + '/bin/dashcam';
121
+ return npmPrefix.trim() + "/bin/dashcam";
108
122
  }
109
123
 
110
124
  /**
@@ -117,28 +131,27 @@ class Dashcam {
117
131
  const shell = this._getShell();
118
132
  const apiRoot = this._getApiRoot();
119
133
 
120
- if (this.client.os === 'windows') {
121
-
134
+ if (this.client.os === "windows") {
122
135
  const dashcamPath = await this._getDashcamPath();
123
- this._log('debug', 'Dashcam executable path:', dashcamPath);
124
-
136
+ this._log("debug", "Dashcam executable path:", dashcamPath);
137
+
125
138
  // Authenticate with TD_API_ROOT
126
139
  const authOutput = await this.client.exec(
127
140
  shell,
128
141
  `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" auth ${key}`,
129
142
  120000,
130
- false
143
+ false,
131
144
  );
132
- this._log('debug', 'Auth output:', authOutput);
145
+ this._log("debug", "Auth output:", authOutput);
133
146
  } else {
134
147
  // Linux/Mac authentication with TD_API_ROOT
135
148
  const authOutput = await this.client.exec(
136
149
  shell,
137
150
  `TD_API_ROOT="${apiRoot}" dashcam auth ${key}`,
138
151
  120000,
139
- false
152
+ false,
140
153
  );
141
- this._log('debug', 'Auth output:', authOutput);
154
+ this._log("debug", "Auth output:", authOutput);
142
155
  }
143
156
 
144
157
  this._authenticated = true;
@@ -154,24 +167,24 @@ class Dashcam {
154
167
  const shell = this._getShell();
155
168
  const apiRoot = this._getApiRoot();
156
169
 
157
- if (this.client.os === 'windows') {
170
+ if (this.client.os === "windows") {
158
171
  // Create log file if it doesn't exist
159
172
  const createFileOutput = await this.client.exec(
160
173
  shell,
161
174
  `New-Item -ItemType File -Path "${path}" -Force`,
162
175
  10000,
163
- false
176
+ false,
164
177
  );
165
- this._log('debug', 'Create log file output:', createFileOutput);
178
+ this._log("debug", "Create log file output:", createFileOutput);
166
179
 
167
180
  const dashcamPath = await this._getDashcamPath();
168
181
  const addLogOutput = await this.client.exec(
169
182
  shell,
170
183
  `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" logs --add --type=file --file="${path}" --name="${name}"`,
171
184
  120000,
172
- false
185
+ false,
173
186
  );
174
- this._log('debug', 'Add log tracking output:', addLogOutput);
187
+ this._log("debug", "Add log tracking output:", addLogOutput);
175
188
  } else {
176
189
  // Create log file
177
190
  await this.client.exec(shell, `touch ${path}`, 10000, false);
@@ -181,9 +194,9 @@ class Dashcam {
181
194
  shell,
182
195
  `TD_API_ROOT="${apiRoot}" dashcam logs --add --type=file --file="${path}" --name="${name}"`,
183
196
  10000,
184
- false
197
+ false,
185
198
  );
186
- this._log('debug', 'Add log tracking output:', addLogOutput);
199
+ this._log("debug", "Add log tracking output:", addLogOutput);
187
200
  }
188
201
  }
189
202
 
@@ -198,22 +211,22 @@ class Dashcam {
198
211
  const dashcamPath = await this._getDashcamPath();
199
212
  const apiRoot = this._getApiRoot();
200
213
 
201
- if (this.client.os === 'windows') {
214
+ if (this.client.os === "windows") {
202
215
  const addLogOutput = await this.client.exec(
203
216
  shell,
204
217
  `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" logs --add --type=application --application="${application}" --name="${name}"`,
205
218
  120000,
206
- false
219
+ false,
207
220
  );
208
- this._log('debug', 'Add application log tracking output:', addLogOutput);
221
+ this._log("debug", "Add application log tracking output:", addLogOutput);
209
222
  } else {
210
223
  const addLogOutput = await this.client.exec(
211
224
  shell,
212
225
  `TD_API_ROOT="${apiRoot}" dashcam logs --add --type=application --application="${application}" --name="${name}"`,
213
226
  10000,
214
- false
227
+ false,
215
228
  );
216
- this._log('debug', 'Add application log tracking output:', addLogOutput);
229
+ this._log("debug", "Add application log tracking output:", addLogOutput);
217
230
  }
218
231
  }
219
232
 
@@ -228,22 +241,22 @@ class Dashcam {
228
241
  const dashcamPath = await this._getDashcamPath();
229
242
  const apiRoot = this._getApiRoot();
230
243
 
231
- if (this.client.os === 'windows') {
244
+ if (this.client.os === "windows") {
232
245
  const addLogOutput = await this.client.exec(
233
246
  shell,
234
247
  `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" logs --add --type=web --pattern="${pattern}" --name="${name}"`,
235
248
  120000,
236
- false
249
+ false,
237
250
  );
238
- this._log('debug', 'Add web log tracking output:', addLogOutput);
251
+ this._log("debug", "Add web log tracking output:", addLogOutput);
239
252
  } else {
240
253
  const addLogOutput = await this.client.exec(
241
254
  shell,
242
255
  `TD_API_ROOT="${apiRoot}" dashcam logs --add --type=web --pattern="${pattern}" --name="${name}"`,
243
256
  10000,
244
- false
257
+ false,
245
258
  );
246
- this._log('debug', 'Add web log tracking output:', addLogOutput);
259
+ this._log("debug", "Add web log tracking output:", addLogOutput);
247
260
  }
248
261
  }
249
262
 
@@ -253,7 +266,7 @@ class Dashcam {
253
266
  */
254
267
  async start() {
255
268
  if (this.recording) {
256
- this._log('warn', 'Dashcam already recording');
269
+ this._log("warn", "Dashcam already recording");
257
270
  return;
258
271
  }
259
272
 
@@ -265,13 +278,14 @@ class Dashcam {
265
278
  const shell = this._getShell();
266
279
  const apiRoot = this._getApiRoot();
267
280
 
268
- if (this.client.os === 'windows') {
281
+ if (this.client.os === "windows") {
269
282
  const dashcamPath = await this._getDashcamPath();
270
-
283
+
271
284
  // Start dashcam record and redirect output with TD_API_ROOT
272
- const outputFile = 'C:\\Users\\testdriver\\.dashcam-cli\\dashcam-start.log';
285
+ const outputFile =
286
+ "C:\\Users\\testdriver\\.dashcam-cli\\dashcam-start.log";
273
287
  // const titleArg = this.title ? ` --title=\`"${this.title.replace(/"/g, '`"')}\`"` : '';
274
- let titleArg = '';
288
+ let titleArg = "";
275
289
  const startScript = `
276
290
  try {
277
291
  $env:TD_API_ROOT="${apiRoot}"
@@ -289,34 +303,44 @@ class Dashcam {
289
303
  `;
290
304
 
291
305
  // add 2>&1" -PassThru
292
-
306
+
293
307
  // Capture startTime right before issuing the dashcam command to sync with actual recording start
294
308
  this.startTime = Date.now();
295
- const startOutput = await this.client.exec(shell, startScript, 10000, false);
296
- this._log('debug', 'Start-Process output:', startOutput);
297
-
309
+ const startOutput = await this.client.exec(
310
+ shell,
311
+ startScript,
312
+ 10000,
313
+ false,
314
+ );
315
+ this._log("debug", "Start-Process output:", startOutput);
316
+
298
317
  // Wait and check output
299
- await new Promise(resolve => setTimeout(resolve, 2000));
318
+ await new Promise((resolve) => setTimeout(resolve, 2000));
300
319
  const dashcamOutput = await this.client.exec(
301
320
  shell,
302
321
  `Get-Content "${outputFile}" -ErrorAction SilentlyContinue`,
303
322
  10000,
304
- false
323
+ false,
305
324
  );
306
- this._log('debug', 'Dashcam record output:', dashcamOutput);
307
-
325
+ this._log("debug", "Dashcam record output:", dashcamOutput);
326
+
308
327
  // Give process time to initialize
309
- await new Promise(resolve => setTimeout(resolve, 5000));
310
-
311
- this._log('debug', 'Dashcam recording started');
328
+ await new Promise((resolve) => setTimeout(resolve, 5000));
329
+
330
+ this._log("debug", "Dashcam recording started");
312
331
  } else {
313
332
  // Linux/Mac with TD_API_ROOT
314
- this._log('debug', 'Starting dashcam recording on Linux/Mac...');
315
- const titleArg = this.title ? ` --title="${this.title.replace(/"/g, '\"')}"` : '';
333
+ this._log("debug", "Starting dashcam recording on Linux/Mac...");
334
+ const titleArg = this.title
335
+ ? ` --title="${this.title.replace(/"/g, '"')}"`
336
+ : "";
316
337
  // Capture startTime right before issuing the dashcam command to sync with actual recording start
317
338
  this.startTime = Date.now();
318
- await this.client.exec(shell, `TD_API_ROOT="${apiRoot}" dashcam record${titleArg} >/dev/null 2>&1 &`);
319
- this._log('debug', 'Dashcam recording started');
339
+ await this.client.exec(
340
+ shell,
341
+ `TD_API_ROOT="${apiRoot}" dashcam record${titleArg} >/dev/null 2>&1 &`,
342
+ );
343
+ this._log("debug", "Dashcam recording started");
320
344
  }
321
345
 
322
346
  this.recording = true;
@@ -329,7 +353,7 @@ class Dashcam {
329
353
  */
330
354
  setTitle(title) {
331
355
  this.title = title;
332
- this._log('info', `Set dashcam recording title: ${title}`);
356
+ this._log("debug", `Set dashcam recording title: ${title}`);
333
357
  }
334
358
 
335
359
  /**
@@ -339,28 +363,38 @@ class Dashcam {
339
363
  async stop() {
340
364
  if (!this.recording) {
341
365
  // Internal log only - don't spam user console
342
- this._log('warn', 'Dashcam not recording');
366
+ this._log("warn", "Dashcam not recording");
343
367
  return null;
344
368
  }
345
369
 
346
- this._log('debug', 'Stopping dashcam and retrieving URL...');
370
+ this._log("debug", "Stopping dashcam and retrieving URL...");
347
371
  const shell = this._getShell();
348
372
  const apiRoot = this._getApiRoot();
349
373
  let output;
350
374
 
351
- if (this.client.os === 'windows') {
352
- this._log('info', 'Stopping dashcam process on Windows...');
353
-
375
+ if (this.client.os === "windows") {
376
+ this._log("debug", "Stopping dashcam process on Windows...");
377
+
354
378
  const dashcamPath = await this._getDashcamPath();
355
-
379
+
356
380
  // Stop and get output with TD_API_ROOT
357
- output = await this.client.exec(shell, `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" stop`, 300000, false);
358
- this._log('debug', 'Dashcam stop command output:', output);
381
+ output = await this.client.exec(
382
+ shell,
383
+ `$env:TD_API_ROOT="${apiRoot}"; & "${dashcamPath}" stop`,
384
+ 300000,
385
+ process.env.DEBUG == "true" ? true : false,
386
+ );
387
+ this._log("debug", "Dashcam stop command output:", output);
359
388
  } else {
360
389
  // Linux/Mac with TD_API_ROOT
361
390
  const dashcamPath = await this._getDashcamPath();
362
- output = await this.client.exec(shell, `TD_API_ROOT="${apiRoot}" "${dashcamPath}" stop`, 300000, false);
363
- this._log('debug', 'Dashcam command output:', output);
391
+ output = await this.client.exec(
392
+ shell,
393
+ `TD_API_ROOT="${apiRoot}" "${dashcamPath}" stop`,
394
+ 300000,
395
+ process.env.DEBUG == "true" ? true : false,
396
+ );
397
+ this._log("debug", "Dashcam command output:", output);
364
398
  }
365
399
 
366
400
  this.recording = false;
@@ -369,25 +403,29 @@ class Dashcam {
369
403
  if (output) {
370
404
  // Look for replay URL with optional query parameters (most specific)
371
405
  // Matches: http://localhost:3001/replay/abc123?share=xyz or https://app.dashcam.io/replay/abc123
372
- const replayUrlMatch = output.match(/https?:\/\/[^\s"',}]+\/replay\/[^\s"',}]+/);
406
+ const replayUrlMatch = output.match(
407
+ /https?:\/\/[^\s"',}]+\/replay\/[^\s"',}]+/,
408
+ );
373
409
  if (replayUrlMatch) {
374
410
  let url = replayUrlMatch[0];
375
411
  // Remove trailing punctuation but keep query params
376
- url = url.replace(/[.,;:!\)\]]+$/, '').trim();
412
+ url = url.replace(/[.,;:!\)\]]+$/, "").trim();
377
413
  return url;
378
414
  }
379
-
415
+
380
416
  // Fallback: any dashcam.io or testdriver.ai URL
381
- const dashcamUrlMatch = output.match(/https?:\/\/(?:app\.)?(?:dashcam\.io|testdriver\.ai)[^\s"',}]+/);
417
+ const dashcamUrlMatch = output.match(
418
+ /https?:\/\/(?:app\.)?(?:dashcam\.io|testdriver\.ai)[^\s"',}]+/,
419
+ );
382
420
  if (dashcamUrlMatch) {
383
421
  let url = dashcamUrlMatch[0];
384
- url = url.replace(/[.,;:!\?\)\]]+$/, '').trim();
422
+ url = url.replace(/[.,;:!\?\)\]]+$/, "").trim();
385
423
  return url;
386
424
  }
387
-
388
- this._log('warn', 'No replay URL found in dashcam output');
425
+
426
+ this._log("warn", "No replay URL found in dashcam output");
389
427
  } else {
390
- this._log('warn', 'Dashcam command returned no output');
428
+ this._log("warn", "Dashcam command returned no output");
391
429
  }
392
430
 
393
431
  return null;
@@ -398,24 +436,26 @@ class Dashcam {
398
436
  * @private
399
437
  */
400
438
  _log(level, ...args) {
401
- const message = args.map(arg =>
402
- typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
403
- ).join(' ');
404
-
439
+ const message = args
440
+ .map((arg) =>
441
+ typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg),
442
+ )
443
+ .join(" ");
444
+
405
445
  const logMessage = `[DASHCAM] ${message}`;
406
-
446
+
407
447
  // Use the TestDriver logger based on level
408
448
  switch (level) {
409
- case 'error':
449
+ case "error":
410
450
  logger.error(logMessage);
411
451
  break;
412
- case 'warn':
452
+ case "warn":
413
453
  logger.warn(logMessage);
414
454
  break;
415
- case 'debug':
455
+ case "debug":
416
456
  logger.debug(logMessage);
417
457
  break;
418
- case 'info':
458
+ case "info":
419
459
  default:
420
460
  logger.info(logMessage);
421
461
  break;