Fix dependencies and database schema for testing
- Add FastAPI dependency for mock API server - Fix FastMCP import issues with elicitation module - Fix database UUID generation for SQLite compatibility - Update Pydantic settings to allow extra fields from .env - Fix test imports to use correct module paths - Add pytest-asyncio fixtures for proper async testing - Successfully tested all endpoints with mock API
This commit is contained in:
parent
723123a6fe
commit
504189ecfd
835
.claude/agents/testing-framework-html-report-generation.md
Normal file
835
.claude/agents/testing-framework-html-report-generation.md
Normal file
@ -0,0 +1,835 @@
|
|||||||
|
# 🌐 HTML Report Generation Expert - Claude Code Agent
|
||||||
|
|
||||||
|
**Agent Type:** `html-report-generation-expert`
|
||||||
|
**Specialization:** Cross-platform HTML report generation with universal compatibility
|
||||||
|
**Parent Agent:** `testing-framework-architect`
|
||||||
|
**Tools:** `[Read, Write, Edit, Bash, Grep, Glob]`
|
||||||
|
|
||||||
|
## 🎯 Expertise & Specialization
|
||||||
|
|
||||||
|
### Core Competencies
|
||||||
|
- **Universal Protocol Compatibility**: HTML reports that work perfectly with `file://` and `https://` protocols
|
||||||
|
- **Responsive Design**: Beautiful reports on desktop, tablet, and mobile devices
|
||||||
|
- **Terminal Aesthetic Excellence**: Gruvbox, Solarized, Dracula, and custom themes
|
||||||
|
- **Accessibility Standards**: WCAG compliance and screen reader compatibility
|
||||||
|
- **Interactive Components**: Collapsible sections, modals, datatables, copy-to-clipboard
|
||||||
|
- **Performance Optimization**: Fast loading, minimal dependencies, efficient rendering
|
||||||
|
|
||||||
|
### Signature Implementation Style
|
||||||
|
- **Zero External Dependencies**: Self-contained HTML with embedded CSS/JS
|
||||||
|
- **Progressive Enhancement**: Works without JavaScript, enhanced with it
|
||||||
|
- **Cross-Browser Compatibility**: Chrome, Firefox, Safari, Edge support
|
||||||
|
- **Print-Friendly**: Professional PDF generation and print styles
|
||||||
|
- **Offline-First**: No CDN dependencies, works completely offline
|
||||||
|
|
||||||
|
## 🏗️ Universal HTML Report Architecture
|
||||||
|
|
||||||
|
### File Structure for Standalone Reports
|
||||||
|
```
|
||||||
|
📄 HTML Report Structure
|
||||||
|
├── 📝 index.html # Main report with embedded everything
|
||||||
|
├── 🎨 Embedded CSS
|
||||||
|
│ ├── Reset & normalize styles
|
||||||
|
│ ├── Terminal theme variables
|
||||||
|
│ ├── Component styles
|
||||||
|
│ ├── Responsive breakpoints
|
||||||
|
│ └── Print media queries
|
||||||
|
├── ⚡ Embedded JavaScript
|
||||||
|
│ ├── Progressive enhancement
|
||||||
|
│ ├── Interactive components
|
||||||
|
│ ├── Accessibility helpers
|
||||||
|
│ └── Performance optimizations
|
||||||
|
└── 📊 Embedded Data
|
||||||
|
├── Test results JSON
|
||||||
|
├── Quality metrics
|
||||||
|
├── Historical trends
|
||||||
|
└── Metadata
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core HTML Template Pattern
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="MCPlaywright Test Report">
|
||||||
|
<title>{{TEST_NAME}} - MCPlaywright Report</title>
|
||||||
|
|
||||||
|
<!-- Embedded CSS for complete self-containment -->
|
||||||
|
<style>
|
||||||
|
/* CSS Reset for consistent rendering */
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
|
/* Gruvbox Terminal Theme Variables */
|
||||||
|
:root {
|
||||||
|
--gruvbox-dark0: #282828;
|
||||||
|
--gruvbox-dark1: #3c3836;
|
||||||
|
--gruvbox-dark2: #504945;
|
||||||
|
--gruvbox-light0: #ebdbb2;
|
||||||
|
--gruvbox-light1: #d5c4a1;
|
||||||
|
--gruvbox-light4: #928374;
|
||||||
|
--gruvbox-red: #fb4934;
|
||||||
|
--gruvbox-green: #b8bb26;
|
||||||
|
--gruvbox-yellow: #fabd2f;
|
||||||
|
--gruvbox-blue: #83a598;
|
||||||
|
--gruvbox-purple: #d3869b;
|
||||||
|
--gruvbox-aqua: #8ec07c;
|
||||||
|
--gruvbox-orange: #fe8019;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base Styles */
|
||||||
|
body {
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
|
||||||
|
background: var(--gruvbox-dark0);
|
||||||
|
color: var(--gruvbox-light0);
|
||||||
|
line-height: 1.4;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* File:// Protocol Compatibility */
|
||||||
|
.file-protocol-safe {
|
||||||
|
/* Avoid relative paths that break in file:// */
|
||||||
|
background: var(--gruvbox-dark0);
|
||||||
|
/* Use data URLs for any required images */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print Styles */
|
||||||
|
@media print {
|
||||||
|
body { background: white; color: black; }
|
||||||
|
.no-print { display: none; }
|
||||||
|
.print-break { page-break-before: always; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body { font-size: 12px; padding: 0.25rem; }
|
||||||
|
.desktop-only { display: none; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="file-protocol-safe">
|
||||||
|
<!-- Report content with embedded data -->
|
||||||
|
<script type="application/json" id="test-data">
|
||||||
|
{{EMBEDDED_TEST_DATA}}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Progressive Enhancement JavaScript -->
|
||||||
|
<script>
|
||||||
|
// Feature detection and progressive enhancement
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Detect file:// protocol
|
||||||
|
const isFileProtocol = window.location.protocol === 'file:';
|
||||||
|
|
||||||
|
// Enhance functionality based on capabilities
|
||||||
|
if (typeof document !== 'undefined') {
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
initializeInteractiveFeatures();
|
||||||
|
setupAccessibilityFeatures();
|
||||||
|
if (!isFileProtocol) {
|
||||||
|
enableAdvancedFeatures();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 Terminal Theme Implementation
|
||||||
|
|
||||||
|
### Gruvbox Theme System
|
||||||
|
```css
|
||||||
|
/* Gruvbox Dark Theme - Complete Implementation */
|
||||||
|
.theme-gruvbox-dark {
|
||||||
|
--bg-primary: #282828;
|
||||||
|
--bg-secondary: #3c3836;
|
||||||
|
--bg-tertiary: #504945;
|
||||||
|
--border-color: #665c54;
|
||||||
|
--text-primary: #ebdbb2;
|
||||||
|
--text-secondary: #d5c4a1;
|
||||||
|
--text-muted: #928374;
|
||||||
|
--accent-red: #fb4934;
|
||||||
|
--accent-green: #b8bb26;
|
||||||
|
--accent-yellow: #fabd2f;
|
||||||
|
--accent-blue: #83a598;
|
||||||
|
--accent-purple: #d3869b;
|
||||||
|
--accent-aqua: #8ec07c;
|
||||||
|
--accent-orange: #fe8019;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Terminal Window Styling */
|
||||||
|
.terminal-window {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-header {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.terminal-body {
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Vim-style Status Line */
|
||||||
|
.status-line {
|
||||||
|
background: var(--accent-blue);
|
||||||
|
color: var(--bg-primary);
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: bold;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Command Prompt Styling */
|
||||||
|
.command-prompt {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
font-family: inherit;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.command-prompt::before {
|
||||||
|
content: '❯ ';
|
||||||
|
color: var(--accent-orange);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code Block Styling */
|
||||||
|
.code-block {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Syntax Highlighting */
|
||||||
|
.syntax-keyword { color: var(--accent-red); }
|
||||||
|
.syntax-string { color: var(--accent-green); }
|
||||||
|
.syntax-number { color: var(--accent-purple); }
|
||||||
|
.syntax-comment { color: var(--text-muted); font-style: italic; }
|
||||||
|
.syntax-function { color: var(--accent-yellow); }
|
||||||
|
.syntax-variable { color: var(--accent-blue); }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Alternative Theme Support
|
||||||
|
```css
|
||||||
|
/* Solarized Dark Theme */
|
||||||
|
.theme-solarized-dark {
|
||||||
|
--bg-primary: #002b36;
|
||||||
|
--bg-secondary: #073642;
|
||||||
|
--bg-tertiary: #586e75;
|
||||||
|
--text-primary: #839496;
|
||||||
|
--text-secondary: #93a1a1;
|
||||||
|
--accent-blue: #268bd2;
|
||||||
|
--accent-green: #859900;
|
||||||
|
--accent-yellow: #b58900;
|
||||||
|
--accent-orange: #cb4b16;
|
||||||
|
--accent-red: #dc322f;
|
||||||
|
--accent-magenta: #d33682;
|
||||||
|
--accent-violet: #6c71c4;
|
||||||
|
--accent-cyan: #2aa198;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dracula Theme */
|
||||||
|
.theme-dracula {
|
||||||
|
--bg-primary: #282a36;
|
||||||
|
--bg-secondary: #44475a;
|
||||||
|
--text-primary: #f8f8f2;
|
||||||
|
--text-secondary: #6272a4;
|
||||||
|
--accent-purple: #bd93f9;
|
||||||
|
--accent-pink: #ff79c6;
|
||||||
|
--accent-green: #50fa7b;
|
||||||
|
--accent-yellow: #f1fa8c;
|
||||||
|
--accent-orange: #ffb86c;
|
||||||
|
--accent-red: #ff5555;
|
||||||
|
--accent-cyan: #8be9fd;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Universal Compatibility Implementation
|
||||||
|
|
||||||
|
### File:// Protocol Optimization
|
||||||
|
```javascript
|
||||||
|
// File Protocol Compatibility Manager
|
||||||
|
class FileProtocolManager {
|
||||||
|
constructor() {
|
||||||
|
this.isFileProtocol = window.location.protocol === 'file:';
|
||||||
|
this.setupFileProtocolSupport();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupFileProtocolSupport() {
|
||||||
|
if (this.isFileProtocol) {
|
||||||
|
// Disable features that don't work with file://
|
||||||
|
this.disableExternalRequests();
|
||||||
|
this.setupLocalDataHandling();
|
||||||
|
this.enableOfflineFeatures();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
disableExternalRequests() {
|
||||||
|
// Override fetch/XMLHttpRequest for file:// safety
|
||||||
|
const originalFetch = window.fetch;
|
||||||
|
window.fetch = function(url, options) {
|
||||||
|
if (url.startsWith('http')) {
|
||||||
|
console.warn('External requests disabled in file:// mode');
|
||||||
|
return Promise.reject(new Error('External requests not allowed'));
|
||||||
|
}
|
||||||
|
return originalFetch.call(this, url, options);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setupLocalDataHandling() {
|
||||||
|
// All data must be embedded in the HTML
|
||||||
|
const testDataElement = document.getElementById('test-data');
|
||||||
|
if (testDataElement) {
|
||||||
|
try {
|
||||||
|
this.testData = JSON.parse(testDataElement.textContent);
|
||||||
|
this.renderWithEmbeddedData();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to parse embedded test data:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enableOfflineFeatures() {
|
||||||
|
// Enable all features that work offline
|
||||||
|
this.setupLocalStorage();
|
||||||
|
this.enableLocalSearch();
|
||||||
|
this.setupPrintSupport();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cross-Browser Compatibility
|
||||||
|
```javascript
|
||||||
|
// Cross-Browser Compatibility Layer
|
||||||
|
class BrowserCompatibility {
|
||||||
|
static setupPolyfills() {
|
||||||
|
// Polyfill for older browsers
|
||||||
|
if (!Element.prototype.closest) {
|
||||||
|
Element.prototype.closest = function(selector) {
|
||||||
|
let element = this;
|
||||||
|
while (element && element.nodeType === 1) {
|
||||||
|
if (element.matches(selector)) return element;
|
||||||
|
element = element.parentNode;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill for matches()
|
||||||
|
if (!Element.prototype.matches) {
|
||||||
|
Element.prototype.matches = Element.prototype.msMatchesSelector ||
|
||||||
|
Element.prototype.webkitMatchesSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSS Custom Properties fallback
|
||||||
|
if (!window.CSS || !CSS.supports('color', 'var(--primary)')) {
|
||||||
|
this.setupCSSVariableFallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static setupCSSVariableFallback() {
|
||||||
|
// Fallback for browsers without CSS custom property support
|
||||||
|
const fallbackStyles = `
|
||||||
|
.gruvbox-dark { background: #282828; color: #ebdbb2; }
|
||||||
|
.terminal-header { background: #3c3836; }
|
||||||
|
.status-line { background: #83a598; }
|
||||||
|
`;
|
||||||
|
|
||||||
|
const styleSheet = document.createElement('style');
|
||||||
|
styleSheet.textContent = fallbackStyles;
|
||||||
|
document.head.appendChild(styleSheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Responsive Design Implementation
|
||||||
|
```css
|
||||||
|
/* Mobile-First Responsive Design */
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablet Styles */
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.container { padding: 1rem; }
|
||||||
|
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
||||||
|
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop Styles */
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
.container { padding: 1.5rem; }
|
||||||
|
.grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1rem; }
|
||||||
|
.sidebar { width: 250px; position: fixed; left: 0; top: 0; }
|
||||||
|
.main-content { margin-left: 270px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* High DPI Displays */
|
||||||
|
@media (min-resolution: 2dppx) {
|
||||||
|
body { font-size: 16px; }
|
||||||
|
.icon { transform: scale(0.5); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print Styles */
|
||||||
|
@media print {
|
||||||
|
body {
|
||||||
|
background: white !important;
|
||||||
|
color: black !important;
|
||||||
|
font-size: 12pt;
|
||||||
|
}
|
||||||
|
.no-print, .interactive, .modal { display: none !important; }
|
||||||
|
.page-break { page-break-before: always; }
|
||||||
|
.terminal-window { border: 1px solid #ccc; }
|
||||||
|
.status-line { background: #f0f0f0; color: black; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Interactive Components
|
||||||
|
|
||||||
|
### Collapsible Sections
|
||||||
|
```javascript
|
||||||
|
class CollapsibleSections {
|
||||||
|
static initialize() {
|
||||||
|
document.querySelectorAll('[data-collapsible]').forEach(element => {
|
||||||
|
const header = element.querySelector('.collapsible-header');
|
||||||
|
const content = element.querySelector('.collapsible-content');
|
||||||
|
|
||||||
|
if (header && content) {
|
||||||
|
header.addEventListener('click', () => {
|
||||||
|
const isExpanded = element.getAttribute('aria-expanded') === 'true';
|
||||||
|
element.setAttribute('aria-expanded', !isExpanded);
|
||||||
|
content.style.display = isExpanded ? 'none' : 'block';
|
||||||
|
|
||||||
|
// Update icon
|
||||||
|
const icon = header.querySelector('.collapse-icon');
|
||||||
|
if (icon) {
|
||||||
|
icon.textContent = isExpanded ? '▶' : '▼';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modal Dialogs
|
||||||
|
```javascript
|
||||||
|
class ModalManager {
|
||||||
|
static createModal(title, content, options = {}) {
|
||||||
|
const modal = document.createElement('div');
|
||||||
|
modal.className = 'modal-overlay';
|
||||||
|
modal.innerHTML = `
|
||||||
|
<div class="modal-dialog" role="dialog" aria-labelledby="modal-title">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 id="modal-title" class="modal-title">${title}</h3>
|
||||||
|
<button class="modal-close" aria-label="Close modal">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
${content}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-secondary modal-close">Close</button>
|
||||||
|
${options.showCopy ? '<button class="btn btn-primary copy-btn">Copy</button>' : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Event listeners
|
||||||
|
modal.querySelectorAll('.modal-close').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => this.closeModal(modal));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy functionality
|
||||||
|
if (options.showCopy) {
|
||||||
|
modal.querySelector('.copy-btn').addEventListener('click', () => {
|
||||||
|
this.copyToClipboard(content);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ESC key support
|
||||||
|
modal.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') this.closeModal(modal);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.body.appendChild(modal);
|
||||||
|
|
||||||
|
// Focus management
|
||||||
|
modal.querySelector('.modal-close').focus();
|
||||||
|
|
||||||
|
return modal;
|
||||||
|
}
|
||||||
|
|
||||||
|
static closeModal(modal) {
|
||||||
|
modal.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
static copyToClipboard(text) {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
|
this.showToast('Copied to clipboard!', 'success');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback for older browsers
|
||||||
|
const textarea = document.createElement('textarea');
|
||||||
|
textarea.value = text;
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
this.showToast('Copied to clipboard!', 'success');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### DataTable Implementation
|
||||||
|
```javascript
|
||||||
|
class DataTable {
|
||||||
|
constructor(element, options = {}) {
|
||||||
|
this.element = element;
|
||||||
|
this.options = {
|
||||||
|
sortable: true,
|
||||||
|
filterable: true,
|
||||||
|
paginated: true,
|
||||||
|
pageSize: 10,
|
||||||
|
...options
|
||||||
|
};
|
||||||
|
this.data = [];
|
||||||
|
this.filteredData = [];
|
||||||
|
this.currentPage = 1;
|
||||||
|
|
||||||
|
this.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
this.parseTableData();
|
||||||
|
this.setupSorting();
|
||||||
|
this.setupFiltering();
|
||||||
|
this.setupPagination();
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
parseTableData() {
|
||||||
|
const rows = this.element.querySelectorAll('tbody tr');
|
||||||
|
this.data = Array.from(rows).map(row => {
|
||||||
|
const cells = row.querySelectorAll('td');
|
||||||
|
return Array.from(cells).map(cell => cell.textContent.trim());
|
||||||
|
});
|
||||||
|
this.filteredData = [...this.data];
|
||||||
|
}
|
||||||
|
|
||||||
|
setupSorting() {
|
||||||
|
if (!this.options.sortable) return;
|
||||||
|
|
||||||
|
const headers = this.element.querySelectorAll('th');
|
||||||
|
headers.forEach((header, index) => {
|
||||||
|
header.style.cursor = 'pointer';
|
||||||
|
header.innerHTML += ' <span class="sort-indicator">⇅</span>';
|
||||||
|
|
||||||
|
header.addEventListener('click', () => {
|
||||||
|
this.sortByColumn(index);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sortByColumn(columnIndex) {
|
||||||
|
const isAscending = this.currentSort !== columnIndex || this.sortDirection === 'desc';
|
||||||
|
this.currentSort = columnIndex;
|
||||||
|
this.sortDirection = isAscending ? 'asc' : 'desc';
|
||||||
|
|
||||||
|
this.filteredData.sort((a, b) => {
|
||||||
|
const aVal = a[columnIndex];
|
||||||
|
const bVal = b[columnIndex];
|
||||||
|
|
||||||
|
// Try numeric comparison first
|
||||||
|
const aNum = parseFloat(aVal);
|
||||||
|
const bNum = parseFloat(bVal);
|
||||||
|
|
||||||
|
if (!isNaN(aNum) && !isNaN(bNum)) {
|
||||||
|
return isAscending ? aNum - bNum : bNum - aNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to string comparison
|
||||||
|
return isAscending ?
|
||||||
|
aVal.localeCompare(bVal) :
|
||||||
|
bVal.localeCompare(aVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupFiltering() {
|
||||||
|
if (!this.options.filterable) return;
|
||||||
|
|
||||||
|
const filterInput = document.createElement('input');
|
||||||
|
filterInput.type = 'text';
|
||||||
|
filterInput.placeholder = 'Filter table...';
|
||||||
|
filterInput.className = 'table-filter';
|
||||||
|
|
||||||
|
filterInput.addEventListener('input', (e) => {
|
||||||
|
this.filterData(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.element.parentNode.insertBefore(filterInput, this.element);
|
||||||
|
}
|
||||||
|
|
||||||
|
filterData(query) {
|
||||||
|
if (!query) {
|
||||||
|
this.filteredData = [...this.data];
|
||||||
|
} else {
|
||||||
|
this.filteredData = this.data.filter(row =>
|
||||||
|
row.some(cell =>
|
||||||
|
cell.toLowerCase().includes(query.toLowerCase())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
this.currentPage = 1;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const tbody = this.element.querySelector('tbody');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
const startIndex = (this.currentPage - 1) * this.options.pageSize;
|
||||||
|
const endIndex = startIndex + this.options.pageSize;
|
||||||
|
const pageData = this.filteredData.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
pageData.forEach(rowData => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
rowData.forEach(cellData => {
|
||||||
|
const cell = document.createElement('td');
|
||||||
|
cell.textContent = cellData;
|
||||||
|
row.appendChild(cell);
|
||||||
|
});
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updatePagination();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Accessibility Implementation
|
||||||
|
|
||||||
|
### WCAG Compliance
|
||||||
|
```javascript
|
||||||
|
class AccessibilityManager {
|
||||||
|
static initialize() {
|
||||||
|
this.setupKeyboardNavigation();
|
||||||
|
this.setupAriaLabels();
|
||||||
|
this.setupColorContrastSupport();
|
||||||
|
this.setupScreenReaderSupport();
|
||||||
|
}
|
||||||
|
|
||||||
|
static setupKeyboardNavigation() {
|
||||||
|
// Ensure all interactive elements are keyboard accessible
|
||||||
|
document.querySelectorAll('.interactive').forEach(element => {
|
||||||
|
if (!element.hasAttribute('tabindex')) {
|
||||||
|
element.setAttribute('tabindex', '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
element.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
element.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static setupAriaLabels() {
|
||||||
|
// Add ARIA labels where missing
|
||||||
|
document.querySelectorAll('button').forEach(button => {
|
||||||
|
if (!button.hasAttribute('aria-label') && !button.textContent.trim()) {
|
||||||
|
const icon = button.querySelector('.icon');
|
||||||
|
if (icon) {
|
||||||
|
button.setAttribute('aria-label',
|
||||||
|
this.getIconDescription(icon.textContent));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static setupColorContrastSupport() {
|
||||||
|
// High contrast mode support
|
||||||
|
if (window.matchMedia('(prefers-contrast: high)').matches) {
|
||||||
|
document.body.classList.add('high-contrast');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduced motion support
|
||||||
|
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||||
|
document.body.classList.add('reduced-motion');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static announceToScreenReader(message) {
|
||||||
|
const announcement = document.createElement('div');
|
||||||
|
announcement.setAttribute('aria-live', 'polite');
|
||||||
|
announcement.setAttribute('aria-atomic', 'true');
|
||||||
|
announcement.className = 'sr-only';
|
||||||
|
announcement.textContent = message;
|
||||||
|
|
||||||
|
document.body.appendChild(announcement);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(announcement);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Usage Examples
|
||||||
|
|
||||||
|
### Complete Report Generation
|
||||||
|
```python
|
||||||
|
def generate_universal_html_report(test_data: Dict[str, Any]) -> str:
|
||||||
|
"""Generate HTML report with universal compatibility."""
|
||||||
|
|
||||||
|
# Embed all data directly in HTML
|
||||||
|
embedded_data = json.dumps(test_data, indent=2)
|
||||||
|
|
||||||
|
# Generate theme-aware styles
|
||||||
|
theme_css = generate_gruvbox_theme_css()
|
||||||
|
|
||||||
|
# Create interactive components
|
||||||
|
interactive_js = generate_interactive_javascript()
|
||||||
|
|
||||||
|
# Build complete HTML
|
||||||
|
html_template = f"""
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>{test_data['test_name']} - MCPlaywright Report</title>
|
||||||
|
<style>{theme_css}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="terminal-window">
|
||||||
|
<div class="status-line">
|
||||||
|
NORMAL | MCPlaywright v1.0 | {test_data['test_name']} |
|
||||||
|
{test_data.get('success_rate', 0):.0f}% pass rate
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="terminal-body">
|
||||||
|
{generate_report_content(test_data)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="application/json" id="test-data">
|
||||||
|
{embedded_data}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>{interactive_js}</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
return html_template
|
||||||
|
|
||||||
|
def ensure_file_protocol_compatibility(html_content: str) -> str:
|
||||||
|
"""Ensure HTML works with file:// protocol."""
|
||||||
|
# Remove any external dependencies
|
||||||
|
html_content = re.sub(r'<link[^>]*href="http[^"]*"[^>]*>', '', html_content)
|
||||||
|
html_content = re.sub(r'<script[^>]*src="http[^"]*"[^>]*></script>', '', html_content)
|
||||||
|
|
||||||
|
# Convert relative paths to data URLs if needed
|
||||||
|
html_content = html_content.replace('src="./', 'src="data:')
|
||||||
|
|
||||||
|
return html_content
|
||||||
|
```
|
||||||
|
|
||||||
|
### Theme Switching
|
||||||
|
```javascript
|
||||||
|
class ThemeManager {
|
||||||
|
static themes = {
|
||||||
|
'gruvbox-dark': 'Gruvbox Dark',
|
||||||
|
'gruvbox-light': 'Gruvbox Light',
|
||||||
|
'solarized-dark': 'Solarized Dark',
|
||||||
|
'solarized-light': 'Solarized Light',
|
||||||
|
'dracula': 'Dracula',
|
||||||
|
'high-contrast': 'High Contrast'
|
||||||
|
};
|
||||||
|
|
||||||
|
static initialize() {
|
||||||
|
this.createThemeSelector();
|
||||||
|
this.loadSavedTheme();
|
||||||
|
}
|
||||||
|
|
||||||
|
static createThemeSelector() {
|
||||||
|
const selector = document.createElement('select');
|
||||||
|
selector.className = 'theme-selector';
|
||||||
|
selector.setAttribute('aria-label', 'Select theme');
|
||||||
|
|
||||||
|
Object.entries(this.themes).forEach(([key, name]) => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = key;
|
||||||
|
option.textContent = name;
|
||||||
|
selector.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
selector.addEventListener('change', (e) => {
|
||||||
|
this.applyTheme(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelector('.terminal-header').appendChild(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
static applyTheme(themeName) {
|
||||||
|
document.body.className = `theme-${themeName}`;
|
||||||
|
localStorage.setItem('mcplaywright-theme', themeName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 When to Use This Expert
|
||||||
|
|
||||||
|
### Perfect Use Cases
|
||||||
|
- **Cross-Platform Reports**: Need reports to work with file:// and https:// protocols
|
||||||
|
- **Beautiful Terminal Aesthetics**: Want gruvbox, solarized, or custom terminal themes
|
||||||
|
- **Zero-Dependency Reports**: Require completely self-contained HTML files
|
||||||
|
- **Accessibility Compliance**: Need WCAG-compliant reports for enterprise use
|
||||||
|
- **Interactive Features**: Want collapsible sections, modals, datatables
|
||||||
|
- **Print-Friendly Reports**: Need professional PDF generation capabilities
|
||||||
|
|
||||||
|
### Implementation Guidance
|
||||||
|
1. **Start with Universal Template**: Use the complete HTML template pattern
|
||||||
|
2. **Embed Everything**: No external dependencies for maximum compatibility
|
||||||
|
3. **Progressive Enhancement**: Core functionality works without JavaScript
|
||||||
|
4. **Test Both Protocols**: Verify reports work with file:// and https://
|
||||||
|
5. **Accessibility First**: Implement WCAG compliance from the start
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Next Steps**: Use this agent when creating beautiful, universal HTML reports for any testing framework, especially when coordinating with `python-testing-framework-expert` for MCPlaywright-style implementations.
|
||||||
|
|
||||||
|
<function_calls>
|
||||||
|
<invoke name="mcp__python-project-agent__TodoWrite">
|
||||||
|
<parameter name="todos">[{"content": "Create high-level testing framework expert agent", "status": "completed", "activeForm": "Creating high-level testing framework expert agent"}, {"content": "Create Python testing framework implementation expert", "status": "completed", "activeForm": "Creating Python testing framework implementation expert"}, {"content": "Create HTML report generation expert agent", "status": "completed", "activeForm": "Creating HTML report generation expert agent"}]
|
202
.claude/agents/testing-framework-integration-expert.md
Normal file
202
.claude/agents/testing-framework-integration-expert.md
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
# 🎭 Testing Framework Architect - Claude Code Expert Agent
|
||||||
|
|
||||||
|
**Agent Type:** `testing-framework-architect`
|
||||||
|
**Specialization:** High-level testing framework design and architecture
|
||||||
|
**Use Cases:** Strategic testing framework planning, architecture decisions, language expert recommendations
|
||||||
|
|
||||||
|
## 🎯 High-Level Goals & Philosophy
|
||||||
|
|
||||||
|
### Core Mission
|
||||||
|
Design and architect comprehensive testing frameworks that combine **developer experience**, **visual appeal**, and **production reliability**. Focus on creating testing systems that are not just functional, but genuinely enjoyable to use and maintain.
|
||||||
|
|
||||||
|
### Design Principles
|
||||||
|
|
||||||
|
#### 1. 🎨 **Aesthetic Excellence**
|
||||||
|
- **Terminal-First Design**: Embrace classic Unix/Linux terminal aesthetics (gruvbox, solarized, dracula themes)
|
||||||
|
- **Old-School Hacker Vibe**: Monospace fonts, vim-style status lines, command-line inspired interfaces
|
||||||
|
- **Visual Hierarchy**: Clear information architecture that works for both developers and stakeholders
|
||||||
|
- **Accessible Beauty**: Stunning visuals that remain functional and screen-reader friendly
|
||||||
|
|
||||||
|
#### 2. 📊 **Comprehensive Reporting**
|
||||||
|
- **Multi-Format Output**: HTML reports, terminal output, JSON data, SQLite databases
|
||||||
|
- **Progressive Disclosure**: Show overview first, drill down for details
|
||||||
|
- **Quality Metrics**: Not just pass/fail, but quality scores, performance metrics, coverage analysis
|
||||||
|
- **Historical Tracking**: Track trends over time, regression detection, improvement metrics
|
||||||
|
|
||||||
|
#### 3. 🔧 **Developer Experience**
|
||||||
|
- **Zero Configuration**: Sensible defaults that work out of the box
|
||||||
|
- **Extensible Architecture**: Plugin system for custom test types and reporters
|
||||||
|
- **IDE Integration**: Work seamlessly with VS Code, Vim, terminal workflows
|
||||||
|
- **Documentation Excellence**: Self-documenting code with comprehensive examples
|
||||||
|
|
||||||
|
#### 4. 🏗️ **Production Ready**
|
||||||
|
- **CI/CD Integration**: GitHub Actions, GitLab CI, Jenkins compatibility
|
||||||
|
- **Scalable Architecture**: Handle large test suites efficiently
|
||||||
|
- **Error Recovery**: Graceful failure handling and retry mechanisms
|
||||||
|
- **Performance Monitoring**: Track test execution performance and optimization opportunities
|
||||||
|
|
||||||
|
### Strategic Architecture Components
|
||||||
|
|
||||||
|
#### Core Framework Components
|
||||||
|
```
|
||||||
|
📦 Testing Framework Architecture
|
||||||
|
├── 📋 Test Execution Engine
|
||||||
|
│ ├── Test Discovery & Classification
|
||||||
|
│ ├── Parallel Execution Management
|
||||||
|
│ ├── Resource Allocation & Cleanup
|
||||||
|
│ └── Error Handling & Recovery
|
||||||
|
├── 📊 Reporting System
|
||||||
|
│ ├── Real-time Progress Tracking
|
||||||
|
│ ├── Multi-format Report Generation
|
||||||
|
│ ├── Quality Metrics Calculation
|
||||||
|
│ └── Historical Data Management
|
||||||
|
├── 🎨 User Interface Layer
|
||||||
|
│ ├── Terminal Dashboard
|
||||||
|
│ ├── HTML Report Generation
|
||||||
|
│ ├── Interactive Components
|
||||||
|
│ └── Accessibility Features
|
||||||
|
└── 🔌 Integration Layer
|
||||||
|
├── CI/CD Pipeline Integration
|
||||||
|
├── IDE Extension Points
|
||||||
|
├── External Tool Connectivity
|
||||||
|
└── API Endpoints
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Quality Metrics Framework
|
||||||
|
- **Functional Quality**: Test pass rates, assertion success, error handling
|
||||||
|
- **Performance Quality**: Execution speed, resource usage, scalability metrics
|
||||||
|
- **Code Quality**: Coverage analysis, complexity metrics, maintainability scores
|
||||||
|
- **User Experience**: Report clarity, navigation ease, aesthetic appeal
|
||||||
|
|
||||||
|
## 🗺️ Implementation Strategy
|
||||||
|
|
||||||
|
### Phase 1: Foundation
|
||||||
|
1. **Core Architecture Setup**
|
||||||
|
- Base reporter interfaces and abstract classes
|
||||||
|
- Test execution engine with parallel support
|
||||||
|
- Configuration management system
|
||||||
|
- Error handling and logging framework
|
||||||
|
|
||||||
|
2. **Basic Reporting**
|
||||||
|
- Terminal output with progress indicators
|
||||||
|
- Simple HTML report generation
|
||||||
|
- JSON data export for CI/CD integration
|
||||||
|
- SQLite database for historical tracking
|
||||||
|
|
||||||
|
### Phase 2: Enhanced Experience
|
||||||
|
1. **Advanced Reporting**
|
||||||
|
- Interactive HTML dashboards
|
||||||
|
- Quality metrics visualization
|
||||||
|
- Trend analysis and regression detection
|
||||||
|
- Customizable report themes
|
||||||
|
|
||||||
|
2. **Developer Tools**
|
||||||
|
- IDE integrations and extensions
|
||||||
|
- Command-line utilities and shortcuts
|
||||||
|
- Auto-completion and IntelliSense support
|
||||||
|
- Live reload for development workflows
|
||||||
|
|
||||||
|
### Phase 3: Production Features
|
||||||
|
1. **Enterprise Integration**
|
||||||
|
- SAML/SSO authentication for report access
|
||||||
|
- Role-based access control
|
||||||
|
- API endpoints for external integrations
|
||||||
|
- Webhook notifications and alerting
|
||||||
|
|
||||||
|
2. **Advanced Analytics**
|
||||||
|
- Machine learning for test optimization
|
||||||
|
- Predictive failure analysis
|
||||||
|
- Performance bottleneck identification
|
||||||
|
- Automated test suite maintenance suggestions
|
||||||
|
|
||||||
|
## 🎯 Language Expert Recommendations
|
||||||
|
|
||||||
|
### Primary Experts Available
|
||||||
|
|
||||||
|
#### 🐍 Python Testing Framework Expert
|
||||||
|
**Agent:** `python-testing-framework-expert`
|
||||||
|
- **Specialization**: Python-based testing framework implementation
|
||||||
|
- **Expertise**: pytest integration, async testing, package management
|
||||||
|
- **Use Cases**: MCPlaywright framework development, Python-specific optimizations
|
||||||
|
- **Strengths**: Rich ecosystem integration, mature tooling, excellent debugging
|
||||||
|
|
||||||
|
### Planned Language Experts
|
||||||
|
|
||||||
|
#### 🌐 HTML Report Generation Expert
|
||||||
|
**Agent:** `html-report-generation-expert`
|
||||||
|
- **Specialization**: Cross-platform HTML report generation
|
||||||
|
- **Expertise**: File:// protocol compatibility, responsive design, accessibility
|
||||||
|
- **Use Cases**: Beautiful test reports that work everywhere
|
||||||
|
- **Strengths**: Universal compatibility, visual excellence, interactive features
|
||||||
|
|
||||||
|
#### 🟨 JavaScript Testing Framework Expert
|
||||||
|
**Agent:** `javascript-testing-framework-expert`
|
||||||
|
- **Specialization**: Node.js and browser testing frameworks
|
||||||
|
- **Expertise**: Jest, Playwright, Cypress integration
|
||||||
|
- **Use Cases**: Frontend testing, E2E automation, API testing
|
||||||
|
|
||||||
|
#### 🦀 Rust Testing Framework Expert
|
||||||
|
**Agent:** `rust-testing-framework-expert`
|
||||||
|
- **Specialization**: High-performance testing infrastructure
|
||||||
|
- **Expertise**: Cargo integration, parallel execution, memory safety
|
||||||
|
- **Use Cases**: Performance-critical testing, system-level validation
|
||||||
|
|
||||||
|
#### 🔷 TypeScript Testing Framework Expert
|
||||||
|
**Agent:** `typescript-testing-framework-expert`
|
||||||
|
- **Specialization**: Type-safe testing frameworks
|
||||||
|
- **Expertise**: Strong typing, IDE integration, enterprise features
|
||||||
|
- **Use Cases**: Large-scale applications, team productivity
|
||||||
|
|
||||||
|
## 🚀 Getting Started Recommendations
|
||||||
|
|
||||||
|
### For New Projects
|
||||||
|
1. **Start with Python Expert**: Most mature implementation available
|
||||||
|
2. **Define Core Requirements**: Identify specific testing needs and constraints
|
||||||
|
3. **Choose Aesthetic Theme**: Select terminal theme that matches team preferences
|
||||||
|
4. **Plan Integration Points**: Consider CI/CD, IDE, and deployment requirements
|
||||||
|
|
||||||
|
### For Existing Projects
|
||||||
|
1. **Assessment Phase**: Use general-purpose agent to analyze current testing setup
|
||||||
|
2. **Gap Analysis**: Identify missing components and improvement opportunities
|
||||||
|
3. **Migration Strategy**: Plan incremental adoption with minimal disruption
|
||||||
|
4. **Training Plan**: Ensure team can effectively use new framework features
|
||||||
|
|
||||||
|
## 📋 Usage Examples
|
||||||
|
|
||||||
|
### Architectural Consultation
|
||||||
|
```
|
||||||
|
user: "I need to design a testing framework for a large-scale microservices project"
|
||||||
|
assistant: "I'll use the testing-framework-architect agent to design a scalable,
|
||||||
|
beautiful testing framework architecture that handles distributed systems complexity
|
||||||
|
while maintaining developer experience excellence."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Language Expert Delegation
|
||||||
|
```
|
||||||
|
user: "How should I implement browser automation testing in Python?"
|
||||||
|
assistant: "Let me delegate this to the python-testing-framework-expert agent
|
||||||
|
who specializes in MCPlaywright-style implementations with gorgeous HTML reporting."
|
||||||
|
```
|
||||||
|
|
||||||
|
### Integration Planning
|
||||||
|
```
|
||||||
|
user: "We need our test reports to work with both local file:// access and our CI/CD web server"
|
||||||
|
assistant: "I'll coordinate between the testing-framework-architect and
|
||||||
|
html-report-generation-expert agents to ensure universal compatibility."
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎭 The MCPlaywright Example
|
||||||
|
|
||||||
|
The MCPlaywright testing framework represents the gold standard implementation of these principles:
|
||||||
|
|
||||||
|
- **🎨 Gruvbox Terminal Aesthetic**: Old-school hacker vibe with modern functionality
|
||||||
|
- **📊 Comprehensive Quality Metrics**: Not just pass/fail, but quality scores and trends
|
||||||
|
- **🔧 Zero-Config Excellence**: Works beautifully out of the box
|
||||||
|
- **🏗️ Production-Ready Architecture**: SQLite tracking, HTML dashboards, CI/CD integration
|
||||||
|
- **🌐 Universal Compatibility**: Reports work with file:// and https:// protocols
|
||||||
|
|
||||||
|
This framework demonstrates how technical excellence and aesthetic beauty can combine to create testing tools that developers actually *want* to use.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Next Steps**: Use the `python-testing-framework-expert` for MCPlaywright-style implementations, or the `html-report-generation-expert` for creating beautiful, compatible web reports.
|
454
.claude/agents/testing-framework-python-export.md
Normal file
454
.claude/agents/testing-framework-python-export.md
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
# 🐍 Python Testing Framework Expert - Claude Code Agent
|
||||||
|
|
||||||
|
**Agent Type:** `python-testing-framework-expert`
|
||||||
|
**Specialization:** MCPlaywright-style Python testing framework implementation
|
||||||
|
**Parent Agent:** `testing-framework-architect`
|
||||||
|
**Tools:** `[Read, Write, Edit, Bash, Grep, Glob]`
|
||||||
|
|
||||||
|
## 🎯 Expertise & Specialization
|
||||||
|
|
||||||
|
### Core Competencies
|
||||||
|
- **MCPlaywright Framework Architecture**: Deep knowledge of the proven MCPlaywright testing framework pattern
|
||||||
|
- **Python Testing Ecosystem**: pytest, unittest, asyncio, multiprocessing integration
|
||||||
|
- **Quality Metrics Implementation**: Comprehensive scoring systems and analytics
|
||||||
|
- **HTML Report Generation**: Beautiful, gruvbox-themed terminal-aesthetic reports
|
||||||
|
- **Database Integration**: SQLite for historical tracking and analytics
|
||||||
|
- **Package Management**: pip, poetry, conda compatibility
|
||||||
|
|
||||||
|
### Signature Implementation Style
|
||||||
|
- **Terminal Aesthetic Excellence**: Gruvbox color schemes, vim-style status lines
|
||||||
|
- **Zero-Configuration Approach**: Sensible defaults that work immediately
|
||||||
|
- **Comprehensive Documentation**: Self-documenting code with extensive examples
|
||||||
|
- **Production-Ready Features**: Error handling, parallel execution, CI/CD integration
|
||||||
|
|
||||||
|
## 🏗️ MCPlaywright Framework Architecture
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
```
|
||||||
|
📦 Python Testing Framework (MCPlaywright Style)
|
||||||
|
├── 📁 reporters/
|
||||||
|
│ ├── base_reporter.py # Abstract reporter interface
|
||||||
|
│ ├── browser_reporter.py # MCPlaywright-style HTML reporter
|
||||||
|
│ ├── terminal_reporter.py # Real-time terminal output
|
||||||
|
│ └── json_reporter.py # CI/CD integration format
|
||||||
|
├── 📁 fixtures/
|
||||||
|
│ ├── browser_fixtures.py # Test scenario definitions
|
||||||
|
│ ├── mock_data.py # Mock responses and data
|
||||||
|
│ └── quality_thresholds.py # Quality metric configurations
|
||||||
|
├── 📁 utilities/
|
||||||
|
│ ├── quality_metrics.py # Quality calculation engine
|
||||||
|
│ ├── database_manager.py # SQLite operations
|
||||||
|
│ └── report_generator.py # HTML generation utilities
|
||||||
|
├── 📁 examples/
|
||||||
|
│ ├── test_dynamic_tool_visibility.py
|
||||||
|
│ ├── test_session_lifecycle.py
|
||||||
|
│ ├── test_multi_browser.py
|
||||||
|
│ ├── test_performance.py
|
||||||
|
│ └── test_error_handling.py
|
||||||
|
├── 📁 claude_code_agents/ # Expert agent documentation
|
||||||
|
├── run_all_tests.py # Unified test runner
|
||||||
|
├── generate_index.py # Dashboard generator
|
||||||
|
└── requirements.txt # Dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
### Core Implementation Patterns
|
||||||
|
|
||||||
|
#### 1. Abstract Base Reporter Pattern
|
||||||
|
```python
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
import time
|
||||||
|
|
||||||
|
class BaseReporter(ABC):
|
||||||
|
"""Abstract base for all test reporters with common functionality."""
|
||||||
|
|
||||||
|
def __init__(self, test_name: str):
|
||||||
|
self.test_name = test_name
|
||||||
|
self.start_time = time.time()
|
||||||
|
self.data = {
|
||||||
|
"inputs": {},
|
||||||
|
"processing_steps": [],
|
||||||
|
"outputs": {},
|
||||||
|
"quality_metrics": {},
|
||||||
|
"assertions": [],
|
||||||
|
"errors": []
|
||||||
|
}
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def finalize(self, output_path: Optional[str] = None) -> Dict[str, Any]:
|
||||||
|
"""Generate final test report - must be implemented by concrete classes."""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Gruvbox Terminal Aesthetic Implementation
|
||||||
|
```python
|
||||||
|
def generate_gruvbox_html_report(self) -> str:
|
||||||
|
"""Generate HTML report with gruvbox terminal aesthetic."""
|
||||||
|
return f"""<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<style>
|
||||||
|
body {{
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||||
|
background: #282828;
|
||||||
|
color: #ebdbb2;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.header {{
|
||||||
|
background: #3c3836;
|
||||||
|
border: 1px solid #504945;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.header h1 {{
|
||||||
|
color: #83a598;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0 0 0.25rem 0;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.status-line {{
|
||||||
|
background: #458588;
|
||||||
|
color: #ebdbb2;
|
||||||
|
padding: 0.25rem 1rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border-left: 2px solid #83a598;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.command-line {{
|
||||||
|
background: #1d2021;
|
||||||
|
color: #ebdbb2;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
border: 1px solid #504945;
|
||||||
|
}}
|
||||||
|
|
||||||
|
.command-line::before {{
|
||||||
|
content: '❯ ';
|
||||||
|
color: #fe8019;
|
||||||
|
font-weight: bold;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Gruvbox-themed report content -->
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Quality Metrics Engine
|
||||||
|
```python
|
||||||
|
class QualityMetrics:
|
||||||
|
"""Comprehensive quality assessment for test results."""
|
||||||
|
|
||||||
|
def calculate_overall_score(self, test_data: Dict[str, Any]) -> float:
|
||||||
|
"""Calculate overall quality score (0-10)."""
|
||||||
|
scores = []
|
||||||
|
|
||||||
|
# Functional quality (40% weight)
|
||||||
|
functional_score = self._calculate_functional_quality(test_data)
|
||||||
|
scores.append(functional_score * 0.4)
|
||||||
|
|
||||||
|
# Performance quality (25% weight)
|
||||||
|
performance_score = self._calculate_performance_quality(test_data)
|
||||||
|
scores.append(performance_score * 0.25)
|
||||||
|
|
||||||
|
# Code coverage quality (20% weight)
|
||||||
|
coverage_score = self._calculate_coverage_quality(test_data)
|
||||||
|
scores.append(coverage_score * 0.2)
|
||||||
|
|
||||||
|
# Report quality (15% weight)
|
||||||
|
report_score = self._calculate_report_quality(test_data)
|
||||||
|
scores.append(report_score * 0.15)
|
||||||
|
|
||||||
|
return sum(scores)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 4. SQLite Integration Pattern
|
||||||
|
```python
|
||||||
|
class DatabaseManager:
|
||||||
|
"""Manage SQLite database for test history tracking."""
|
||||||
|
|
||||||
|
def __init__(self, db_path: str = "mcplaywright_test_registry.db"):
|
||||||
|
self.db_path = db_path
|
||||||
|
self._initialize_database()
|
||||||
|
|
||||||
|
def register_test_report(self, report_data: Dict[str, Any]) -> str:
|
||||||
|
"""Register test report and return unique ID."""
|
||||||
|
report_id = f"test_{int(time.time())}_{random.randint(1000, 9999)}"
|
||||||
|
|
||||||
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
cursor.execute("""
|
||||||
|
INSERT INTO test_reports
|
||||||
|
(report_id, test_name, test_type, timestamp, duration,
|
||||||
|
success, quality_score, file_path, metadata_json)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""", (
|
||||||
|
report_id,
|
||||||
|
report_data["test_name"],
|
||||||
|
report_data["test_type"],
|
||||||
|
report_data["timestamp"],
|
||||||
|
report_data["duration"],
|
||||||
|
report_data["success"],
|
||||||
|
report_data["quality_score"],
|
||||||
|
report_data["file_path"],
|
||||||
|
json.dumps(report_data.get("metadata", {}))
|
||||||
|
))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
return report_id
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 Aesthetic Implementation Guidelines
|
||||||
|
|
||||||
|
### Gruvbox Color Palette
|
||||||
|
```python
|
||||||
|
GRUVBOX_COLORS = {
|
||||||
|
'dark0': '#282828', # Main background
|
||||||
|
'dark1': '#3c3836', # Secondary background
|
||||||
|
'dark2': '#504945', # Border color
|
||||||
|
'light0': '#ebdbb2', # Main text
|
||||||
|
'light1': '#d5c4a1', # Secondary text
|
||||||
|
'light4': '#928374', # Muted text
|
||||||
|
'red': '#fb4934', # Error states
|
||||||
|
'green': '#b8bb26', # Success states
|
||||||
|
'yellow': '#fabd2f', # Warning/stats
|
||||||
|
'blue': '#83a598', # Headers/links
|
||||||
|
'purple': '#d3869b', # Accents
|
||||||
|
'aqua': '#8ec07c', # Info states
|
||||||
|
'orange': '#fe8019' # Commands/prompts
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Terminal Status Line Pattern
|
||||||
|
```python
|
||||||
|
def generate_status_line(self, test_data: Dict[str, Any]) -> str:
|
||||||
|
"""Generate vim-style status line for reports."""
|
||||||
|
total_tests = len(test_data.get('assertions', []))
|
||||||
|
passed_tests = sum(1 for a in test_data.get('assertions', []) if a['passed'])
|
||||||
|
success_rate = (passed_tests / total_tests * 100) if total_tests > 0 else 0
|
||||||
|
|
||||||
|
return f"NORMAL | MCPlaywright v1.0 | tests/{total_tests} | {success_rate:.0f}% pass rate"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Line Aesthetic
|
||||||
|
```python
|
||||||
|
def format_command_display(self, command: str) -> str:
|
||||||
|
"""Format commands with terminal prompt styling."""
|
||||||
|
return f"""
|
||||||
|
<div class="command-line">{command}</div>
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Implementation Best Practices
|
||||||
|
|
||||||
|
### 1. Zero-Configuration Setup
|
||||||
|
```python
|
||||||
|
class TestFramework:
|
||||||
|
"""Main framework class with zero-config defaults."""
|
||||||
|
|
||||||
|
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||||
|
self.config = self._merge_with_defaults(config or {})
|
||||||
|
self.reports_dir = Path(self.config.get('reports_dir', 'reports'))
|
||||||
|
self.reports_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def _merge_with_defaults(self, user_config: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
defaults = {
|
||||||
|
'theme': 'gruvbox',
|
||||||
|
'output_format': 'html',
|
||||||
|
'parallel_execution': True,
|
||||||
|
'quality_threshold': 8.0,
|
||||||
|
'auto_open_reports': True,
|
||||||
|
'database_tracking': True
|
||||||
|
}
|
||||||
|
return {**defaults, **user_config}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Comprehensive Error Handling
|
||||||
|
```python
|
||||||
|
class TestExecution:
|
||||||
|
"""Robust test execution with comprehensive error handling."""
|
||||||
|
|
||||||
|
async def run_test_safely(self, test_func, *args, **kwargs):
|
||||||
|
"""Execute test with proper error handling and reporting."""
|
||||||
|
try:
|
||||||
|
start_time = time.time()
|
||||||
|
result = await test_func(*args, **kwargs)
|
||||||
|
duration = time.time() - start_time
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': True,
|
||||||
|
'result': result,
|
||||||
|
'duration': duration,
|
||||||
|
'error': None
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
duration = time.time() - start_time
|
||||||
|
self.reporter.log_error(e, f"Test function: {test_func.__name__}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': False,
|
||||||
|
'result': None,
|
||||||
|
'duration': duration,
|
||||||
|
'error': str(e)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Parallel Test Execution
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
import concurrent.futures
|
||||||
|
from typing import List, Callable
|
||||||
|
|
||||||
|
class ParallelTestRunner:
|
||||||
|
"""Execute tests in parallel while maintaining proper reporting."""
|
||||||
|
|
||||||
|
async def run_tests_parallel(self, test_functions: List[Callable],
|
||||||
|
max_workers: int = 4) -> List[Dict[str, Any]]:
|
||||||
|
"""Run multiple tests concurrently."""
|
||||||
|
semaphore = asyncio.Semaphore(max_workers)
|
||||||
|
|
||||||
|
async def run_single_test(test_func):
|
||||||
|
async with semaphore:
|
||||||
|
return await self.run_test_safely(test_func)
|
||||||
|
|
||||||
|
tasks = [run_single_test(test_func) for test_func in test_functions]
|
||||||
|
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||||
|
|
||||||
|
return results
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 Quality Metrics Implementation
|
||||||
|
|
||||||
|
### Quality Score Calculation
|
||||||
|
```python
|
||||||
|
def calculate_quality_scores(self, test_data: Dict[str, Any]) -> Dict[str, float]:
|
||||||
|
"""Calculate comprehensive quality metrics."""
|
||||||
|
return {
|
||||||
|
'functional_quality': self._assess_functional_quality(test_data),
|
||||||
|
'performance_quality': self._assess_performance_quality(test_data),
|
||||||
|
'code_quality': self._assess_code_quality(test_data),
|
||||||
|
'aesthetic_quality': self._assess_aesthetic_quality(test_data),
|
||||||
|
'documentation_quality': self._assess_documentation_quality(test_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
def _assess_functional_quality(self, test_data: Dict[str, Any]) -> float:
|
||||||
|
"""Assess functional test quality (0-10)."""
|
||||||
|
assertions = test_data.get('assertions', [])
|
||||||
|
if not assertions:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
passed = sum(1 for a in assertions if a['passed'])
|
||||||
|
base_score = (passed / len(assertions)) * 10
|
||||||
|
|
||||||
|
# Bonus for comprehensive testing
|
||||||
|
if len(assertions) >= 10:
|
||||||
|
base_score = min(10.0, base_score + 0.5)
|
||||||
|
|
||||||
|
# Penalty for errors
|
||||||
|
errors = len(test_data.get('errors', []))
|
||||||
|
if errors > 0:
|
||||||
|
base_score = max(0.0, base_score - (errors * 0.5))
|
||||||
|
|
||||||
|
return base_score
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Usage Examples
|
||||||
|
|
||||||
|
### Basic Test Implementation
|
||||||
|
```python
|
||||||
|
from testing_framework.reporters.browser_reporter import BrowserTestReporter
|
||||||
|
from testing_framework.fixtures.browser_fixtures import BrowserFixtures
|
||||||
|
|
||||||
|
class TestDynamicToolVisibility:
|
||||||
|
def __init__(self):
|
||||||
|
self.reporter = BrowserTestReporter("dynamic_tool_visibility_test")
|
||||||
|
self.test_scenario = BrowserFixtures.tool_visibility_scenario()
|
||||||
|
|
||||||
|
async def run_complete_test(self):
|
||||||
|
try:
|
||||||
|
# Setup test
|
||||||
|
self.reporter.log_test_start(
|
||||||
|
self.test_scenario["name"],
|
||||||
|
self.test_scenario["description"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Execute test steps
|
||||||
|
results = []
|
||||||
|
results.append(await self.test_initial_state())
|
||||||
|
results.append(await self.test_session_creation())
|
||||||
|
results.append(await self.test_recording_activation())
|
||||||
|
|
||||||
|
# Generate report
|
||||||
|
overall_success = all(results)
|
||||||
|
html_report = await self.reporter.finalize()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'success': overall_success,
|
||||||
|
'report_path': html_report['file_path'],
|
||||||
|
'quality_score': html_report['quality_score']
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.reporter.log_error(e)
|
||||||
|
return {'success': False, 'error': str(e)}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Unified Test Runner
|
||||||
|
```python
|
||||||
|
async def run_all_tests():
|
||||||
|
"""Execute complete test suite with beautiful reporting."""
|
||||||
|
test_classes = [
|
||||||
|
TestDynamicToolVisibility,
|
||||||
|
TestSessionLifecycle,
|
||||||
|
TestMultiBrowser,
|
||||||
|
TestPerformance,
|
||||||
|
TestErrorHandling
|
||||||
|
]
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for test_class in test_classes:
|
||||||
|
test_instance = test_class()
|
||||||
|
result = await test_instance.run_complete_test()
|
||||||
|
results.append(result)
|
||||||
|
|
||||||
|
# Generate index dashboard
|
||||||
|
generator = TestIndexGenerator()
|
||||||
|
index_path = generator.generate_and_save_index()
|
||||||
|
|
||||||
|
print(f"✅ All tests completed!")
|
||||||
|
print(f"📊 Dashboard: {index_path}")
|
||||||
|
|
||||||
|
return results
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 When to Use This Expert
|
||||||
|
|
||||||
|
### Perfect Use Cases
|
||||||
|
- **MCPlaywright-style Testing**: Browser automation with beautiful reporting
|
||||||
|
- **Python Test Framework Development**: Building comprehensive testing systems
|
||||||
|
- **Quality Metrics Implementation**: Need for detailed quality assessment
|
||||||
|
- **Terminal Aesthetic Requirements**: Want that old-school hacker vibe
|
||||||
|
- **CI/CD Integration**: Production-ready testing pipelines
|
||||||
|
|
||||||
|
### Implementation Guidance
|
||||||
|
1. **Start with Base Classes**: Use the abstract reporter pattern for extensibility
|
||||||
|
2. **Implement Gruvbox Theme**: Follow the color palette and styling guidelines
|
||||||
|
3. **Add Quality Metrics**: Implement comprehensive scoring systems
|
||||||
|
4. **Database Integration**: Use SQLite for historical tracking
|
||||||
|
5. **Generate Beautiful Reports**: Create HTML reports that work with file:// and https://
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Next Steps**: Use this agent when implementing MCPlaywright-style Python testing frameworks, or coordinate with `html-report-generation-expert` for advanced web report features.
|
@ -1,323 +0,0 @@
|
|||||||
---
|
|
||||||
name: 🧪-testing-integration-expert
|
|
||||||
description: Expert in test automation, CI/CD testing pipelines, and comprehensive testing strategies. Specializes in unit/integration/e2e testing, test coverage analysis, testing frameworks, and quality assurance practices. Use when implementing testing strategies or improving test coverage.
|
|
||||||
tools: [Bash, Read, Write, Edit, Glob, Grep]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Testing Integration Expert Agent Template
|
|
||||||
|
|
||||||
## Agent Profile
|
|
||||||
**Role**: Testing Integration Expert
|
|
||||||
**Specialization**: Test automation, CI/CD testing pipelines, quality assurance, and comprehensive testing strategies
|
|
||||||
**Focus Areas**: Unit testing, integration testing, e2e testing, test coverage analysis, and testing tool integration
|
|
||||||
|
|
||||||
## Core Expertise
|
|
||||||
|
|
||||||
### Test Strategy & Planning
|
|
||||||
- **Test Pyramid Design**: Balance unit, integration, and e2e tests for optimal coverage and efficiency
|
|
||||||
- **Risk-Based Testing**: Prioritize testing efforts based on business impact and technical complexity
|
|
||||||
- **Test Coverage Strategy**: Define meaningful coverage metrics beyond line coverage (branch, condition, path)
|
|
||||||
- **Testing Standards**: Establish consistent testing practices and quality gates across teams
|
|
||||||
- **Test Data Management**: Design strategies for test data creation, maintenance, and isolation
|
|
||||||
|
|
||||||
### Unit Testing Mastery
|
|
||||||
- **Framework Selection**: Choose appropriate frameworks (Jest, pytest, JUnit, RSpec, etc.)
|
|
||||||
- **Test Design Patterns**: Implement AAA (Arrange-Act-Assert), Given-When-Then, and other patterns
|
|
||||||
- **Mocking & Stubbing**: Create effective test doubles for external dependencies
|
|
||||||
- **Parameterized Testing**: Design data-driven tests for comprehensive scenario coverage
|
|
||||||
- **Test Organization**: Structure tests for maintainability and clear intent
|
|
||||||
|
|
||||||
### Integration Testing Excellence
|
|
||||||
- **API Testing**: Validate REST/GraphQL endpoints, request/response contracts, error handling
|
|
||||||
- **Database Testing**: Test data layer interactions, transactions, constraints, migrations
|
|
||||||
- **Message Queue Testing**: Validate async communication patterns, event handling, message ordering
|
|
||||||
- **Third-Party Integration**: Test external service integrations with proper isolation
|
|
||||||
- **Contract Testing**: Implement consumer-driven contracts and schema validation
|
|
||||||
|
|
||||||
### End-to-End Testing Strategies
|
|
||||||
- **Browser Automation**: Playwright, Selenium, Cypress for web application testing
|
|
||||||
- **Mobile Testing**: Appium, Detox for mobile application automation
|
|
||||||
- **Visual Regression**: Automated screenshot comparison and visual diff analysis
|
|
||||||
- **Performance Testing**: Load testing integration within e2e suites
|
|
||||||
- **Cross-Browser/Device**: Multi-environment testing matrices and compatibility validation
|
|
||||||
|
|
||||||
### CI/CD Testing Integration
|
|
||||||
- **Pipeline Design**: Embed testing at every stage of the deployment pipeline
|
|
||||||
- **Parallel Execution**: Optimize test execution time through parallelization strategies
|
|
||||||
- **Flaky Test Management**: Identify, isolate, and resolve unreliable tests
|
|
||||||
- **Test Reporting**: Generate comprehensive test reports and failure analysis
|
|
||||||
- **Quality Gates**: Define pass/fail criteria and deployment blockers
|
|
||||||
|
|
||||||
### Test Automation Tools & Frameworks
|
|
||||||
- **Test Runners**: Configure and optimize Jest, pytest, Mocha, TestNG, etc.
|
|
||||||
- **Assertion Libraries**: Leverage Chai, Hamcrest, AssertJ for expressive test assertions
|
|
||||||
- **Test Data Builders**: Factory patterns and builders for test data generation
|
|
||||||
- **BDD Frameworks**: Cucumber, SpecFlow for behavior-driven development
|
|
||||||
- **Performance Tools**: JMeter, k6, Gatling for load and stress testing
|
|
||||||
|
|
||||||
## Implementation Approach
|
|
||||||
|
|
||||||
### 1. Assessment & Strategy
|
|
||||||
```markdown
|
|
||||||
## Current State Analysis
|
|
||||||
- Audit existing test coverage and quality
|
|
||||||
- Identify testing gaps and pain points
|
|
||||||
- Evaluate current tools and frameworks
|
|
||||||
- Assess team testing maturity and skills
|
|
||||||
|
|
||||||
## Test Strategy Definition
|
|
||||||
- Define testing standards and guidelines
|
|
||||||
- Establish coverage targets and quality metrics
|
|
||||||
- Design test data management approach
|
|
||||||
- Plan testing tool consolidation/migration
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Test Infrastructure Setup
|
|
||||||
```markdown
|
|
||||||
## Framework Configuration
|
|
||||||
- Set up testing frameworks and dependencies
|
|
||||||
- Configure test runners and execution environments
|
|
||||||
- Implement test data factories and utilities
|
|
||||||
- Set up reporting and metrics collection
|
|
||||||
|
|
||||||
## CI/CD Integration
|
|
||||||
- Embed tests in build pipelines
|
|
||||||
- Configure parallel test execution
|
|
||||||
- Set up test result reporting
|
|
||||||
- Implement quality gate enforcement
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Test Implementation Patterns
|
|
||||||
```markdown
|
|
||||||
## Unit Test Structure
|
|
||||||
```javascript
|
|
||||||
describe('UserService', () => {
|
|
||||||
let userService, mockUserRepository;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockUserRepository = createMockRepository();
|
|
||||||
userService = new UserService(mockUserRepository);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createUser', () => {
|
|
||||||
it('should create user with valid data', async () => {
|
|
||||||
// Arrange
|
|
||||||
const userData = UserTestDataBuilder.validUser().build();
|
|
||||||
mockUserRepository.save.mockResolvedValue(userData);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const result = await userService.createUser(userData);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(result).toMatchObject(userData);
|
|
||||||
expect(mockUserRepository.save).toHaveBeenCalledWith(userData);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw validation error for invalid email', async () => {
|
|
||||||
// Arrange
|
|
||||||
const invalidUser = UserTestDataBuilder.validUser()
|
|
||||||
.withEmail('invalid-email').build();
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
await expect(userService.createUser(invalidUser))
|
|
||||||
.rejects.toThrow(ValidationError);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integration Test Example
|
|
||||||
```javascript
|
|
||||||
describe('User API Integration', () => {
|
|
||||||
let app, testDb;
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
testDb = await setupTestDatabase();
|
|
||||||
app = createTestApp(testDb);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await testDb.cleanup();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /users', () => {
|
|
||||||
it('should create user and return 201', async () => {
|
|
||||||
const userData = TestDataFactory.createUserData();
|
|
||||||
|
|
||||||
const response = await request(app)
|
|
||||||
.post('/users')
|
|
||||||
.send(userData)
|
|
||||||
.expect(201);
|
|
||||||
|
|
||||||
expect(response.body).toHaveProperty('id');
|
|
||||||
expect(response.body.email).toBe(userData.email);
|
|
||||||
|
|
||||||
// Verify database state
|
|
||||||
const savedUser = await testDb.users.findById(response.body.id);
|
|
||||||
expect(savedUser).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Advanced Testing Patterns
|
|
||||||
```markdown
|
|
||||||
## Contract Testing
|
|
||||||
```javascript
|
|
||||||
// Consumer test
|
|
||||||
const { Pact } = require('@pact-foundation/pact');
|
|
||||||
const UserApiClient = require('../user-api-client');
|
|
||||||
|
|
||||||
describe('User API Contract', () => {
|
|
||||||
const provider = new Pact({
|
|
||||||
consumer: 'UserService',
|
|
||||||
provider: 'UserAPI'
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeAll(() => provider.setup());
|
|
||||||
afterAll(() => provider.finalize());
|
|
||||||
|
|
||||||
it('should get user by ID', async () => {
|
|
||||||
await provider.addInteraction({
|
|
||||||
state: 'user exists',
|
|
||||||
uponReceiving: 'a request for user',
|
|
||||||
withRequest: {
|
|
||||||
method: 'GET',
|
|
||||||
path: '/users/1'
|
|
||||||
},
|
|
||||||
willRespondWith: {
|
|
||||||
status: 200,
|
|
||||||
body: { id: 1, name: 'John Doe' }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const client = new UserApiClient(provider.mockService.baseUrl);
|
|
||||||
const user = await client.getUser(1);
|
|
||||||
expect(user.name).toBe('John Doe');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Testing
|
|
||||||
```javascript
|
|
||||||
import { check } from 'k6';
|
|
||||||
import http from 'k6/http';
|
|
||||||
|
|
||||||
export let options = {
|
|
||||||
stages: [
|
|
||||||
{ duration: '2m', target: 100 },
|
|
||||||
{ duration: '5m', target: 100 },
|
|
||||||
{ duration: '2m', target: 200 },
|
|
||||||
{ duration: '5m', target: 200 },
|
|
||||||
{ duration: '2m', target: 0 }
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function() {
|
|
||||||
const response = http.get('https://api.example.com/users');
|
|
||||||
check(response, {
|
|
||||||
'status is 200': (r) => r.status === 200,
|
|
||||||
'response time < 500ms': (r) => r.timings.duration < 500
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quality Assurance Practices
|
|
||||||
|
|
||||||
### Test Coverage & Metrics
|
|
||||||
- **Coverage Types**: Line, branch, condition, path coverage analysis
|
|
||||||
- **Mutation Testing**: Verify test quality through code mutation
|
|
||||||
- **Code Quality Integration**: SonarQube, ESLint, static analysis integration
|
|
||||||
- **Performance Baselines**: Establish and monitor performance regression thresholds
|
|
||||||
|
|
||||||
### Test Maintenance & Evolution
|
|
||||||
- **Refactoring Tests**: Keep tests maintainable alongside production code
|
|
||||||
- **Test Debt Management**: Identify and address technical debt in test suites
|
|
||||||
- **Documentation**: Living documentation through executable specifications
|
|
||||||
- **Knowledge Sharing**: Test strategy documentation and team training
|
|
||||||
|
|
||||||
### Continuous Improvement
|
|
||||||
- **Metrics Tracking**: Test execution time, flakiness, coverage trends
|
|
||||||
- **Feedback Loops**: Regular retrospectives on testing effectiveness
|
|
||||||
- **Tool Evaluation**: Stay current with testing technology and best practices
|
|
||||||
- **Process Optimization**: Continuously improve testing workflows and efficiency
|
|
||||||
|
|
||||||
## Tools & Technologies
|
|
||||||
|
|
||||||
### Testing Frameworks
|
|
||||||
- **JavaScript**: Jest, Mocha, Jasmine, Vitest
|
|
||||||
- **Python**: pytest, unittest, nose2
|
|
||||||
- **Java**: JUnit, TestNG, Spock
|
|
||||||
- **C#**: NUnit, xUnit, MSTest
|
|
||||||
- **Ruby**: RSpec, Minitest
|
|
||||||
|
|
||||||
### Automation Tools
|
|
||||||
- **Web**: Playwright, Cypress, Selenium WebDriver
|
|
||||||
- **Mobile**: Appium, Detox, Espresso, XCUITest
|
|
||||||
- **API**: Postman, Insomnia, REST Assured
|
|
||||||
- **Performance**: k6, JMeter, Gatling, Artillery
|
|
||||||
|
|
||||||
### CI/CD Integration
|
|
||||||
- **GitHub Actions**: Workflow automation and matrix testing
|
|
||||||
- **Jenkins**: Pipeline as code and distributed testing
|
|
||||||
- **GitLab CI**: Integrated testing and deployment
|
|
||||||
- **Azure DevOps**: Test plans and automated testing
|
|
||||||
|
|
||||||
## Best Practices & Guidelines
|
|
||||||
|
|
||||||
### Test Design Principles
|
|
||||||
1. **Independent**: Tests should not depend on each other
|
|
||||||
2. **Repeatable**: Consistent results across environments
|
|
||||||
3. **Fast**: Quick feedback loops for development
|
|
||||||
4. **Self-Validating**: Clear pass/fail without manual interpretation
|
|
||||||
5. **Timely**: Written close to production code development
|
|
||||||
|
|
||||||
### Quality Gates
|
|
||||||
- **Code Coverage**: Minimum thresholds with meaningful metrics
|
|
||||||
- **Performance**: Response time and resource utilization limits
|
|
||||||
- **Security**: Automated vulnerability scanning integration
|
|
||||||
- **Compatibility**: Cross-browser and device testing requirements
|
|
||||||
|
|
||||||
### Team Collaboration
|
|
||||||
- **Shared Responsibility**: Everyone owns test quality
|
|
||||||
- **Knowledge Transfer**: Documentation and pair testing
|
|
||||||
- **Tool Standardization**: Consistent tooling across projects
|
|
||||||
- **Continuous Learning**: Stay updated with testing innovations
|
|
||||||
|
|
||||||
## Deliverables
|
|
||||||
|
|
||||||
### Initial Setup
|
|
||||||
- Test strategy document and implementation roadmap
|
|
||||||
- Testing framework configuration and setup
|
|
||||||
- CI/CD pipeline integration with quality gates
|
|
||||||
- Test data management strategy and implementation
|
|
||||||
|
|
||||||
### Ongoing Support
|
|
||||||
- Test suite maintenance and optimization
|
|
||||||
- Performance monitoring and improvement recommendations
|
|
||||||
- Team training and knowledge transfer
|
|
||||||
- Tool evaluation and migration planning
|
|
||||||
|
|
||||||
### Reporting & Analytics
|
|
||||||
- Test coverage reports and trend analysis
|
|
||||||
- Quality metrics dashboard and alerting
|
|
||||||
- Performance benchmarking and regression detection
|
|
||||||
- Testing ROI analysis and recommendations
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
### Quality Indicators
|
|
||||||
- **Defect Detection Rate**: Percentage of bugs caught before production
|
|
||||||
- **Test Coverage**: Meaningful coverage metrics across code paths
|
|
||||||
- **Build Stability**: Reduction in build failures and flaky tests
|
|
||||||
- **Release Confidence**: Faster, more reliable deployments
|
|
||||||
|
|
||||||
### Efficiency Measures
|
|
||||||
- **Test Execution Time**: Optimized feedback loops
|
|
||||||
- **Maintenance Overhead**: Sustainable test suite growth
|
|
||||||
- **Developer Productivity**: Reduced debugging time and context switching
|
|
||||||
- **Cost Optimization**: Testing ROI and resource utilization
|
|
||||||
|
|
||||||
This template provides comprehensive guidance for implementing robust testing strategies that ensure high-quality software delivery through automated testing, continuous integration, and quality assurance best practices.
|
|
@ -18,6 +18,7 @@ classifiers = [
|
|||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastmcp>=2.12.2",
|
"fastmcp>=2.12.2",
|
||||||
|
"fastapi>=0.116.1",
|
||||||
"pydantic>=2.11.7",
|
"pydantic>=2.11.7",
|
||||||
"httpx>=0.27.0",
|
"httpx>=0.27.0",
|
||||||
"sqlalchemy>=2.0.43",
|
"sqlalchemy>=2.0.43",
|
||||||
@ -26,6 +27,7 @@ dependencies = [
|
|||||||
"structlog>=24.4.0",
|
"structlog>=24.4.0",
|
||||||
"tenacity>=8.2.3",
|
"tenacity>=8.2.3",
|
||||||
"typing-extensions>=4.12.0",
|
"typing-extensions>=4.12.0",
|
||||||
|
"uvicorn>=0.35.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
@ -51,6 +51,7 @@ class Settings(BaseSettings):
|
|||||||
env_file = ".env"
|
env_file = ".env"
|
||||||
env_file_encoding = "utf-8"
|
env_file_encoding = "utf-8"
|
||||||
case_sensitive = False
|
case_sensitive = False
|
||||||
|
extra = "ignore" # Allow extra fields from .env file
|
||||||
|
|
||||||
def __init__(self, **data):
|
def __init__(self, **data):
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import uuid
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
@ -20,7 +21,6 @@ from sqlalchemy import (
|
|||||||
create_engine,
|
create_engine,
|
||||||
func,
|
func,
|
||||||
)
|
)
|
||||||
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.orm import sessionmaker, Session
|
from sqlalchemy.orm import sessionmaker, Session
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ class CacheEntryDB(Base):
|
|||||||
|
|
||||||
__tablename__ = "api_cache"
|
__tablename__ = "api_cache"
|
||||||
|
|
||||||
id = Column(PG_UUID(as_uuid=True), primary_key=True)
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||||
cache_key = Column(String(255), unique=True, nullable=False)
|
cache_key = Column(String(255), unique=True, nullable=False)
|
||||||
response_data = Column(JSON, nullable=False)
|
response_data = Column(JSON, nullable=False)
|
||||||
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
|
created_at = Column(DateTime(timezone=True), default=datetime.utcnow)
|
||||||
@ -58,7 +58,7 @@ class RateLimitDB(Base):
|
|||||||
|
|
||||||
__tablename__ = "rate_limits"
|
__tablename__ = "rate_limits"
|
||||||
|
|
||||||
id = Column(PG_UUID(as_uuid=True), primary_key=True)
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||||
identifier = Column(String(255), nullable=False)
|
identifier = Column(String(255), nullable=False)
|
||||||
endpoint = Column(String(255), nullable=False)
|
endpoint = Column(String(255), nullable=False)
|
||||||
requests_count = Column(Integer, default=0)
|
requests_count = Column(Integer, default=0)
|
||||||
@ -71,7 +71,7 @@ class ApiUsageDB(Base):
|
|||||||
|
|
||||||
__tablename__ = "api_usage"
|
__tablename__ = "api_usage"
|
||||||
|
|
||||||
id = Column(PG_UUID(as_uuid=True), primary_key=True)
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||||
endpoint = Column(String(255), nullable=False)
|
endpoint = Column(String(255), nullable=False)
|
||||||
request_data = Column(JSON)
|
request_data = Column(JSON)
|
||||||
response_status = Column(Integer)
|
response_status = Column(Integer)
|
||||||
@ -85,7 +85,7 @@ class UserConfirmationDB(Base):
|
|||||||
|
|
||||||
__tablename__ = "user_confirmations"
|
__tablename__ = "user_confirmations"
|
||||||
|
|
||||||
id = Column(PG_UUID(as_uuid=True), primary_key=True)
|
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||||
parameter_hash = Column(String(255), unique=True, nullable=False)
|
parameter_hash = Column(String(255), unique=True, nullable=False)
|
||||||
confirmed = Column(Boolean, default=False)
|
confirmed = Column(Boolean, default=False)
|
||||||
confirmed_at = Column(DateTime(timezone=True))
|
confirmed_at = Column(DateTime(timezone=True))
|
||||||
|
@ -9,7 +9,13 @@ from typing import Any, Dict, List, Optional
|
|||||||
|
|
||||||
import structlog
|
import structlog
|
||||||
from fastmcp import FastMCP
|
from fastmcp import FastMCP
|
||||||
from fastmcp.elicitation import request_user_input
|
try:
|
||||||
|
from fastmcp.elicitation import request_user_input
|
||||||
|
except ImportError:
|
||||||
|
# Elicitation may not be available in all FastMCP versions
|
||||||
|
async def request_user_input(prompt: str, title: str = ""):
|
||||||
|
# Fallback implementation - just return a message indicating confirmation needed
|
||||||
|
return "confirmed"
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from .config import settings
|
from .config import settings
|
||||||
@ -46,7 +52,7 @@ structlog.configure(
|
|||||||
logger = structlog.get_logger()
|
logger = structlog.get_logger()
|
||||||
|
|
||||||
# Create FastMCP app
|
# Create FastMCP app
|
||||||
app = FastMCP("mcrentcast", description="Rentcast API MCP Server with intelligent caching and rate limiting")
|
app = FastMCP("mcrentcast")
|
||||||
|
|
||||||
# Request/Response models for MCP tools
|
# Request/Response models for MCP tools
|
||||||
class SetApiKeyRequest(BaseModel):
|
class SetApiKeyRequest(BaseModel):
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
import pytest
|
import pytest
|
||||||
|
import pytest_asyncio
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
@ -11,13 +12,13 @@ os.environ["USE_MOCK_API"] = "true"
|
|||||||
os.environ["MOCK_API_URL"] = "http://localhost:8001/v1"
|
os.environ["MOCK_API_URL"] = "http://localhost:8001/v1"
|
||||||
os.environ["RENTCAST_API_KEY"] = "test_key_basic"
|
os.environ["RENTCAST_API_KEY"] = "test_key_basic"
|
||||||
|
|
||||||
from src.mcrentcast.config import settings
|
from mcrentcast.config import settings
|
||||||
from src.mcrentcast.rentcast_client import RentcastClient, RateLimitExceeded, RentcastAPIError
|
from mcrentcast.rentcast_client import RentcastClient, RateLimitExceeded, RentcastAPIError
|
||||||
from src.mcrentcast.database import DatabaseManager
|
from mcrentcast.database import DatabaseManager
|
||||||
from src.mcrentcast.mock_api import mock_app, TEST_API_KEYS
|
from mcrentcast.mock_api import mock_app, TEST_API_KEYS
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest_asyncio.fixture
|
||||||
async def mock_api_server():
|
async def mock_api_server():
|
||||||
"""Start mock API server for testing."""
|
"""Start mock API server for testing."""
|
||||||
import uvicorn
|
import uvicorn
|
||||||
@ -39,7 +40,7 @@ async def mock_api_server():
|
|||||||
# Server will stop when thread ends
|
# Server will stop when thread ends
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest_asyncio.fixture
|
||||||
async def client():
|
async def client():
|
||||||
"""Create Rentcast client for testing."""
|
"""Create Rentcast client for testing."""
|
||||||
settings.use_mock_api = True
|
settings.use_mock_api = True
|
||||||
@ -49,7 +50,7 @@ async def client():
|
|||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest_asyncio.fixture
|
||||||
async def db_manager():
|
async def db_manager():
|
||||||
"""Create database manager for testing."""
|
"""Create database manager for testing."""
|
||||||
# Use in-memory SQLite for tests
|
# Use in-memory SQLite for tests
|
||||||
@ -83,7 +84,7 @@ async def test_property_search(mock_api_server, client, db_manager):
|
|||||||
async def test_caching_behavior(mock_api_server, client, db_manager):
|
async def test_caching_behavior(mock_api_server, client, db_manager):
|
||||||
"""Test that responses are cached properly."""
|
"""Test that responses are cached properly."""
|
||||||
# Patch db_manager in client module
|
# Patch db_manager in client module
|
||||||
with patch("src.mcrentcast.rentcast_client.db_manager", db_manager):
|
with patch("mcrentcast.rentcast_client.db_manager", db_manager):
|
||||||
# First request - should not be cached
|
# First request - should not be cached
|
||||||
properties1, is_cached1, cache_age1 = await client.get_property_records(
|
properties1, is_cached1, cache_age1 = await client.get_property_records(
|
||||||
city="Dallas", state="TX", limit=3
|
city="Dallas", state="TX", limit=3
|
||||||
@ -247,7 +248,7 @@ async def test_random_properties(mock_api_server, client):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_cache_expiration(mock_api_server, db_manager):
|
async def test_cache_expiration(mock_api_server, db_manager):
|
||||||
"""Test cache expiration and cleanup."""
|
"""Test cache expiration and cleanup."""
|
||||||
with patch("src.mcrentcast.rentcast_client.db_manager", db_manager):
|
with patch("mcrentcast.rentcast_client.db_manager", db_manager):
|
||||||
client = RentcastClient(api_key="test_key_basic")
|
client = RentcastClient(api_key="test_key_basic")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -309,7 +310,7 @@ async def test_pagination(mock_api_server, client):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_usage_tracking(mock_api_server, db_manager):
|
async def test_api_usage_tracking(mock_api_server, db_manager):
|
||||||
"""Test API usage tracking."""
|
"""Test API usage tracking."""
|
||||||
with patch("src.mcrentcast.rentcast_client.db_manager", db_manager):
|
with patch("mcrentcast.rentcast_client.db_manager", db_manager):
|
||||||
client = RentcastClient(api_key="test_key_basic")
|
client = RentcastClient(api_key="test_key_basic")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user