Some checks are pending
Test Dashboard / test-and-dashboard (push) Waiting to run
Links README to Ryan's AI discernment article, which discusses the documentation rewrite process and connects to the model's perspective in the collaborations archive.
964 lines
35 KiB
HTML
964 lines
35 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MCP Office Tools - Test Dashboard</title>
|
|
<style>
|
|
/* Microsoft Office Color Palette */
|
|
:root {
|
|
/* Office App Colors */
|
|
--word-blue: #2B579A;
|
|
--excel-green: #217346;
|
|
--powerpoint-orange: #D24726;
|
|
--outlook-blue: #0078D4;
|
|
|
|
/* Fluent Design Colors */
|
|
--primary-blue: #0078D4;
|
|
--success-green: #107C10;
|
|
--warning-orange: #FF8C00;
|
|
--error-red: #D83B01;
|
|
--neutral-gray: #605E5C;
|
|
--light-gray: #F3F2F1;
|
|
--lighter-gray: #FAF9F8;
|
|
--border-gray: #E1DFDD;
|
|
|
|
/* Status Colors */
|
|
--pass-green: #107C10;
|
|
--fail-red: #D83B01;
|
|
--skip-yellow: #FFB900;
|
|
|
|
/* Backgrounds */
|
|
--bg-primary: #FFFFFF;
|
|
--bg-secondary: #FAF9F8;
|
|
--bg-tertiary: #F3F2F1;
|
|
|
|
/* Text */
|
|
--text-primary: #201F1E;
|
|
--text-secondary: #605E5C;
|
|
--text-light: #8A8886;
|
|
}
|
|
|
|
/* Reset and Base Styles */
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
|
|
background: var(--bg-secondary);
|
|
color: var(--text-primary);
|
|
line-height: 1.6;
|
|
}
|
|
|
|
/* Header */
|
|
.header {
|
|
background: linear-gradient(135deg, var(--primary-blue) 0%, var(--word-blue) 100%);
|
|
color: white;
|
|
padding: 2rem 2rem 3rem;
|
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.header-content {
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 2rem;
|
|
font-weight: 600;
|
|
margin-bottom: 0.5rem;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.office-icons {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.office-icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 700;
|
|
font-size: 16px;
|
|
color: white;
|
|
}
|
|
|
|
.icon-word { background: var(--word-blue); }
|
|
.icon-excel { background: var(--excel-green); }
|
|
.icon-powerpoint { background: var(--powerpoint-orange); }
|
|
|
|
.header-meta {
|
|
opacity: 0.9;
|
|
font-size: 0.9rem;
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
/* Main Container */
|
|
.container {
|
|
max-width: 1400px;
|
|
margin: -2rem auto 2rem;
|
|
padding: 0 2rem;
|
|
}
|
|
|
|
/* Summary Cards */
|
|
.summary-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 1.5rem;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.summary-card {
|
|
background: var(--bg-primary);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
border: 1px solid var(--border-gray);
|
|
transition: transform 0.2s, box-shadow 0.2s;
|
|
}
|
|
|
|
.summary-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
|
}
|
|
|
|
.card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.card-title {
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.card-value {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
line-height: 1;
|
|
}
|
|
|
|
.card-subtitle {
|
|
font-size: 0.875rem;
|
|
color: var(--text-light);
|
|
margin-top: 0.5rem;
|
|
}
|
|
|
|
.status-badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 12px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
}
|
|
|
|
.badge-pass { background: rgba(16, 124, 16, 0.1); color: var(--pass-green); }
|
|
.badge-fail { background: rgba(216, 59, 1, 0.1); color: var(--fail-red); }
|
|
.badge-skip { background: rgba(255, 185, 0, 0.1); color: var(--skip-yellow); }
|
|
|
|
/* Controls */
|
|
.controls {
|
|
background: var(--bg-primary);
|
|
border-radius: 8px;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
border: 1px solid var(--border-gray);
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
}
|
|
|
|
.search-box {
|
|
flex: 1;
|
|
min-width: 300px;
|
|
position: relative;
|
|
}
|
|
|
|
.search-box input {
|
|
width: 100%;
|
|
padding: 0.75rem 1rem 0.75rem 2.5rem;
|
|
border: 2px solid var(--border-gray);
|
|
border-radius: 4px;
|
|
font-size: 0.875rem;
|
|
font-family: inherit;
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
.search-box input:focus {
|
|
outline: none;
|
|
border-color: var(--primary-blue);
|
|
}
|
|
|
|
.search-icon {
|
|
position: absolute;
|
|
left: 0.875rem;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: var(--text-light);
|
|
}
|
|
|
|
.filter-group {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.filter-btn {
|
|
padding: 0.5rem 1rem;
|
|
border: 2px solid var(--border-gray);
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
border-radius: 4px;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
font-family: inherit;
|
|
}
|
|
|
|
.filter-btn:hover {
|
|
border-color: var(--primary-blue);
|
|
background: var(--lighter-gray);
|
|
}
|
|
|
|
.filter-btn.active {
|
|
background: var(--primary-blue);
|
|
color: white;
|
|
border-color: var(--primary-blue);
|
|
}
|
|
|
|
.filter-btn.word.active { background: var(--word-blue); border-color: var(--word-blue); }
|
|
.filter-btn.excel.active { background: var(--excel-green); border-color: var(--excel-green); }
|
|
.filter-btn.powerpoint.active { background: var(--powerpoint-orange); border-color: var(--powerpoint-orange); }
|
|
|
|
/* Test Results */
|
|
.test-results {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.test-item {
|
|
background: var(--bg-primary);
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
border: 1px solid var(--border-gray);
|
|
overflow: hidden;
|
|
transition: box-shadow 0.2s;
|
|
}
|
|
|
|
.test-item:hover {
|
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
|
}
|
|
|
|
.test-header {
|
|
padding: 1.25rem 1.5rem;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
transition: background 0.2s;
|
|
}
|
|
|
|
.test-header:hover {
|
|
background: var(--lighter-gray);
|
|
}
|
|
|
|
.test-status-icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 700;
|
|
font-size: 14px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.status-pass {
|
|
background: var(--pass-green);
|
|
color: white;
|
|
}
|
|
|
|
.status-fail {
|
|
background: var(--fail-red);
|
|
color: white;
|
|
}
|
|
|
|
.status-skip {
|
|
background: var(--skip-yellow);
|
|
color: white;
|
|
}
|
|
|
|
.test-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.test-name {
|
|
font-weight: 600;
|
|
font-size: 1rem;
|
|
color: var(--text-primary);
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.test-meta {
|
|
font-size: 0.875rem;
|
|
color: var(--text-light);
|
|
display: flex;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.test-category-badge {
|
|
display: inline-block;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 4px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
color: white;
|
|
}
|
|
|
|
.category-word { background: var(--word-blue); }
|
|
.category-excel { background: var(--excel-green); }
|
|
.category-powerpoint { background: var(--powerpoint-orange); }
|
|
.category-universal { background: var(--outlook-blue); }
|
|
.category-server { background: var(--neutral-gray); }
|
|
.category-other { background: var(--text-light); }
|
|
|
|
.test-duration {
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.expand-icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--lighter-gray);
|
|
color: var(--text-secondary);
|
|
transition: transform 0.2s, background 0.2s;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.test-header:hover .expand-icon {
|
|
background: var(--light-gray);
|
|
}
|
|
|
|
.test-item.expanded .expand-icon {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.test-details {
|
|
display: none;
|
|
border-top: 1px solid var(--border-gray);
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
.test-item.expanded .test-details {
|
|
display: block;
|
|
}
|
|
|
|
.details-section {
|
|
padding: 1.5rem;
|
|
border-bottom: 1px solid var(--border-gray);
|
|
}
|
|
|
|
.details-section:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.section-title {
|
|
font-weight: 600;
|
|
font-size: 0.875rem;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: 0.75rem;
|
|
}
|
|
|
|
.code-block {
|
|
background: var(--text-primary);
|
|
color: #D4D4D4;
|
|
padding: 1rem;
|
|
border-radius: 4px;
|
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
font-size: 0.875rem;
|
|
line-height: 1.5;
|
|
overflow-x: auto;
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
}
|
|
|
|
.error-block {
|
|
background: rgba(216, 59, 1, 0.05);
|
|
border-left: 4px solid var(--error-red);
|
|
padding: 1rem;
|
|
border-radius: 4px;
|
|
color: var(--error-red);
|
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
font-size: 0.875rem;
|
|
line-height: 1.5;
|
|
overflow-x: auto;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.inputs-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
gap: 1rem;
|
|
}
|
|
|
|
.input-item {
|
|
background: var(--bg-primary);
|
|
padding: 1rem;
|
|
border-radius: 4px;
|
|
border: 1px solid var(--border-gray);
|
|
}
|
|
|
|
.input-label {
|
|
font-weight: 600;
|
|
font-size: 0.75rem;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.input-value {
|
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
font-size: 0.875rem;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.file-link {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
color: var(--primary-blue);
|
|
text-decoration: none;
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 4px;
|
|
background: rgba(43, 87, 154, 0.1);
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.file-link:hover {
|
|
background: rgba(43, 87, 154, 0.2);
|
|
text-decoration: underline;
|
|
}
|
|
|
|
/* Empty State */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 4rem 2rem;
|
|
color: var(--text-light);
|
|
}
|
|
|
|
.empty-state-icon {
|
|
font-size: 4rem;
|
|
margin-bottom: 1rem;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
/* Footer */
|
|
.footer {
|
|
text-align: center;
|
|
padding: 2rem;
|
|
color: var(--text-light);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
/* Progress Bar */
|
|
.progress-bar {
|
|
width: 100%;
|
|
height: 8px;
|
|
background: var(--light-gray);
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
margin-top: 1rem;
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, var(--success-green) 0%, var(--excel-green) 100%);
|
|
transition: width 0.3s ease;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.container {
|
|
padding: 0 1rem;
|
|
}
|
|
|
|
.header {
|
|
padding: 1.5rem 1rem 2rem;
|
|
}
|
|
|
|
.header h1 {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.summary-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.controls {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.search-box {
|
|
min-width: 100%;
|
|
}
|
|
}
|
|
|
|
/* Utility Classes */
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
|
|
.text-muted {
|
|
color: var(--text-light);
|
|
}
|
|
|
|
.text-success {
|
|
color: var(--pass-green);
|
|
}
|
|
|
|
.text-error {
|
|
color: var(--fail-red);
|
|
}
|
|
|
|
.text-warning {
|
|
color: var(--skip-yellow);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Header -->
|
|
<header class="header">
|
|
<div class="header-content">
|
|
<h1>
|
|
<div class="office-icons">
|
|
<div class="office-icon icon-word">W</div>
|
|
<div class="office-icon icon-excel">X</div>
|
|
<div class="office-icon icon-powerpoint">P</div>
|
|
</div>
|
|
MCP Office Tools - Test Dashboard
|
|
</h1>
|
|
<div class="header-meta">
|
|
<span id="test-timestamp">Loading...</span>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Main Container -->
|
|
<div class="container">
|
|
<!-- Summary Cards -->
|
|
<div class="summary-grid">
|
|
<div class="summary-card">
|
|
<div class="card-header">
|
|
<div class="card-title">Total Tests</div>
|
|
</div>
|
|
<div class="card-value" id="total-tests">0</div>
|
|
<div class="card-subtitle">Test cases executed</div>
|
|
</div>
|
|
|
|
<div class="summary-card">
|
|
<div class="card-header">
|
|
<div class="card-title">Passed</div>
|
|
<span class="status-badge badge-pass">
|
|
<span>✓</span>
|
|
</span>
|
|
</div>
|
|
<div class="card-value text-success" id="passed-tests">0</div>
|
|
<div class="card-subtitle">
|
|
<span id="pass-rate">0%</span> pass rate
|
|
</div>
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" id="pass-progress" style="width: 0%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="summary-card">
|
|
<div class="card-header">
|
|
<div class="card-title">Failed</div>
|
|
<span class="status-badge badge-fail">
|
|
<span>✗</span>
|
|
</span>
|
|
</div>
|
|
<div class="card-value text-error" id="failed-tests">0</div>
|
|
<div class="card-subtitle">Tests with errors</div>
|
|
</div>
|
|
|
|
<div class="summary-card">
|
|
<div class="card-header">
|
|
<div class="card-title">Duration</div>
|
|
</div>
|
|
<div class="card-value" id="total-duration" style="font-size: 2rem;">0s</div>
|
|
<div class="card-subtitle">Total execution time</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Controls -->
|
|
<div class="controls">
|
|
<div class="search-box">
|
|
<span class="search-icon">🔍</span>
|
|
<input
|
|
type="text"
|
|
id="search-input"
|
|
placeholder="Search tests by name, module, or category..."
|
|
autocomplete="off"
|
|
>
|
|
</div>
|
|
<div class="filter-group">
|
|
<button class="filter-btn active" data-filter="all">All</button>
|
|
<button class="filter-btn word" data-filter="Word">Word</button>
|
|
<button class="filter-btn excel" data-filter="Excel">Excel</button>
|
|
<button class="filter-btn powerpoint" data-filter="PowerPoint">PowerPoint</button>
|
|
<button class="filter-btn" data-filter="Universal">Universal</button>
|
|
<button class="filter-btn" data-filter="Server">Server</button>
|
|
</div>
|
|
<div class="filter-group">
|
|
<button class="filter-btn" data-status="passed">Passed</button>
|
|
<button class="filter-btn" data-status="failed">Failed</button>
|
|
<button class="filter-btn" data-status="skipped">Skipped</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Test Results -->
|
|
<div id="test-results" class="test-results">
|
|
<!-- Tests will be dynamically inserted here -->
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div id="empty-state" class="empty-state hidden">
|
|
<div class="empty-state-icon">📭</div>
|
|
<h2>No Test Results Found</h2>
|
|
<p>Run tests with: <code>pytest --dashboard-output=reports/test_results.json</code></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<footer class="footer">
|
|
<p>MCP Office Tools Test Dashboard | Generated with ❤️ using pytest</p>
|
|
</footer>
|
|
|
|
<script>
|
|
// Dashboard Application
|
|
class TestDashboard {
|
|
constructor() {
|
|
this.data = null;
|
|
this.filteredTests = [];
|
|
this.activeFilters = {
|
|
category: 'all',
|
|
status: null,
|
|
search: ''
|
|
};
|
|
|
|
this.init();
|
|
}
|
|
|
|
async init() {
|
|
await this.loadData();
|
|
this.setupEventListeners();
|
|
this.render();
|
|
}
|
|
|
|
async loadData() {
|
|
try {
|
|
// Try embedded data first (works with file:// URLs)
|
|
const embeddedScript = document.getElementById('test-results-data');
|
|
if (embeddedScript) {
|
|
this.data = JSON.parse(embeddedScript.textContent);
|
|
this.filteredTests = this.data.tests;
|
|
return;
|
|
}
|
|
// Fallback to fetch (works with http:// URLs)
|
|
const response = await fetch('test_results.json');
|
|
this.data = await response.json();
|
|
this.filteredTests = this.data.tests;
|
|
} catch (error) {
|
|
console.error('Failed to load test results:', error);
|
|
document.getElementById('empty-state').classList.remove('hidden');
|
|
}
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Search
|
|
document.getElementById('search-input').addEventListener('input', (e) => {
|
|
this.activeFilters.search = e.target.value.toLowerCase();
|
|
this.applyFilters();
|
|
});
|
|
|
|
// Category filters
|
|
document.querySelectorAll('[data-filter]').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
document.querySelectorAll('[data-filter]').forEach(b => b.classList.remove('active'));
|
|
e.target.classList.add('active');
|
|
this.activeFilters.category = e.target.dataset.filter;
|
|
this.applyFilters();
|
|
});
|
|
});
|
|
|
|
// Status filters
|
|
document.querySelectorAll('[data-status]').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('active')) {
|
|
e.target.classList.remove('active');
|
|
this.activeFilters.status = null;
|
|
} else {
|
|
document.querySelectorAll('[data-status]').forEach(b => b.classList.remove('active'));
|
|
e.target.classList.add('active');
|
|
this.activeFilters.status = e.target.dataset.status;
|
|
}
|
|
this.applyFilters();
|
|
});
|
|
});
|
|
}
|
|
|
|
applyFilters() {
|
|
this.filteredTests = this.data.tests.filter(test => {
|
|
// Category filter
|
|
if (this.activeFilters.category !== 'all' && test.category !== this.activeFilters.category) {
|
|
return false;
|
|
}
|
|
|
|
// Status filter
|
|
if (this.activeFilters.status && test.outcome !== this.activeFilters.status) {
|
|
return false;
|
|
}
|
|
|
|
// Search filter
|
|
if (this.activeFilters.search) {
|
|
const searchStr = this.activeFilters.search;
|
|
const matchName = test.name.toLowerCase().includes(searchStr);
|
|
const matchModule = test.module.toLowerCase().includes(searchStr);
|
|
const matchCategory = test.category.toLowerCase().includes(searchStr);
|
|
|
|
if (!matchName && !matchModule && !matchCategory) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
this.renderTests();
|
|
}
|
|
|
|
render() {
|
|
if (!this.data) return;
|
|
|
|
this.renderSummary();
|
|
this.renderTests();
|
|
}
|
|
|
|
renderSummary() {
|
|
const { metadata, summary } = this.data;
|
|
|
|
// Timestamp
|
|
const timestamp = new Date(metadata.start_time).toLocaleString();
|
|
document.getElementById('test-timestamp').textContent = `Run on ${timestamp}`;
|
|
|
|
// Summary cards
|
|
document.getElementById('total-tests').textContent = summary.total;
|
|
document.getElementById('passed-tests').textContent = summary.passed;
|
|
document.getElementById('failed-tests').textContent = summary.failed;
|
|
document.getElementById('pass-rate').textContent = `${summary.pass_rate.toFixed(1)}%`;
|
|
document.getElementById('pass-progress').style.width = `${summary.pass_rate}%`;
|
|
|
|
// Duration
|
|
const duration = metadata.duration.toFixed(2);
|
|
document.getElementById('total-duration').textContent = `${duration}s`;
|
|
}
|
|
|
|
renderTests() {
|
|
const container = document.getElementById('test-results');
|
|
const emptyState = document.getElementById('empty-state');
|
|
|
|
if (this.filteredTests.length === 0) {
|
|
container.innerHTML = '';
|
|
emptyState.classList.remove('hidden');
|
|
return;
|
|
}
|
|
|
|
emptyState.classList.add('hidden');
|
|
|
|
container.innerHTML = this.filteredTests.map(test => this.createTestItem(test)).join('');
|
|
|
|
// Add click handlers for expand/collapse
|
|
container.querySelectorAll('.test-header').forEach(header => {
|
|
header.addEventListener('click', () => {
|
|
header.parentElement.classList.toggle('expanded');
|
|
});
|
|
});
|
|
}
|
|
|
|
createTestItem(test) {
|
|
const statusIcon = this.getStatusIcon(test.outcome);
|
|
const categoryClass = `category-${test.category.toLowerCase()}`;
|
|
const duration = (test.duration * 1000).toFixed(0); // ms
|
|
|
|
return `
|
|
<div class="test-item" data-test-id="${test.nodeid}">
|
|
<div class="test-header">
|
|
<div class="test-status-icon status-${test.outcome}">
|
|
${statusIcon}
|
|
</div>
|
|
<div class="test-info">
|
|
<div class="test-name">${this.escapeHtml(test.name)}</div>
|
|
<div class="test-meta">
|
|
<span class="test-category-badge ${categoryClass}">${test.category}</span>
|
|
<span>${test.module}</span>
|
|
<span class="test-duration">${duration}ms</span>
|
|
</div>
|
|
</div>
|
|
<div class="expand-icon">▼</div>
|
|
</div>
|
|
<div class="test-details">
|
|
${this.createTestDetails(test)}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
createTestDetails(test) {
|
|
let html = '';
|
|
|
|
// Inputs
|
|
if (test.inputs && Object.keys(test.inputs).length > 0) {
|
|
html += `
|
|
<div class="details-section">
|
|
<div class="section-title">Test Inputs</div>
|
|
<div class="inputs-grid">
|
|
${Object.entries(test.inputs).map(([key, value]) => `
|
|
<div class="input-item">
|
|
<div class="input-label">${this.escapeHtml(key)}</div>
|
|
<div class="input-value">${this.formatInputValue(key, value)}</div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Outputs
|
|
if (test.outputs) {
|
|
html += `
|
|
<div class="details-section">
|
|
<div class="section-title">Test Outputs</div>
|
|
<div class="code-block">${this.escapeHtml(JSON.stringify(test.outputs, null, 2))}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Error
|
|
if (test.error) {
|
|
html += `
|
|
<div class="details-section">
|
|
<div class="section-title">Error Details</div>
|
|
<div class="error-block">${this.escapeHtml(test.error)}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Traceback
|
|
if (test.traceback) {
|
|
html += `
|
|
<div class="details-section">
|
|
<div class="section-title">Traceback</div>
|
|
<div class="error-block">${this.escapeHtml(test.traceback)}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Full path
|
|
html += `
|
|
<div class="details-section">
|
|
<div class="section-title">Test Path</div>
|
|
<div class="code-block">${this.escapeHtml(test.nodeid)}</div>
|
|
</div>
|
|
`;
|
|
|
|
return html;
|
|
}
|
|
|
|
getStatusIcon(outcome) {
|
|
switch (outcome) {
|
|
case 'passed': return '✓';
|
|
case 'failed': return '✗';
|
|
case 'skipped': return '⊘';
|
|
default: return '?';
|
|
}
|
|
}
|
|
|
|
formatInputValue(key, value) {
|
|
const strValue = typeof value === 'string' ? value : JSON.stringify(value);
|
|
// Detect file paths - relative (test_files/...) or absolute
|
|
const isRelativePath = strValue.startsWith('test_files/');
|
|
const isAbsolutePath = /^["']?(\/[^"']+|[A-Z]:\\[^"']+)["']?$/i.test(strValue);
|
|
const isFilePath = isRelativePath || isAbsolutePath || key.toLowerCase().includes('file') || key.toLowerCase().includes('path');
|
|
|
|
if (isFilePath && (isRelativePath || isAbsolutePath)) {
|
|
// Extract the actual path (remove quotes if present)
|
|
const cleanPath = strValue.replace(/^["']|["']$/g, '');
|
|
const fileName = cleanPath.split('/').pop() || cleanPath.split('\\').pop();
|
|
const fileExt = fileName.split('.').pop()?.toLowerCase() || '';
|
|
// Choose icon based on file type
|
|
let icon = '📄';
|
|
if (['xlsx', 'xls', 'csv'].includes(fileExt)) icon = '📊';
|
|
else if (['docx', 'doc'].includes(fileExt)) icon = '📝';
|
|
else if (['pptx', 'ppt'].includes(fileExt)) icon = '📽️';
|
|
|
|
// Use relative path for relative files, file:// for absolute paths
|
|
const href = isRelativePath ? this.escapeHtml(cleanPath) : `file://${this.escapeHtml(cleanPath)}`;
|
|
const downloadAttr = isRelativePath ? 'download' : '';
|
|
return `<a href="${href}" class="file-link" title="Download ${this.escapeHtml(fileName)}" ${downloadAttr} target="_blank">${icon} ${this.escapeHtml(fileName)}</a>`;
|
|
}
|
|
return this.escapeHtml(strValue);
|
|
}
|
|
|
|
escapeHtml(text) {
|
|
if (text === null || text === undefined) return '';
|
|
const div = document.createElement('div');
|
|
div.textContent = String(text);
|
|
return div.innerHTML;
|
|
}
|
|
}
|
|
|
|
// Initialize dashboard when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', () => new TestDashboard());
|
|
} else {
|
|
new TestDashboard();
|
|
}
|
|
</script>
|
|
<script type="application/json" id="test-results-data">{"metadata": {"start_time": "2026-01-11T00:28:31.202459", "end_time": "2026-01-11T00:28:33.718606", "duration": 1.2442383766174316, "exit_status": 0, "pytest_version": "9.0.2", "test_types": ["pytest", "torture_test"]}, "summary": {"total": 6, "passed": 5, "failed": 0, "skipped": 1, "pass_rate": 83.33333333333334}, "categories": {"Excel": {"total": 4, "passed": 3, "failed": 0, "skipped": 1}, "Word": {"total": 2, "passed": 2, "failed": 0, "skipped": 0}}, "tests": [{"name": "Excel Data Analysis", "nodeid": "torture_test.py::test_excel_data_analysis", "category": "Excel", "outcome": "passed", "duration": 0.17873024940490723, "timestamp": "2026-01-11T00:28:33.696485", "module": "torture_test", "class": null, "function": "test_excel_data_analysis", "inputs": {"file": "test_files/test_data.xlsx"}, "outputs": {"sheets_analyzed": ["Test Data"]}, "error": null, "traceback": null}, {"name": "Excel Formula Extraction", "nodeid": "torture_test.py::test_excel_formula_extraction", "category": "Excel", "outcome": "passed", "duration": 0.0032067298889160156, "timestamp": "2026-01-11T00:28:33.699697", "module": "torture_test", "class": null, "function": "test_excel_formula_extraction", "inputs": {"file": "test_files/test_data.xlsx"}, "outputs": {"total_formulas": 8}, "error": null, "traceback": null}, {"name": "Excel Chart Data Generation", "nodeid": "torture_test.py::test_excel_chart_generation", "category": "Excel", "outcome": "passed", "duration": 0.0025446414947509766, "timestamp": "2026-01-11T00:28:33.702246", "module": "torture_test", "class": null, "function": "test_excel_chart_generation", "inputs": {"file": "test_files/test_data.xlsx", "x_column": "Category", "y_columns": ["Value"]}, "outputs": {"chart_libraries": 2}, "error": null, "traceback": null}, {"name": "Word Structure Analysis", "nodeid": "torture_test.py::test_word_structure_analysis", "category": "Word", "outcome": "passed", "duration": 0.010314226150512695, "timestamp": "2026-01-11T00:28:33.712565", "module": "torture_test", "class": null, "function": "test_word_structure_analysis", "inputs": {"file": "test_files/test_document.docx"}, "outputs": {"total_headings": 0}, "error": null, "traceback": null}, {"name": "Word Table Extraction", "nodeid": "torture_test.py::test_word_table_extraction", "category": "Word", "outcome": "passed", "duration": 0.005824089050292969, "timestamp": "2026-01-11T00:28:33.718393", "module": "torture_test", "class": null, "function": "test_word_table_extraction", "inputs": {"file": "test_files/test_document.docx"}, "outputs": {"total_tables": 0}, "error": null, "traceback": null}, {"name": "Real Excel File Analysis (FORScan)", "nodeid": "torture_test.py::test_real_excel_analysis", "category": "Excel", "outcome": "skipped", "duration": 0, "timestamp": "2026-01-11T00:28:33.718405", "module": "torture_test", "class": null, "function": "test_real_excel_analysis", "inputs": {"file": "/home/rpm/FORScan Lite spreadsheets v1.1/FORScan Lite spreadsheet - PIDs.xlsx"}, "outputs": null, "error": "File not found: /home/rpm/FORScan Lite spreadsheets v1.1/FORScan Lite spreadsheet - PIDs.xlsx", "traceback": null}]}</script>
|
|
</body>
|
|
</html>
|