testronaut 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/turnIntent.js +51 -20
- package/core/turnLoop.js +8 -4
- package/package.json +1 -1
- package/tools/generateHtmlReport.js +83 -0
package/core/turnIntent.js
CHANGED
|
@@ -13,7 +13,36 @@ function pickArg(args, keys) {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// core/turnIntent.js
|
|
17
|
+
const ICONS = {
|
|
18
|
+
navigate: '🧭',
|
|
19
|
+
click_text: '🖱️',
|
|
20
|
+
click: '🖱️',
|
|
21
|
+
type: '⌨️',
|
|
22
|
+
fill: '⌨️',
|
|
23
|
+
press: '⌨️',
|
|
24
|
+
hover: '🪄',
|
|
25
|
+
wait_for_selector: '⏳',
|
|
26
|
+
wait: '⏳',
|
|
27
|
+
screenshot: '📸',
|
|
28
|
+
get_dom: '🧩',
|
|
29
|
+
check_text: '🔍',
|
|
30
|
+
scroll: '↕️',
|
|
31
|
+
upload_file: '📤',
|
|
32
|
+
download_file: '📥',
|
|
33
|
+
expand_menu: '📂',
|
|
34
|
+
set_viewport: '🖥️',
|
|
35
|
+
default: '⚙️',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const EMOJI_ON_DEFAULT =
|
|
39
|
+
process.env.TESTRONAUT_EMOJI === '1' || process.env.TESTRONAUT_EMOJI === 'true';
|
|
40
|
+
|
|
41
|
+
const withIcon = (name, s, { emoji = EMOJI_ON_DEFAULT } = {}) =>
|
|
42
|
+
emoji ? `${ICONS[name] || ICONS.default} ${s}` : s;
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
export function summarizeToolCall(fnName, args = {}, opts = {}) {
|
|
17
46
|
const safeArgs = redactArgs(fnName, args);
|
|
18
47
|
|
|
19
48
|
const selector = pickArg(safeArgs, ['selector','xpath','css','role','testId']);
|
|
@@ -29,33 +58,35 @@ export function summarizeToolCall(fnName, args = {}) {
|
|
|
29
58
|
const tmo = timeout ? ` [≤${timeout}ms]` : '';
|
|
30
59
|
|
|
31
60
|
switch (fnName) {
|
|
32
|
-
case 'navigate':
|
|
33
|
-
case 'click_text':
|
|
34
|
-
case 'click':
|
|
61
|
+
case 'navigate': return withIcon('navigate', `Navigate to ${truncateMiddle(url || '(no url)', 80)}`, opts);
|
|
62
|
+
case 'click_text': return withIcon('click_text', `Click text${val}`, opts);
|
|
63
|
+
case 'click': return withIcon('click', `Click element${sel}`, opts);
|
|
35
64
|
case 'type':
|
|
36
|
-
case 'fill':
|
|
37
|
-
case 'press':
|
|
38
|
-
case 'hover':
|
|
39
|
-
case 'wait_for_selector':
|
|
40
|
-
|
|
41
|
-
case '
|
|
42
|
-
case '
|
|
43
|
-
case '
|
|
44
|
-
case '
|
|
45
|
-
case '
|
|
46
|
-
case '
|
|
47
|
-
case '
|
|
48
|
-
|
|
65
|
+
case 'fill': return withIcon('type', `Type${val}${selector ? ` into ${truncateMiddle(selector, 60)}` : ''}`, opts);
|
|
66
|
+
case 'press': return withIcon('press', `Press key${val || ''}${selector ? ` in ${truncateMiddle(selector, 60)}` : ''}`, opts);
|
|
67
|
+
case 'hover': return withIcon('hover', `Hover over${sel || val || ''}`, opts);
|
|
68
|
+
case 'wait_for_selector':
|
|
69
|
+
return withIcon('wait_for_selector', `Wait for${sel}${tmo}`, opts);
|
|
70
|
+
case 'wait': return withIcon('wait', `Wait${tmo || ''}`, opts);
|
|
71
|
+
case 'screenshot': return withIcon('screenshot', `Capture screenshot${selector ? ` of ${truncateMiddle(selector, 60)}` : ''}`, opts);
|
|
72
|
+
case 'get_dom': return withIcon('get_dom', `Inspect DOM (focused extract)`, opts);
|
|
73
|
+
case 'check_text': return withIcon('check_text', `Check page for${val || ' specific text'}`, opts);
|
|
74
|
+
case 'scroll': return withIcon('scroll', `Scroll to coords (${x ?? '?'}, ${y ?? '?'})`, opts);
|
|
75
|
+
case 'upload_file': return withIcon('upload_file', `Upload file ${truncateMiddle(file || '(unknown)', 60)}${selector ? ` via ${truncateMiddle(selector, 60)}` : ''}`, opts);
|
|
76
|
+
case 'download_file':
|
|
77
|
+
return withIcon('download_file', `Download file to ${truncateMiddle(file || '(unknown)', 60)}`, opts);
|
|
78
|
+
case 'expand_menu': return withIcon('expand_menu', `Expand menu${sel || ''}`, opts);
|
|
79
|
+
case 'set_viewport': return withIcon('set_viewport', `Set viewport ${safeArgs?.width}×${safeArgs?.height}`, opts);
|
|
49
80
|
default:
|
|
50
|
-
return `Run ${fnName} with ${truncateMiddle(JSON.stringify(safeArgs ?? {}), 80)}
|
|
81
|
+
return withIcon('default', `Run ${fnName} with ${truncateMiddle(JSON.stringify(safeArgs ?? {}), 80)}`, opts);
|
|
51
82
|
}
|
|
52
83
|
}
|
|
53
84
|
|
|
54
|
-
export function summarizeTurnIntentFromMessage(msg) {
|
|
85
|
+
export function summarizeTurnIntentFromMessage(msg, opts = {}) {
|
|
55
86
|
try {
|
|
56
87
|
if (msg?.tool_calls?.length) {
|
|
57
88
|
return msg.tool_calls
|
|
58
|
-
.map(c => summarizeToolCall(c.function?.name || 'unknown', JSON.parse(c.function?.arguments || '{}')))
|
|
89
|
+
.map(c => summarizeToolCall(c.function?.name || 'unknown', JSON.parse(c.function?.arguments || '{}'), opts))
|
|
59
90
|
.join(' → ');
|
|
60
91
|
}
|
|
61
92
|
if (msg?.content) return `Reasoning: ${truncateMiddle(msg.content, 120)}`;
|
package/core/turnLoop.js
CHANGED
|
@@ -180,10 +180,14 @@ export const turnLoop = async (
|
|
|
180
180
|
|
|
181
181
|
const msg = response.choices[0].message;
|
|
182
182
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
// Plain (no emoji) for storage:
|
|
184
|
+
const planPlain = summarizeTurnIntentFromMessage(msg, { emoji: true });
|
|
185
|
+
// Emoji (toggleable) for console/event log:
|
|
186
|
+
const planDisplay = summarizeTurnIntentFromMessage(msg, { /* emoji default via env */ });
|
|
187
|
+
|
|
188
|
+
currentStep.summary = planPlain; // stored in JSON
|
|
189
|
+
currentStep.events.unshift(`📝 Plan: ${planDisplay}`);
|
|
190
|
+
console.log(`📝 Plan: ${planDisplay}`);
|
|
187
191
|
|
|
188
192
|
if (msg.tool_calls?.length) {
|
|
189
193
|
console.log('Processing tool calls...');
|
package/package.json
CHANGED
|
@@ -64,6 +64,9 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
64
64
|
<span class="name">${prettyTitle}</span>
|
|
65
65
|
<span class="status ${statusClass}">${badge(m.status)}</span>
|
|
66
66
|
<span class="meta">steps: ${steps.length} • duration: ${mDurationSec}s</span>
|
|
67
|
+
<span class="toolbar">
|
|
68
|
+
<button class="btn-mini toggle" data-scope="submission" aria-label="Expand">▼</button>
|
|
69
|
+
</span>
|
|
67
70
|
</summary>
|
|
68
71
|
<div class="steps">
|
|
69
72
|
${stepItems || '<div class="empty">No steps recorded.</div>'}
|
|
@@ -101,12 +104,16 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
101
104
|
<span class="name">${esc(missionName)}</span>
|
|
102
105
|
<span class="status ${status === 'failed' ? 'bad' : 'ok'}">${badge(status)}</span>
|
|
103
106
|
<span class="meta">submissions: ${subs.length} • steps: ${totalSteps} • duration: ${groupDur}s</span>
|
|
107
|
+
<span class="toolbar">
|
|
108
|
+
<button class="btn-mini toggle" data-scope="mission" aria-label="Expand">▼</button>
|
|
109
|
+
</span>
|
|
104
110
|
</summary>
|
|
105
111
|
<div class="group-body">
|
|
106
112
|
${subs.map(submissionBlock).join('')}
|
|
107
113
|
</div>
|
|
108
114
|
</details>
|
|
109
115
|
`;
|
|
116
|
+
|
|
110
117
|
};
|
|
111
118
|
|
|
112
119
|
const groupsHtml = Object.entries(grouped).map(([name, subs]) => missionGroupBlock(name, subs)).join('');
|
|
@@ -199,6 +206,22 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
199
206
|
.mission-group > summary:hover{ background: rgba(255,255,255,.08); }
|
|
200
207
|
|
|
201
208
|
.group-body{ padding:12px; display:grid; gap:10px; }
|
|
209
|
+
// .toolbar{display:flex; gap:8px; margin-left:auto;}
|
|
210
|
+
// .btn-mini{
|
|
211
|
+
// font-size:11px; padding:4px 8px; border-radius:8px;
|
|
212
|
+
// background: rgba(255,255,255,.06); color: var(--text);
|
|
213
|
+
// border:1px solid var(--hairline); cursor:pointer;
|
|
214
|
+
// }
|
|
215
|
+
// .btn-mini:hover{ background: rgba(255,255,255,.10); }
|
|
216
|
+
|
|
217
|
+
.toolbar{display:flex; gap:8px; margin-left:auto;}
|
|
218
|
+
.btn-mini{
|
|
219
|
+
font-size:12px; padding:2px 8px; border-radius:8px;
|
|
220
|
+
background: rgba(255,255,255,.06); color: var(--text);
|
|
221
|
+
border:1px solid var(--hairline); cursor:pointer; line-height:1.2;
|
|
222
|
+
width:28px; text-align:center;
|
|
223
|
+
}
|
|
224
|
+
.btn-mini:hover{ background: rgba(255,255,255,.10); }
|
|
202
225
|
|
|
203
226
|
.mission-submission > summary{
|
|
204
227
|
font-weight:700; background: rgba(255,255,255,.06); border-top:1px solid var(--hairline);
|
|
@@ -268,6 +291,66 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
268
291
|
<div class="container">
|
|
269
292
|
${groupsHtml || '<div class="glass empty">No missions recorded.</div>'}
|
|
270
293
|
</div>
|
|
294
|
+
<script>
|
|
295
|
+
(function () {
|
|
296
|
+
function setOpenAll(root, selector, open) {
|
|
297
|
+
root.querySelectorAll(selector).forEach(function (el) {
|
|
298
|
+
if (el && 'open' in el) el.open = open;
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
function areAllOpen(root, selector) {
|
|
302
|
+
var list = root.querySelectorAll(selector);
|
|
303
|
+
if (!list.length) return false;
|
|
304
|
+
for (var i=0;i<list.length;i++) { if (!list[i].open) return false; }
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
function refreshBtn(btn) {
|
|
308
|
+
var scope = btn.getAttribute('data-scope');
|
|
309
|
+
var root = btn.closest(scope === 'mission' ? '.mission-group' : '.mission-submission');
|
|
310
|
+
if (!root) return;
|
|
311
|
+
var allOpen = scope === 'mission'
|
|
312
|
+
? (areAllOpen(root, '.mission-submission') && areAllOpen(root, '.mission-submission .step'))
|
|
313
|
+
: areAllOpen(root, '.step');
|
|
314
|
+
btn.textContent = allOpen ? '▲' : '▼';
|
|
315
|
+
btn.setAttribute('aria-label', allOpen ? 'Collapse' : 'Expand');
|
|
316
|
+
btn.dataset.open = allOpen ? 'true' : 'false';
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Click handler (single toggle)
|
|
320
|
+
document.addEventListener('click', function (e) {
|
|
321
|
+
var btn = e.target && e.target.closest && e.target.closest('button.toggle');
|
|
322
|
+
if (!btn) return;
|
|
323
|
+
e.preventDefault(); e.stopPropagation();
|
|
324
|
+
|
|
325
|
+
var scope = btn.getAttribute('data-scope');
|
|
326
|
+
var root = btn.closest(scope === 'mission' ? '.mission-group' : '.mission-submission');
|
|
327
|
+
if (!root) return;
|
|
328
|
+
|
|
329
|
+
var currentlyOpen = btn.dataset.open === 'true';
|
|
330
|
+
var nextOpen = !currentlyOpen;
|
|
331
|
+
|
|
332
|
+
if (scope === 'mission') {
|
|
333
|
+
root.open = true; // ensure visible
|
|
334
|
+
setOpenAll(root, '.mission-submission', nextOpen);
|
|
335
|
+
setOpenAll(root, '.mission-submission .step', nextOpen);
|
|
336
|
+
} else {
|
|
337
|
+
root.open = true;
|
|
338
|
+
setOpenAll(root, '.step', nextOpen);
|
|
339
|
+
}
|
|
340
|
+
refreshBtn(btn);
|
|
341
|
+
}, true);
|
|
342
|
+
|
|
343
|
+
// Keep arrows in sync if user toggles details manually
|
|
344
|
+
document.addEventListener('toggle', function () {
|
|
345
|
+
document.querySelectorAll('button.toggle').forEach(refreshBtn);
|
|
346
|
+
}, true);
|
|
347
|
+
|
|
348
|
+
// Initial refresh
|
|
349
|
+
document.querySelectorAll('button.toggle').forEach(refreshBtn);
|
|
350
|
+
})();
|
|
351
|
+
</script>
|
|
352
|
+
|
|
353
|
+
|
|
271
354
|
</body>
|
|
272
355
|
</html>`;
|
|
273
356
|
|