veza/apps/web/e2e/tests/ui-audit.spec.ts

93 lines
3.6 KiB
TypeScript

import { test, expect, Page } from '@playwright/test';
import { loginAsUser, TEST_CONFIG } from '../utils/test-helpers';
/**
* UI/UX Dynamic Audit Suite
* Scans pages for:
* - Interactive elements that are too small (< 44x44px)
* - Console errors
* - Broken images/links
* - Overflow issues
*/
const PAGES_TO_AUDIT = [
'/dashboard',
'/library',
'/marketplace',
'/settings',
'/profile/me',
'/studio',
'/messages' // Often problematic
];
test.describe('Dynamic UI/UX Audit', () => {
let consoleErrors: string[] = [];
test.beforeEach(async ({ page }) => {
// Capture console errors
page.on('console', msg => {
if (msg.type() === 'error') consoleErrors.push(`[${page.url()}] ${msg.text()}`);
});
// Login once
await loginAsUser(page);
});
for (const path of PAGES_TO_AUDIT) {
test(`Audit page: ${path}`, async ({ page }) => {
console.log(`\n🔍 Auditing ${path}...`);
await page.goto(`${TEST_CONFIG.FRONTEND_URL}${path}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000); // Allow animations/layout to settle
// 1. Check for Console Errors
if (consoleErrors.length > 0) {
console.log(`⚠️ Console Errors on ${path}:`, consoleErrors);
consoleErrors = []; // Reset for next check logic (though beforeeach resets too, strictly speaking this is shared scope in this loop impl if not careful, but playwright isolates tests)
}
// 2. Interactive Element Sizing (Mobile Friendly Check)
// Find all buttons and anchors
const interactiveElements = await page.locator('button:visible, a:visible, [role="button"]:visible').all();
let smallTargets = 0;
for (const el of interactiveElements) {
const box = await el.boundingBox();
if (box) {
// Check if smaller than 44px in either dimension (Apple guidelines)
// We allow smaller if it's strictly an icon-only button inside a toolbar, but warn generally
if (box.width < 32 || box.height < 32) { // 32 is lenient, 44 is ideal
const html = await el.evaluate(e => e.outerHTML);
// console.log(`⚠️ Small touch target (${Math.round(box.width)}x${Math.round(box.height)}):`, html.substring(0, 100));
smallTargets++;
}
}
}
if (smallTargets > 0) {
console.log(`⚠️ Found ${smallTargets} interactive elements smaller than 32x32px on ${path}`);
}
// 3. Overflow Detection
const hasHorizontalScroll = await page.evaluate(() => {
return document.body.scrollWidth > window.innerWidth;
});
if (hasHorizontalScroll) {
console.log(`🔴 Layout Issue: Horizontal scroll detected on ${path}`);
}
// 4. Broken Image Detection
const images = await page.locator('img').all();
for (const img of images) {
const isBroken = await img.evaluate((i: HTMLImageElement) => {
return !i.complete || i.naturalWidth === 0;
});
if (isBroken) {
const src = await img.getAttribute('src');
console.log(`🔴 Broken Image: ${src}`);
}
}
console.log(`✅ Audit complete for ${path}`);
});
}
});