testeranto 0.158.1 → 0.160.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/notify.sh ADDED
@@ -0,0 +1,47 @@
1
+ #!/bin/zsh
2
+
3
+ # NOTIFICATION SYSTEM DOCUMENTATION
4
+ # ================================
5
+ # This script plays system sounds for CI/CD notifications. The AI assistant uses it to:
6
+ # 1. Alert when human input is needed
7
+ # 2. Signal completion of tasks
8
+ # 3. Indicate errors/warnings
9
+
10
+ # USAGE:
11
+ # ./notify.sh [sound] [volume]
12
+ # sound: Any from the list below (default: Ping)
13
+ # volume: 0-1 (default: 1)
14
+
15
+ # AVAILABLE SOUNDS:
16
+ # Basso - Low pitch error sound (use for critical failures)
17
+ # Blow - Quick negative sound
18
+ # Bottle - Light glass ping
19
+ # Frog - Unique attention-grabber
20
+ # Funk - Positive completion sound
21
+ # Glass - Clean success notification
22
+ # Hero - Triumphant completion
23
+ # Morse - For process-related events
24
+ # Ping - Default neutral notification
25
+ # Pop - Light positive sound
26
+ # Purr - Subtle background notification
27
+ # Sosumi - Urgent alert sound
28
+ # Submarine- Deep warning sound
29
+ # Tink - High-pitched attention sound
30
+
31
+ # STANDARD USAGE PATTERNS:
32
+ # [ACTION NEEDED] -> ./notify.sh Sosumi 0.8
33
+ # [COMPLETED] -> ./notify.sh Glass 0.5
34
+ # [ERROR] -> ./notify.sh Basso 1
35
+ # [WARNING] -> ./notify.sh Tink 0.6
36
+ # [WORKING] -> ./notify.sh Morse 0.3
37
+
38
+ SOUND=${1:-Ping}
39
+ VOLUME=${2:-1} # Range: 0 (silent) to 1 (full volume)
40
+
41
+ # Play the specified system sound
42
+ afplay -v $VOLUME /System/Library/Sounds/$SOUND.aiff
43
+
44
+ # Exit codes:
45
+ # 0 - Success
46
+ # 1 - Invalid sound specified
47
+ # 2 - Volume out of range
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "testeranto",
3
3
  "description": "the AI powered BDD test framework for typescript projects",
4
- "version": "0.158.1",
4
+ "version": "0.160.0",
5
5
  "engines": {
6
6
  "node": "18.18.0"
7
7
  },
@@ -212,4 +212,4 @@
212
212
  "url": "^0.11.4",
213
213
  "uuid": "^10.0.0"
214
214
  }
