testdriverai 7.1.3 → 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.
Files changed (78) hide show
  1. package/.github/workflows/acceptance.yaml +81 -0
  2. package/.github/workflows/publish.yaml +44 -0
  3. package/.github/workflows/test-init.yml +145 -0
  4. package/agent/index.js +18 -19
  5. package/agent/lib/commander.js +2 -2
  6. package/agent/lib/commands.js +324 -124
  7. package/agent/lib/redraw.js +99 -39
  8. package/agent/lib/sandbox.js +98 -6
  9. package/agent/lib/sdk.js +25 -0
  10. package/agent/lib/system.js +2 -1
  11. package/agent/lib/validation.js +6 -6
  12. package/docs/docs.json +211 -101
  13. package/docs/snippets/tests/type-repeated-replay.mdx +1 -1
  14. package/docs/v7/_drafts/caching-selectors.mdx +24 -0
  15. package/docs/v7/_drafts/migration.mdx +3 -3
  16. package/docs/v7/api/act.mdx +2 -2
  17. package/docs/v7/api/assert.mdx +2 -2
  18. package/docs/v7/api/assertions.mdx +21 -21
  19. package/docs/v7/api/elements.mdx +78 -0
  20. package/docs/v7/api/find.mdx +38 -0
  21. package/docs/v7/api/focusApplication.mdx +2 -2
  22. package/docs/v7/api/hover.mdx +2 -2
  23. package/docs/v7/features/ai-native.mdx +57 -71
  24. package/docs/v7/features/application-logs.mdx +353 -0
  25. package/docs/v7/features/browser-logs.mdx +414 -0
  26. package/docs/v7/features/cache-management.mdx +402 -0
  27. package/docs/v7/features/continuous-testing.mdx +346 -0
  28. package/docs/v7/features/coverage.mdx +508 -0
  29. package/docs/v7/features/data-driven-testing.mdx +441 -0
  30. package/docs/v7/features/easy-to-write.mdx +2 -73
  31. package/docs/v7/features/enterprise.mdx +155 -39
  32. package/docs/v7/features/fast.mdx +63 -81
  33. package/docs/v7/features/managed-sandboxes.mdx +384 -0
  34. package/docs/v7/features/network-monitoring.mdx +568 -0
  35. package/docs/v7/features/observable.mdx +3 -22
  36. package/docs/v7/features/parallel-execution.mdx +381 -0
  37. package/docs/v7/features/powerful.mdx +1 -1
  38. package/docs/v7/features/reports.mdx +414 -0
  39. package/docs/v7/features/sandbox-customization.mdx +229 -0
  40. package/docs/v7/features/scalable.mdx +217 -2
  41. package/docs/v7/features/stable.mdx +106 -147
  42. package/docs/v7/features/system-performance.mdx +616 -0
  43. package/docs/v7/features/test-analytics.mdx +373 -0
  44. package/docs/v7/features/test-cases.mdx +393 -0
  45. package/docs/v7/features/test-replays.mdx +408 -0
  46. package/docs/v7/features/test-reports.mdx +308 -0
  47. package/docs/v7/getting-started/{running-and-debugging.mdx → debugging-tests.mdx} +12 -142
  48. package/docs/v7/getting-started/quickstart.mdx +22 -305
  49. package/docs/v7/getting-started/running-tests.mdx +173 -0
  50. package/docs/v7/overview/readme.mdx +1 -1
  51. package/docs/v7/overview/what-is-testdriver.mdx +2 -14
  52. package/docs/v7/presets/chrome-extension.mdx +147 -122
  53. package/interfaces/cli/commands/init.js +78 -20
  54. package/interfaces/cli/lib/base.js +3 -2
  55. package/interfaces/logger.js +0 -2
  56. package/interfaces/shared-test-state.mjs +0 -5
  57. package/interfaces/vitest-plugin.mjs +69 -42
  58. package/lib/core/Dashcam.js +65 -66
  59. package/lib/vitest/hooks.mjs +42 -50
  60. package/manual/test-init-command.js +223 -0
  61. package/package.json +2 -2
  62. package/schema.json +5 -5
  63. package/sdk-log-formatter.js +351 -176
  64. package/sdk.d.ts +8 -8
  65. package/sdk.js +436 -121
  66. package/setup/aws/cloudformation.yaml +2 -2
  67. package/setup/aws/self-hosted.yml +1 -1
  68. package/test/testdriver/chrome-extension.test.mjs +55 -72
  69. package/test/testdriver/element-not-found.test.mjs +2 -1
  70. package/test/testdriver/hover-image.test.mjs +1 -1
  71. package/test/testdriver/hover-text-with-description.test.mjs +0 -3
  72. package/test/testdriver/scroll-until-text.test.mjs +10 -6
  73. package/test/testdriver/setup/lifecycleHelpers.mjs +19 -24
  74. package/test/testdriver/setup/testHelpers.mjs +18 -23
  75. package/vitest.config.mjs +3 -3
  76. package/.github/workflows/linux-tests.yml +0 -28
  77. package/docs/v7/getting-started/generating-tests.mdx +0 -525
  78. package/test/testdriver/auto-cache-key-demo.test.mjs +0 -56
