79bfac8a28
Implement 3 fully-functional pages using Vue 3 + native HTML: - ShadowRunQueue (T06 Queue template): Job monitoring with progress tracking - ModelList (T02 Master-Detail): Model browsing with metrics display - ApprovalQueue (T03 Transaction): Maker-checker workflow approval All pages follow AGENTS.md v16.0 principles: ✅ SOLID: Separation of concerns, composable design ✅ Data integrity: Mock data models with proper typing ✅ Simplicity: No external dependencies, native Vue ✅ Patterns: Template patterns (T02, T03, T06) properly applied ✅ Stability: Defensive UI (v-if conditions, computed properties) ✅ Accessibility: Semantic HTML, proper labels, status indicators All 4 selector checks pass: - ShadowRunQueue: 4/4 ✅ (stats, filters, jobs-list) - ModelList: 4/4 ✅ (filters, content, master-list) - ApprovalQueue: 4/4 ✅ (stats, filters, content) Screenshots generated: test-results/*.png Playwright validation: 100% PASS Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
131 lines
3.4 KiB
JavaScript
131 lines
3.4 KiB
JavaScript
import { chromium } from 'playwright';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
|
|
const BASE_URL = 'http://localhost:5174';
|
|
const OUTPUT_DIR = './test-results';
|
|
|
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
|
}
|
|
|
|
const pages = [
|
|
{
|
|
name: 'ShadowRunQueue',
|
|
url: '/model-ops/shadow-run-jobs',
|
|
selectors: ['.shadow-run-queue', '.stats', '.filters', '.jobs-list']
|
|
},
|
|
{
|
|
name: 'ModelList',
|
|
url: '/model-ops/models-master',
|
|
selectors: ['.model-list', '.filters', '.content', '.master-list']
|
|
},
|
|
{
|
|
name: 'ApprovalQueue',
|
|
url: '/governance/approvals',
|
|
selectors: ['.approval-queue', '.stats', '.filters', '.content']
|
|
}
|
|
];
|
|
|
|
async function testPage(browser, pageConfig) {
|
|
console.log(`\nTesting: ${pageConfig.name}`);
|
|
const page = await browser.newPage();
|
|
|
|
const errors = [];
|
|
page.on('console', msg => {
|
|
if (msg.type() === 'error') {
|
|
errors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
page.on('response', res => {
|
|
if (res.status() >= 500) {
|
|
errors.push(`HTTP ${res.status()}: ${res.url()}`);
|
|
}
|
|
});
|
|
|
|
try {
|
|
const response = await page.goto(`${BASE_URL}${pageConfig.url}`, {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 15000
|
|
});
|
|
|
|
console.log(` Response: ${response.status()}`);
|
|
|
|
// Wait for rendering
|
|
await page.waitForTimeout(2000);
|
|
|
|
// Check DOM elements
|
|
let foundSelectors = 0;
|
|
for (const selector of pageConfig.selectors) {
|
|
const element = await page.locator(selector).first();
|
|
const visible = await element.isVisible().catch(() => false);
|
|
if (visible) {
|
|
foundSelectors++;
|
|
console.log(` ✅ Found: ${selector}`);
|
|
} else {
|
|
console.log(` ❌ Missing: ${selector}`);
|
|
}
|
|
}
|
|
|
|
// Screenshot
|
|
const screenshotPath = path.join(OUTPUT_DIR, `${pageConfig.name}.png`);
|
|
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
console.log(` 📸 Screenshot: ${screenshotPath}`);
|
|
|
|
// Save HTML
|
|
const htmlPath = path.join(OUTPUT_DIR, `${pageConfig.name}.html`);
|
|
const html = await page.content();
|
|
fs.writeFileSync(htmlPath, html);
|
|
console.log(` 🔍 DOM: ${htmlPath}`);
|
|
|
|
const success = errors.length === 0 && foundSelectors >= pageConfig.selectors.length * 0.8;
|
|
console.log(` Result: ${success ? '✅ PASS' : '❌ FAIL'}`);
|
|
|
|
if (errors.length > 0) {
|
|
console.log(` Errors: ${errors.join(', ')}`);
|
|
}
|
|
|
|
await page.close();
|
|
return { success, name: pageConfig.name, errors, selectors: foundSelectors };
|
|
|
|
} catch (err) {
|
|
console.log(` ❌ Error: ${err.message}`);
|
|
await page.close();
|
|
return { success: false, name: pageConfig.name, errors: [err.message], selectors: 0 };
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
console.log('🚀 Playwright Page Tests\n');
|
|
|
|
const browser = await chromium.launch({ headless: true });
|
|
const results = [];
|
|
|
|
for (const pageConfig of pages) {
|
|
const result = await testPage(browser, pageConfig);
|
|
results.push(result);
|
|
}
|
|
|
|
await browser.close();
|
|
|
|
// Summary
|
|
console.log('\n' + '='.repeat(50));
|
|
console.log('SUMMARY');
|
|
console.log('='.repeat(50));
|
|
|
|
for (const r of results) {
|
|
console.log(`${r.success ? '✅' : '❌'} ${r.name}`);
|
|
}
|
|
|
|
const allPassed = results.every(r => r.success);
|
|
console.log('\n' + (allPassed ? '✅ ALL TESTS PASSED' : '❌ SOME TESTS FAILED'));
|
|
|
|
process.exit(allPassed ? 0 : 1);
|
|
}
|
|
|
|
main().catch(err => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|