215
- }
215
+ }
package/src/Init.ts CHANGED
@@ -6,11 +6,11 @@ export default async () => {
6
6
  `testeranto/`,
7
7
  `testeranto/bundles/`,
8
8
  `testeranto/bundles/node`,
9
- `testeranto/bundles/pure`,
10
9
  `testeranto/bundles/web`,
11
- `testeranto/externalTests/`,
12
- `testeranto/features/`,
10
+ `testeranto/bundles/pure`,
13
11
  `testeranto/reports/`,
12
+ `testeranto/features/`,
13
+ `testeranto/externalTests/`,
14
14
  ].forEach((f) => {
15
15
  try {
16
16
  fs.mkdirSync(`${process.cwd()}/${f}`);
@@ -1,5 +1,185 @@
1
1
  import React, { useEffect, useState } from 'react';
2
- import { Navbar, Nav, Tab, Container, Alert, Badge, Table, Button } from 'react-bootstrap';
2
+ import { Tab } from 'react-bootstrap';
3
+ import { Card, ListGroup, Badge } from 'react-bootstrap';
4
+
5
+ const BuildLogViewer = ({ logs, runtime }: { logs: any, runtime: string }) => {
6
+ if (!logs) return <Alert variant="info">Loading {runtime.toLowerCase()} build logs...</Alert>;
7
+
8
+ const hasErrors = logs.errors?.length > 0;
9
+ const hasWarnings = logs.warnings?.length > 0;
10
+ const [activeTab, setActiveTab] = useState('summary');
11
+
12
+ return (
13
+ <div>
14
+ <Tab.Container activeKey={activeTab} onSelect={(k) => setActiveTab(k || 'summary')}>
15
+ <Nav variant="tabs" className="mb-3">
16
+ <Nav.Item>
17
+ <Nav.Link eventKey="summary">
18
+ Build Summary
19
+ </Nav.Link>
20
+ </Nav.Item>
21
+ <Nav.Item>
22
+ <Nav.Link eventKey="warnings">
23
+ {hasWarnings ? `⚠️ Warnings (${logs.warnings.length})` : 'Warnings'}
24
+ </Nav.Link>
25
+ </Nav.Item>
26
+ <Nav.Item>
27
+ <Nav.Link eventKey="errors">
28
+ {hasErrors ? `❌ Errors (${logs.errors.length})` : 'Errors'}
29
+ </Nav.Link>
30
+ </Nav.Item>
31
+ </Nav>
32
+
33
+ <Tab.Content>
34
+ <Tab.Pane eventKey="summary">
35
+ <Card>
36
+ <Card.Header className="d-flex justify-content-between align-items-center">
37
+ <h5>Build Summary</h5>
38
+ <div>
39
+ {hasErrors && (
40
+ <Badge bg="danger" className="me-2">
41
+ {logs.errors.length} Error{logs.errors.length !== 1 ? 's' : ''}
42
+ </Badge>
43
+ )}
44
+ {hasWarnings && (
45
+ <Badge bg="warning" text="dark">
46
+ {logs.warnings.length} Warning{logs.warnings.length !== 1 ? 's' : ''}
47
+ </Badge>
48
+ )}
49
+ {!hasErrors && !hasWarnings && (
50
+ <Badge bg="success">Build Successful</Badge>
51
+ )}
52
+ </div>
53
+ </Card.Header>
54
+ <Card.Body>
55
+ <div className="mb-3">
56
+ <h6>Input Files ({Object.keys(logs.metafile?.inputs || {}).length})</h6>
57
+ <ListGroup className="max-h-200 overflow-auto">
58
+ {Object.keys(logs.metafile?.inputs || {}).map((file) => (
59
+ <ListGroup.Item key={file} className="py-2">
60
+ <code>{file}</code>
61
+ <div className="text-muted small">
62
+ {logs.metafile.inputs[file].bytes} bytes
63
+ </div>
64
+ </ListGroup.Item>
65
+ ))}
66
+ </ListGroup>
67
+ </div>
68
+ <div>
69
+ <h6>Output Files ({Object.keys(logs.metafile?.outputs || {}).length})</h6>
70
+ <ListGroup className="max-h-200 overflow-auto">
71
+ {Object.keys(logs.metafile?.outputs || {}).map((file) => (
72
+ <ListGroup.Item key={file} className="py-2">
73
+ <code>{file}</code>
74
+ <div className="text-muted small">
75
+ {logs.metafile.outputs[file].bytes} bytes
76
+ {logs.metafile.outputs[file].entryPoint && (
77
+ <span className="ms-2 badge bg-info">Entry Point</span>
78
+ )}
79
+ </div>
80
+ </ListGroup.Item>
81
+ ))}
82
+ </ListGroup>
83
+ </div>
84
+ </Card.Body>
85
+ </Card>
86
+
87
+ </Tab.Pane>
88
+ <Tab.Pane eventKey="warnings">
89
+ {hasWarnings ? (
90
+ <Card className="border-warning">
91
+ <Card.Header className="bg-warning text-white d-flex justify-content-between align-items-center">
92
+ <span>Build Warnings ({logs.warnings.length})</span>
93
+ <Badge bg="light" text="dark">
94
+ {new Date().toLocaleString()}
95
+ </Badge>
96
+ </Card.Header>
97
+ <Card.Body className="p-0">
98
+ <ListGroup variant="flush">
99
+ {logs.warnings.map((warn: any, i: number) => (
100
+ <ListGroup.Item key={i} className="text-warning">
101
+ <div className="d-flex justify-content-between">
102
+ <strong>
103
+ {warn.location?.file || 'Unknown file'}
104
+ {warn.location?.line && `:${warn.location.line}`}
105
+ </strong>
106
+ <small className="text-muted">
107
+ {warn.pluginName ? `[${warn.pluginName}]` : ''}
108
+ </small>
109
+ </div>
110
+ <div className="mt-1">
111
+ <pre className="mb-0 p-2 bg-light rounded">
112
+ {warn.text || warn.message || JSON.stringify(warn)}
113
+ </pre>
114
+ </div>
115
+ {warn.detail && (
116
+ <div className="mt-1 small text-muted">
117
+ <pre className="mb-0 p-2 bg-light rounded">
118
+ {warn.detail}
119
+ </pre>
120
+ </div>
121
+ )}
122
+ </ListGroup.Item>
123
+ ))}
124
+ </ListGroup>
125
+ </Card.Body>
126
+ </Card>
127
+ ) : (
128
+ <Alert variant="info">No warnings found</Alert>
129
+ )}
130
+ </Tab.Pane>
131
+ <Tab.Pane eventKey="errors">
132
+ {hasErrors ? (
133
+ <Card className="border-danger">
134
+ <Card.Header className="bg-danger text-white d-flex justify-content-between align-items-center">
135
+ <span>Build Errors ({logs.errors.length})</span>
136
+ <Badge bg="light" text="dark">
137
+ {new Date().toLocaleString()}
138
+ </Badge>
139
+ </Card.Header>
140
+ <Card.Body className="p-0">
141
+ <ListGroup variant="flush">
142
+ {logs.errors.map((err: any, i: number) => (
143
+ <ListGroup.Item key={i} className="text-danger">
144
+ <div className="d-flex justify-content-between">
145
+ <strong>
146
+ {err.location?.file || 'Unknown file'}
147
+ {err.location?.line && `:${err.location.line}`}
148
+ </strong>
149
+ <small className="text-muted">
150
+ {err.pluginName ? `[${err.pluginName}]` : ''}
151
+ </small>
152
+ </div>
153
+ <div className="mt-1">
154
+ <pre className="mb-0 p-2 bg-light rounded">
155
+ {err.text || err.message || JSON.stringify(err)}
156
+ </pre>
157
+ </div>
158
+ {err.detail && (
159
+ <div className="mt-1 small text-muted">
160
+ <pre className="mb-0 p-2 bg-light rounded">
161
+ {err.detail}
162
+ </pre>
163
+ </div>
164
+ )}
165
+ </ListGroup.Item>
166
+ ))}
167
+ </ListGroup>
168
+ </Card.Body>
169
+ </Card>
170
+ ) : (
171
+ <Alert variant="success">
172
+ <h5>No Errors Found</h5>
173
+ <p className="mb-0">The build completed without any errors.</p>
174
+ </Alert>
175
+ )}
176
+ </Tab.Pane>
177
+ </Tab.Content>
178
+ </Tab.Container>
179
+ </div>
180
+ );
181
+ };
182
+ import { Navbar, Nav, Tab, Container, Alert, Badge, Table, Button, Card } from 'react-bootstrap';
3
183
  import { useParams, useNavigate, useLocation, Link } from 'react-router-dom';
4
184
 
5
185
  import { ISummary } from './Types';
@@ -95,24 +275,37 @@ export const ProjectPage = () => {
95
275
  title={projectName}
96
276
  backLink="/"
97
277
  navItems={[
98
- { to: `#tests`, label: 'Tests', active: route === 'tests' },
278
+ {
279
+ to: `#tests`,
280
+ label: Object.values(summary).some(t => t.runTimeErrors > 0) ? '❌ Tests' :
281
+ Object.values(summary).some(t => t.typeErrors > 0 || t.staticErrors > 0) ? '⚠️ Tests' : '✅ Tests',
282
+ active: route === 'tests',
283
+ className: Object.values(summary).some(t => t.runTimeErrors > 0) ? 'text-danger fw-bold' :
284
+ Object.values(summary).some(t => t.typeErrors > 0 || t.staticErrors > 0) ? 'text-warning fw-bold' : ''
285
+ },
99
286
  {
100
287
  to: `#node`,
101
- label: nodeLogs?.errors?.length ? '❌ Node Build' : '✅ Node Build',
288
+ label: nodeLogs?.errors?.length ? '❌ Node Build' :
289
+ nodeLogs?.warnings?.length ? '⚠️ Node Build' : 'Node Build',
102
290
  active: route === 'node',
103
- className: nodeLogs?.errors?.length ? 'text-danger fw-bold' : 'text-success fw-bold'
291
+ className: nodeLogs?.errors?.length ? 'text-danger fw-bold' :
292
+ nodeLogs?.warnings?.length ? 'text-warning fw-bold' : ''
104
293
  },
105
294
  {
106
295
  to: `#web`,
107
- label: webLogs?.errors?.length ? '❌ Web Build' : '✅ Web Build',
296
+ label: webLogs?.errors?.length ? '❌ Web Build' :
297
+ webLogs?.warnings?.length ? '⚠️ Web Build' : 'Web Build',
108
298
  active: route === 'web',
109
- className: webLogs?.errors?.length ? 'text-danger fw-bold' : 'text-success fw-bold'
299
+ className: webLogs?.errors?.length ? 'text-danger fw-bold' :
300
+ webLogs?.warnings?.length ? 'text-warning fw-bold' : ''
110
301
  },
111
302
  {
112
303
  to: `#pure`,
113
- label: pureLogs?.errors?.length ? '❌ Pure Build' : '✅ Pure Build',
304
+ label: pureLogs?.errors?.length ? '❌ Pure Build' :
305
+ pureLogs?.warnings?.length ? '⚠️ Pure Build' : 'Pure Build',
114
306
  active: route === 'pure',
115
- className: pureLogs?.errors?.length ? 'text-danger fw-bold' : 'text-success fw-bold'
307
+ className: pureLogs?.errors?.length ? 'text-danger fw-bold' :
308
+ pureLogs?.warnings?.length ? 'text-warning fw-bold' : ''
116
309
  },
117
310
  ]}
118
311
  />
@@ -129,7 +322,6 @@ export const ProjectPage = () => {
129
322
  <thead>
130
323
  <tr>
131
324
  <th>Test</th>
132
- <th>Build logs</th>
133
325
  <th>BDD Errors</th>
134
326
  <th>Type Errors</th>
135
327
  <th>Lint Errors</th>
@@ -147,11 +339,6 @@ export const ProjectPage = () => {
147
339
  {testName}
148
340
  </a>
149
341
  </td>
150
- <td>
151
- <a href={`#/projects/${projectName}#${runTime}`}>
152
- {runTime} {testData.runTimeErrors === 0 ? '✅' : '❌'}
153
- </a>
154
- </td>
155
342
  <td>
156
343
  <a href={`#/projects/${projectName}/tests/${encodeURIComponent(testName)}/${runTime}#results`}>
157
344
  {testData.runTimeErrors === 0 ? '✅ Passed' :
@@ -176,34 +363,13 @@ export const ProjectPage = () => {
176
363
  </Table>
177
364
  </Tab.Pane>
178
365
  <Tab.Pane eventKey="node">
179
- <ul>
180
- {nodeLogs.errors.map((err, i) => (
181
- <li key={i}>{err.text || err.message || JSON.stringify(err)}</li>
182
- ))}
183
- </ul>
184
- <pre className="bg-dark text-white p-3">
185
- {nodeLogs ? JSON.stringify(nodeLogs, null, 2) : 'Loading node build logs...'}
186
- </pre>
366
+ <BuildLogViewer logs={nodeLogs} runtime="Node" />
187
367
  </Tab.Pane>
188
368
  <Tab.Pane eventKey="web">
189
- <ul>
190
- {webLogs.errors.map((err, i) => (
191
- <li key={i}>{err.text || err.message || JSON.stringify(err)}</li>
192
- ))}
193
- </ul>
194
- <pre className="bg-dark text-white p-3">
195
- {webLogs ? JSON.stringify(webLogs, null, 2) : 'Loading web build logs...'}
196
- </pre>
369
+ <BuildLogViewer logs={webLogs} runtime="Web" />
197
370
  </Tab.Pane>
198
371
  <Tab.Pane eventKey="pure">
199
- <ul>
200
- {pureLogs.errors.map((err, i) => (
201
- <li key={i}>{err.text || err.message || JSON.stringify(err)}</li>
202
- ))}
203
- </ul>
204
- <pre className="bg-dark text-white p-3">
205
- {pureLogs ? JSON.stringify(pureLogs, null, 2) : 'Loading pure build logs...'}
206
- </pre>
372
+ <BuildLogViewer logs={pureLogs} runtime="Pure" />
207
373
  </Tab.Pane>
208
374
  </Tab.Content>
209
375
  </Tab.Container>
@@ -54,9 +54,9 @@ export const ProjectsPage = () => {
54
54
  return {
55
55
  name,
56
56
  testCount: Object.keys(summary).length,
57
- nodeStatus: nodeData.errors?.length ? 'failed' : 'success',
58
- webStatus: webData.errors?.length ? 'failed' : 'success',
59
- pureStatus: pureData.errors?.length ? 'failed' : 'success',
57
+ nodeStatus: nodeData.errors?.length ? 'failed' : nodeData.warnings?.length ? 'warning' : 'success',
58
+ webStatus: webData.errors?.length ? 'failed' : webData.warnings?.length ? 'warning' : 'success',
59
+ pureStatus: pureData.errors?.length ? 'failed' : pureData.warnings?.length ? 'warning' : 'success',
60
60
  config: Object.keys(configData).length,
61
61
  };
62
62
  })
@@ -77,6 +77,7 @@ export const ProjectsPage = () => {
77
77
  switch (status) {
78
78
  case 'success': return '✅';
79
79
  case 'failed': return '❌';
80
+ case 'warning': return '⚠️';
80
81
  default: return '❓';
81
82
  }
82
83
  };
@@ -112,11 +113,17 @@ export const ProjectsPage = () => {
112
113
  <div style={{ maxHeight: '200px', overflowY: 'auto' }}>
113
114
  {summaries[project.name] ? (
114
115
  Object.keys(summaries[project.name]).map(testName => {
116
+ const testData = summaries[project.name][testName];
115
117
  const runTime = configs[project.name].tests.find((t) => t[0] === testName)[1];
118
+ const hasRuntimeErrors = testData.runTimeErrors > 0;
119
+ const hasStaticErrors = testData.typeErrors > 0 || testData.staticErrors > 0;
116
120
 
117
121
  return (
118
122
  <div key={testName}>
119
- <a href={`#/projects/${project.name}/tests/${encodeURIComponent(testName)}/${runTime}`}>
123
+ <a
124
+ href={`#/projects/${project.name}/tests/${encodeURIComponent(testName)}/${runTime}`}
125
+ >
126
+ {hasRuntimeErrors ? '❌ ' : hasStaticErrors ? '⚠️ ' : ''}
120
127
  {testName.split('/').pop()}
121
128
  </a>
122
129
  </div>
@@ -128,27 +135,24 @@ export const ProjectsPage = () => {
128
135
  </div>
129
136
  </td>
130
137
  <td>
131
- <a href={`#/projects/${project.name}#node`}>
132
- {getStatusIcon(project.nodeStatus)}
133
- {project.nodeStatus === 'failed' && (
134
- <Badge bg="danger" className="ms-2">Failed</Badge>
135
- )}
138
+ <a
139
+ href={`#/projects/${project.name}#node`}
140
+ >
141
+ {getStatusIcon(project.nodeStatus)} Node build logs
136
142
  </a>
137
143
  </td>
138
144
  <td>
139
- <a href={`#/projects/${project.name}#web`}>
140
- {getStatusIcon(project.webStatus)}
141
- {project.webStatus === 'failed' && (
142
- <Badge bg="danger" className="ms-2">Failed</Badge>
143
- )}
145
+ <a
146
+ href={`#/projects/${project.name}#web`}
147
+ >
148
+ {getStatusIcon(project.webStatus)} Web build logs
144
149
  </a>
145
150
  </td>
146
151
  <td>
147
- <a href={`#/projects/${project.name}#pure`}>
148
- {getStatusIcon(project.pureStatus)}
149
- {project.pureStatus === 'failed' && (
150
- <Badge bg="danger" className="ms-2">Failed</Badge>
151
- )}
152
+ <a
153
+ href={`#/projects/${project.name}#pure`}
154
+ >
155
+ {getStatusIcon(project.pureStatus)} Pure build logs
152
156
  </a>
153
157
  </td>
154
158
  </tr>
package/src/TestPage.tsx CHANGED
@@ -43,6 +43,13 @@ export const TestPage = () => {
43
43
  const [lintErrors, setLintErrors] = useState<string>('');
44
44
  const [loading, setLoading] = useState(true);
45
45
  const [error, setError] = useState<string | null>(null);
46
+ const [testsExist, setTestsExist] = useState<boolean>(true);
47
+ const [errorCounts, setErrorCounts] = useState({
48
+ typeErrors: 0,
49
+ staticErrors: 0,
50
+ runTimeErrors: 0
51
+ });
52
+ const [summary, setSummary] = useState<any>(null);
46
53
 
47
54
  const { projectName, '*': splat } = useParams();
48
55
  const pathParts = splat ? splat.split('/') : [];
@@ -57,13 +64,36 @@ export const TestPage = () => {
57
64
 
58
65
  const fetchData = async () => {
59
66
  try {
60
- const { testData, logs, typeErrors, lintErrors } = await fetchTestData(projectName, testPath, runtime);
61
- setTestData(testData);
62
- setLogs(logs);
63
- setTypeErrors(typeErrors);
64
- setLintErrors(lintErrors);
67
+ // First fetch test data
68
+ const testResponse = await fetchTestData(projectName, testPath, runtime);
69
+ setTestData(testResponse.testData);
70
+ setTestsExist(!!testResponse.testData);
71
+ setLogs(testResponse.logs);
72
+ setTypeErrors(testResponse.typeErrors);
73
+ setLintErrors(testResponse.lintErrors);
74
+
75
+ // Then fetch summary.json
76
+ try {
77
+ const summaryResponse = await fetch(`reports/${projectName}/summary.json`);
78
+ if (!summaryResponse.ok) throw new Error('Failed to fetch summary');
79
+ const allSummaries = await summaryResponse.json();
80
+ const testSummary = allSummaries[testPath];
81
+
82
+ console.log("testSummary", testSummary)
83
+ if (testSummary) {
84
+ setSummary(testSummary);
85
+ setErrorCounts({
86
+ typeErrors: testSummary.typeErrors || 0,
87
+ staticErrors: testSummary.staticErrors || 0,
88
+ runTimeErrors: testSummary.runTimeErrors || 0
89
+ });
90
+ }
91
+ } catch (err) {
92
+ console.error('Failed to load summary:', err);
93
+ }
65
94
  } catch (err) {
66
95
  setError(err instanceof Error ? err.message : 'Unknown error');
96
+ setTestsExist(false);
67
97
  } finally {
68
98
  setLoading(false);
69
99
  }
@@ -83,30 +113,30 @@ export const TestPage = () => {
83
113
  navItems={[
84
114
  {
85
115
  to: `#results`,
86
- label: testData?.givens.some(g => g.whens.some(w => w.error) || g.thens.some(t => t.error))
116
+ label: !testsExist
87
117
  ? '❌ BDD'
88
- : '✅ BDD',
118
+ : testData?.givens.some(g => g.whens.some(w => w.error) || g.thens.some(t => t.error))
119
+ ? '❌ BDD'
120
+ : '✅ BDD',
89
121
  active: route === 'results'
90
122
  },
91
123
  {
92
124
  to: `#logs`,
93
- label: logs?.includes('error') || logs?.includes('fail')
94
- ? '❌ Logs'
95
- : '✅ Logs',
125
+ label: `Runtime logs`,
96
126
  active: route === 'logs'
97
127
  },
98
128
  {
99
129
  to: `#types`,
100
- label: typeErrors
101
- ? `❌ ${typeErrors.split('\n').filter(l => l.includes('error')).length} Type Errors`
102
- : '✅ Type check',
130
+ label: errorCounts.typeErrors > 0
131
+ ? `tsc ( * ${errorCounts.typeErrors})`
132
+ : 'tsc ✅ ',
103
133
  active: route === 'types'
104
134
  },
105
135
  {
106
136
  to: `#lint`,
107
- label: lintErrors
108
- ? `❌ ${lintErrors.split('\n').filter(l => l.includes('error')).length} Lint Errors`
109
- : '✅ Lint',
137
+ label: errorCounts.staticErrors > 0
138
+ ? `eslint ( *${errorCounts.staticErrors}) `
139
+ : 'eslint ✅',
110
140
  active: route === 'lint'
111
141
  },
112
142
 
@@ -114,7 +144,18 @@ export const TestPage = () => {
114
144
  rightContent={
115
145
  <Button
116
146
  variant="info"
117
- onClick={() => alert("Magic robot activated!")}
147
+ onClick={async () => {
148
+ try {
149
+ const promptPath = `testeranto/reports/${projectName}/${testPath.split('.').slice(0, -1).join('.')}/${runtime}/prompt.txt`;
150
+ const messagePath = `testeranto/reports/${projectName}/${testPath.split('.').slice(0, -1).join('.')}/${runtime}/message.txt`;
151
+ const command = `aider --load ${promptPath} --message-file ${messagePath}`;
152
+ await navigator.clipboard.writeText(command);
153
+ alert("Copied aider command to clipboard!");
154
+ } catch (err) {
155
+ alert("Failed to copy command to clipboard");
156
+ console.error("Copy failed:", err);
157
+ }
158
+ }}
118
159
  className="ms-2"
119
160
  >
120
161
  🤖
@@ -132,7 +173,27 @@ export const TestPage = () => {
132
173
 
133
174
  <Tab.Content className="mt-3">
134
175
  <Tab.Pane eventKey="results">
135
- {testData ? (
176
+ {!testsExist ? (
177
+ <Alert variant="danger" className="mt-3">
178
+ <h4>Tests did not run to completion</h4>
179
+ <p>The test results file (tests.json) was not found or could not be loaded.</p>
180
+ <div className="mt-3">
181
+ <Button
182
+ variant="outline-light"
183
+ onClick={() => setRoute('logs')}
184
+ className="me-2"
185
+ >
186
+ View Runtime Logs
187
+ </Button>
188
+ <Button
189
+ variant="outline-light"
190
+ onClick={() => navigate(`/projects/${projectName}#${runtime}`)}
191
+ >
192
+ View Build Logs
193
+ </Button>
194
+ </div>
195
+ </Alert>
196
+ ) : testData ? (
136
197
  <div className="test-results">
137
198
  <div className="mb-3">
138
199