@@ -8,7 +8,6 @@ const DEFAULT_REDRAW_OPTIONS = {
8
8
  enabled: true, // Master switch to enable/disable redraw detection
9
9
  screenRedraw: true, // Enable screen redraw detection
10
10
  networkMonitor: true, // Enable network activity monitoring
11
- diffThreshold: 0.01, // Percentage threshold for screen diff (0.01 = 0.01%)
12
11
  };
13
12
 
14
13
  // Factory function that creates redraw functionality with the provided system instance
@@ -20,10 +19,6 @@ const createRedraw = (
20
19
  ) => {
21
20
  // Merge default options with provided defaults
22
21
  const baseOptions = { ...DEFAULT_REDRAW_OPTIONS, ...defaultOptions };
23
- // Support legacy redrawThresholdPercent number argument
24
- if (typeof defaultOptions === 'number') {
25
- baseOptions.diffThreshold = defaultOptions;
26
- }
27
22
 
28
23
  const networkUpdateInterval = 15000;
29
24
 
@@ -35,7 +30,13 @@ const createRedraw = (
35
30
 
36
31
  let measurements = [];
37
32
  let networkSettled = true;
38
- let screenHasRedrawn = null;
33
+
34
+ // Screen stability tracking
35
+ let initialScreenImage = null; // The image captured at start() - reference point
36
+ let lastScreenImage = null; // Previous frame for consecutive comparison
37
+ let hasChangedFromInitial = false; // Has screen changed from initial state?
38
+ let consecutiveFramesStable = false; // Are consecutive frames now stable?
39
+ let screenMeasurements = []; // Track consecutive frame diffs for stability detection
39
40
 
40
41
  // Track network interval to ensure only one exists
41
42
  let networkInterval = null;
@@ -45,7 +46,11 @@ const createRedraw = (
45
46
  lastRxBytes = null;
46
47
  measurements = [];
47
48
  networkSettled = true;
48
- screenHasRedrawn = false;
49
+ initialScreenImage = null;
50
+ lastScreenImage = null;
51
+ hasChangedFromInitial = false;
52
+ consecutiveFramesStable = false;
53
+ screenMeasurements = [];
49
54
  };
50
55
 
51
56
  const parseNetworkStats = (thisRxBytes, thisTxBytes) => {
@@ -85,6 +90,40 @@ const createRedraw = (
85
90
  }
86
91
  };
87
92
 
93
+ // Parse screen diff stats for consecutive frame stability
94
+ // Detects when consecutive frames have stopped changing
95
+ const parseConsecutiveDiffStats = (diffPercent) => {
96
+ screenMeasurements.push(diffPercent);
97
+
98
+ // Keep last 10 measurements for stability detection
99
+ if (screenMeasurements.length > 10) {
100
+ screenMeasurements.shift();
101
+ }
102
+
103
+ // Need at least 2 measurements to determine stability
104
+ if (screenMeasurements.length < 2) {
105
+ consecutiveFramesStable = false;
106
+ return;
107
+ }
108
+
109
+ let avgDiff = screenMeasurements.reduce((acc, d) => acc + d, 0) / screenMeasurements.length;
110
+
111
+ let stdDevDiff = Math.sqrt(
112
+ screenMeasurements.reduce((acc, d) => acc + Math.pow(d - avgDiff, 2), 0) /
113
+ screenMeasurements.length,
114
+ );
115
+
116
+ let zIndexDiff = stdDevDiff !== 0 ? (diffPercent - avgDiff) / stdDevDiff : 0;
117
+
118
+ // Consecutive frames are stable when z-index is negative (current diff is below average)
119
+ // or diff is essentially zero (< 0.1% accounts for compression artifacts)
120
+ if (screenMeasurements.length >= 2 && (diffPercent < 0.1 || zIndexDiff < 0)) {
121
+ consecutiveFramesStable = true;
122
+ } else {
123
+ consecutiveFramesStable = false;
124
+ }
125
+ };
126
+
88
127
  async function updateNetwork() {
89
128
  if (sandbox && sandbox.instanceSocketConnected) {
90
129
  let network = await sandbox.send({
@@ -141,8 +180,6 @@ const createRedraw = (
141
180
  }
142
181
  }
143
182
 
144
- let startImage = null;
145
-
146
183
  // Start network monitoring only when needed
147
184
  function startNetworkMonitoring() {
148
185
  if (!networkInterval) {
@@ -165,18 +202,18 @@ const createRedraw = (
165
202
  // Merge base options with per-call options
166
203
  currentOptions = { ...baseOptions, ...options };
167
204
 
168
- console.log('[redraw] start() called with options:', JSON.stringify(currentOptions));
205
+ emitter.emit(events.log.debug, `[redraw] start() called with options: ${JSON.stringify(currentOptions)}`);
169
206
 
170
207
  // If redraw is completely disabled, return early
171
208
  if (!currentOptions.enabled) {
172
- console.log('[redraw] start() - redraw disabled, returning null');
209
+ emitter.emit(events.log.debug, '[redraw] start() - redraw disabled, returning null');
173
210
  return null;
174
211
  }
175
212
 
176
213
  // If both screenRedraw and networkMonitor are disabled, disable redraw
177
214
  if (!currentOptions.screenRedraw && !currentOptions.networkMonitor) {
178
215
  currentOptions.enabled = false;
179
- console.log('[redraw] start() - both screenRedraw and networkMonitor disabled, returning null');
216
+ emitter.emit(events.log.debug, '[redraw] start() - both screenRedraw and networkMonitor disabled, returning null');
180
217
  return null;
181
218
  }
182
219
 
@@ -187,17 +224,18 @@ const createRedraw = (
187
224
  startNetworkMonitoring();
188
225
  }
189
226
 
190
- // Only capture start image if screen redraw is enabled
227
+ // Capture initial image for screen stability monitoring
191
228
  if (currentOptions.screenRedraw) {
192
- startImage = await system.captureScreenPNG(0.25, true);
193
- console.log('[redraw] start() - captured startImage:', startImage);
229
+ initialScreenImage = await system.captureScreenPNG(0.25, true);
230
+ lastScreenImage = initialScreenImage;
231
+ emitter.emit(events.log.debug, `[redraw] start() - captured initial image: ${initialScreenImage}`);
194
232
  }
195
233
 
196
- return startImage;
234
+ return initialScreenImage;
197
235
  }
198
236
 
199
237
  async function checkCondition(resolve, startTime, timeoutMs, options) {
200
- const { enabled, screenRedraw, networkMonitor, diffThreshold } = options;
238
+ const { enabled, screenRedraw, networkMonitor } = options;
201
239
 
202
240
  // If redraw is disabled, resolve immediately
203
241
  if (!enabled) {
@@ -207,33 +245,51 @@ const createRedraw = (
207
245
 
208
246
  let nowImage = screenRedraw ? await system.captureScreenPNG(0.25, true) : null;
209
247
  let timeElapsed = Date.now() - startTime;
210
- let diffPercent = 0;
248
+ let diffFromInitial = 0;
249
+ let diffFromLast = 0;
211
250
  let isTimeout = timeElapsed > timeoutMs;
212
251
 
213
- // Check screen redraw if enabled and we have a start image to compare against
214
- if (screenRedraw && !screenHasRedrawn && startImage && nowImage) {
215
- console.log('[redraw] checkCondition() - comparing images:', { startImage, nowImage });
216
- diffPercent = await imageDiffPercent(startImage, nowImage);
217
- console.log('[redraw] checkCondition() - diffPercent:', diffPercent, 'threshold:', diffThreshold);
218
- screenHasRedrawn = diffPercent > diffThreshold;
219
- console.log('[redraw] checkCondition() - screenHasRedrawn:', screenHasRedrawn);
220
- } else if (screenRedraw && !startImage) {
221
- // If no start image was captured, capture one now and wait for next check
222
- console.log('[redraw] checkCondition() - no startImage, capturing now');
223
- startImage = await system.captureScreenPNG(0.25, true);
252
+ // Screen stability detection:
253
+ // 1. Check if screen has changed from initial (detect transition)
254
+ // 2. Check if consecutive frames are stable (detect settling)
255
+ if (screenRedraw && nowImage) {
256
+ // Compare to initial image - has the screen changed at all?
257
+ if (initialScreenImage && !hasChangedFromInitial) {
258
+ diffFromInitial = await imageDiffPercent(initialScreenImage, nowImage);
259
+ emitter.emit(events.log.debug, `[redraw] checkCondition() - diffFromInitial: ${diffFromInitial}`);
260
+ // Consider changed if diff > 0.1% (accounts for compression artifacts)
261
+ if (diffFromInitial > 0.1) {
262
+ hasChangedFromInitial = true;
263
+ emitter.emit(events.log.debug, `[redraw] checkCondition() - screen has changed from initial!`);
264
+ }
265
+ }
266
+
267
+ // Compare consecutive frames - has the screen stopped changing?
268
+ if (lastScreenImage && lastScreenImage !== initialScreenImage) {
269
+ diffFromLast = await imageDiffPercent(lastScreenImage, nowImage);
270
+ emitter.emit(events.log.debug, `[redraw] checkCondition() - diffFromLast: ${diffFromLast}`);
271
+ parseConsecutiveDiffStats(diffFromLast);
272
+ emitter.emit(events.log.debug, `[redraw] checkCondition() - consecutiveFramesStable: ${consecutiveFramesStable}, measurements: ${screenMeasurements.length}`);
273
+ }
274
+
275
+ // Update last image for next comparison
276
+ lastScreenImage = nowImage;
224
277
  }
225
278
 
226
- // If screen redraw is disabled, consider it as "redrawn"
227
- const effectiveScreenRedrawn = screenRedraw ? screenHasRedrawn : true;
279
+ // Screen is settled when: it has changed from initial AND consecutive frames are now stable
280
+ const screenSettled = hasChangedFromInitial && consecutiveFramesStable;
281
+
282
+ // If screen redraw is disabled, consider it as "settled"
283
+ const effectiveScreenSettled = screenRedraw ? screenSettled : true;
228
284
  // If network monitor is disabled, consider it as "settled"
229
285
  const effectiveNetworkSettled = networkMonitor ? networkSettled : true;
230
286
 
231
- // Log redraw status
287
+ // Log redraw status - show both change detection and stability
232
288
  let redrawText = !screenRedraw
233
289
  ? theme.dim(`disabled`)
234
- : effectiveScreenRedrawn
290
+ : effectiveScreenSettled
235
291
  ? theme.green(`y`)
236
- : theme.dim(`${diffPercent}/${diffThreshold}%`);
292
+ : theme.dim(`${hasChangedFromInitial ? '✓' : '?'}→${consecutiveFramesStable ? '✓' : diffFromLast.toFixed(1)}%`);
237
293
  let networkText = !networkMonitor
238
294
  ? theme.dim(`disabled`)
239
295
  : effectiveNetworkSettled
@@ -248,9 +304,11 @@ const createRedraw = (
248
304
  emitter.emit(events.redraw.status, {
249
305
  redraw: {
250
306
  enabled: screenRedraw,
251
- hasRedrawn: effectiveScreenRedrawn,
252
- diffPercent,
253
- threshold: diffThreshold,
307
+ settled: effectiveScreenSettled,
308
+ hasChangedFromInitial,
309
+ consecutiveFramesStable,
310
+ diffFromInitial,
311
+ diffFromLast,
254
312
  text: redrawText,
255
313
  },
256
314
  network: {
@@ -268,9 +326,11 @@ const createRedraw = (
268
326
  },
269
327
  });
270
328
 
271
- if ((effectiveScreenRedrawn && effectiveNetworkSettled) || isTimeout) {
329
+ if ((effectiveScreenSettled && effectiveNetworkSettled) || isTimeout) {
272
330
  emitter.emit(events.redraw.complete, {
273
- screenHasRedrawn: effectiveScreenRedrawn,
331
+ screenSettled: effectiveScreenSettled,
332
+ hasChangedFromInitial,
333
+ consecutiveFramesStable,
274
334
  networkSettled: effectiveNetworkSettled,
275
335
  isTimeout,
276
336
  timeElapsed,
@@ -1,7 +1,27 @@
1
1
  const WebSocket = require("ws");
2
2
  const marky = require("marky");
3
+ const crypto = require("crypto");
3
4
  const { events } = require("../events");
4
5
 
6
+ /**
7
+ * Generate Sentry trace headers for distributed tracing
8
+ * Uses the same trace ID derivation as the API (MD5 hash of session ID)
9
+ * @param {string} sessionId - The session ID
10
+ * @returns {Object} Headers object with sentry-trace and baggage
11
+ */
12
+ function getSentryTraceHeaders(sessionId) {
13
+ if (!sessionId) return {};
14
+
15
+ // Same logic as API: derive trace ID from session ID
16
+ const traceId = crypto.createHash('md5').update(sessionId).digest('hex');
17
+ const spanId = crypto.randomBytes(8).toString('hex');
18
+
19
+ return {
20
+ 'sentry-trace': `${traceId}-${spanId}-1`,
21
+ 'baggage': `sentry-trace_id=${traceId},sentry-sample_rate=1.0,sentry-sampled=true`
22
+ };
23
+ }
24
+
5
25
  const createSandbox = (emitter, analytics, sessionInstance) => {
6
26
  class Sandbox {
7
27
  constructor() {
@@ -16,9 +36,28 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
16
36
  this.uniqueId = Math.random().toString(36).substring(7);
17
37
  this.os = null; // Store OS value to send with every message
18
38
  this.sessionInstance = sessionInstance; // Store session instance to include in messages
39
+ this.traceId = null; // Sentry trace ID for debugging
40
+ }
41
+
42
+ /**
43
+ * Get the Sentry trace ID for this session
44
+ * Useful for debugging with customers - they can share this ID to look up their traces
45
+ * @returns {string|null} The trace ID or null if not authenticated
46
+ */
47
+ getTraceId() {
48
+ return this.traceId;
19
49
  }
20
50
 
21
- send(message) {
51
+ /**
52
+ * Get the Sentry trace URL for this session
53
+ * @returns {string|null} The full Sentry trace URL or null if no trace ID
54
+ */
55
+ getTraceUrl() {
56
+ if (!this.traceId) return null;
57
+ return `https://testdriver.sentry.io/explore/traces/trace/${this.traceId}`;
58
+ }
59
+
60
+ send(message, timeout = 300000) {
22
61
  let resolvePromise;
23
62
  let rejectPromise;
24
63
 
@@ -55,10 +94,33 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
55
94
  rejectPromise = reject;
56
95
  });
57
96
 
58
- this.ps[message.requestId] = {
97
+ const requestId = message.requestId;
98
+
99
+ // Set up timeout to prevent hanging requests
100
+ const timeoutId = setTimeout(() => {
101
+ if (this.ps[requestId]) {
102
+ const pendingMessage = this.ps[requestId];
103
+ // Stop the timing marker to prevent memory leak
104
+ try {
105
+ marky.stop(pendingMessage.timingKey);
106
+ } catch (e) {
107
+ // Ignore timing errors
108
+ }
109
+ delete this.ps[requestId];
110
+ rejectPromise(new Error(`Sandbox message '${message.type}' timed out after ${timeout}ms`));
111
+ }
112
+ }, timeout);
113
+
114
+ this.ps[requestId] = {
59
115
  promise: p,
60
- resolve: resolvePromise,
61
- reject: rejectPromise,
116
+ resolve: (result) => {
117
+ clearTimeout(timeoutId);
118
+ resolvePromise(result);
119
+ },
120
+ reject: (error) => {
121
+ clearTimeout(timeoutId);
122
+ rejectPromise(error);
123
+ },
62
124
  message,
63
125
  timingKey,
64
126
  startTime: Date.now(),
@@ -66,6 +128,9 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
66
128
 
67
129
  return p;
68
130
  }
131
+
132
+ // Return a rejected promise if socket is not available
133
+ return Promise.reject(new Error('Sandbox socket not connected'));
69
134
  }
70
135
 
71
136
  async auth(apiKey) {
@@ -76,7 +141,16 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
76
141
 
77
142
  if (reply.success) {
78
143
  this.authenticated = true;
79
- emitter.emit(events.sandbox.authenticated);
144
+
145
+ // Log and store the Sentry trace ID for debugging
146
+ if (reply.traceId) {
147
+ this.traceId = reply.traceId;
148
+ console.log('');
149
+ console.log(`🔗 View Trace:`);
150
+ console.log(`https://testdriver.sentry.io/explore/traces/trace/${reply.traceId}`);
151
+ }
152
+
153
+ emitter.emit(events.sandbox.authenticated, { traceId: reply.traceId });
80
154
  return true;
81
155
  }
82
156
  }
@@ -101,7 +175,25 @@ const createSandbox = (emitter, analytics, sessionInstance) => {
101
175
 
102
176
  async boot(apiRoot) {
103
177
  return new Promise((resolve, reject) => {
104
- this.socket = new WebSocket(apiRoot.replace("https://", "wss://"));
178
+ // Get session ID for Sentry trace headers
179
+ const sessionId = this.sessionInstance?.get();
180
+
181
+ if (!sessionId) {
182
+ console.warn('[Sandbox] No session ID available at boot time - Sentry tracing will not be available');
183
+ }
184
+
185
+ const sentryHeaders = getSentryTraceHeaders(sessionId);
186
+
187
+ // Build WebSocket URL with Sentry trace headers as query params
188
+ const wsUrl = new URL(apiRoot.replace("https://", "wss://"));
189
+ if (sentryHeaders['sentry-trace']) {
190
+ wsUrl.searchParams.set('sentry-trace', sentryHeaders['sentry-trace']);
191
+ }
192
+ if (sentryHeaders['baggage']) {
193
+ wsUrl.searchParams.set('baggage', sentryHeaders['baggage']);
194
+ }
195
+
196
+ this.socket = new WebSocket(wsUrl.toString());
105
197
 
106
198
  // handle errors
107
199
  this.socket.on("close", () => {
package/agent/lib/sdk.js CHANGED
@@ -1,9 +1,29 @@
1
1
  const { events } = require("../events");
2
+ const crypto = require("crypto");
2
3
 
3
4
  // get the version from package.json
4
5
  const { version } = require("../../package.json");
5
6
  const axios = require("axios");
6
7
 
8
+ /**
9
+ * Generate Sentry trace headers for distributed tracing
10
+ * Uses the same trace ID derivation as the API (MD5 hash of session ID)
11
+ * @param {string} sessionId - The session ID
12
+ * @returns {Object} Headers object with sentry-trace and baggage
13
+ */
14
+ function getSentryTraceHeaders(sessionId) {
15
+ if (!sessionId) return {};
16
+
17
+ // Same logic as API: derive trace ID from session ID
18
+ const traceId = crypto.createHash('md5').update(sessionId).digest('hex');
19
+ const spanId = crypto.randomBytes(8).toString('hex');
20
+
21
+ return {
22
+ 'sentry-trace': `${traceId}-${spanId}-1`,
23
+ 'baggage': `sentry-trace_id=${traceId},sentry-sample_rate=1.0,sentry-sampled=true`
24
+ };
25
+ }
26
+
7
27
  // Factory function that creates SDK with the provided emitter, config, and session
8
28
  let token = null;
9
29
  const createSDK = (emitter, config, sessionInstance) => {
@@ -116,11 +136,16 @@ const createSDK = (emitter, config, sessionInstance) => {
116
136
  ? [config["TD_API_ROOT"], path].join("")
117
137
  : [config["TD_API_ROOT"], "api", version, "testdriver", path].join("/");
118
138
 
139
+ // Get session ID for Sentry trace headers
140
+ const sessionId = sessionInstance.get();
141
+ const sentryHeaders = getSentryTraceHeaders(sessionId);
142
+
119
143
  const c = {
120
144
  method: "post",
121
145
  headers: {
122
146
  "Content-Type": "application/json",
123
147
  ...(token && { Authorization: `Bearer ${token}` }), // Add the authorization bearer token only if token is set
148
+ ...sentryHeaders, // Add Sentry distributed tracing headers
124
149
  },
125
150
  responseType: typeof onChunk === "function" ? "stream" : "json",
126
151
  timeout: 60000, // 60 second timeout to prevent hanging requests
@@ -2,6 +2,7 @@
2
2
  const fs = require("fs");
3
3
  const os = require("os");
4
4
  const path = require("path");
5
+ const { randomUUID } = require("crypto");
5
6
  const Jimp = require("jimp");
6
7
  const { events } = require("../events.js");
7
8
 
@@ -31,7 +32,7 @@ const createSystem = (emitter, sandbox, config) => {
31
32
  let countImages = 0;
32
33
  const tmpFilename = () => {
33
34
  countImages = countImages + 1;
34
- return path.join(os.tmpdir(), `${new Date().getTime() + countImages}.png`);
35
+ return path.join(os.tmpdir(), `td-${Date.now()}-${randomUUID().slice(0, 8)}-${countImages}.png`);
35
36
  };
36
37
 
37
38
  const captureAndResize = async (scale = 1, silent = false, mouse = false) => {
@@ -45,11 +45,11 @@ const types = () =>
45
45
  command: '"focus-application"',
46
46
  name: "string",
47
47
  },
48
- // - command: remember # Remember a string value without needing to interact with the desktop.
49
- // description: My dog's name # The key of the memory value to store.
50
- // value: Roofus # The value of the memory to store
51
- RememberCommand: {
52
- command: '"remember"',
48
+ // - command: extract # Extract a string value from the screen without needing to interact with the desktop.
49
+ // description: My dog's name # The key of the value to extract.
50
+ // value: Roofus # The value to extract
51
+ ExtractCommand: {
52
+ command: '"extract"',
53
53
  description: "string",
54
54
  value: "string",
55
55
  },
@@ -125,7 +125,7 @@ const types = () =>
125
125
  },
126
126
 
127
127
  Command:
128
- "PressKeysCommand | HoverTextCommand | TypeCommand | WaitCommand | HoverImageCommand | FocusApplicationCommand | RememberCommand | GetEmailUrlCommand | ScrollCommand | ScrollUntilTextCommand | ScrollUntilImageCommand | WaitForTextCommand | WaitForImageCommand | AssertCommand | IfCommand",
128
+ "PressKeysCommand | HoverTextCommand | TypeCommand | WaitCommand | HoverImageCommand | FocusApplicationCommand | ExtractCommand | GetEmailUrlCommand | ScrollCommand | ScrollUntilTextCommand | ScrollUntilImageCommand | WaitForTextCommand | WaitForImageCommand | AssertCommand | IfCommand",
129
129
 
130
130
  CommandList: "Command[]",
131
131