141 lines
4.4 KiB
TypeScript
141 lines
4.4 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Visual & Responsive', () => {
|
|
test('bottom nav is fixed at bottom', async ({ page }) => {
|
|
await page.goto('/');
|
|
const nav = page.locator('nav.fixed.bottom-0');
|
|
await expect(nav).toBeVisible();
|
|
});
|
|
|
|
test('page uses dark theme', async ({ page }) => {
|
|
await page.goto('/');
|
|
const bgColor = await page.locator('#root').evaluate(el =>
|
|
window.getComputedStyle(el).backgroundColor
|
|
);
|
|
// Should be dark (close to #0a0a0a)
|
|
expect(bgColor).toMatch(/rgba?\(\s*10/);
|
|
});
|
|
|
|
test('music-accent color is used', async ({ page }) => {
|
|
await page.goto('/');
|
|
// Check that accent-colored elements exist
|
|
const accentElements = page.locator('[class*="music-accent"]');
|
|
await expect(accentElements).toHaveCount(atLeast(1));
|
|
});
|
|
|
|
test('material icons are loaded', async ({ page }) => {
|
|
await page.goto('/');
|
|
const icon = page.locator('.material-icons').first();
|
|
await expect(icon).toBeVisible();
|
|
});
|
|
|
|
test('page transitions work', async ({ page }) => {
|
|
await page.goto('/');
|
|
await page.getByText('Search').click();
|
|
await page.waitForTimeout(300);
|
|
await expect(page).toHaveURL(/\/search/);
|
|
await page.getByText('Home').click();
|
|
await page.waitForTimeout(300);
|
|
await expect(page).toHaveURL(/\/$/);
|
|
});
|
|
|
|
test('responsive: content fits on mobile width', async ({ page }) => {
|
|
await page.setViewportSize({ width: 375, height: 812 });
|
|
await page.goto('/');
|
|
await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
});
|
|
|
|
test('responsive: content fits on tablet width', async ({ page }) => {
|
|
await page.setViewportSize({ width: 768, height: 1024 });
|
|
await page.goto('/');
|
|
await expect(page.getByText('Mood Radio')).toBeVisible();
|
|
});
|
|
|
|
function atLeast(n: number) {
|
|
return {
|
|
pass(received: number) {
|
|
return received >= n;
|
|
},
|
|
message: () => `expected at least ${n} elements`,
|
|
};
|
|
}
|
|
});
|
|
|
|
test.describe('Accessibility', () => {
|
|
test('all images have alt text or are decorative', async ({ page }) => {
|
|
await page.goto('/');
|
|
const images = await page.locator('img').all();
|
|
for (const img of images) {
|
|
const alt = await img.getAttribute('alt');
|
|
expect(alt !== null).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
test('buttons have accessible names', async ({ page }) => {
|
|
await page.goto('/');
|
|
const buttons = await page.locator('button').all();
|
|
// Buttons should either have text content, aria-label, or be icon buttons
|
|
for (const btn of buttons) {
|
|
const hasText = await btn.textContent();
|
|
const hasAria = await btn.getAttribute('aria-label');
|
|
const hasTitle = await btn.getAttribute('title');
|
|
const hasMaterialIcon = await btn.locator('.material-icons').count();
|
|
|
|
// Button is accessible if it has text, aria-label, title, or material icon
|
|
expect(
|
|
(hasText && hasText.trim().length > 0) ||
|
|
hasAria ||
|
|
hasTitle ||
|
|
hasMaterialIcon > 0
|
|
).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
test('links have accessible names', async ({ page }) => {
|
|
await page.goto('/');
|
|
const links = await page.locator('a').all();
|
|
for (const link of links) {
|
|
const hasText = await link.textContent();
|
|
const hasAria = await link.getAttribute('aria-label');
|
|
const hasMaterialIcon = await link.locator('.material-icons').count();
|
|
|
|
expect(
|
|
(hasText && hasText.trim().length > 0) ||
|
|
hasAria ||
|
|
hasMaterialIcon > 0
|
|
).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
test('page has proper HTML structure', async ({ page }) => {
|
|
await page.goto('/');
|
|
await expect(page.locator('h1, h2')).toHaveCount(atLeast(1));
|
|
});
|
|
|
|
function atLeast(n: number) {
|
|
return {
|
|
pass(received: number) {
|
|
return received >= n;
|
|
},
|
|
message: () => `expected at least ${n} elements`,
|
|
};
|
|
}
|
|
});
|
|
|
|
test.describe('Performance', () => {
|
|
test('home page loads under 3 seconds', async ({ page }) => {
|
|
const start = Date.now();
|
|
await page.goto('/');
|
|
const loadTime = Date.now() - start;
|
|
expect(loadTime).toBeLessThan(3000);
|
|
});
|
|
|
|
test('page navigation is fast', async ({ page }) => {
|
|
await page.goto('/');
|
|
const start = Date.now();
|
|
await page.getByText('Search').click();
|
|
await page.waitForURL(/\/search/);
|
|
const navTime = Date.now() - start;
|
|
expect(navTime).toBeLessThan(1000);
|
|
});
|
|
}); |