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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testronaut",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "main": "./runner/testronaut.js",
5
5
  "bin": {
6
6
  "testronaut": "./bin/cli.js"
@@ -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(...pre.map(fn => ({ goal: fn, label: 'pre-mission' })));
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(...main.map(fn => ({ goal: fn, label: 'mission' })));
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(...post.map(fn => ({ goal: fn, label: 'post-mission' })));
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">${esc(m.missionName || path.basename(m.file || `mission_${idx+1}`))}</span>
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 • file: ${esc(m.file || '')}</span>
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:600; }
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: #fafafa; border-bottom:1px solid var(--border); border-radius: 10px 10px 0 0; }
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
- <div class="pill">Missions: ${esc(summary.totalMissions ?? missions.length)}</div>
107
- <div class="pill ok">Passed: ${esc(summary.passed ?? missions.filter(m=>m.status==='passed').length)}</div>
108
- <div class="pill bad">Failed: ${esc(summary.failed ?? missions.filter(m=>m.status==='failed').length)}</div>
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
- ${missions.map(missionBlock).join('')}
179
+ ${Object.entries(grouped).map(([name, subs]) => missionGroupBlock(name, subs)).join('')}
112
180
 
113
181
  </body>
114
182
  </html>