testronaut 1.0.14 → 1.0.15
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/agent.js +2 -0
- package/package.json +1 -1
- package/runner/testronaut.js +24 -3
- package/tools/generateHtmlReport.js +79 -11
package/core/agent.js
CHANGED
|
@@ -47,6 +47,8 @@ export async function runAgent(goals, missionName, maxTurns = 20, ) {
|
|
|
47
47
|
|
|
48
48
|
missionResults.push({
|
|
49
49
|
missionName,
|
|
50
|
+
submissionType: goal.submissionType || goal.label,
|
|
51
|
+
submissionName: goal.submissionName || null,
|
|
50
52
|
status: result.success ? 'passed' : 'failed',
|
|
51
53
|
steps: JSON.parse(JSON.stringify(steps)), // 👈 deep-clone to avoid refs
|
|
52
54
|
startTime: Date.now() - 1, // set real values if you have them
|
package/package.json
CHANGED
package/runner/testronaut.js
CHANGED
|
@@ -26,15 +26,36 @@ export async function runMissions({ preMission, mission, postMission }, missionN
|
|
|
26
26
|
const goals = [];
|
|
27
27
|
|
|
28
28
|
if (pre.length) {
|
|
29
|
-
goals.push(
|
|
29
|
+
goals.push(
|
|
30
|
+
...pre.map((fn, i) => ({
|
|
31
|
+
goal: fn,
|
|
32
|
+
label: 'pre-mission', // legacy label
|
|
33
|
+
submissionType: 'premission', // normalized
|
|
34
|
+
submissionName: fn?.name || `pre-${i + 1}`,
|
|
35
|
+
}))
|
|
36
|
+
);
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
if (main.length) {
|
|
33
|
-
goals.push(
|
|
40
|
+
goals.push(
|
|
41
|
+
...main.map((fn, i) => ({
|
|
42
|
+
goal: fn,
|
|
43
|
+
label: 'mission',
|
|
44
|
+
submissionType: 'mission',
|
|
45
|
+
submissionName: fn?.name || `mission-${i + 1}`,
|
|
46
|
+
}))
|
|
47
|
+
);
|
|
34
48
|
}
|
|
35
49
|
|
|
36
50
|
if (post.length) {
|
|
37
|
-
goals.push(
|
|
51
|
+
goals.push(
|
|
52
|
+
...post.map((fn, i) => ({
|
|
53
|
+
goal: fn,
|
|
54
|
+
label: 'post-mission',
|
|
55
|
+
submissionType: 'postmission',
|
|
56
|
+
submissionName: fn?.name || `post-${i + 1}`,
|
|
57
|
+
}))
|
|
58
|
+
);
|
|
38
59
|
}
|
|
39
60
|
|
|
40
61
|
console.log(
|
|
@@ -13,7 +13,9 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
13
13
|
status === 'passed' ? '✅ Passed' :
|
|
14
14
|
status === 'failed' ? '❌ Failed' : (status || '—');
|
|
15
15
|
|
|
16
|
-
const missionBlock = (m, idx) => {
|
|
16
|
+
// const missionBlock = (m, idx) => {
|
|
17
|
+
// Render ONE submission (premission/mission/postmission)
|
|
18
|
+
const submissionBlock = (m, idx) => {
|
|
17
19
|
const mDurationSec = m.endTime && m.startTime
|
|
18
20
|
? ((m.endTime - m.startTime) / 1000).toFixed(2)
|
|
19
21
|
: '—';
|
|
@@ -37,13 +39,21 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
37
39
|
`;
|
|
38
40
|
}).join('');
|
|
39
41
|
|
|
42
|
+
// Build a clearer title: "<mission> — premission: login"
|
|
43
|
+
const type = (m.submissionType || '').toLowerCase();
|
|
44
|
+
const typeLabel =
|
|
45
|
+
type === 'premission' ? 'premission' :
|
|
46
|
+
type === 'postmission' ? 'postmission' :
|
|
47
|
+
'mission';
|
|
48
|
+
const prettyTitle = `${esc(m.missionName || 'Mission')} — ${typeLabel}${m.submissionName ? `: ${esc(m.submissionName)}` : ''}`;
|
|
49
|
+
|
|
50
|
+
|
|
40
51
|
return `
|
|
41
|
-
<details class="mission">
|
|
52
|
+
<details class="mission-submission">
|
|
42
53
|
<summary>
|
|
43
|
-
<span class="name">${
|
|
54
|
+
<span class="name">${prettyTitle}</span>
|
|
44
55
|
<span class="status ${m.status === 'passed' ? 'ok' : (m.status === 'failed' ? 'bad' : '')}">${badge(m.status)}</span>
|
|
45
|
-
<span class="meta">steps: ${steps.length} • duration: ${mDurationSec}s
|
|
46
|
-
</summary>
|
|
56
|
+
<span class="meta">steps: ${steps.length} • duration: ${mDurationSec}s</span>
|
|
47
57
|
<div class="steps">
|
|
48
58
|
${stepItems || '<div class="empty">No steps recorded.</div>'}
|
|
49
59
|
</div>
|
|
@@ -51,6 +61,42 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
51
61
|
`;
|
|
52
62
|
};
|
|
53
63
|
|
|
64
|
+
// Group submissions by missionName
|
|
65
|
+
const grouped = missions.reduce((acc, m) => {
|
|
66
|
+
const key = m.missionName || '(unnamed mission)';
|
|
67
|
+
(acc[key] ||= []).push(m);
|
|
68
|
+
return acc;
|
|
69
|
+
}, {});
|
|
70
|
+
|
|
71
|
+
// Compute overall status for a group (any failure => failed)
|
|
72
|
+
const groupStatus = (subs) => subs.some(s => s.status === 'failed') ? 'failed' : 'passed';
|
|
73
|
+
|
|
74
|
+
// Render a mission group with its submissions collapsed inside
|
|
75
|
+
const missionGroupBlock = (missionName, subs) => {
|
|
76
|
+
const status = groupStatus(subs);
|
|
77
|
+
const totalSteps = subs.reduce((n, s) => n + (Array.isArray(s.steps) ? s.steps.length : 0), 0);
|
|
78
|
+
const firstStart = Math.min(...subs.map(s => s.startTime || 0).filter(Boolean));
|
|
79
|
+
const lastEnd = Math.max(...subs.map(s => s.endTime || 0).filter(Boolean));
|
|
80
|
+
const groupDur = (firstStart && lastEnd) ? ((lastEnd - firstStart) / 1000).toFixed(2) : '—';
|
|
81
|
+
|
|
82
|
+
// consistent order: pre → mission → post
|
|
83
|
+
const order = { premission: 0, mission: 1, postmission: 2 };
|
|
84
|
+
subs.sort((a, b) => (order[(a.submissionType||'mission')] ?? 1) - (order[(b.submissionType||'mission')] ?? 1));
|
|
85
|
+
|
|
86
|
+
return `
|
|
87
|
+
<details class="mission-group">
|
|
88
|
+
<summary>
|
|
89
|
+
<span class="name">${esc(missionName)}</span>
|
|
90
|
+
<span class="status ${status === 'passed' ? 'ok' : 'bad'}">${badge(status)}</span>
|
|
91
|
+
<span class="meta">submissions: ${subs.length} • steps: ${totalSteps} • duration: ${groupDur}s</span>
|
|
92
|
+
</summary>
|
|
93
|
+
<div class="group-body">
|
|
94
|
+
${subs.map(submissionBlock).join('')}
|
|
95
|
+
</div>
|
|
96
|
+
</details>
|
|
97
|
+
`;
|
|
98
|
+
};
|
|
99
|
+
|
|
54
100
|
const html = `
|
|
55
101
|
<!DOCTYPE html>
|
|
56
102
|
<html>
|
|
@@ -77,14 +123,24 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
77
123
|
|
|
78
124
|
details { background: var(--card); border:1px solid var(--border); border-radius:10px; margin:10px 0; }
|
|
79
125
|
summary { cursor:pointer; padding:12px 14px; display:flex; align-items:center; gap:12px; }
|
|
80
|
-
.mission > summary { font-weight:
|
|
126
|
+
.mission-group > summary { font-weight:700; }
|
|
127
|
+
.mission-submission > summary { font-weight:600; }
|
|
128
|
+
/* Better contrast on summaries */
|
|
129
|
+
.mission-submission > summary { background:#f3f4f6; }
|
|
130
|
+
.mission-submission > summary:hover { background:#e5e7eb; }
|
|
131
|
+
.mission-group > summary:hover { background:#f9fafb; }
|
|
132
|
+
/* Focus ring */
|
|
133
|
+
summary:focus-visible { outline: 2px solid #94a3b8; outline-offset: 2px; border-radius: 8px; }
|
|
134
|
+
/* Disclosure caret */
|
|
135
|
+
summary::marker { color: var(--muted); }
|
|
136
|
+
.group-body { padding: 0 10px 10px; }
|
|
81
137
|
.name { flex:1; }
|
|
82
138
|
.status.ok { color: var(--ok); }
|
|
83
139
|
.status.bad { color: var(--bad); }
|
|
84
140
|
.meta { color: var(--muted); font-size:12px; }
|
|
85
141
|
|
|
86
142
|
.steps { padding: 0 14px 12px; }
|
|
87
|
-
.step summary { background: #
|
|
143
|
+
.step summary { background: #f8fafc; border-bottom:1px solid var(--border); border-radius: 10px 10px 0 0; }
|
|
88
144
|
.turn { font-weight:600; }
|
|
89
145
|
.step-result.ok { color: var(--ok); }
|
|
90
146
|
.step-result.bad { color: var(--bad); }
|
|
@@ -103,12 +159,24 @@ export function generateHtmlReport(report, outputPath) {
|
|
|
103
159
|
</div>
|
|
104
160
|
|
|
105
161
|
<div class="summary">
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
162
|
+
${(() => {
|
|
163
|
+
const groupNames = Object.keys(grouped);
|
|
164
|
+
const totals = groupNames.reduce((acc, name) => {
|
|
165
|
+
const st = groupStatus(grouped[name]);
|
|
166
|
+
acc.total += 1;
|
|
167
|
+
acc.passed += (st === 'passed') ? 1 : 0;
|
|
168
|
+
acc.failed += (st === 'failed') ? 1 : 0;
|
|
169
|
+
return acc;
|
|
170
|
+
}, { total: 0, passed: 0, failed: 0 });
|
|
171
|
+
return `
|
|
172
|
+
<div class="pill">Missions: ${esc(summary.totalMissions ?? totals.total)}</div>
|
|
173
|
+
<div class="pill ok">Passed: ${esc(summary.passed ?? totals.passed)}</div>
|
|
174
|
+
<div class="pill bad">Failed: ${esc(summary.failed ?? totals.failed)}</div>
|
|
175
|
+
`;
|
|
176
|
+
})()}
|
|
109
177
|
</div>
|
|
110
178
|
|
|
111
|
-
${
|
|
179
|
+
${Object.entries(grouped).map(([name, subs]) => missionGroupBlock(name, subs)).join('')}
|
|
112
180
|
|
|
113
181
|
</body>
|
|
114
182
|
</html>
|