mcp-office-tools/reports/test_dashboard.html
Ryan Malloy 35869b6099
Some checks are pending
Test Dashboard / test-and-dashboard (push) Waiting to run
Add behind-the-scenes link to discernment blog post
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.
2026-01-11 02:02:34 -07:00

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>