testdriverai 7.2.71 → 7.2.72
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/debugger/bg.png +0 -0
- package/debugger/icon.png +0 -0
- package/debugger/index.html +797 -0
- package/debugger/td.png +0 -0
- package/debugger/tray-buffered.png +0 -0
- package/debugger/tray.png +0 -0
- package/package.json +3 -1
- package/sdk-log-formatter.js +930 -0
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
6
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
7
|
+
<link
|
|
8
|
+
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap"
|
|
9
|
+
rel="stylesheet"
|
|
10
|
+
/>
|
|
11
|
+
<link rel="icon" type="image/png" href="icon.png" />
|
|
12
|
+
<title>TestDriver - Debugger</title>
|
|
13
|
+
<style>
|
|
14
|
+
body {
|
|
15
|
+
background-color: #111;
|
|
16
|
+
background-image: url("bg.png");
|
|
17
|
+
background-repeat: repeat;
|
|
18
|
+
background-size: auto;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* {
|
|
22
|
+
margin: 0;
|
|
23
|
+
padding: 0;
|
|
24
|
+
box-sizing: border-box;
|
|
25
|
+
font-family: "IBM Plex Mono", monospace;
|
|
26
|
+
font-weight: 400;
|
|
27
|
+
font-style: normal;
|
|
28
|
+
font-size: 14px;
|
|
29
|
+
scrollbar-width: none; /* Hide scrollbars for Firefox */
|
|
30
|
+
pointer-events: none;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#overlay {
|
|
34
|
+
position: absolute;
|
|
35
|
+
top: 0;
|
|
36
|
+
left: 0;
|
|
37
|
+
transform-origin: top left;
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
html,
|
|
42
|
+
body {
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
width: 100%;
|
|
45
|
+
height: 100%;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
*::-webkit-scrollbar {
|
|
49
|
+
display: none; /* Hide scrollbars for WebKit browsers */
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@keyframes animate-glow {
|
|
53
|
+
0% {
|
|
54
|
+
opacity: 0;
|
|
55
|
+
filter: brightness(3) saturate(3);
|
|
56
|
+
transform: scale(0.8, 0.8);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
30% {
|
|
60
|
+
opacity: 1;
|
|
61
|
+
filter: brightness(1) saturate(1);
|
|
62
|
+
transform: scale(1, 1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
100% {
|
|
66
|
+
/* opacity: 0; */
|
|
67
|
+
opacity: 1;
|
|
68
|
+
transform: scale(1, 1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.overlay {
|
|
73
|
+
position: relative;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.screenshot {
|
|
78
|
+
position: absolute;
|
|
79
|
+
inset: 0;
|
|
80
|
+
z-index: 1;
|
|
81
|
+
opacity: 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.box {
|
|
85
|
+
border: 1px solid #b0cf34;
|
|
86
|
+
position: absolute;
|
|
87
|
+
border-radius: 5px;
|
|
88
|
+
animation-duration: 5s;
|
|
89
|
+
animation-delay: 0s;
|
|
90
|
+
animation-timing-function: cubic-bezier(0.26, 0.53, 0.74, 1.48);
|
|
91
|
+
animation-fill-mode: backwards;
|
|
92
|
+
animation-name: animate-glow;
|
|
93
|
+
animation-timing-function: ease;
|
|
94
|
+
animation-fill-mode: forwards;
|
|
95
|
+
border-radius: 5px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.effects {
|
|
99
|
+
inset: 0;
|
|
100
|
+
position: absolute;
|
|
101
|
+
z-index: 20;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#mouse {
|
|
105
|
+
margin-left: -100px;
|
|
106
|
+
margin-top: -100px;
|
|
107
|
+
width: 50px;
|
|
108
|
+
height: 50px;
|
|
109
|
+
opacity: 0;
|
|
110
|
+
position: absolute;
|
|
111
|
+
transform: translate(-50%, -50%);
|
|
112
|
+
border-radius: 70%;
|
|
113
|
+
background: #b0cf34;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#mouse #dot {
|
|
117
|
+
width: 7px;
|
|
118
|
+
height: 7px;
|
|
119
|
+
position: absolute;
|
|
120
|
+
top: 50%;
|
|
121
|
+
left: 50%;
|
|
122
|
+
transform: translate(-50%, -50%);
|
|
123
|
+
border-radius: 50%;
|
|
124
|
+
background-color: black;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.single-click {
|
|
128
|
+
animation: singleClick 0.7s ease-in-out forwards;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.double-click {
|
|
132
|
+
animation: doubleClick 0.7s ease-in-out forwards;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@keyframes singleClick {
|
|
136
|
+
0% {
|
|
137
|
+
opacity: 1;
|
|
138
|
+
transform: translate(-50%, -50%) scale(1);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
100% {
|
|
142
|
+
opacity: 0;
|
|
143
|
+
transform: translate(-50%, -50%) scale(2);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@keyframes doubleClick {
|
|
148
|
+
0% {
|
|
149
|
+
opacity: 1;
|
|
150
|
+
transform: translate(-50%, -50%) scale(1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
45% {
|
|
154
|
+
opacity: 0;
|
|
155
|
+
transform: translate(-50%, -50%) scale(1.2);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
55% {
|
|
159
|
+
opacity: 1;
|
|
160
|
+
transform: translate(-50%, -50%) scale(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
100% {
|
|
164
|
+
opacity: 0;
|
|
165
|
+
transform: scale(2);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#boxes {
|
|
170
|
+
position: absolute;
|
|
171
|
+
top: 25px;
|
|
172
|
+
left: 0;
|
|
173
|
+
right: 0;
|
|
174
|
+
bottom: 0;
|
|
175
|
+
z-index: 2;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
#vm-iframe {
|
|
179
|
+
display: none;
|
|
180
|
+
border: none;
|
|
181
|
+
pointer-events: auto;
|
|
182
|
+
position: absolute;
|
|
183
|
+
top: 0px;
|
|
184
|
+
left: 0px;
|
|
185
|
+
width: 100%;
|
|
186
|
+
height: 100%;
|
|
187
|
+
z-index: 1;
|
|
188
|
+
overflow: hidden;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Loading screen styles */
|
|
192
|
+
.loading-screen {
|
|
193
|
+
position: absolute;
|
|
194
|
+
top: 0;
|
|
195
|
+
left: 0;
|
|
196
|
+
width: 100%;
|
|
197
|
+
height: 100%;
|
|
198
|
+
background-color: black;
|
|
199
|
+
display: flex;
|
|
200
|
+
flex-direction: column;
|
|
201
|
+
align-items: center;
|
|
202
|
+
justify-content: center;
|
|
203
|
+
z-index: 10;
|
|
204
|
+
transition: opacity 0.5s ease-out;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.loading-screen.hidden {
|
|
208
|
+
opacity: 0;
|
|
209
|
+
pointer-events: none;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.loading-text {
|
|
213
|
+
color: #b0cf34;
|
|
214
|
+
font-size: 18px;
|
|
215
|
+
font-weight: 500;
|
|
216
|
+
margin-top: 10px;
|
|
217
|
+
animation: fadeInOut 1.5s infinite;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
@keyframes pulse {
|
|
221
|
+
0% {
|
|
222
|
+
transform: scale(1);
|
|
223
|
+
box-shadow: 0 8px 32px rgba(176, 207, 52, 0.3);
|
|
224
|
+
}
|
|
225
|
+
50% {
|
|
226
|
+
transform: scale(1.05);
|
|
227
|
+
box-shadow: 0 12px 48px rgba(176, 207, 52, 0.5);
|
|
228
|
+
}
|
|
229
|
+
100% {
|
|
230
|
+
transform: scale(1);
|
|
231
|
+
box-shadow: 0 8px 32px rgba(176, 207, 52, 0.3);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@keyframes fadeInOut {
|
|
236
|
+
0%,
|
|
237
|
+
100% {
|
|
238
|
+
opacity: 0.6;
|
|
239
|
+
}
|
|
240
|
+
50% {
|
|
241
|
+
opacity: 1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.status {
|
|
246
|
+
position: absolute;
|
|
247
|
+
top: 20px;
|
|
248
|
+
left: 20px;
|
|
249
|
+
background: rgba(0, 0, 0, 0.8);
|
|
250
|
+
color: #b0cf34;
|
|
251
|
+
padding: 8px 12px;
|
|
252
|
+
border-radius: 4px;
|
|
253
|
+
font-size: 12px;
|
|
254
|
+
z-index: 11;
|
|
255
|
+
opacity: 0;
|
|
256
|
+
transition: opacity 0.3s ease;
|
|
257
|
+
pointer-events: none;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.status.visible {
|
|
261
|
+
opacity: 1;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.bounding-box {
|
|
265
|
+
position: absolute;
|
|
266
|
+
border: 2px solid #b0cf34;
|
|
267
|
+
background: rgba(176, 207, 52, 0.1);
|
|
268
|
+
pointer-events: none;
|
|
269
|
+
z-index: 9;
|
|
270
|
+
border-radius: 3px;
|
|
271
|
+
animation: animate-glow 0.6s ease-out;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.interaction-overlay {
|
|
275
|
+
position: absolute;
|
|
276
|
+
top: 0;
|
|
277
|
+
left: 0;
|
|
278
|
+
width: 100%;
|
|
279
|
+
height: 100%;
|
|
280
|
+
background: rgba(0, 0, 0, 0.5);
|
|
281
|
+
display: flex;
|
|
282
|
+
flex-direction: column;
|
|
283
|
+
align-items: center;
|
|
284
|
+
justify-content: center;
|
|
285
|
+
z-index: 12;
|
|
286
|
+
opacity: 0;
|
|
287
|
+
pointer-events: none;
|
|
288
|
+
transition: opacity 0.3s ease;
|
|
289
|
+
cursor: pointer;
|
|
290
|
+
color: white;
|
|
291
|
+
font-size: 16px;
|
|
292
|
+
font-weight: 500;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.interaction-overlay.visible {
|
|
296
|
+
opacity: 1;
|
|
297
|
+
pointer-events: auto;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.interaction-overlay .lock-icon {
|
|
301
|
+
width: 48px;
|
|
302
|
+
height: 48px;
|
|
303
|
+
margin-bottom: 12px;
|
|
304
|
+
fill: white;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.interaction-overlay .message {
|
|
308
|
+
text-align: center;
|
|
309
|
+
user-select: none;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.close-button {
|
|
313
|
+
position: fixed;
|
|
314
|
+
top: 12px;
|
|
315
|
+
right: 12px;
|
|
316
|
+
z-index: 100;
|
|
317
|
+
background: rgba(0, 0, 0, 0.8);
|
|
318
|
+
border: 1px solid #444;
|
|
319
|
+
color: #fff;
|
|
320
|
+
padding: 8px 16px;
|
|
321
|
+
border-radius: 6px;
|
|
322
|
+
cursor: pointer;
|
|
323
|
+
font-size: 13px;
|
|
324
|
+
font-weight: 500;
|
|
325
|
+
pointer-events: auto;
|
|
326
|
+
transition: all 0.2s ease;
|
|
327
|
+
display: flex;
|
|
328
|
+
align-items: center;
|
|
329
|
+
gap: 6px;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.close-button:hover {
|
|
333
|
+
background: rgba(220, 53, 69, 0.9);
|
|
334
|
+
border-color: #dc3545;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.close-button svg {
|
|
338
|
+
width: 14px;
|
|
339
|
+
height: 14px;
|
|
340
|
+
fill: currentColor;
|
|
341
|
+
}
|
|
342
|
+
</style>
|
|
343
|
+
</head>
|
|
344
|
+
<body>
|
|
345
|
+
<!-- Close window button -->
|
|
346
|
+
<button class="close-button" onclick="window.close()" title="Close this window">
|
|
347
|
+
<svg viewBox="0 0 24 24"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
|
|
348
|
+
Close
|
|
349
|
+
</button>
|
|
350
|
+
|
|
351
|
+
<!-- Loading screen -->
|
|
352
|
+
<div class="loading-screen" id="loading-screen">
|
|
353
|
+
<div class="testdriver-logo">
|
|
354
|
+
<img
|
|
355
|
+
src="https://framerusercontent.com/images/eMQDjLmOzfeu91XBo1VPlIc1WmY.png"
|
|
356
|
+
height="120"
|
|
357
|
+
alt="TestDriver Logo"
|
|
358
|
+
/>
|
|
359
|
+
</div>
|
|
360
|
+
<div class="loading-text">Loading...</div>
|
|
361
|
+
</div>
|
|
362
|
+
|
|
363
|
+
<div class="overlay" id="overlay">
|
|
364
|
+
<div class="effects" id="effects">
|
|
365
|
+
<div class="mouse" id="mouse"></div>
|
|
366
|
+
<div class="screenshot" id="screenshot"></div>
|
|
367
|
+
<div class="status" id="status"></div>
|
|
368
|
+
<div class="interaction-overlay" id="interaction-overlay">
|
|
369
|
+
<svg class="lock-icon" viewBox="0 0 24 24">
|
|
370
|
+
<path
|
|
371
|
+
d="M18,8h-1V6c0-2.76-2.24-5-5-5S7,3.24,7,6v2H6c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V10C20,8.9,19.1,8,18,8z M12,17c-1.1,0-2-0.9-2-2s0.9-2,2-2s2,0.9,2,2S13.1,17,12,17z M15.1,8H8.9V6c0-1.71,1.39-3.1,3.1-3.1s3.1,1.39,3.1,3.1V8z"
|
|
372
|
+
/>
|
|
373
|
+
</svg>
|
|
374
|
+
<div class="message">Click to interact with VM</div>
|
|
375
|
+
</div>
|
|
376
|
+
</div>
|
|
377
|
+
<iframe id="vm-iframe" src="" credentialless></iframe>
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
<script>
|
|
381
|
+
// get data from URL parameters
|
|
382
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
383
|
+
const data = urlParams.get("data");
|
|
384
|
+
let parsedData;
|
|
385
|
+
if (data) {
|
|
386
|
+
try {
|
|
387
|
+
parsedData = JSON.parse(atob(data));
|
|
388
|
+
console.log("Data from URL:", parsedData);
|
|
389
|
+
// You can use parsedData here if needed
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.error("Error parsing data from URL:", error);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
alert(
|
|
395
|
+
"Improperly formatted URL. Please ensure the data parameter is present.",
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const iframe = document.querySelector("#vm-iframe");
|
|
400
|
+
|
|
401
|
+
// Detect if OS is Linux
|
|
402
|
+
const isLinux = parsedData.os === "linux";
|
|
403
|
+
const topBarOffset = isLinux ? 14 : 0;
|
|
404
|
+
|
|
405
|
+
// set overlay width and height to match the given resolution
|
|
406
|
+
const overlayWidth = parsedData.resolution[0];
|
|
407
|
+
const overlayHeight = parsedData.resolution[1];
|
|
408
|
+
|
|
409
|
+
iframe.style.display = "block";
|
|
410
|
+
iframe.src = parsedData.url;
|
|
411
|
+
iframe.style.width = overlayWidth + "px";
|
|
412
|
+
// Increase iframe height by 14px for Linux to account for top bar
|
|
413
|
+
iframe.style.height = overlayHeight + topBarOffset + "px";
|
|
414
|
+
|
|
415
|
+
// Calculate scale factor to fit within window if needed
|
|
416
|
+
const windowWidth = window.innerWidth;
|
|
417
|
+
const windowHeight = window.innerHeight;
|
|
418
|
+
const padding = 20; // Add some padding to prevent cropping
|
|
419
|
+
const scaleX = (windowWidth - padding * 2) / overlayWidth;
|
|
420
|
+
const scaleY = (windowHeight - padding * 2) / overlayHeight;
|
|
421
|
+
const scale = Math.min(scaleX, scaleY, 1); // Don't scale up, only down
|
|
422
|
+
|
|
423
|
+
console.log("window:", {
|
|
424
|
+
width: windowWidth,
|
|
425
|
+
height: windowHeight,
|
|
426
|
+
});
|
|
427
|
+
console.log("overlay:", {
|
|
428
|
+
width: overlayWidth,
|
|
429
|
+
height: overlayHeight,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const overlay = document.getElementById("overlay");
|
|
433
|
+
overlay.style.width = overlayWidth + "px";
|
|
434
|
+
overlay.style.height = overlayHeight + "px";
|
|
435
|
+
|
|
436
|
+
console.log("scale", scale);
|
|
437
|
+
// Apply scaling if needed
|
|
438
|
+
if (scale < 1) {
|
|
439
|
+
overlay.style.transform = `scale(${scale})`;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// WebSocket connection
|
|
443
|
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
444
|
+
const ws = new WebSocket(`${protocol}//${window.location.host}`);
|
|
445
|
+
|
|
446
|
+
// WebSocket event handlers
|
|
447
|
+
const eventHandlers = new Map();
|
|
448
|
+
|
|
449
|
+
const addEventHandler = (event, callback) => {
|
|
450
|
+
if (!eventHandlers.has(event)) {
|
|
451
|
+
eventHandlers.set(event, []);
|
|
452
|
+
}
|
|
453
|
+
eventHandlers.get(event).push(callback);
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// Activity tracking for title updates
|
|
457
|
+
let idleTimeout = null;
|
|
458
|
+
const IDLE_THRESHOLD = 5000; // 5 seconds
|
|
459
|
+
|
|
460
|
+
// Get test file name for title (use just filename, not full path)
|
|
461
|
+
const testFileName = parsedData?.testFile
|
|
462
|
+
? parsedData.testFile.split('/').pop().split('\\\\').pop()
|
|
463
|
+
: 'TestDriver';
|
|
464
|
+
|
|
465
|
+
const setTitle = (status) => {
|
|
466
|
+
document.title = `[${status}] ${testFileName}`;
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const resetIdleTimer = () => {
|
|
470
|
+
setTitle("Running");
|
|
471
|
+
if (idleTimeout) clearTimeout(idleTimeout);
|
|
472
|
+
idleTimeout = setTimeout(() => {
|
|
473
|
+
setTitle("Idle");
|
|
474
|
+
}, IDLE_THRESHOLD);
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
ws.addEventListener("message", (message) => {
|
|
478
|
+
resetIdleTimer();
|
|
479
|
+
try {
|
|
480
|
+
const data = JSON.parse(message.data);
|
|
481
|
+
console.log("WebSocket message received:", data);
|
|
482
|
+
const handlers = eventHandlers.get(data.event);
|
|
483
|
+
if (handlers) {
|
|
484
|
+
handlers.forEach((callback) => callback(null, data.data));
|
|
485
|
+
}
|
|
486
|
+
} catch (error) {
|
|
487
|
+
console.error("Error parsing WebSocket message:", error);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
ws.addEventListener("open", () => {
|
|
492
|
+
console.log("WebSocket connected to TestDriver.ai overlay");
|
|
493
|
+
|
|
494
|
+
// Hide loading screen
|
|
495
|
+
const loadingScreen = document.getElementById("loading-screen");
|
|
496
|
+
loadingScreen.classList.add("hidden");
|
|
497
|
+
|
|
498
|
+
// Show connection status briefly
|
|
499
|
+
document.getElementById("status").textContent = "Connected";
|
|
500
|
+
document.getElementById("status").classList.add("visible");
|
|
501
|
+
setTimeout(() => {
|
|
502
|
+
document.getElementById("status").classList.remove("visible");
|
|
503
|
+
}, 2000);
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
ws.addEventListener("error", (error) => {
|
|
507
|
+
console.error("WebSocket error:", error);
|
|
508
|
+
document.getElementById("status").textContent = "Connection Error";
|
|
509
|
+
document.getElementById("status").classList.add("visible");
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
ws.addEventListener("close", () => {
|
|
513
|
+
console.log("WebSocket disconnected");
|
|
514
|
+
if (idleTimeout) clearTimeout(idleTimeout);
|
|
515
|
+
setTitle("Done");
|
|
516
|
+
document.getElementById("status").textContent = "Disconnected";
|
|
517
|
+
document.getElementById("status").classList.add("visible");
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// Event handling (same as original)
|
|
521
|
+
const events = {
|
|
522
|
+
mouseClick: "mouse-click",
|
|
523
|
+
mouseMove: "mouse-move",
|
|
524
|
+
screenCapture: {
|
|
525
|
+
start: "screen-capture:start",
|
|
526
|
+
end: "screen-capture:end",
|
|
527
|
+
error: "screen-capture:error",
|
|
528
|
+
},
|
|
529
|
+
interactive: "interactive",
|
|
530
|
+
terminal: {
|
|
531
|
+
stdout: "terminal:stdout",
|
|
532
|
+
stderr: "terminal:stderr",
|
|
533
|
+
},
|
|
534
|
+
matches: {
|
|
535
|
+
show: "matches:show",
|
|
536
|
+
},
|
|
537
|
+
vm: {
|
|
538
|
+
show: "vm:show",
|
|
539
|
+
},
|
|
540
|
+
test: {
|
|
541
|
+
start: "test:start",
|
|
542
|
+
stop: "test:stop",
|
|
543
|
+
success: "test:success",
|
|
544
|
+
error: "test:error",
|
|
545
|
+
},
|
|
546
|
+
error: {
|
|
547
|
+
fatal: "error:fatal",
|
|
548
|
+
general: "error:general",
|
|
549
|
+
sdk: "error:sdk",
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// Title state handlers for immediate feedback
|
|
554
|
+
addEventHandler(events.test.start, () => {
|
|
555
|
+
if (idleTimeout) clearTimeout(idleTimeout);
|
|
556
|
+
setTitle("Running");
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
addEventHandler(events.test.stop, () => {
|
|
560
|
+
if (idleTimeout) clearTimeout(idleTimeout);
|
|
561
|
+
setTitle("Stopped");
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
addEventHandler(events.test.success, () => {
|
|
565
|
+
if (idleTimeout) clearTimeout(idleTimeout);
|
|
566
|
+
setTitle("Passed");
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
addEventHandler(events.test.error, () => {
|
|
570
|
+
if (idleTimeout) clearTimeout(idleTimeout);
|
|
571
|
+
setTitle("Failed");
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
addEventHandler(events.error.fatal, () => {
|
|
575
|
+
if (idleTimeout) clearTimeout(idleTimeout);
|
|
576
|
+
setTitle("Error");
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
addEventHandler(events.error.sdk, () => {
|
|
580
|
+
if (idleTimeout) clearTimeout(idleTimeout);
|
|
581
|
+
setTitle("Error");
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const effects = document.getElementById("effects");
|
|
585
|
+
const mouse = document.getElementById("mouse");
|
|
586
|
+
const screenshotElement = document.getElementById("screenshot");
|
|
587
|
+
const interactionOverlay = document.getElementById("interaction-overlay");
|
|
588
|
+
|
|
589
|
+
let boundingBoxesTimeout;
|
|
590
|
+
let interactionTimeout;
|
|
591
|
+
let isInteractionEnabled = false;
|
|
592
|
+
|
|
593
|
+
// Show interaction overlay on hover, hide on click, and bring back after 30 seconds
|
|
594
|
+
const showInteractionOverlay = () => {
|
|
595
|
+
if (!isInteractionEnabled) {
|
|
596
|
+
interactionOverlay.classList.add("visible");
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
const hideInteractionOverlay = () => {
|
|
601
|
+
interactionOverlay.classList.remove("visible");
|
|
602
|
+
isInteractionEnabled = true;
|
|
603
|
+
|
|
604
|
+
// Clear any existing timeout
|
|
605
|
+
if (interactionTimeout) {
|
|
606
|
+
clearTimeout(interactionTimeout);
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
// Event listeners for interaction overlay
|
|
611
|
+
overlay.addEventListener("mouseenter", showInteractionOverlay);
|
|
612
|
+
overlay.addEventListener("mouseleave", () => {
|
|
613
|
+
if (!isInteractionEnabled) {
|
|
614
|
+
interactionOverlay.classList.remove("visible");
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
interactionOverlay.addEventListener("click", hideInteractionOverlay);
|
|
619
|
+
|
|
620
|
+
const drawBoxes = (boxes) => {
|
|
621
|
+
// Remove existing boxes
|
|
622
|
+
document.querySelectorAll(".bounding-box").forEach((box) => {
|
|
623
|
+
box.remove();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Add new boxes
|
|
627
|
+
boxes.forEach((box) => {
|
|
628
|
+
const boxElement = document.createElement("div");
|
|
629
|
+
boxElement.className = "bounding-box";
|
|
630
|
+
boxElement.style.left = toCss(box.x);
|
|
631
|
+
boxElement.style.top = toCss(box.y + topBarOffset);
|
|
632
|
+
boxElement.style.width = toCss(box.width);
|
|
633
|
+
boxElement.style.height = toCss(box.height);
|
|
634
|
+
effects.appendChild(boxElement);
|
|
635
|
+
});
|
|
636
|
+
};
|
|
637
|
+
// Screen capture event handlers
|
|
638
|
+
addEventHandler(events.screenCapture.start, (event, data) => {
|
|
639
|
+
if (data?.silent) return;
|
|
640
|
+
screenshotElement.style.opacity = 0;
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
addEventHandler(events.screenCapture.error, (event, data) => {
|
|
644
|
+
if (data?.silent) return;
|
|
645
|
+
screenshotElement.style.opacity = 1;
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
addEventHandler(events.screenCapture.end, (event, data) => {
|
|
649
|
+
if (data?.silent) return;
|
|
650
|
+
screenshotElement.classList.remove("screenshot");
|
|
651
|
+
// Force reflow
|
|
652
|
+
void screenshotElement.offsetWidth;
|
|
653
|
+
screenshotElement.classList.add("screenshot");
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
// Mouse event handlers
|
|
657
|
+
addEventHandler(events.mouseMove, (event, { x, y } = {}) => {
|
|
658
|
+
mouse.style.marginLeft = toCss(x);
|
|
659
|
+
mouse.style.marginTop = toCss(y + topBarOffset);
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
addEventHandler(
|
|
663
|
+
events.mouseClick,
|
|
664
|
+
(event, { x, y, click = "single" } = {}) => {
|
|
665
|
+
mouse.style.marginLeft = toCss(x);
|
|
666
|
+
mouse.style.marginTop = toCss(y + topBarOffset);
|
|
667
|
+
// Reset class so animation can restart
|
|
668
|
+
mouse.setAttribute("class", "mouse");
|
|
669
|
+
// Force reflow
|
|
670
|
+
void mouse.offsetWidth;
|
|
671
|
+
mouse.classList.add(`${click}-click`);
|
|
672
|
+
},
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
// Matches and VM event handlers
|
|
676
|
+
addEventHandler(events.matches.show, (event, closeMatches = []) => {
|
|
677
|
+
if (boundingBoxesTimeout) clearTimeout(boundingBoxesTimeout);
|
|
678
|
+
drawBoxes(closeMatches);
|
|
679
|
+
boundingBoxesTimeout = setTimeout(() => drawBoxes([]), 10000);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
addEventHandler(events.terminal.stdout, (event, data) => {
|
|
683
|
+
console.log("Terminal stdout:", data);
|
|
684
|
+
// Could be used to display terminal output in UI
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
addEventHandler(events.terminal.stderr, (event, data) => {
|
|
688
|
+
console.log("Terminal stderr:", data);
|
|
689
|
+
// Could be used to display terminal errors in UI
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const toCss = (size) => {
|
|
693
|
+
if (typeof size === "number") {
|
|
694
|
+
return `${size}px`;
|
|
695
|
+
}
|
|
696
|
+
return size;
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
// Throttle function to limit how often a function can run
|
|
700
|
+
function throttle(fn, wait) {
|
|
701
|
+
let lastTime = 0;
|
|
702
|
+
return function (...args) {
|
|
703
|
+
const now = Date.now();
|
|
704
|
+
if (now - lastTime >= wait) {
|
|
705
|
+
lastTime = now;
|
|
706
|
+
fn.apply(this, args);
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const resizeOverlay = () => {
|
|
712
|
+
if (parsedData && parsedData.resolution) {
|
|
713
|
+
const overlayWidth = parsedData.resolution[0];
|
|
714
|
+
const overlayHeight = parsedData.resolution[1];
|
|
715
|
+
const windowWidth = window.innerWidth;
|
|
716
|
+
const windowHeight = window.innerHeight;
|
|
717
|
+
const scaleX = windowWidth / overlayWidth;
|
|
718
|
+
const scaleY = windowHeight / overlayHeight;
|
|
719
|
+
const scale = Math.min(scaleX, scaleY);
|
|
720
|
+
|
|
721
|
+
console.log("scale", scale);
|
|
722
|
+
|
|
723
|
+
console.log(
|
|
724
|
+
"resolution:",
|
|
725
|
+
JSON.stringify({
|
|
726
|
+
width: overlayWidth,
|
|
727
|
+
height: overlayHeight,
|
|
728
|
+
}),
|
|
729
|
+
);
|
|
730
|
+
|
|
731
|
+
console.log(
|
|
732
|
+
"window:",
|
|
733
|
+
JSON.stringify({
|
|
734
|
+
width: windowWidth,
|
|
735
|
+
height: windowHeight,
|
|
736
|
+
}),
|
|
737
|
+
);
|
|
738
|
+
const overlay = document.getElementById("overlay");
|
|
739
|
+
if (scale < 1) {
|
|
740
|
+
overlay.style.transform = `scale(${scale})`;
|
|
741
|
+
} else {
|
|
742
|
+
overlay.style.transform = "none";
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// get the new width and height after scaling
|
|
746
|
+
const scaledWidth = overlayWidth * scale;
|
|
747
|
+
const scaledHeight = overlayHeight * scale;
|
|
748
|
+
|
|
749
|
+
console.log("scaledWidth", JSON.stringify(scaledWidth));
|
|
750
|
+
console.log("scaledHeight", JSON.stringify(scaledHeight));
|
|
751
|
+
|
|
752
|
+
// use the scaled width and height to center the overlay
|
|
753
|
+
overlay.style.position = "absolute";
|
|
754
|
+
if (scale < 1) {
|
|
755
|
+
// When scaling down, use the original dimensions and let CSS transform handle the scaling
|
|
756
|
+
overlay.style.width = `${overlayWidth}px`;
|
|
757
|
+
overlay.style.height = `${overlayHeight}px`;
|
|
758
|
+
overlay.style.left = `${(windowWidth - scaledWidth) / 2}px`;
|
|
759
|
+
overlay.style.top = `${(windowHeight - scaledHeight) / 2}px`;
|
|
760
|
+
} else {
|
|
761
|
+
// When no scaling is needed, just center the overlay at its original size
|
|
762
|
+
overlay.style.width = `${overlayWidth}px`;
|
|
763
|
+
overlay.style.height = `${overlayHeight}px`;
|
|
764
|
+
overlay.style.left = `${(windowWidth - overlayWidth) / 2}px`;
|
|
765
|
+
overlay.style.top = `${(windowHeight - overlayHeight) / 2}px`;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
console.log(
|
|
769
|
+
"left,top",
|
|
770
|
+
JSON.stringify({
|
|
771
|
+
left:
|
|
772
|
+
scale < 1
|
|
773
|
+
? (windowWidth - scaledWidth) / 2
|
|
774
|
+
: (windowWidth - overlayWidth) / 2,
|
|
775
|
+
top:
|
|
776
|
+
scale < 1
|
|
777
|
+
? (windowHeight - scaledHeight) / 2
|
|
778
|
+
: (windowHeight - overlayHeight) / 2,
|
|
779
|
+
}),
|
|
780
|
+
);
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
resizeOverlay();
|
|
784
|
+
|
|
785
|
+
// Handle window resize to recalculate scale (throttled)
|
|
786
|
+
window.addEventListener("resize", throttle(resizeOverlay, 100));
|
|
787
|
+
|
|
788
|
+
setInterval(resizeOverlay, 1000);
|
|
789
|
+
|
|
790
|
+
// Handle window blur/focus for screen locking
|
|
791
|
+
window.addEventListener("blur", () => {
|
|
792
|
+
showInteractionOverlay();
|
|
793
|
+
isInteractionEnabled = false;
|
|
794
|
+
});
|
|
795
|
+
</script>
|
|
796
|
+
</body>
|
|
797
|
+
</html>
|