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.
- package/.github/workflows/acceptance.yaml +81 -0
- package/.github/workflows/publish.yaml +44 -0
- package/.github/workflows/test-init.yml +145 -0
- package/agent/index.js +18 -19
- package/agent/lib/commander.js +2 -2
- package/agent/lib/commands.js +324 -124
- 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/_drafts/migration.mdx +3 -3
- package/docs/v7/api/act.mdx +2 -2
- package/docs/v7/api/assert.mdx +2 -2
- package/docs/v7/api/assertions.mdx +21 -21
- 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/readme.mdx +1 -1
- 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 +78 -20
- 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/manual/test-init-command.js +223 -0
- package/package.json +2 -2
- package/schema.json +5 -5
- package/sdk-log-formatter.js +351 -176
- package/sdk.d.ts +8 -8
- package/sdk.js +436 -121
- 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/hover-text-with-description.test.mjs +0 -3
- 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/redraw.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
227
|
+
// Capture initial image for screen stability monitoring
|
|
191
228
|
if (currentOptions.screenRedraw) {
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
234
|
+
return initialScreenImage;
|
|
197
235
|
}
|
|
198
236
|
|
|
199
237
|
async function checkCondition(resolve, startTime, timeoutMs, options) {
|
|
200
|
-
const { enabled, screenRedraw, networkMonitor
|
|
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
|
|
248
|
+
let diffFromInitial = 0;
|
|
249
|
+
let diffFromLast = 0;
|
|
211
250
|
let isTimeout = timeElapsed > timeoutMs;
|
|
212
251
|
|
|
213
|
-
//
|
|
214
|
-
if
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
//
|
|
227
|
-
const
|
|
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
|
-
:
|
|
290
|
+
: effectiveScreenSettled
|
|
235
291
|
? theme.green(`y`)
|
|
236
|
-
: theme.dim(`${
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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 ((
|
|
329
|
+
if ((effectiveScreenSettled && effectiveNetworkSettled) || isTimeout) {
|
|
272
330
|
emitter.emit(events.redraw.complete, {
|
|
273
|
-
|
|
331
|
+
screenSettled: effectiveScreenSettled,
|
|
332
|
+
hasChangedFromInitial,
|
|
333
|
+
consecutiveFramesStable,
|
|
274
334
|
networkSettled: effectiveNetworkSettled,
|
|
275
335
|
isTimeout,
|
|
276
336
|
timeElapsed,
|
package/agent/lib/sandbox.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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:
|
|
61
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/agent/lib/system.js
CHANGED
|
@@ -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(),
|
|
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) => {
|
package/agent/lib/validation.js
CHANGED
|
@@ -45,11 +45,11 @@ const types = () =>
|
|
|
45
45
|
command: '"focus-application"',
|
|
46
46
|
name: "string",
|
|
47
47
|
},
|
|
48
|
-
// - command:
|
|
49
|
-
// description: My dog's name # The key of the
|
|
50
|
-
// value: Roofus # The value
|
|
51
|
-
|
|
52
|
-
command: '"
|
|
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 |
|
|
128
|
+
"PressKeysCommand | HoverTextCommand | TypeCommand | WaitCommand | HoverImageCommand | FocusApplicationCommand | ExtractCommand | GetEmailUrlCommand | ScrollCommand | ScrollUntilTextCommand | ScrollUntilImageCommand | WaitForTextCommand | WaitForImageCommand | AssertCommand | IfCommand",
|
|
129
129
|
|
|
130
130
|
CommandList: "Command[]",
|
|
131
131
|
|