import { test, expect } from '@playwright/test'; test.describe('Tema Claro/Escuro', () => { test('deve ter toggle de tema visível', async ({ page }) => { await page.goto('/'); const themeToggle = page.getByRole('button').filter({ hasText: /sol|lua|theme/i }); await expect(themeToggle).toBeVisible(); }); test('deve alternar para tema escuro', async ({ page }) => { await page.goto('/'); const themeToggle = page.getByRole('button').filter({ hasText: /sol/i }).first(); if (await themeToggle.isVisible()) { await themeToggle.click(); // Verificar se adicionou classe dark const isDark = await page.evaluate(() => document.documentElement.classList.contains('dark')); expect(isDark).toBe(true); } }); test('deve alternar para tema claro', async ({ page }) => { await page.goto('/'); // Primeiro mudar para escuro await page.evaluate(() => document.documentElement.classList.add('dark')); const themeToggle = page.getByRole('button').filter({ hasText: /lua/i }).first(); if (await themeToggle.isVisible()) { await themeToggle.click(); // Verificar se removeu classe dark const isDark = await page.evaluate(() => document.documentElement.classList.contains('dark')); expect(isDark).toBe(false); } }); test('deve persistir preferência de tema após refresh', async ({ page }) => { await page.goto('/'); // Mudar para escuro await page.evaluate(() => document.documentElement.classList.add('dark')); await page.reload(); // Verificar se continua escuro const isDark = await page.evaluate(() => document.documentElement.classList.contains('dark')); expect(isDark).toBe(true); }); test('formulários funcionam em ambos os temas', async ({ page }) => { await page.goto('/auth/register'); // Testar tema claro await page.getByLabel(/nome/i).fill('Teste'); await expect(page.getByLabel(/nome/i)).toHaveValue('Teste'); // Mudar para escuro await page.evaluate(() => document.documentElement.classList.add('dark')); // Campo deve continuar funcional await page.getByLabel(/nome/i).fill(' Teste Atualizado'); await expect(page.getByLabel(/nome/i)).toHaveValue('Teste Teste Atualizado'); }); test('textos legíveis em tema claro', async ({ page }) => { await page.goto('/'); // Verificar contraste usando getComputedStyle const bodyColor = await page.evaluate(() => { const styles = window.getComputedStyle(document.body); return styles.color; }); // Cor não deve ser muito clara (rgb deve ter valores baixos para bom contraste em fundo branco) expect(bodyColor).toMatch(/rgb\((19[0-9]|2[0-4][0-9]|255)/); }); test('textos legíveis em tema escuro', async ({ page }) => { await page.goto('/'); await page.evaluate(() => document.documentElement.classList.add('dark')); const bodyColor = await page.evaluate(() => { const styles = window.getComputedStyle(document.body); return styles.color; }); // Cor deve ser clara (rgb deve ter valores altos para bom contraste em fundo escuro) expect(bodyColor).toMatch(/rgb\((19[0-9]|2[0-4][0-9]|255)/); }); }); test.describe('Responsividade - Mobile', () => { test.describe('320px - Muito pequeno', () => { test('deve ajustar layout', async ({ page }) => { await page.setViewportSize({ width: 320, height: 568 }); await page.goto('/'); // Conteúdo deve estar visível sem scroll horizontal const hasHorizontalScroll = await page.evaluate(() => { return document.body.scrollWidth > document.body.clientWidth; }); expect(hasHorizontalScroll).toBe(false); }); }); test.describe('375px - iPhone SE', () => { test.beforeEach(async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); }); test('menu deve ser responsivo', async ({ page }) => { await page.goto('/'); const nav = page.locator('nav'); await expect(nav).toBeVisible(); }); test('cards devem se ajustar', async ({ page }) => { await page.goto('/dashboard/student'); // Cards devem estar em coluna única const cardsContainer = page.locator('.grid, .flex').first(); if (await cardsContainer.isVisible()) { const gridCols = await cardsContainer.evaluate(el => { return window.getComputedStyle(el).gridTemplateColumns; }); // Deve ter 1 coluna em mobile expect(gridCols || '1fr').toContain('1fr'); } }); }); test.describe('768px - Tablet', () => { test.beforeEach(async ({ page }) => { await page.setViewportSize({ width: 768, height: 1024 }); }); test('tabelas com scroll horizontal', async ({ page }) => { await page.goto('/admin/dashboard/payments'); const table = page.locator('table').first(); if (await table.isVisible()) { // Tabela deve estar em container com overflow const container = await table.locator('..').evaluate(el => { return window.getComputedStyle(el).overflowX; }); expect(container || 'auto').toMatch(/auto|scroll/); } }); test('cards podem estar em 2 colunas', async ({ page }) => { await page.goto('/dashboard/student'); const cardsContainer = page.locator('.grid, .flex').first(); if (await cardsContainer.isVisible()) { // Pode ter 2 colunas em tablet await expect(cardsContainer).toBeVisible(); } }); }); }); test.describe('Modais', () => { test('deve abrir e fechar corretamente', async ({ page }) => { await page.goto('/admin/dashboard/class/edit/6975e7cfb41e291a99a79ece'); // Não há modais nessa página específica, mas o padrão é: // await page.getByRole('button', { name: /editar/i }).click(); // await expect(page.locator('.fixed.inset-0')).toBeVisible(); // Fechar com botão // await page.getByRole('button', { name: /✕|fechar|cancelar/i }).click(); // await expect(page.locator('.fixed.inset-0').not.toBeVisible(); }); test('deve fechar ao clicar fora', async ({ page }) => { // Abrir modal // await page.getByRole('button', { name: /abrir/i }).click(); // await expect(page.locator('.fixed.inset-0')).toBeVisible(); // Clicar no backdrop (fora do modal) // const modal = page.locator('.bg-white.dark\\:bg-neutral-900'); // const modalBox = await modal.boundingBox(); // await page.mouse.click(modalBox.x - 10, modalBox.y); // await expect(page.locator('.fixed.inset-0').not.toBeVisible(); }); test('deve funcionar em mobile', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.goto('/admin/dashboard/payments/by-class'); // Modal deve ocupar tela cheia em mobile // await page.getByRole('button', { name: /ver/i }).click(); // const modal = page.locator('.fixed.inset-0'); // await expect(modal).toBeVisible(); // Deve ter largura total em mobile // const modalWidth = await modal.evaluate(el => el.offsetWidth); // expect(modalWidth).toBe(375); }); }); test.describe('Hover States', () => { test('hover-card-light deve ser sutil', async ({ page }) => { await page.goto('/admin/dashboard/exam-templates'); const card = page.locator('.hover-card-light').first(); if (await card.isVisible()) { await card.hover(); // Verificar cor de fundo após hover const bgColor = await card.evaluate(el => { return window.getComputedStyle(el).backgroundColor; }); // Deve ser uma cor clara (rgb alto) expect(bgColor).toMatch(/rgb\((24[0-9]|25[0-5])/); // 240-255 } }); test('botões devem ter hover funcionando', async ({ page }) => { await page.goto('/'); const button = page.getByRole('link', { name: 'Home' }).first(); await button.hover(); // Deve mudar cor ou estilo const hasHoverStyle = await button.evaluate(el => { return el.matches(':hover'); }); expect(hasHoverStyle).toBe(true); }); }); test.describe('Loading States', () => { test('deve mostrar indicador de carregamento', async ({ page }) => { await page.goto('/admin/dashboard/class/add'); // Preencher formulário await page.getByLabel(/título/i).fill('Turma Teste Loading'); await page.getByRole('button', { name: /salvar/i }).click(); // Pode mostrar loading ou redirecionar rapidamente // await expect(page.getByText(/salvando|carregando/i)).toBeVisible({ timeout: 2000 }); }); }); test.describe('Formulários - Validação Visual', () => { test('campos obrigatórios devem ter indicador visual', async ({ page }) => { await page.goto('/auth/register'); const requiredFields = page.locator('input[required], select[required]'); const count = await requiredFields.count(); expect(count).toBeGreaterThan(0); }); test('campos com erro devem ter borda vermelha', async ({ page }) => { await page.goto('/auth/register'); await page.getByRole('button', { name: /cadastrar/i }).click(); const errorInput = page.locator('input').first(); const borderColor = await errorInput.evaluate(el => { return window.getComputedStyle(el).borderColor; }); // Deve ser vermelha expect(borderColor).toMatch(/rgb\(2(?:[0-4]\d|5[0-5])|#[ef]/); // Vermelho }); });