+
${escapeHtml(result.name)}
+
+ ${result.status.toUpperCase()}
+ Duration: ${durationText}
+ at ${new Date(result.timestamp).toLocaleTimeString()}
+
+ ${errorHtml}
+
+ `;
+
+ // Add click handler to expand/collapse error details
+ if (hasError) {
+ item.addEventListener('click', function(e) {
+ if (!e.target.closest('.test-result-error a')) {
+ this.classList.toggle('expanded');
+ }
+ });
+ }
+
+ return item;
+}
+
+/**
+ * Get status icon and class based on test status
+ */
+function getStatusInfo(status) {
+ const statusLower = status.toLowerCase();
+
+ const statusMap = {
+ passed: { icon: '✓', statusClass: 'status-passed' },
+ failed: { icon: '✗', statusClass: 'status-failed' },
+ skipped: { icon: '—', statusClass: 'status-skipped' }
+ };
+
+ return statusMap[statusLower] || { icon: '?', statusClass: 'status-unknown' };
+}
+
+/**
+ * Clear all test results
+ */
+async function clearResults() {
+ if (!confirm('Are you sure you want to clear all test results?')) {
+ return;
+ }
+
+ try {
+ const response = await fetch(API_BASE_URL, {
+ method: 'DELETE'
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to clear test results');
+ }
+
+ allResults = [];
+ currentSummary = null;
+ loadResults();
+
+ } catch (error) {
+ console.error('Error clearing test results:', error);
+ alert('Failed to clear test results: ' + error.message);
+ }
+}
+
+/**
+ * Escape HTML special characters
+ */
+function escapeHtml(text) {
+ if (!text) return '';
+ const div = document.createElement('div');
+ div.textContent = text;
+ return div.innerHTML;
+}
+
+/**
+ * Format duration in milliseconds to a readable string
+ */
+function formatDuration(ms) {
+ if (ms < 1000) return `${ms} ms`;
+ return `${(ms / 1000).toFixed(2)} s`;
+}
diff --git a/components/pom.xml b/components/pom.xml
index ee175298d92..f6a2e74655a 100644
--- a/components/pom.xml
+++ b/components/pom.xml
@@ -83,6 +83,7 @@