Initial commit
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Admin - Atribuição de Provas (Assignments)', () => {
|
||||
// TODO: Fazer login como admin
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/dispatcher/);
|
||||
// await page.getByRole('button', { name: /admin/i }).click();
|
||||
// });
|
||||
|
||||
test.describe('Lista de Atribuições', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/assignments');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de atribuições', async ({ page }) => {
|
||||
await expect(page.getByText(/atribuições|assignments/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter botão de criar atribuição', async ({ page }) => {
|
||||
await expect(page.getByRole('button', { name: /nova|criar/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar turma, template e datas', async ({ page }) => {
|
||||
const assignmentRow = page.locator('tr').nth(1);
|
||||
if (await assignmentRow.isVisible()) {
|
||||
await expect(assignmentRow.getByText(/\d{4}-\d{2}-\d{2}/)).toBeVisible(); // Data
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Criar Nova Atribuição', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/assignments/add');
|
||||
});
|
||||
|
||||
test('deve mostrar formulário de criação', async ({ page }) => {
|
||||
await expect(page.getByRole('combobox', { name: /turma|class/i })).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: /template/i })).toBeVisible();
|
||||
await expect(page.getByLabel(/data início|start date/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/data fim|end date/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/tempo limite|time limit/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve listar turmas disponíveis', async ({ page }) => {
|
||||
const classSelect = page.getByRole('combobox', { name: /turma|class/i });
|
||||
const options = await classSelect.locator('option').count();
|
||||
|
||||
expect(options).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('deve listar templates disponíveis', async ({ page }) => {
|
||||
const templateSelect = page.getByRole('combobox', { name: /template/i });
|
||||
const options = await templateSelect.locator('option').count();
|
||||
|
||||
// Pode ser 0 se não houver templates
|
||||
expect(options).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
|
||||
test('deve criar atribuição com dados válidos', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
// Selecionar turma
|
||||
const classSelect = page.getByRole('combobox', { name: /turma|class/i });
|
||||
if ((await classSelect.locator('option').count()) > 0) {
|
||||
await classSelect.selectOption({ index: 0 });
|
||||
}
|
||||
|
||||
// Selecionar template
|
||||
const templateSelect = page.getByRole('combobox', { name: /template/i });
|
||||
if ((await templateSelect.locator('option').count()) > 0) {
|
||||
await templateSelect.selectOption({ index: 0 });
|
||||
}
|
||||
|
||||
// Datas
|
||||
await page.getByLabel(/data início|start date/i).fill('2025-02-01');
|
||||
await page.getByLabel(/data fim|end date/i).fill('2025-02-28');
|
||||
|
||||
// Tempo limite (em minutos)
|
||||
await page.getByLabel(/tempo limite|time limit/i).fill('60');
|
||||
|
||||
await page.getByRole('button', { name: /criar|salvar/i }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/admin\/dashboard\/assignments/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('deve validar data fim posterior a data início', async ({ page }) => {
|
||||
await page.getByLabel(/data início|start date/i).fill('2025-02-15');
|
||||
await page.getByLabel(/data fim|end date/i).fill('2025-02-01');
|
||||
|
||||
await page.getByRole('button', { name: /criar|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/fim.*posterior|data inválida/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve validar campos obrigatórios', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /criar|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/obrigatório|required/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Atribuição por Turma', () => {
|
||||
test('deve pré-selecionar turma se vier da página da turma', async ({ page }) => {
|
||||
// TODO: Testar vindo da página de detalhes da turma
|
||||
// await page.goto('/admin/dashboard/class/edit/class-id');
|
||||
// await page.getByRole('button', { name: /atribuir prova/i }).click();
|
||||
|
||||
// const classSelect = page.getByRole('combobox', { name: /turma/i });
|
||||
// const value = await classSelect.inputValue();
|
||||
// expect(value).toBe('class-id');
|
||||
});
|
||||
|
||||
test('deve mostrar informações da turma', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/assignments/add');
|
||||
|
||||
// Após selecionar turma, pode mostrar info
|
||||
const classSelect = page.getByRole('combobox', { name: /turma/i });
|
||||
if ((await classSelect.locator('option').count()) > 0) {
|
||||
await classSelect.selectOption({ index: 0 });
|
||||
|
||||
// Pode mostrar número de alunos
|
||||
// await expect(page.getByText(/\d+ alunos/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Editar Atribuição', () => {
|
||||
test('deve carregar dados da atribuição', async ({ page }) => {
|
||||
// TODO: Editar atribuição existente
|
||||
await page.goto('/admin/dashboard/assignments/edit/assignment-id');
|
||||
|
||||
await expect(page.getByRole('combobox', { name: /turma/i })).not.toBeEmpty();
|
||||
await expect(page.getByRole('combobox', { name: /template/i })).not.toBeEmpty();
|
||||
});
|
||||
|
||||
test('deve permitir modificar datas e tempo', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/assignments/edit/assignment-id');
|
||||
|
||||
await page.getByLabel(/data início|start date/i).fill('2025-03-01');
|
||||
await page.getByLabel(/data fim|end date/i).fill('2025-03-31');
|
||||
await page.getByLabel(/tempo limite|time limit/i).fill('90');
|
||||
|
||||
await page.getByRole('button', { name: /salvar|atualizar/i }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/admin\/dashboard\/assignments/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('não deve modificar turma da atribuição', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/assignments/edit/assignment-id');
|
||||
|
||||
const classSelect = page.getByRole('combobox', { name: /turma/i });
|
||||
const isDisabled = await classSelect.isDisabled();
|
||||
|
||||
// Geralmente não permite mudar a turma
|
||||
expect(isDisabled).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Excluir Atribuição', () => {
|
||||
test('deve confirmar antes de excluir', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/assignments');
|
||||
|
||||
const deleteButton = page.getByRole('button', { name: /excluir/i }).first();
|
||||
if (await deleteButton.isVisible()) {
|
||||
await deleteButton.click();
|
||||
|
||||
await expect(page.getByText(/tem certeza|deseja excluir/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /confirmar|sim/i })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /cancelar|não/i })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve remover atribuição após excluir', async ({ page }) => {
|
||||
// TODO: Implementar
|
||||
// const beforeCount = await page.locator('[data-testid="assignment-row"]').count();
|
||||
|
||||
// await page.getByRole('button', { name: /excluir/i }).first().click();
|
||||
// await page.getByRole('button', { name: /confirmar/i }).click();
|
||||
|
||||
// await expect(page.locator('[data-testid="assignment-row"]').count()).toBe(beforeCount - 1);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Status da Atribuição', () => {
|
||||
test('deve mostrar status da atribuição', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/assignments');
|
||||
|
||||
// Status possíveis: draft, active, expired
|
||||
const statusBadge = page.locator('[data-testid="status-badge"]').first();
|
||||
if (await statusBadge.isVisible()) {
|
||||
await expect(statusBadge).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar atribuições expiradas separadas', async ({ page }) => {
|
||||
// Pode ter filtro ou aba para expiradas
|
||||
const expiredFilter = page.getByRole('tab', { name: /expiradas/i });
|
||||
if (await expiredFilter.isVisible()) {
|
||||
await expiredFilter.click();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar número de alunos que fizeram a prova', async ({ page }) => {
|
||||
const assignmentRow = page.locator('tr').nth(1);
|
||||
if (await assignmentRow.isVisible()) {
|
||||
await expect(assignmentRow.getByText(/\d+\/\d+/)).toBeVisible(); // Ex: 5/18
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Duplicar Atribuição', () => {
|
||||
test('deve permitir duplicar atribuição existente', async ({ page }) => {
|
||||
// TODO: Testar duplicação
|
||||
// await page.goto('/admin/dashboard/assignments');
|
||||
// await page.getByRole('button', { name: /duplicar/i }).first().click();
|
||||
|
||||
// await expect(page.getByLabel(/título/i)).toContainText('cópia');
|
||||
// await page.getByRole('button', { name: /criar/i }).click();
|
||||
|
||||
// Deve criar nova atribuição com mesmos dados
|
||||
});
|
||||
|
||||
test('deve incrementar nome ao duplicar', async ({ page }) => {
|
||||
// TODO: Testar se nome é incrementado automaticamente
|
||||
// await page.getByRole('button', { name: /duplicar/i }).first().click();
|
||||
|
||||
// const nameField = page.getByLabel(/título/i);
|
||||
// const originalName = await nameField.inputValue();
|
||||
// await expect(originalName).toMatch(/\(2\)|cópia/i);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Visualização do Template', () => {
|
||||
test('deve mostrar preview do template ao selecionar', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/assignments/add');
|
||||
|
||||
const templateSelect = page.getByRole('combobox', { name: /template/i });
|
||||
if ((await templateSelect.locator('option').count()) > 0) {
|
||||
await templateSelect.selectOption({ index: 0 });
|
||||
|
||||
// Pode mostrar número de questões
|
||||
// await expect(page.getByText(/\d+ questões/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve linkar para página do template', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/assignments/add');
|
||||
|
||||
const templateLink = page.getByRole('link', { name: /ver template/i });
|
||||
if (await templateLink.isVisible()) {
|
||||
await templateLink.click();
|
||||
|
||||
await expect(page).toHaveURL(/\/exam-templates/i);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,163 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Admin - Categorias', () => {
|
||||
// TODO: Fazer login como admin
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/admin\/dashboard/);
|
||||
// });
|
||||
|
||||
test.describe('Lista de Categorias', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/categories');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de categorias', async ({ page }) => {
|
||||
await expect(page.getByText(/categorias|categories/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar cor de cada categoria', async ({ page }) => {
|
||||
const categoryCard = page.locator('[data-testid="category-card"], .card').first();
|
||||
if (await categoryCard.isVisible()) {
|
||||
// Deve ter badge/cor visual
|
||||
const colorBadge = categoryCard.locator('[class*="bg-"]').first();
|
||||
await expect(colorBadge).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve ter botão de adicionar nova categoria', async ({ page }) => {
|
||||
await expect(page.getByRole('link', { name: /adicionar|nova|criar/i })).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Criar Nova Categoria', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/categories/add');
|
||||
});
|
||||
|
||||
test('deve mostrar formulário de criação', async ({ page }) => {
|
||||
await expect(page.getByLabel(/nome|name/i)).toBeVisible();
|
||||
await expect(page.getByText(/cor|color/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /salvar|criar/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter opções de cor para selecionar', async ({ page }) => {
|
||||
// Deve ter seletor de cores ou botões de cor
|
||||
const colorSelector = page.getByRole('combobox', { name: /cor|color/i });
|
||||
const colorButtons = page.locator('[class*="bg-emerald-"], [class*="bg-blue-"], [class*="bg-red-"]');
|
||||
|
||||
const hasColorOption = await colorSelector.isVisible() || (await colorButtons.count()) > 0;
|
||||
expect(hasColorOption).toBe(true);
|
||||
});
|
||||
|
||||
test('deve criar categoria com dados válidos', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await page.getByLabel(/nome|name/i).fill(`Categoria ${timestamp}`);
|
||||
|
||||
// Selecionar cor
|
||||
const colorSelector = page.getByRole('combobox', { name: /cor|color/i });
|
||||
if (await colorSelector.isVisible()) {
|
||||
await colorSelector.selectOption({ index: 0 });
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: /salvar|criar/i }).click();
|
||||
|
||||
// Deve redirecionar para lista ou mostrar sucesso
|
||||
await expect(page).toHaveURL(/\/admin\/dashboard\/categories/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('deve validar nome obrigatório', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /salvar|criar/i }).click();
|
||||
|
||||
await expect(page.getByText(/nome.*obrigatório|required/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Editar Categoria', () => {
|
||||
test('deve carregar dados da categoria', async ({ page }) => {
|
||||
// TODO: Usar ID de categoria de teste
|
||||
await page.goto('/admin/dashboard/categories/edit/category-id');
|
||||
|
||||
await expect(page.getByLabel(/nome|name/i)).not.toBeEmpty();
|
||||
});
|
||||
|
||||
test('deve permitir modificar nome e cor', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/categories/edit/category-id');
|
||||
|
||||
await page.getByLabel(/nome|name/i).fill('Categoria Editada');
|
||||
|
||||
const colorSelector = page.getByRole('combobox', { name: /cor|color/i });
|
||||
if (await colorSelector.isVisible()) {
|
||||
await colorSelector.selectOption({ index: 1 });
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: /salvar|atualizar/i }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/admin\/dashboard\/categories/, { timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Excluir Categoria', () => {
|
||||
test('deve confirmar antes de excluir', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/categories');
|
||||
|
||||
const deleteButton = page.getByRole('button', { name: /excluir|delete/i }).first();
|
||||
if (await deleteButton.isVisible()) {
|
||||
await deleteButton.click();
|
||||
|
||||
await expect(page.getByText(/tem certeza|deseja excluir/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /confirmar|sim/i })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /cancelar|não/i })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve excluir categoria sem arquivos vinculados', async ({ page }) => {
|
||||
// TODO: Criar categoria sem arquivos e excluir
|
||||
// await page.goto('/admin/dashboard/categories');
|
||||
// await page.getByRole('button', { name: /excluir/i }).first().click();
|
||||
// await page.getByRole('button', { name: /confirmar/i }).click();
|
||||
|
||||
// await expect(page.getByText(/categoria excluída|sucesso/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('não deve excluir categoria com arquivos vinculados', async ({ page }) => {
|
||||
// TODO: Tentar excluir categoria em uso
|
||||
// await page.goto('/admin/dashboard/categories/edit/used-category');
|
||||
// await page.getByRole('button', { name: /excluir/i }).click();
|
||||
// await page.getByRole('button', { name: /confirmar/i }).click();
|
||||
|
||||
// await expect(page.getByText(/possui arquivos|em uso|vinculada/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Cores de Categoria', () => {
|
||||
test('deve ter múltiplas opções de cor', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/categories/add');
|
||||
|
||||
const colorSelector = page.getByRole('combobox', { name: /cor|color/i });
|
||||
if (await colorSelector.isVisible()) {
|
||||
const options = await colorSelector.locator('option').count();
|
||||
expect(options).toBeGreaterThan(1);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar preview da cor selecionada', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/categories/add');
|
||||
|
||||
const colorSelector = page.getByRole('combobox', { name: /cor|color/i });
|
||||
if (await colorSelector.isVisible()) {
|
||||
await colorSelector.selectOption({ index: 0 });
|
||||
|
||||
// Pode haver um preview visual
|
||||
const colorPreview = page.locator('[class*="rounded-full"][class*="bg-"]').first();
|
||||
if (await colorPreview.isVisible()) {
|
||||
await expect(colorPreview).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,155 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Admin - Tipos de Classe (ClassTypes)', () => {
|
||||
// TODO: Fazer login como admin
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/admin\/dashboard/);
|
||||
// });
|
||||
|
||||
test.describe('Lista de ClassTypes', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/classTypes');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de tipos de classe', async ({ page }) => {
|
||||
await expect(page.getByText(/tipos de classe|class types/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter botão de adicionar novo tipo', async ({ page }) => {
|
||||
await expect(page.getByRole('link', { name: /adicionar|novo|criar/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar nome, preço e ações de cada tipo', async ({ page }) => {
|
||||
const typeCard = page.locator('[data-testid="classtype-card"], .card').first();
|
||||
if (await typeCard.isVisible()) {
|
||||
await expect(typeCard.getByText(/preço|price/i)).toBeVisible();
|
||||
await expect(typeCard.getByRole('link', { name: /editar/i })).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Criar Novo ClassType', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/classTypes/add');
|
||||
});
|
||||
|
||||
test('deve mostrar formulário de criação', async ({ page }) => {
|
||||
await expect(page.getByLabel(/nome|name/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/preço|price/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /salvar|criar/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve criar tipo com dados válidos', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await page.getByLabel(/nome|name/i).fill(`Tipo Teste ${timestamp}`);
|
||||
await page.getByLabel(/preço|price/i).fill('150,00');
|
||||
|
||||
await page.getByRole('button', { name: /salvar|criar/i }).click();
|
||||
|
||||
// Deve redirecionar para lista ou mostrar sucesso
|
||||
await expect(page).toHaveURL(/\/admin\/dashboard\/classTypes/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('deve validar campo nome obrigatório', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /salvar|criar/i }).click();
|
||||
|
||||
await expect(page.getByText(/nome.*obrigatório|required/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Editar ClassType', () => {
|
||||
test('deve carregar dados do tipo', async ({ page }) => {
|
||||
// TODO: Usar ID de classType de teste
|
||||
await page.goto('/admin/dashboard/classTypes/edit/67ca5e4cfb41e291a99a7a28');
|
||||
|
||||
await expect(page.getByLabel(/nome|name/i)).not.toBeEmpty();
|
||||
});
|
||||
|
||||
test('deve permitir modificar dados', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/classTypes/edit/67ca5e4cfb41e291a99a7a28');
|
||||
|
||||
const originalName = await page.getByLabel(/nome|name/i).inputValue();
|
||||
await page.getByLabel(/nome|name/i).fill(`${originalName} (Editado)`);
|
||||
|
||||
await page.getByRole('button', { name: /salvar|atualizar/i }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/admin\/dashboard\/classTypes/, { timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Excluir ClassType', () => {
|
||||
test('deve confirmar exclusão', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/classTypes');
|
||||
|
||||
const deleteButton = page.getByRole('button', { name: /excluir|delete/i }).first();
|
||||
if (await deleteButton.isVisible()) {
|
||||
await deleteButton.click();
|
||||
|
||||
// Deve pedir confirmação
|
||||
await expect(page.getByText(/tem certeza|deseja excluir/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /confirmar|sim/i })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /cancelar|não/i })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('não deve excluir se há turmas vinculadas', async ({ page }) => {
|
||||
// TODO: Tentar excluir classType que tem turmas
|
||||
// await page.goto('/admin/dashboard/classTypes/edit/used-id');
|
||||
// await page.getByRole('button', { name: /excluir/i }).click();
|
||||
// await page.getByRole('button', { name: /confirmar/i }).click();
|
||||
|
||||
// await expect(page.getByText(/possui turmas|em uso/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Arquivos do ClassType', () => {
|
||||
test('deve mostrar arquivos vinculados', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/classTypes/files/67ca5e4cfb41e291a99a7a28');
|
||||
|
||||
await expect(page.getByText(/arquivos|files/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve permitir adicionar arquivos', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/classTypes/files/67ca5e4cfb41e291a99a7a28');
|
||||
|
||||
const addButton = page.getByRole('link', { name: /adicionar|upload/i }).first();
|
||||
if (await addButton.isVisible()) {
|
||||
await addButton.click();
|
||||
await expect(page).toHaveURL(/\/files\/add/i);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Preço Padrão', () => {
|
||||
test('deve aceitar formato decimal com vírgula', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/classTypes/add');
|
||||
|
||||
await page.getByLabel(/preço|price/i).fill('150,50');
|
||||
|
||||
// Não deve mostrar erro de formato
|
||||
await page.getByRole('button', { name: /salvar/i }).click();
|
||||
// Pode mostrar erro de outros campos obrigatórios
|
||||
});
|
||||
|
||||
test('deve aceitar formato decimal com ponto', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/classTypes/add');
|
||||
|
||||
await page.getByLabel(/preço|price/i).fill('150.50');
|
||||
|
||||
await page.getByRole('button', { name: /salvar/i }).click();
|
||||
});
|
||||
|
||||
test('deve aceitar preço zero ou vazio', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/classTypes/add');
|
||||
|
||||
await page.getByLabel(/preço|price/i).fill('0');
|
||||
|
||||
await page.getByRole('button', { name: /salvar/i }).click();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,195 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Admin - Turmas (Classes)', () => {
|
||||
// TODO: Fazer login como admin antes de cada teste
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/admin\/dashboard/);
|
||||
// });
|
||||
|
||||
test.describe('Lista de Turmas', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de turmas', async ({ page }) => {
|
||||
await expect(page.getByText(/turmas|classes/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter botão de adicionar nova turma', async ({ page }) => {
|
||||
await expect(page.getByRole('link', { name: /adicionar|nova|criar/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter busca funcionando', async ({ page }) => {
|
||||
const searchInput = page.getByPlaceholder(/buscar|pesquisar|search/i);
|
||||
if (await searchInput.isVisible()) {
|
||||
await searchInput.fill('Inglês');
|
||||
await page.waitForTimeout(500);
|
||||
// Verificar se filtrou
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Criar Nova Turma', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/add');
|
||||
});
|
||||
|
||||
test('deve mostrar formulário de criação', async ({ page }) => {
|
||||
await expect(page.getByLabel(/tipo de classe|class type/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/título/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/data de início|start date/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/horário|time/i)).toBeVisible();
|
||||
await expect(page.getByText(/dias da semana/i)).toBeVisible();
|
||||
await expect(page.getByText(/professores/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve validar campos obrigatórios', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /salvar|criar/i }).click();
|
||||
|
||||
await expect(page.getByText(/obrigatório|required/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve validar data de término posterior a data de início', async ({ page }) => {
|
||||
await page.getByLabel(/data de início/i).fill('2025-01-10');
|
||||
await page.getByLabel(/data de término|end date/i).fill('2025-01-01');
|
||||
await page.getByRole('button', { name: /salvar|criar/i }).click();
|
||||
|
||||
await expect(page.getByText(/término não pode ser anterior|data inválida/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve criar turma com dados válidos', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
// Selecionar tipo de classe
|
||||
await page.getByLabel(/tipo de classe/i).selectOption({ index: 0 });
|
||||
|
||||
// Preencher título
|
||||
await page.getByLabel(/título/i).fill(`Turma Teste ${timestamp}`);
|
||||
|
||||
// Data de início
|
||||
await page.getByLabel(/data de início/i).fill('2025-01-01');
|
||||
|
||||
// Horário
|
||||
await page.getByLabel(/horário/i).fill('19:00');
|
||||
|
||||
// Selecionar professores
|
||||
const teacherCheckbox = page.locator('input[type="checkbox"]').first();
|
||||
if (await teacherCheckbox.isVisible()) {
|
||||
await teacherCheckbox.check();
|
||||
}
|
||||
|
||||
// Selecionar dias da semana
|
||||
await page.getByText(/seg/i).click();
|
||||
|
||||
// Salvar
|
||||
await page.getByRole('button', { name: /salvar|criar/i }).click();
|
||||
|
||||
// Deve redirecionar para lista
|
||||
await expect(page).toHaveURL(/\/admin\/dashboard\/class/, { timeout: 15000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Editar Turma Existente', () => {
|
||||
test('deve carregar dados da turma', async ({ page }) => {
|
||||
// TODO: Usar ID de turma de teste
|
||||
await page.goto('/admin/dashboard/class/edit/6975e7cfb41e291a99a79ece');
|
||||
|
||||
await expect(page.getByLabel(/título/i)).not.toBeEmpty();
|
||||
});
|
||||
|
||||
test('deve permitir modificar professores', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/edit/6975e7cfb41e291a99a79ece');
|
||||
|
||||
// Desmarcar professor selecionado
|
||||
const checkedTeacher = page.locator('input[type="checkbox"]:checked').first();
|
||||
if (await checkedTeacher.isVisible()) {
|
||||
await checkedTeacher.uncheck();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve permitir adicionar alunos via select', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/edit/6975e7cfb41e291a99a79ece');
|
||||
|
||||
const studentSelect = page.getByRole('combobox').filter({ hasText: /adicionar aluno/i });
|
||||
if (await studentSelect.isVisible()) {
|
||||
// Selecionar primeiro aluno disponível
|
||||
await studentSelect.selectOption({ index: 1 });
|
||||
|
||||
// Verificar se apareceu tag do aluno
|
||||
await expect(page.locator('.student-tag')).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve permitir remover aluno da seleção', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/edit/6975e7cfb41e291a99a79ece');
|
||||
|
||||
// Se há tags de alunos, tentar remover
|
||||
const studentTag = page.locator('.student-tag').first();
|
||||
if (await studentTag.isVisible()) {
|
||||
await studentTag.getByRole('button').click();
|
||||
// Verificar se foi removido
|
||||
}
|
||||
});
|
||||
|
||||
test('deve salvar alterações com sucesso', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/edit/6975e7cfb41e291a99a79ece');
|
||||
|
||||
const originalTitle = await page.getByLabel(/título/i).inputValue();
|
||||
await page.getByLabel(/título/i).fill(`${originalTitle} (editado)`);
|
||||
|
||||
await page.getByRole('button', { name: /salvar|atualizar/i }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/admin\/dashboard\/class/, { timeout: 15000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Dias da Semana - Checkbox', () => {
|
||||
test('deve mostrar todos os dias', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/add');
|
||||
|
||||
const days = ['Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sab', 'Dom'];
|
||||
for (const day of days) {
|
||||
await expect(page.getByText(day, { exact: true })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve permitir selecionar múltiplos dias', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/add');
|
||||
|
||||
await page.getByText('Seg').click();
|
||||
await page.getByText('Qua').click();
|
||||
await page.getByText('Sex').click();
|
||||
|
||||
// Verificar se estão marcados
|
||||
const segCheckbox = page.getByText('Seg').locator('input[type="checkbox"]');
|
||||
await expect(segCheckbox).toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Professores - Checkbox', () => {
|
||||
test('deve mostrar lista de professores disponíveis', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/add');
|
||||
|
||||
await expect(page.getByText(/professores/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve permitir selecionar múltiplos professores', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/add');
|
||||
|
||||
const checkboxes = page.locator('input[type="checkbox"]');
|
||||
const count = await checkboxes.count();
|
||||
|
||||
if (count >= 2) {
|
||||
await checkboxes.nth(0).check();
|
||||
await checkboxes.nth(1).check();
|
||||
|
||||
await expect(checkboxes.nth(0)).toBeChecked();
|
||||
await expect(checkboxes.nth(1)).toBeChecked();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,328 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Admin - Templates de Prova', () => {
|
||||
// TODO: Fazer login como admin
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/dispatcher/);
|
||||
// await page.getByRole('button', { name: /admin/i }).click();
|
||||
// });
|
||||
|
||||
test.describe('Lista de Templates', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de templates', async ({ page }) => {
|
||||
await expect(page.getByText(/templates|modelos/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter botão de criar novo template', async ({ page }) => {
|
||||
await expect(page.getByRole('button', { name: /novo|criar/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar informações do template', async ({ page }) => {
|
||||
const templateCard = page.locator('[data-testid="template-card"], .card').first();
|
||||
if (await templateCard.isVisible()) {
|
||||
await expect(templateCard.getByText(/questões|questions/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Criar Novo Template', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Abrir modal de criação
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
await page.getByRole('button', { name: /novo|criar/i }).click();
|
||||
});
|
||||
|
||||
test('deve mostrar modal de criação', async ({ page }) => {
|
||||
await expect(page.locator('.fixed.inset-0')).toBeVisible();
|
||||
await expect(page.getByText(/novo template|criar template/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter campos básicos', async ({ page }) => {
|
||||
await expect(page.getByLabel(/título|title/i)).toBeVisible();
|
||||
await expect(page.getByPlaceholder(/breve descrição/i)).toBeVisible();
|
||||
await expect(page.getByPlaceholder(/instruções/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/tempo limite|time limit/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter seção de questões', async ({ page }) => {
|
||||
await expect(page.getByText(/questões|questions/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /adicionar questão/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter checkbox "público"', async ({ page }) => {
|
||||
await expect(page.getByRole('checkbox', { name: /público|public/i })).toBeVisible();
|
||||
await expect(page.getByText(/outros professores podem usar/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve salvar template mínimo', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
await page.getByLabel(/título|title/i).fill(`Template ${timestamp}`);
|
||||
await page.getByLabel(/tempo limite|time limit/i).fill('60');
|
||||
|
||||
await page.getByRole('button', { name: /criar|salvar/i }).click();
|
||||
|
||||
// Deve fechar modal
|
||||
await expect(page.locator('.fixed.inset-0')).not.toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Adicionar Questões', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
await page.getByRole('button', { name: /novo/i }).click();
|
||||
await page.getByLabel(/título|title/i).fill('Template com Questões');
|
||||
});
|
||||
|
||||
test('deve adicionar questão de múltipla escolha', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /adicionar questão/i }).click();
|
||||
|
||||
// Deve mostrar nova seção de questão
|
||||
await expect(page.getByText(/questão \d+/i)).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: /tipo/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve alterar tipo da questão', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /adicionar questão/i }).click();
|
||||
|
||||
const typeSelect = page.getByRole('combobox').filter({ hasText: /tipo/i }).first();
|
||||
await typeSelect.selectOption('text');
|
||||
|
||||
// Não deve mais mostrar opções de múltipla escolha
|
||||
await expect(page.getByText(/opções de resposta/i)).not.toBeVisible();
|
||||
await expect(page.getByText(/será corrigida manualmente/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve preencher enunciado obrigatório', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /adicionar questão/i }).click();
|
||||
|
||||
await page.getByPlaceholder(/enunciado/i).fill('Qual é a capital do Brasil?');
|
||||
|
||||
await page.getByRole('button', { name: /criar|salvar/i }).click();
|
||||
|
||||
// Pode mostrar erro de questões vazias
|
||||
});
|
||||
|
||||
test('deve adicionar opções à questão de múltipla escolha', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /adicionar questão/i }).click();
|
||||
|
||||
// Já deve vir com 2 opções
|
||||
await expect(page.getByPlaceholder(/opção \d+/i).nth(0)).toBeVisible();
|
||||
await expect(page.getByPlaceholder(/opção \d+/i).nth(1)).toBeVisible();
|
||||
|
||||
// Adicionar terceira opção
|
||||
await page.getByRole('button', { name: /adicionar opção/i }).click();
|
||||
await expect(page.getByPlaceholder(/opção \d+/i).nth(2)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve marcar opção correta', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /adicionar questão/i }).click();
|
||||
|
||||
const correctRadio = page.locator('input[type="radio"]').first();
|
||||
await correctRadio.check();
|
||||
|
||||
await expect(correctRadio).toBeChecked();
|
||||
});
|
||||
|
||||
test('deve remover opção', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /adicionar questão/i }).click();
|
||||
await page.getByRole('button', { name: /adicionar opção/i }).click();
|
||||
|
||||
// Remover segunda opção
|
||||
const deleteButtons = page.getByRole('button', { name: /excluir|remover/i, exact: true });
|
||||
const count = await deleteButtons.count();
|
||||
|
||||
if (count >= 2) {
|
||||
await deleteButtons.nth(1).click();
|
||||
|
||||
// Deve voltar a ter 2 opções (mínimo)
|
||||
await expect(page.getByPlaceholder(/opção \d+/i).nth(0)).toBeVisible();
|
||||
await expect(page.getByPlaceholder(/opção \d+/i).nth(1)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve remover questão', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /adicionar questão/i }).click();
|
||||
|
||||
const deleteQuestionButton = page.locator('[data-testid="question-card"]').first()
|
||||
.getByRole('button', { name: /excluir|lixeira/i });
|
||||
|
||||
if (await deleteQuestionButton.isVisible()) {
|
||||
const beforeCount = await page.locator('[data-testid="question-card"]').count();
|
||||
|
||||
await deleteQuestionButton.click();
|
||||
|
||||
const afterCount = await page.locator('[data-testid="question-card"]').count();
|
||||
expect(afterCount).toBe(beforeCount - 1);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve validar mínimo de 2 opções para múltipla escolha', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /adicionar questão/i }).click();
|
||||
|
||||
// Remover opções até ter menos de 2
|
||||
const deleteButton = page.getByRole('button', { name: /excluir opção/i });
|
||||
const deleteCount = await deleteButton.count();
|
||||
|
||||
if (deleteCount >= 2) {
|
||||
await deleteButton.nth(0).click();
|
||||
await deleteButton.nth(0).click();
|
||||
|
||||
await page.getByRole('button', { name: /criar|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/mínimo \d+ opções|pelo menos 2 opções/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve definir pontos da questão', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /adicionar questão/i }).click();
|
||||
|
||||
const pointsInput = page.getByRole('spinbutton', { name: /pontos/i });
|
||||
if (await pointsInput.isVisible()) {
|
||||
await pointsInput.fill('5');
|
||||
|
||||
const value = await pointsInput.inputValue();
|
||||
expect(value).toBe('5');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Editar Template', () => {
|
||||
test('deve carregar dados do template', async ({ page }) => {
|
||||
// TODO: Editar template existente
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
await page.getByRole('button', { name: /editar/i }).first().click();
|
||||
|
||||
await expect(page.getByLabel(/título|title/i)).not.toBeEmpty();
|
||||
});
|
||||
|
||||
test('deve carregar questões existentes', async ({ page }) => {
|
||||
// TODO: Template com questões
|
||||
await page.goto('/admin/dashboard/exam-templates/edit/template-id');
|
||||
|
||||
const questionCards = page.locator('[data-testid="question-card"]');
|
||||
const count = await questionCards.count();
|
||||
|
||||
if (count > 0) {
|
||||
await expect(questionCards.nth(0).getByPlaceholder(/enunciado/i)).not.toBeEmpty();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Excluir Template', () => {
|
||||
test('deve confirmar antes de excluir', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
|
||||
const deleteButton = page.getByRole('button', { name: /excluir/i }).first();
|
||||
if (await deleteButton.isVisible()) {
|
||||
await deleteButton.click();
|
||||
|
||||
await expect(page.getByText(/tem certeza|deseja excluir/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('não deve excluir se há tentativas', async ({ page }) => {
|
||||
// TODO: Tentar excluir template usado
|
||||
// await page.goto('/admin/dashboard/exam-templates/edit/used-template');
|
||||
// await page.getByRole('button', { name: /excluir/i }).click();
|
||||
// await page.getByRole('button', { name: /confirmar/i }).click();
|
||||
|
||||
// await expect(page.getByText(/possui tentativas|em uso/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Template Público', () => {
|
||||
test('deve marcar como público', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
await page.getByRole('button', { name: /novo/i }).click();
|
||||
|
||||
await page.getByRole('checkbox', { name: /público/i }).check();
|
||||
|
||||
await page.getByLabel(/título|title/i).fill('Template Público Teste');
|
||||
|
||||
await page.getByRole('button', { name: /criar/i }).click();
|
||||
});
|
||||
|
||||
test('template público deve aparecer para professores', async ({ page }) => {
|
||||
// TODO: Fazer login como professor e verificar se template público aparece
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
|
||||
// await page.goto('/dashboard/teacher/exam-templates');
|
||||
|
||||
// await expect(page.getByText(/Template Público Teste/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Tags e Categoria', () => {
|
||||
test('deve aceitar tags separadas por vírgula', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
await page.getByRole('button', { name: /novo/i }).click();
|
||||
|
||||
await page.getByLabel(/tags/i).fill('básico, revisão, unidade 1');
|
||||
|
||||
await page.getByLabel(/título|title/i).fill('Template com Tags');
|
||||
await page.getByRole('button', { name: /criar/i }).click();
|
||||
|
||||
// Editar para verificar se tags foram salvas
|
||||
// await page.getByRole('button', { name: /editar/i }).first().click();
|
||||
// await expect(page.getByRole('textbox', { name: /tags/i })).toHaveValue(/básico|revisão/);
|
||||
});
|
||||
|
||||
test('deve aceitar categoria', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
await page.getByRole('button', { name: /novo/i }).click();
|
||||
|
||||
await page.getByLabel(/categoria/i).fill('Inglês');
|
||||
|
||||
await page.getByLabel(/título|title/i).fill('Template com Categoria');
|
||||
await page.getByRole('button', { name: /criar/i }).click();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Modal do Template', () => {
|
||||
test('deve fechar ao clicar no X', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
await page.getByRole('button', { name: /novo/i }).click();
|
||||
|
||||
await expect(page.locator('.fixed.inset-0')).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: /✕|fechar/i }).click();
|
||||
|
||||
await expect(page.locator('.fixed.inset-0')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('deve fechar ao clicar fora do modal', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
await page.getByRole('button', { name: /novo/i }).click();
|
||||
|
||||
const modal = page.locator('.bg-white.dark\\:bg-neutral-900');
|
||||
const box = await modal.boundingBox();
|
||||
|
||||
// Clicar fora
|
||||
await page.mouse.click(box.x - 10, box.y);
|
||||
|
||||
await expect(page.locator('.fixed.inset-0')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('botão cancelar deve fechar sem salvar', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/exam-templates');
|
||||
await page.getByRole('button', { name: /novo/i }).click();
|
||||
|
||||
await page.getByLabel(/título|title/i).fill('Template Cancelado');
|
||||
|
||||
await page.getByRole('button', { name: /cancelar/i }).click();
|
||||
|
||||
await expect(page.locator('.fixed.inset-0')).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,271 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Admin - Arquivos/Files', () => {
|
||||
// TODO: Fazer login como admin
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/admin\/dashboard/);
|
||||
// });
|
||||
|
||||
test.describe('Lista de Arquivos', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de arquivos', async ({ page }) => {
|
||||
await expect(page.getByText(/arquivos|files/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter botão de upload', async ({ page }) => {
|
||||
await expect(page.getByRole('link', { name: /upload|adicionar|novo/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar informações do arquivo', async ({ page }) => {
|
||||
const fileRow = page.locator('tr').nth(1); // Pular header
|
||||
if (await fileRow.isVisible()) {
|
||||
await expect(fileRow.getByText(/\.(pdf|doc|png|jpg)/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Upload de Arquivo', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files/add');
|
||||
});
|
||||
|
||||
test('deve mostrar formulário de upload', async ({ page }) => {
|
||||
await expect(page.getByLabelText(/título|title/i)).toBeVisible();
|
||||
await expect(page.getByLabelText(/arquivo|file/i)).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: /categoria/i })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /upload|salvar/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve aceitar upload de arquivo PDF', async ({ page }) => {
|
||||
// TODO: Criar arquivo PDF temporário
|
||||
// const fileInput = page.getByLabelText(/arquivo/i);
|
||||
// await fileInput.setInputFiles('tests/fixtures/test.pdf');
|
||||
|
||||
await page.getByLabelText(/título|title/i).fill('Arquivo Teste PDF');
|
||||
|
||||
const categorySelect = page.getByRole('combobox', { name: /categoria/i });
|
||||
if (await categorySelect.isVisible()) {
|
||||
await categorySelect.selectOption({ index: 0 });
|
||||
}
|
||||
|
||||
// await page.getByRole('button', { name: /upload|salvar/i }).click();
|
||||
// await expect(page).toHaveURL(/\/admin\/dashboard\/files/, { timeout: 15000 });
|
||||
});
|
||||
|
||||
test('deve aceitar upload de arquivo de imagem', async ({ page }) => {
|
||||
// const fileInput = page.getByLabelText(/arquivo/i);
|
||||
// await fileInput.setInputFiles('tests/fixtures/test.png');
|
||||
|
||||
await page.getByLabelText(/título|title/i).fill('Imagem Teste');
|
||||
|
||||
// await page.getByRole('button', { name: /upload|salvar/i }).click();
|
||||
});
|
||||
|
||||
test('deve validar título obrigatório', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /upload|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/título.*obrigatório|title.*required/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Upload por Turma', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// TODO: Usar ID de turma de teste
|
||||
await page.goto('/files/class/class-id/add');
|
||||
});
|
||||
|
||||
test('deve pré-selecionar turma no formulário', async ({ page }) => {
|
||||
// Deve mostrar informações da turma ou ter turma pré-selecionada
|
||||
await expect(page.getByText(/turma|class/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve fazer upload para turma específica', async ({ page }) => {
|
||||
await page.getByLabelText(/título|title/i).fill('Material da Turma');
|
||||
|
||||
// const fileInput = page.getByLabelText(/arquivo/i);
|
||||
// await fileInput.setInputFiles('tests/fixtures/material.pdf');
|
||||
|
||||
// await page.getByRole('button', { name: /upload|salvar/i }).click();
|
||||
|
||||
// Deve redirecionar para detalhes da turma
|
||||
await expect(page).toHaveURL(/\/admin\/dashboard\/class/, { timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Editar Arquivo', () => {
|
||||
test('deve carregar dados do arquivo', async ({ page }) => {
|
||||
// TODO: Usar ID de arquivo de teste
|
||||
await page.goto('/admin/dashboard/files/edit/file-id');
|
||||
|
||||
await expect(page.getByLabelText(/título|title/i)).not.toBeEmpty();
|
||||
});
|
||||
|
||||
test('deve permitir modificar título e descrição', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files/edit/file-id');
|
||||
|
||||
await page.getByLabelText(/título|title/i).fill('Arquivo Editado');
|
||||
|
||||
const descField = page.getByLabelText(/descrição|description/i);
|
||||
if (await descField.isVisible()) {
|
||||
await descField.fill('Descrição atualizada');
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: /salvar|atualizar/i }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/admin\/dashboard\/files/, { timeout: 10000 });
|
||||
});
|
||||
|
||||
test('deve permitir trocar categoria', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files/edit/file-id');
|
||||
|
||||
const categorySelect = page.getByRole('combobox', { name: /categoria/i });
|
||||
if (await categorySelect.isVisible()) {
|
||||
const originalValue = await categorySelect.inputValue();
|
||||
await categorySelect.selectOption({ index: 1 });
|
||||
|
||||
await page.getByRole('button', { name: /salvar|atualizar/i }).click();
|
||||
|
||||
// Verificar se mudou
|
||||
// await page.goto('/admin/dashboard/files/edit/file-id');
|
||||
// const newValue = await categorySelect.inputValue();
|
||||
// expect(newValue).not.toBe(originalValue);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Download de Arquivo', () => {
|
||||
test('deve ter botão de download', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files');
|
||||
|
||||
const downloadButton = page.getByRole('link', { name: /download|baixar/i }).first();
|
||||
if (await downloadButton.isVisible()) {
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
|
||||
await downloadButton.click();
|
||||
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve abrir arquivo em nova aba', async ({ page }) => {
|
||||
const openButton = page.getByRole('link', { name: /abrir|open/i }).first();
|
||||
if (await openButton.isVisible()) {
|
||||
const newPagePromise = page.waitForEvent('popup');
|
||||
|
||||
await openButton.click();
|
||||
|
||||
const newPage = await newPagePromise;
|
||||
await expect(newPage).toHaveURL(/\.pdf|\.png|\.jpg/i);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Excluir Arquivo', () => {
|
||||
test('deve confirmar antes de excluir', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files');
|
||||
|
||||
const deleteButton = page.getByRole('button', { name: /excluir|delete/i }).first();
|
||||
if (await deleteButton.isVisible()) {
|
||||
await deleteButton.click();
|
||||
|
||||
await expect(page.getByText(/tem certeza|deseja excluir/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /confirmar|sim/i })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /cancelar|não/i })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve remover arquivo da lista após excluir', async ({ page }) => {
|
||||
// TODO: Implementar
|
||||
// await page.goto('/admin/dashboard/files');
|
||||
// const fileName = 'Arquivo Para Excluir';
|
||||
|
||||
// const initialCount = await page.getByText(fileName).count();
|
||||
|
||||
// await page.getByRole('button', { name: /excluir/i }).first().click();
|
||||
// await page.getByRole('button', { name: /confirmar/i }).click();
|
||||
|
||||
// await expect(page.getByText(fileName)).toHaveCount(initialCount - 1);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Upload Múltiplo', () => {
|
||||
test('deve aceitar múltiplos arquivos de uma vez', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files/add');
|
||||
|
||||
// const fileInput = page.getByLabelText(/arquivo/i);
|
||||
// Se suportar múltiplos:
|
||||
// await fileInput.setInputFiles([
|
||||
// 'tests/fixtures/file1.pdf',
|
||||
// 'tests/fixtures/file2.pdf'
|
||||
// ]);
|
||||
|
||||
// await page.getByRole('button', { name: /upload|salvar/i }).click();
|
||||
|
||||
// Deve processar todos os arquivos
|
||||
});
|
||||
|
||||
test('deve mostrar barra de progresso durante upload', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files/add');
|
||||
|
||||
// Após selecionar arquivo e iniciar upload
|
||||
// await expect(page.getByText(/enviando|uploading|progresso/i)).toBeVisible();
|
||||
// await expect(page.getByText(/concluído|sucesso/i)).toBeVisible({ timeout: 15000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Validações de Upload', () => {
|
||||
test('deve rejeitar arquivo muito grande', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files/add');
|
||||
|
||||
// Criar arquivo temporário grande
|
||||
// const largeFile = Buffer.alloc(20 * 1024 * 1024); // 20MB
|
||||
// await page.getByLabelText(/arquivo/i).setInputFiles({
|
||||
// name: 'large.pdf',
|
||||
// mimeType: 'application/pdf',
|
||||
// buffer: largeFile
|
||||
// });
|
||||
|
||||
await page.getByRole('button', { name: /upload|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/arquivo muito grande|tamanho máximo/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve rejeitar formato não suportado', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files/add');
|
||||
|
||||
// await page.getByLabelText(/arquivo/i).setInputFiles('tests/fixtures/file.exe');
|
||||
|
||||
await page.getByRole('button', { name: /upload|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/formato não suportado|tipo inválido/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Preview de Arquivo', () => {
|
||||
test('deve mostrar preview de imagem', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files/edit/image-file-id');
|
||||
|
||||
const preview = page.locator('img[src*="/uploads/"]').first();
|
||||
if (await preview.isVisible()) {
|
||||
await expect(preview).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar ícone para PDF', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/files/edit/pdf-file-id');
|
||||
|
||||
const pdfIcon = page.locator('[class*="pdf"]').first();
|
||||
if (await pdfIcon.isVisible()) {
|
||||
await expect(pdfIcon).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,168 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Admin - Pagamentos', () => {
|
||||
// TODO: Fazer login como admin antes de cada teste
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/admin\/dashboard/);
|
||||
// });
|
||||
|
||||
test.describe('Lista de Pagamentos', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/payments');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de pagamentos', async ({ page }) => {
|
||||
await expect(page.getByText(/pagamentos|payments/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter link para visualização por turma', async ({ page }) => {
|
||||
await expect(page.getByRole('link', { name: /por turma|by class/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar colunas principais', async ({ page }) => {
|
||||
await expect(page.getByText(/aluno/i)).toBeVisible();
|
||||
await expect(page.getByText(/valor|amount/i)).toBeVisible();
|
||||
await expect(page.getByText(/status/i)).toBeVisible();
|
||||
await expect(page.getByText(/vencimento|due date/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Pagamentos por Turma', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/payments/by-class');
|
||||
});
|
||||
|
||||
test('deve mostrar cards de turmas', async ({ page }) => {
|
||||
await expect(page.getByText(/turmas|classes/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve expandir turma ao clicar', async ({ page }) => {
|
||||
const firstClassCard = page.locator('.bg-white, .dark\\:bg-neutral-800').first();
|
||||
await firstClassCard.click();
|
||||
|
||||
// Deve mostrar tabela de pagamentos dentro
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar filtros de status', async ({ page }) => {
|
||||
const statusFilter = page.getByRole('combobox');
|
||||
if (await statusFilter.isVisible()) {
|
||||
await statusFilter.selectOption('pending');
|
||||
await page.waitForTimeout(500);
|
||||
// Verificar se filtrou
|
||||
}
|
||||
});
|
||||
|
||||
test('deve ter botões expandir/recolher todos', async ({ page }) => {
|
||||
await expect(page.getByRole('button', { name: /expandir/i })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /recolher/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar estatísticas', async ({ page }) => {
|
||||
await expect(page.getByText(/pendentes/i)).toBeVisible();
|
||||
await expect(page.getByText(/aguardando verificação/i)).toBeVisible();
|
||||
await expect(page.getByText(/pagos/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Aprovação de Pagamento', () => {
|
||||
test('deve mostrar botões de aprovar/rejeitar para pagamentos pendentes', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/payments/by-class');
|
||||
|
||||
// Expandir primeira turma
|
||||
const firstClassCard = page.locator('.bg-white, .dark\\:bg-neutral-800').first();
|
||||
await firstClassCard.click();
|
||||
|
||||
// Procurar botões de ação
|
||||
const approveButton = page.getByRole('button').filter({ hasText: /aprovar|check/i }).first();
|
||||
const rejectButton = page.getByRole('button').filter({ hasText: /rejeitar|x/i }).first();
|
||||
|
||||
// Podem não existir pagamentos pendentes
|
||||
// if (await approveButton.isVisible()) {
|
||||
// await expect(rejectButton).toBeVisible();
|
||||
// }
|
||||
});
|
||||
|
||||
test('deve abrir modal de rejeição ao clicar em rejeitar', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/payments/by-class');
|
||||
|
||||
// Expandir e procurar pagamento para rejeitar
|
||||
const firstClassCard = page.locator('.bg-white, .dark\\:bg-neutral-800').first();
|
||||
await firstClassCard.click();
|
||||
|
||||
const rejectButton = page.getByRole('button').filter({ hasText: /rejeitar|x/i }).first();
|
||||
if (await rejectButton.isVisible()) {
|
||||
await rejectButton.click();
|
||||
|
||||
// Deve abrir modal
|
||||
await expect(page.getByText(/motivo da rejeição/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /confirmar/i })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /cancelar/i })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve ver comprovante', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/payments/by-class');
|
||||
|
||||
// Procurar botão de ver comprovante
|
||||
const viewReceiptButton = page.getByRole('button').filter({ hasText: /ver|olho/i }).first();
|
||||
if (await viewReceiptButton.isVisible()) {
|
||||
await viewReceiptButton.click();
|
||||
|
||||
// Deve abrir modal
|
||||
await expect(page.locator('.fixed.inset-0')).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /fechar|✕/i })).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Labels/Badges de Status', () => {
|
||||
test('deve mostrar badge correto para cada status', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/payments/by-class');
|
||||
|
||||
// Status possíveis: pending_verification, verified, rejected, pending
|
||||
const statuses = [/aguardando/i, /pago|verificado/i, /rejeitado/i, /pendente/i];
|
||||
|
||||
for (const status of statuses) {
|
||||
const badge = page.getByText(status).first();
|
||||
if (await badge.isVisible()) {
|
||||
// Badge deve ter classes de cor
|
||||
await expect(badge).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Hover Cards', () => {
|
||||
test('deve ter hover suave nos cards de turma', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/payments/by-class');
|
||||
|
||||
const firstClassCard = page.locator('.bg-white, .dark\\:bg-neutral-800').first();
|
||||
|
||||
// Hover não deve mudar drasticamente
|
||||
await firstClassCard.hover();
|
||||
await page.waitForTimeout(100);
|
||||
|
||||
// Verificar se card ainda visível e funcional
|
||||
await expect(firstClassCard).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter hover suave nas linhas da tabela', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/payments/by-class');
|
||||
|
||||
const firstClassCard = page.locator('.bg-white, .dark\\:bg-neutral-800').first();
|
||||
await firstClassCard.click();
|
||||
|
||||
const tableRow = page.locator('tr').nth(1); // Pular header
|
||||
if (await tableRow.isVisible()) {
|
||||
await tableRow.hover();
|
||||
await page.waitForTimeout(100);
|
||||
await expect(tableRow).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,304 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Admin - Estatísticas de Provas', () => {
|
||||
// TODO: Fazer login como admin
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/dispatcher/);
|
||||
// await page.getByRole('button', { name: /admin/i }).click();
|
||||
// });
|
||||
|
||||
test.describe('Página de Estatísticas', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
});
|
||||
|
||||
test('deve mostrar header com PageHeader', async ({ page }) => {
|
||||
await expect(page.getByText(/estatísticas de provas/i)).toBeVisible();
|
||||
await expect(page.getByText(/visualize estatísticas/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter filtro de período', async ({ page }) => {
|
||||
await expect(page.getByRole('combobox', { name: /período/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter botão de atualizar', async ({ page }) => {
|
||||
const refreshButton = page.getByRole('button', { name: /atualizar|refresh/i });
|
||||
if (await refreshButton.isVisible()) {
|
||||
await expect(refreshButton).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Estatísticas Agregadas', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
});
|
||||
|
||||
test('deve mostrar total de tentativas', async ({ page }) => {
|
||||
await expect(page.getByText(/total de tentativas/i)).toBeVisible();
|
||||
const totalValue = page.locator('text=/deve-total/').first(); // Ajustar seletor
|
||||
if (await totalValue.isVisible()) {
|
||||
const text = await totalValue.textContent();
|
||||
expect(text).toMatch(/\d+/);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar média de notas', async ({ page }) => {
|
||||
await expect(page.getByText(/média de notas/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar média de aproveitamento', async ({ page }) => {
|
||||
await expect(page.getByText(/média de aproveitamento/i)).toBeVisible();
|
||||
const percentageValue = page.locator('text=/deve-percent/').first();
|
||||
if (await percentageValue.isVisible()) {
|
||||
const text = await percentageValue.textContent();
|
||||
expect(text).toMatch(/\d+%/);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar taxa de aprovação', async ({ page }) => {
|
||||
await expect(page.getByText(/taxa de aprovação/i)).toBeVisible();
|
||||
const passRateValue = page.locator('text=/deve-pass/').first();
|
||||
if (await passRateValue.isVisible()) {
|
||||
const text = await passRateValue.textContent();
|
||||
expect(text).toMatch(/\d+%/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Estatísticas por Turma', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
});
|
||||
|
||||
test('deve mostrar cards de turmas', async ({ page }) => {
|
||||
await expect(page.getByText(/por turma|by class/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar nome da turma', async ({ page }) => {
|
||||
const classCard = page.locator('[data-testid="class-stat-card"]').first();
|
||||
if (await classCard.isVisible()) {
|
||||
await expect(classCard.getByText(/turma|class/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar número de tentativas por turma', async ({ page }) => {
|
||||
const classCard = page.locator('[data-testid="class-stat-card"]').first();
|
||||
if (await classCard.isVisible()) {
|
||||
await expect(classCard.getByText(/tentativas/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar média da turma com cor baseada no desempenho', async ({ page }) => {
|
||||
const classCard = page.locator('[data-testid="class-stat-card"]').first();
|
||||
if (await classCard.isVisible()) {
|
||||
const avgLabel = classCard.locator('[data-testid="average-label"]').first();
|
||||
if (await avgLabel.isVisible()) {
|
||||
// Deve ter cor: emerald (bom), amber (médio), red (ruim)
|
||||
const classes = await avgLabel.getAttribute('class') || '';
|
||||
expect(classes).toMatch(/label-emerald|label-amber|label-red|bg-emerald|bg-amber|bg-red/i);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Estatísticas por Template', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
});
|
||||
|
||||
test('deve mostrar cards de templates', async ({ page }) => {
|
||||
await expect(page.getByText(/por modelo|por template|by template/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar título do template', async ({ page }) => {
|
||||
const templateCard = page.locator('[data-testid="template-stat-card"]').first();
|
||||
if (await templateCard.isVisible()) {
|
||||
await expect(templateCard.getByText(/prova|template/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar número de tentativas por template', async ({ page }) => {
|
||||
const templateCard = page.locator('[data-testid="template-stat-card"]').first();
|
||||
if (await templateCard.isVisible()) {
|
||||
await expect(templateCard.getByText(/tentativas/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Lista de Tentativas', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
});
|
||||
|
||||
test('deve mostrar tabela de tentativas', async ({ page }) => {
|
||||
await expect(page.getByText(/tentativas de prova/i)).toBeVisible();
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar colunas principais', async ({ page }) => {
|
||||
const table = page.locator('table').first();
|
||||
if (await table.isVisible()) {
|
||||
await expect(table.getByText(/aluno|student/i)).toBeVisible();
|
||||
await expect(table.getByText(/turma|class/i)).toBeVisible();
|
||||
await expect(table.getByText(/modelo|template/i)).toBeVisible();
|
||||
await expect(table.getByText(/nota|score/i)).toBeVisible();
|
||||
await expect(table.getByText(/status/i)).toBeVisible();
|
||||
await expect(table.getByText(/data/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar badge de status correção', async ({ page }) => {
|
||||
const table = page.locator('table').first();
|
||||
if (await table.isVisible()) {
|
||||
const statusCell = table.locator('td').last();
|
||||
if (await statusCell.isVisible()) {
|
||||
// Status: fully_graded, partially_graded, auto_graded, pending
|
||||
await expect(statusCell.locator('[data-testid="label"]')).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Paginação', () => {
|
||||
test('deve ter controles de paginação', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
|
||||
const pagination = page.locator('[data-testid="pagination"]');
|
||||
if (await pagination.isVisible()) {
|
||||
await expect(pagination.getByRole('button', { name: /anterior|previous/i })).toBeVisible();
|
||||
await expect(pagination.getByRole('button', { name: /próxima|next/i })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar página atual', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
|
||||
const pageInfo = page.getByText(/página \d+ de \d+/i);
|
||||
if (await pageInfo.isVisible()) {
|
||||
await expect(pageInfo).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve navegar entre páginas', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
|
||||
const nextButton = page.getByRole('button', { name: /próxima|next/i });
|
||||
if (await nextButton.isEnabled()) {
|
||||
await nextButton.click();
|
||||
|
||||
// Deve mudar a página
|
||||
const pageInfo = page.getByText(/página \d+ de \d+/i);
|
||||
if (await pageInfo.isVisible()) {
|
||||
const text = await pageInfo.textContent();
|
||||
expect(text).toMatch(/página [2-9]/);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Filtros', () => {
|
||||
test('deve filtrar por status', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
|
||||
const statusFilter = page.getByRole('combobox', { name: /status/i });
|
||||
if (await statusFilter.isVisible()) {
|
||||
await statusFilter.selectOption('paid');
|
||||
|
||||
// Deve recarregar resultados
|
||||
await page.waitForTimeout(500);
|
||||
// Verificar se filtrou
|
||||
}
|
||||
});
|
||||
|
||||
test('deve filtrar por turma', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
|
||||
const classFilter = page.getByRole('combobox', { name: /turma/i });
|
||||
if (await classFilter.isVisible()) {
|
||||
const initialCount = await page.locator('table tbody tr').count();
|
||||
|
||||
await classFilter.selectOption({ index: 0 });
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
// Pode mostrar menos resultados
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Labels/Badges de Status', () => {
|
||||
test('deve usar Label component corretamente', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
|
||||
const label = page.locator('[data-testid="label"]').first();
|
||||
if (await label.isVisible()) {
|
||||
// Deve ter as classes de cor
|
||||
const classes = await label.getAttribute('class') || '';
|
||||
expect(classes).toMatch(/label-(emerald|amber|red|blue|gray|sky|indigo|purple)/);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar cor correta para cada status', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
|
||||
// Verificar cores dos badges de porcentagem
|
||||
const percentageLabels = page.locator('[data-testid="average-percent-label"]');
|
||||
const count = await percentageLabels.count();
|
||||
|
||||
for (let i = 0; i < Math.min(count, 3); i++) {
|
||||
const label = percentageLabels.nth(i);
|
||||
const classes = await label.getAttribute('class') || '';
|
||||
// >= 70%: emerald, >= 60%: amber, < 60%: red
|
||||
expect(classes).toMatch(/label-(emerald|amber|red)/);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar cor correta para status de correção', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
|
||||
const gradingLabels = page.locator('[data-testid="grading-status-label"]');
|
||||
const count = await gradingLabels.count();
|
||||
|
||||
for (let i = 0; i < Math.min(count, 3); i++) {
|
||||
const label = gradingLabels.nth(i);
|
||||
const classes = await label.getAttribute('class') || '';
|
||||
// fully_graded: emerald, partially_graded: amber, auto_graded: blue, pending: gray
|
||||
expect(classes).toMatch(/label-(emerald|amber|blue|gray)/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Exportação de Dados', () => {
|
||||
test('deve ter botão de exportar CSV', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
|
||||
const exportButton = page.getByRole('button', { name: /exportar|csv/i });
|
||||
if (await exportButton.isVisible()) {
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
|
||||
await exportButton.click();
|
||||
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toMatch(/\.csv$/);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve ter botão de exportar PDF', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/statistics/exams');
|
||||
|
||||
const exportButton = page.getByRole('button', { name: /pdf/i });
|
||||
if (await exportButton.isVisible()) {
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
|
||||
await exportButton.click();
|
||||
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toMatch(/\.pdf$/);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Login', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/auth/login');
|
||||
});
|
||||
|
||||
test('deve mostrar o formulário de login', async ({ page }) => {
|
||||
await expect(page.locator('h1, h2')).toContainText('Login');
|
||||
await expect(page.getByLabel(/email/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/senha/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /entrar|login/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar erro com credenciais inválidas', async ({ page }) => {
|
||||
await page.getByLabel(/email/i).fill('[email protected]');
|
||||
await page.getByLabel(/senha/i).fill('senhaerrada');
|
||||
await page.getByRole('button', { name: /entrar|login/i }).click();
|
||||
|
||||
// Deve mostrar mensagem de erro ou redirecionar para login
|
||||
await expect(page).toHaveURL(/\/auth\/login/);
|
||||
});
|
||||
|
||||
test('deve redirecionar para dispatcher após login bem-sucedido', async ({ page }) => {
|
||||
// TODO: Criar usuário de teste antes de executar
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar|login/i }).click();
|
||||
|
||||
// await expect(page).toHaveURL(/\/dispatcher/);
|
||||
});
|
||||
|
||||
test('deve redirecionar para login se tentar acessar rota protegida', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard');
|
||||
await expect(page).toHaveURL(/\/auth\/login/);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Navbar - Estado Autenticado', () => {
|
||||
test('não autenticado: mostra Home, Cadastro, Login', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const nav = page.locator('nav ul');
|
||||
await expect(nav.getByRole('link', { name: 'Home' })).toBeVisible();
|
||||
await expect(nav.getByRole('link', { name: 'Cadastro' })).toBeVisible();
|
||||
await expect(nav.getByRole('button', { name: 'Login' })).toBeVisible();
|
||||
await expect(nav.getByRole('link', { name: 'Dashboard' })).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('autenticado: mostra Home, Dashboard, Logout (esconde Cadastro)', async ({ page }) => {
|
||||
// TODO: Fazer login primeiro
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar|login/i }).click();
|
||||
|
||||
// await page.waitForURL(/\/dispatcher|\/dashboard/);
|
||||
// await page.goto('/');
|
||||
|
||||
// const nav = page.locator('nav ul');
|
||||
// await expect(nav.getByRole('link', { name: 'Home' })).toBeVisible();
|
||||
// await expect(nav.getByRole('link', { name: 'Cadastro' }).not.toBeVisible();
|
||||
// await expect(nav.getByRole('link', { name: 'Dashboard' })).toBeVisible();
|
||||
// await expect(nav.getByRole('button', { name: /logout|sair/i })).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,103 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Cadastro', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/auth/register');
|
||||
});
|
||||
|
||||
test('deve mostrar o formulário de cadastro', async ({ page }) => {
|
||||
await expect(page.locator('h1, h2')).toContainText(/cadastro|registro/i);
|
||||
await expect(page.getByLabel(/nome completo/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/email/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/senha/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/confirmar senha/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /cadastrar|registrar/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar erro com senhas que não conferem', async ({ page }) => {
|
||||
await page.getByLabel(/nome completo/i).fill('Usuário Teste');
|
||||
await page.getByLabel(/email/i).fill(`teste${Date.now()}@teste.com`);
|
||||
await page.getByLabel(/senha/i).fill('123456');
|
||||
await page.getByLabel(/confirmar senha/i).fill('654321');
|
||||
await page.getByRole('button', { name: /cadastrar|registrar/i }).click();
|
||||
|
||||
await expect(page.getByText(/senhas não conferem|senha não confere/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar erro com senha muito curta', async ({ page }) => {
|
||||
await page.getByLabel(/nome completo/i).fill('Usuário Teste');
|
||||
await page.getByLabel(/email/i).fill(`teste${Date.now()}@teste.com`);
|
||||
await page.getByLabel(/senha/i).fill('123');
|
||||
await page.getByLabel(/confirmar senha/i).fill('123');
|
||||
await page.getByRole('button', { name: /cadastrar|registrar/i }).click();
|
||||
|
||||
await expect(page.getByText(/mínimo de \d+ caracteres|senha muito curta/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar erro com email inválido', async ({ page }) => {
|
||||
await page.getByLabel(/nome completo/i).fill('Usuário Teste');
|
||||
await page.getByLabel(/email/i).fill('email-invalido');
|
||||
await page.getByLabel(/senha/i).fill('123456');
|
||||
await page.getByLabel(/confirmar senha/i).fill('123456');
|
||||
await page.getByRole('button', { name: /cadastrar|registrar/i }).click();
|
||||
|
||||
await expect(page.getByText(/email inválido/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar erro com campos obrigatórios vazios', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /cadastrar|registrar/i }).click();
|
||||
|
||||
await expect(page.getByText(/nome.*obrigatório/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve cadastrar com dados válidos e redirecionar para login', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
await page.getByLabel(/nome completo/i).fill(`Usuário Teste ${timestamp}`);
|
||||
await page.getByLabel(/email/i).fill(`teste${timestamp}@teste.com`);
|
||||
await page.getByLabel(/senha/i).fill('123456');
|
||||
await page.getByLabel(/confirmar senha/i).fill('123456');
|
||||
|
||||
await page.getByRole('button', { name: /cadastrar|registrar/i }).click();
|
||||
|
||||
// Deve redirecionar para login ou mostrar mensagem de sucesso
|
||||
await expect(page).toHaveURL(/\/auth\/login/, { timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Seleção de Role', () => {
|
||||
test('deve mostrar opções quando usuário tem múltiplos roles', async ({ page }) => {
|
||||
// TODO: Fazer login com usuário que tem múltiplos roles
|
||||
await page.goto('/dispatcher');
|
||||
|
||||
// Deve mostrar o dispatcher
|
||||
await expect(page.locator('h1, h2')).toContainText(/escolha.*destino|selecione.*papel/i);
|
||||
});
|
||||
|
||||
test('admin: redireciona para /admin/dashboard', async ({ page }) => {
|
||||
// TODO: Fazer login como admin
|
||||
// await page.goto('/dispatcher');
|
||||
// await page.getByRole('button', { name: /administrador|admin/i }).click();
|
||||
// await expect(page).toHaveURL(/\/admin\/dashboard/);
|
||||
});
|
||||
|
||||
test('professor: redireciona para /dashboard/teacher', async ({ page }) => {
|
||||
// TODO: Fazer login como professor
|
||||
// await page.goto('/dispatcher');
|
||||
// await page.getByRole('button', { name: /professor/i }).click();
|
||||
// await expect(page).toHaveURL(/\/dashboard\/teacher/);
|
||||
});
|
||||
|
||||
test('aluno: redireciona para /dashboard/student', async ({ page }) => {
|
||||
// TODO: Fazer login como aluno
|
||||
// await page.goto('/dispatcher');
|
||||
// await page.getByRole('button', { name: /aluno/i }).click();
|
||||
// await expect(page).toHaveURL(/\/dashboard\/student/);
|
||||
});
|
||||
|
||||
test('responsável: redireciona para /dashboard/guardian', async ({ page }) => {
|
||||
// TODO: Fazer login como responsável
|
||||
// await page.goto('/dispatcher');
|
||||
// await page.getByRole('button', { name: /responsável|guardian/i }).click();
|
||||
// await expect(page).toHaveURL(/\/dashboard\/guardian/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,243 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Casos de Erro Importantes', () => {
|
||||
test.describe('Acesso Não Autorizado', () => {
|
||||
test('usuário não logado tenta acessar rota protegida', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard');
|
||||
|
||||
// Deve redirecionar para login
|
||||
await expect(page).toHaveURL(/\/auth\/login/, { timeout: 5000 });
|
||||
});
|
||||
|
||||
test('usuário com role errado tenta acessar rota admin', async ({ page }) => {
|
||||
// TODO: Fazer login como aluno
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
|
||||
// await page.goto('/admin/dashboard');
|
||||
|
||||
// Deve mostrar "Não autorizado" ou redirecionar
|
||||
// await expect(page.getByText(/não autorizado|not authorized|acesso negado/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('usuário tenta acessar dashboard de outro role', async ({ page }) => {
|
||||
// TODO: Fazer login como aluno
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
|
||||
// Tentar acessar dashboard de professor
|
||||
// await page.goto('/dashboard/teacher');
|
||||
|
||||
// Deve redirecionar ou mostrar erro
|
||||
// await expect(page.getByText(/não autorizado|not authorized/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Sessão Expirada', () => {
|
||||
test('deve redirecionar para login quando sessão expira', async ({ page }) => {
|
||||
// TODO: Simular sessão expirada limpando cookies
|
||||
// await page.goto('/dashboard/student');
|
||||
// await page.context().clearCookies();
|
||||
// await page.reload();
|
||||
|
||||
// await expect(page).toHaveURL(/\/auth\/login/);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Erros de Validação - Formulários', () => {
|
||||
test('email inválido no cadastro', async ({ page }) => {
|
||||
await page.goto('/auth/register');
|
||||
await page.getByLabel(/email/i).fill('email@invalido');
|
||||
await page.getByRole('button', { name: /cadastrar/i }).click();
|
||||
|
||||
await expect(page.getByText(/email inválido/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('senha muito curta', async ({ page }) => {
|
||||
await page.goto('/auth/register');
|
||||
await page.getByLabel(/senha/i).fill('123');
|
||||
await page.getByLabel(/confirmar senha/i).fill('123');
|
||||
await page.getByRole('button', { name: /cadastrar/i }).click();
|
||||
|
||||
await expect(page.getByText(/mínimo.*\d+.*caracteres/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('senhas não conferem', async ({ page }) => {
|
||||
await page.goto('/auth/register');
|
||||
await page.getByLabel(/senha/i).fill('123456');
|
||||
await page.getByLabel(/confirmar senha/i).fill('654321');
|
||||
await page.getByRole('button', { name: /cadastrar/i }).click();
|
||||
|
||||
await expect(page.getByText(/senhas não conferem/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Erros de Validação - Turma', () => {
|
||||
test('data de término anterior à data de início', async ({ page }) => {
|
||||
// TODO: Fazer login como admin
|
||||
await page.goto('/admin/dashboard/class/add');
|
||||
|
||||
await page.getByLabel(/data de início/i).fill('2025-01-10');
|
||||
await page.getByLabel(/data de término|end date/i).fill('2025-01-01');
|
||||
await page.getByRole('button', { name: /salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/término não pode ser anterior|data inválida/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('campos obrigatórios faltando', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/add');
|
||||
await page.getByRole('button', { name: /salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/obrigatório|required/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('sem professores selecionados', async ({ page }) => {
|
||||
await page.goto('/admin/dashboard/class/add');
|
||||
|
||||
await page.getByLabel(/título/i).fill('Turma Sem Professor');
|
||||
await page.getByLabel(/data de início/i).fill('2025-01-01');
|
||||
await page.getByLabel(/horário/i).fill('19:00');
|
||||
|
||||
await page.getByRole('button', { name: /salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/professor.*obrigatório/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Erros de Upload', () => {
|
||||
test('arquivo muito grande', async ({ page }) => {
|
||||
// TODO: Testar upload de arquivo grande
|
||||
// await page.goto('/files/upload');
|
||||
|
||||
// const fileInput = page.getByRole('input').first();
|
||||
// await fileInput.setInputFiles('path/to/large-file.pdf');
|
||||
|
||||
// await expect(page.getByText(/arquivo muito grande|tamanho máximo/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('formato não suportado', async ({ page }) => {
|
||||
// TODO: Testar upload de arquivo não suportado
|
||||
// await page.goto('/files/upload');
|
||||
|
||||
// const fileInput = page.getByRole('input').first();
|
||||
// await fileInput.setInputFiles('path/to/file.exe');
|
||||
|
||||
// await expect(page.getByText(/formato não suportado/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Provas - Restrições', () => {
|
||||
test('não pode iniciar prova após prazo', async ({ page }) => {
|
||||
// TODO: Tentar acessar prova expirada
|
||||
// await page.goto('/dashboard/student/assignments/expired-id/take');
|
||||
|
||||
// await expect(page.getByText(/prazo expirado|não disponível/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('não pode submeter prova duas vezes', async ({ page }) => {
|
||||
// TODO: Submeter prova e tentar submeter novamente
|
||||
// await page.goto('/dashboard/student/assignments/assignment-id/take');
|
||||
|
||||
// // Submeter primeira vez
|
||||
// await page.getByRole('button', { name: /submeter/i }).click();
|
||||
// await page.getByRole('button', { name: /confirmar/i }).click();
|
||||
|
||||
// // Tentar submeter novamente
|
||||
// await page.goto('/dashboard/student/assignments/assignment-id/take');
|
||||
|
||||
// await expect(page.getByText(/já submetida|não disponível/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('não pode editar após submeter', async ({ page }) => {
|
||||
// TODO: Acessar prova já submetida
|
||||
// await page.goto('/dashboard/student/assignments/submitted-id/take');
|
||||
|
||||
// As opções devem estar desabilitadas
|
||||
// const radioButtons = page.locator('input[type="radio"]');
|
||||
// for (const radio of await radioButtons.all()) {
|
||||
// await expect(radio).toBeDisabled();
|
||||
// }
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Erros de Conexão', () => {
|
||||
test('deve mostrar mensagem amigável quando API falha', async ({ page }) => {
|
||||
// TODO: Simular erro de API
|
||||
// await page.route('**/api/**', route => route.abort());
|
||||
|
||||
// await page.goto('/dashboard/student');
|
||||
|
||||
// await expect(page.getByText(/erro de conexão|tente novamente/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Email Já Existe', () => {
|
||||
test('cadastro com email duplicado', async ({ page }) => {
|
||||
await page.goto('/auth/register');
|
||||
|
||||
// Email que já existe no banco
|
||||
await page.getByLabel(/nome completo/i).fill('Usuário Teste');
|
||||
await page.getByLabel(/email/i).fill('[email protected]'); // Assumindo que existe
|
||||
await page.getByLabel(/senha/i).fill('123456');
|
||||
await page.getByLabel(/confirmar senha/i).fill('123456');
|
||||
|
||||
await page.getByRole('button', { name: /cadastrar/i }).click();
|
||||
|
||||
await expect(page.getByText(/email já cadastrado|já existe/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Exclusão com Dependências', () => {
|
||||
test('não pode excluir categoria com arquivos vinculados', async ({ page }) => {
|
||||
// TODO: Testar exclusão de categoria em uso
|
||||
// await page.goto('/admin/dashboard/categories/edit/category-id');
|
||||
|
||||
// await page.getByRole('button', { name: /excluir/i }).click();
|
||||
|
||||
// await expect(page.getByText(/categoria em uso|possui arquivos/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('não pode excluir template com tentativas', async ({ page }) => {
|
||||
// TODO: Testar exclusão de template usado
|
||||
// await page.goto('/admin/dashboard/exam-templates/edit/template-id');
|
||||
|
||||
// await page.getByRole('button', { name: /excluir/i }).click();
|
||||
|
||||
// await expect(page.getByText(/possui tentativas|em uso/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Ward - Email Duplicado', () => {
|
||||
test('não pode cadastrar ward com email existente', async ({ page }) => {
|
||||
// TODO: Fazer login como guardian
|
||||
// await page.goto('/dashboard/guardian/register-student');
|
||||
|
||||
// await page.getByLabel(/nome do aluno/i).fill('Aluno Teste');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]'); // Já existe
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByLabel(/confirmar senha/i).fill('123456');
|
||||
|
||||
// await page.getByRole('button', { name: /cadastrar/i }).click();
|
||||
|
||||
// await expect(page.getByText(/email já cadastrado/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Pagamentos - Confirmação', () => {
|
||||
test('deve confirmar antes de excluir obrigação', async ({ page }) => {
|
||||
// TODO: Testar exclusão com confirmação
|
||||
// await page.goto('/admin/dashboard/payments');
|
||||
|
||||
// const deleteButton = page.getByRole('button', { name: /excluir/i }).first();
|
||||
// await deleteButton.click();
|
||||
|
||||
// await expect(page.getByText(/tem certeza|deseja excluir/i)).toBeVisible();
|
||||
// await expect(page.getByRole('button', { name: /confirmar/i })).toBeVisible();
|
||||
// await expect(page.getByRole('button', { name: /cancelar/i })).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,408 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Responsável - Pagamentos', () => {
|
||||
// TODO: Fazer login como responsável
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/dispatcher|\/dashboard\/guardian/);
|
||||
// });
|
||||
|
||||
test.describe('Dashboard do Responsável', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de wards vinculados', async ({ page }) => {
|
||||
await expect(page.getByText(/alunos|wards|meus alunos/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar resumo financeiro', async ({ page }) => {
|
||||
await expect(page.getByText(/pagamentos|mensalidades|financeiro/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar total de pendências', async ({ page }) => {
|
||||
const pendingValue = page.getByText(/\d+,\d+|pendência/i);
|
||||
if (await pendingValue.isVisible()) {
|
||||
await expect(pendingValue).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Pagamentos por Aluno', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// TODO: Navegar para página de pagamentos de um ward específico
|
||||
// await page.goto('/dashboard/guardian/ward-id/payments');
|
||||
await page.goto('/dashboard/guardian');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de obrigações do aluno', async ({ page }) => {
|
||||
await expect(page.getByText(/obrigações|mensalidades/i)).toBeVisible();
|
||||
await expect(page.locator('table, [data-testid="payment-list"]').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar valor e vencimento de cada obrigação', async ({ page }) => {
|
||||
const table = page.locator('table').first();
|
||||
if (await table.isVisible()) {
|
||||
await expect(table.getByText(/valor|amount|r\$/i)).toBeVisible();
|
||||
await expect(table.getByText(/vencimento|due date/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar status da obrigação', async ({ page }) => {
|
||||
const table = page.locator('table').first();
|
||||
if (await table.isVisible()) {
|
||||
const statusCell = table.locator('tbody tr td').last();
|
||||
if (await statusCell.isVisible()) {
|
||||
// Status: pending, paid, rejected
|
||||
const label = statusCell.locator('[data-testid="status-label"], .label').first();
|
||||
if (await label.isVisible()) {
|
||||
const classes = await label.getAttribute('class') || '';
|
||||
expect(classes).toMatch(/label-(amber|emerald|red|gray)/);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Registrar Pagamento', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// TODO: Navegar para página de pagamentos de um ward
|
||||
await page.goto('/dashboard/guardian/payments/register');
|
||||
});
|
||||
|
||||
test('deve mostrar formulário de registro', async ({ page }) => {
|
||||
await expect(page.getByRole('combobox', { name: /aluno|ward/i })).toBeVisible();
|
||||
await expect(page.getByLabel(/valor|amount/i)).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: /método|payment method/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve listar wards disponíveis', async ({ page }) => {
|
||||
const wardSelect = page.getByRole('combobox', { name: /aluno|ward/i });
|
||||
const options = await wardSelect.locator('option').count();
|
||||
|
||||
expect(options).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('deve permitir selecionar forma de pagamento', async ({ page }) => {
|
||||
const methodSelect = page.getByRole('combobox', { name: /método/i });
|
||||
|
||||
const options = ['pix', 'transferência', 'dinheiro', 'cartão'];
|
||||
const selectOptions = await methodSelect.locator('option').all();
|
||||
|
||||
for (const option of selectOptions) {
|
||||
const text = await option.textContent();
|
||||
expect(options.some(o => o.toLowerCase().includes(text.toLowerCase()))).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Upload de Comprovante', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian/payments/register');
|
||||
});
|
||||
|
||||
test('deve ter campo de upload de comprovante', async ({ page }) => {
|
||||
await expect(page.getByLabelText(/comprovante|recibo/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve aceitar arquivo de imagem', async ({ page }) => {
|
||||
const fileInput = page.getByLabelText(/comprovante/i);
|
||||
// await fileInput.setInputFiles('tests/fixtures/recibo.jpg');
|
||||
|
||||
await expect(fileInput).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve aceitar arquivo PDF', async ({ page }) => {
|
||||
const fileInput = page.getByLabelText(/comprovante/i);
|
||||
// await fileInput.setInputFiles('tests/fixtures/recibo.pdf');
|
||||
|
||||
await expect(fileInput).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar preview do comprovante após upload', async ({ page }) => {
|
||||
const fileInput = page.getByLabelText(/comprovante/i);
|
||||
|
||||
// await fileInput.setInputFiles('tests/fixtures/recibo.jpg');
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
// const preview = page.locator('img[src*="blob:"], .file-upload-preview').first();
|
||||
// if (await preview.isVisible()) {
|
||||
// await expect(preview).toBeVisible();
|
||||
// }
|
||||
});
|
||||
|
||||
test('deve remover comprovante ao clicar em remover', async ({ page }) => {
|
||||
const fileInput = page.getByLabelText(/comprovante/i);
|
||||
|
||||
// await fileInput.setInputFiles('tests/fixtures/recibo.jpg');
|
||||
// await page.waitForTimeout(1000);
|
||||
|
||||
const removeButton = page.getByRole('button', { name: /remover|x/i });
|
||||
if (await removeButton.isVisible()) {
|
||||
await removeButton.click();
|
||||
|
||||
// Campo deve estar vazio
|
||||
// expect(await fileInput.inputValue()).toBe('');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Validações de Pagamento', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian/payments/register');
|
||||
});
|
||||
|
||||
test('deve validar valor obrigatório', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /registrar|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/valor.*obrigatório|amount.*required/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve validar formato de valor (com vírgula)', async ({ page }) => {
|
||||
await page.getByLabel(/valor|amount/i).fill('50,00');
|
||||
|
||||
await page.getByRole('button', { name: /registrar|salvar/i }).click();
|
||||
|
||||
// Não deve mostrar erro de formato
|
||||
// Ou pode converter para formato correto
|
||||
});
|
||||
|
||||
test('deve aceitar valor com ponto', async ({ page }) => {
|
||||
await page.getByLabel(/valor|amount/i).fill('50.00');
|
||||
|
||||
await page.getByRole('button', { name: /registrar|salvar/i }).click();
|
||||
});
|
||||
|
||||
test('deve validar aluno obrigatório', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /registrar|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/aluno.*obrigatório|ward.*required/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Confirmar Pagamento', () => {
|
||||
test('deve mostrar resumo antes de confirmar', async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian/payments/register');
|
||||
|
||||
// Preencher formulário
|
||||
await page.getByRole('combobox', { name: /aluno/i }).selectOption({ index: 0 });
|
||||
await page.getByLabel(/valor/i).fill('150,00');
|
||||
await page.getByRole('combobox', { name: /método/i }).selectOption('pix');
|
||||
|
||||
await page.getByRole('button', { name: /registrar|salvar/i }).click();
|
||||
|
||||
// Pode mostrar modal de confirmação ou resumo
|
||||
const confirmDialog = page.locator('[data-testid="confirm-dialog"]');
|
||||
if (await confirmDialog.isVisible()) {
|
||||
await expect(confirmDialog.getByText(/confirma|registro|pagamento/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /confirmar|sim/i })).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /cancelar|não/i })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve registrar pagamento com sucesso', async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian/payments/register');
|
||||
|
||||
// TODO: Preencher com dados válidos
|
||||
// await page.getByRole('combobox', { name: /aluno/i }).selectOption({ index: 0 });
|
||||
// await page.getByLabel(/valor/i).fill('150,00');
|
||||
// await page.getByRole('combobox', { name: /método/i }).selectOption('pix');
|
||||
// await page.getByLabelText(/comprovante/i).setInputFiles('tests/fixtures/recibo.pdf');
|
||||
|
||||
// await page.getByRole('button', { name: /confirmar/i }).click();
|
||||
|
||||
// await expect(page.getByText(/pagamento registrado|sucesso/i)).toBeVisible();
|
||||
// await expect(page).toHaveURL(/\/dashboard\/guardian/, { timeout: 10000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Histórico de Pagamentos', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian/payments/history');
|
||||
});
|
||||
|
||||
test('deve mostrar histórico de pagamentos', async ({ page }) => {
|
||||
await expect(page.getByText(/histórico|pagamentos/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar filtros de status', async ({ page }) => {
|
||||
const statusFilter = page.getByRole('combobox', { name: /status/i });
|
||||
if (await statusFilter.isVisible()) {
|
||||
await expect(statusFilter).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve filtrar por status', async ({ page }) => {
|
||||
const statusFilter = page.getByRole('combobox', { name: /status/i });
|
||||
if (await statusFilter.isVisible()) {
|
||||
await statusFilter.selectOption('pending');
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Deve mostrar apenas pendentes
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Status de Pagamento', () => {
|
||||
test('deve mostrar status "Pendente" em amarelo', async ({ page }) => {
|
||||
const pendingLabel = page.locator('[data-testid="status-label"]').filter({ hasText: /pendente/i });
|
||||
|
||||
if (await pendingLabel.isVisible()) {
|
||||
const classes = await pendingLabel.getAttribute('class') || '';
|
||||
expect(classes).toMatch(/label-amber|text-amber|bg-amber/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar status "Aguardando Verificação" em azul', async ({ page }) => {
|
||||
const verificationLabel = page.locator('[data-testid="status-label"]').filter({ hasText: /aguardando/i });
|
||||
|
||||
if (await verificationLabel.isVisible()) {
|
||||
const classes = await verificationLabel.getAttribute('class') || '';
|
||||
expect(classes).toMatch(/label-blue|text-blue|bg-blue/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar status "Pago" em verde', async ({ page }) => {
|
||||
const paidLabel = page.locator('[data-testid="status-label"]').filter({ hasText: /pago|verificado/i });
|
||||
|
||||
if (await paidLabel.isVisible()) {
|
||||
const classes = await paidLabel.getAttribute('class') || '';
|
||||
expect(classes).toMatch(/label-emerald|text-emerald|bg-emerald/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar status "Rejeitado" em vermelho', async ({ page }) => {
|
||||
const rejectedLabel = page.locator('[data-testid="status-label"]').filter({ hasText: /rejeitado/i });
|
||||
|
||||
if (await rejectedLabel.isVisible()) {
|
||||
const classes = await rejectedLabel.getAttribute('class') || '';
|
||||
expect(classes).toMatch(/label-red|text-red|bg-red/i);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Notificações', () => {
|
||||
test('deve mostrar notificação de pagamento recebido', async ({ page }) => {
|
||||
// TODO: Testar notificação (pode precisar de WebSocket ou polling)
|
||||
// await page.goto('/dashboard/guardian');
|
||||
|
||||
// Pode ter ícone de notificação
|
||||
const notificationIcon = page.locator('[data-testid="notification-icon"], [data-testid="notification-badge"]');
|
||||
if (await notificationIcon.isVisible()) {
|
||||
const badge = notificationIcon.locator('[data-testid="badge"], .badge');
|
||||
const count = await badge.count();
|
||||
|
||||
if (count > 0) {
|
||||
await expect(badge.first()).toContainText(/\d+/);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar alerta de vencimento próximo', async ({ page }) => {
|
||||
// TODO: Testar alerta de vencimento
|
||||
// await page.goto('/dashboard/guardian');
|
||||
|
||||
// Pode ter banner de alerta
|
||||
const alertBanner = page.locator('[data-testid="due-date-alert"]');
|
||||
if (await alertBanner.isVisible()) {
|
||||
await expect(alertBanner.getByText(/vence em breve|próximo ao vencimento/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Recibo de Pagamento', () => {
|
||||
test('deve permitir visualizar comprovante', async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian/payments/history');
|
||||
|
||||
const viewReceiptButton = page.getByRole('button', { name: /comprovante|recibo/i }).first();
|
||||
if (await viewReceiptButton.isVisible()) {
|
||||
await viewReceiptButton.click();
|
||||
|
||||
// Deve abrir modal com comprovante
|
||||
await expect(page.locator('.fixed.inset-0')).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar comprovante em modal', async ({ page }) => {
|
||||
// TODO: Implementar
|
||||
// await page.getByRole('button', { name: /comprovante/i }).first().click();
|
||||
|
||||
// // Deve mostrar imagem ou PDF
|
||||
// const content = page.locator('img[src*="/uploads/"], iframe').first();
|
||||
// await expect(content).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Múltiplos Wards', () => {
|
||||
test('deve ter filtro para selecionar ward específico', async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian/payments');
|
||||
|
||||
const wardFilter = page.getByRole('combobox', { name: /aluno|ward/i });
|
||||
if (await wardFilter.isVisible()) {
|
||||
await expect(wardFilter).toBeVisible();
|
||||
|
||||
// Deve ter opção "Todos"
|
||||
const allOption = wardFilter.getByRole('option', { name: /todos|all/i });
|
||||
await expect(allOption).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar apenas pagamentos do ward selecionado', async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian/payments');
|
||||
|
||||
const wardFilter = page.getByRole('combobox', { name: /aluno|ward/i });
|
||||
if (await wardFilter.isVisible()) {
|
||||
const options = await wardFilter.locator('option').count();
|
||||
|
||||
if (options > 1) {
|
||||
const beforeCount = await page.locator('[data-testid="payment-row"]').count();
|
||||
|
||||
await wardFilter.selectOption({ index: 1 });
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const afterCount = await page.locator('[data-testid="payment-row"]').count();
|
||||
|
||||
// Pode mostrar menos resultados
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Responsividade - Mobile', () => {
|
||||
test('formulário de registro deve ser usável em mobile', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.goto('/dashboard/guardian/payments/register');
|
||||
|
||||
// Sem scroll horizontal
|
||||
const hasHorizontalScroll = await page.evaluate(() => {
|
||||
return document.body.scrollWidth > document.body.clientWidth;
|
||||
});
|
||||
expect(hasHorizontalScroll).toBe(false);
|
||||
|
||||
// Campos devem estar visíveis
|
||||
await expect(page.getByRole('combobox', { name: /aluno/i })).toBeVisible();
|
||||
await expect(page.getByLabel(/valor/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('tabela de histórico deve ter scroll horizontal em mobile', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.goto('/dashboard/guardian/payments/history');
|
||||
|
||||
const table = page.locator('table').first();
|
||||
if (await table.isVisible()) {
|
||||
const container = table.locator('..');
|
||||
const overflowX = await container.evaluate(el => {
|
||||
return window.getComputedStyle(el).overflowX;
|
||||
});
|
||||
|
||||
// Deve ter scroll ou estar ajustado
|
||||
expect(overflowX || 'visible').toMatch(/auto|scroll|visible/);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Wards - Alunos Vinculados a Responsáveis', () => {
|
||||
// TODO: Fazer login como responsável antes de cada teste
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/dashboard\/guardian/);
|
||||
// });
|
||||
|
||||
test.describe('Dashboard do Responsável', () => {
|
||||
test('deve mostrar lista de alunos vinculados', async ({ page }) => {
|
||||
// TODO: Implementar após login
|
||||
await page.goto('/dashboard/guardian');
|
||||
|
||||
await expect(page.getByText(/alunos|wards|meus alunos/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('cada aluno deve ter link para detalhes', async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian');
|
||||
|
||||
// TODO: Verificar se há cards de alunos com links
|
||||
// const studentCard = page.locator('[data-testid="student-card"]').first();
|
||||
// await expect(studentCard.getByRole('link')).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Cadastro de Ward', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/guardian/register-student');
|
||||
});
|
||||
|
||||
test('deve mostrar formulário de cadastro de aluno', async ({ page }) => {
|
||||
await expect(page.getByLabel(/nome do aluno|nome completo/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/email/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/senha/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/confirmar senha/i)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: /cadastrar|salvar/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve validar campos obrigatórios', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /cadastrar|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/nome.*obrigatório/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar erro com email já existente', async ({ page }) => {
|
||||
await page.getByLabel(/nome do aluno/i).fill('Aluno Teste');
|
||||
await page.getByLabel(/email/i).fill('[email protected]'); // Email que já existe
|
||||
await page.getByLabel(/senha/i).fill('123456');
|
||||
await page.getByLabel(/confirmar senha/i).fill('123456');
|
||||
|
||||
await page.getByRole('button', { name: /cadastrar|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/email já cadastrado|já existe/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar erro com senhas diferentes', async ({ page }) => {
|
||||
await page.getByLabel(/nome do aluno/i).fill('Aluno Teste');
|
||||
await page.getByLabel(/email/i).fill(`novoaluno${Date.now()}@teste.com`);
|
||||
await page.getByLabel(/senha/i).fill('123456');
|
||||
await page.getByLabel(/confirmar senha/i).fill('654321');
|
||||
|
||||
await page.getByRole('button', { name: /cadastrar|salvar/i }).click();
|
||||
|
||||
await expect(page.getByText(/senhas não conferem/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve cadastrar ward com sucesso', async ({ page }) => {
|
||||
const timestamp = Date.now();
|
||||
await page.getByLabel(/nome do aluno/i).fill(`Aluno Ward ${timestamp}`);
|
||||
await page.getByLabel(/email/i).fill(`ward${timestamp}@teste.com`);
|
||||
await page.getByLabel(/senha/i).fill('123456');
|
||||
await page.getByLabel(/confirmar senha/i).fill('123456');
|
||||
|
||||
await page.getByRole('button', { name: /cadastrar|salvar/i }).click();
|
||||
|
||||
// Deve mostrar mensagem de sucesso ou redirecionar
|
||||
await expect(page.getByText(/aluno cadastrado|sucesso/i)).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('novo ward deve aparecer na lista do responsável', async ({ page }) => {
|
||||
// 1. Cadastrar novo ward
|
||||
const timestamp = Date.now();
|
||||
const wardName = `Aluno Ward ${timestamp}`;
|
||||
const wardEmail = `ward${timestamp}@teste.com`;
|
||||
|
||||
await page.goto('/dashboard/guardian/register-student');
|
||||
await page.getByLabel(/nome do aluno/i).fill(wardName);
|
||||
await page.getByLabel(/email/i).fill(wardEmail);
|
||||
await page.getByLabel(/senha/i).fill('123456');
|
||||
await page.getByLabel(/confirmar senha/i).fill('123456');
|
||||
await page.getByRole('button', { name: /cadastrar|salvar/i }).click();
|
||||
|
||||
// 2. Voltar para dashboard e verificar se aparece
|
||||
await page.goto('/dashboard/guardian');
|
||||
await expect(page.getByText(wardName)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Detalhes do Ward', () => {
|
||||
test('deve mostrar turmas do aluno', async ({ page }) => {
|
||||
// TODO: Implementar
|
||||
// await page.goto('/dashboard/guardian/ward-id');
|
||||
// await expect(page.getByText(/turmas|classes/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar pagamentos do aluno', async ({ page }) => {
|
||||
// TODO: Implementar
|
||||
// await page.goto('/dashboard/guardian/ward-id/payments');
|
||||
// await expect(page.getByText(/pagamentos|mensalidades/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar notas do aluno', async ({ page }) => {
|
||||
// TODO: Implementar
|
||||
// await page.goto('/dashboard/guardian/ward-id/grades');
|
||||
// await expect(page.getByText(/notas|provas/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Aluno - Provas', () => {
|
||||
// TODO: Fazer login como aluno antes de cada teste
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/dashboard\/student/);
|
||||
// });
|
||||
|
||||
test.describe('Dashboard do Aluno', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/student');
|
||||
});
|
||||
|
||||
test('deve mostrar turmas matriculadas', async ({ page }) => {
|
||||
await expect(page.getByText(/turmas|classes|minhas turmas/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar seção de provas disponíveis', async ({ page }) => {
|
||||
await expect(page.getByText(/provas|exames|avaliações/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Lista de Provas Disponíveis', () => {
|
||||
test('deve mostrar provas para fazer', async ({ page }) => {
|
||||
await page.goto('/dashboard/student/assignments');
|
||||
|
||||
await expect(page.getByText(/provas|exames/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar informações da prova', async ({ page }) => {
|
||||
await page.goto('/dashboard/student/assignments');
|
||||
|
||||
const examCard = page.locator('[data-testid="assignment-card"]').first();
|
||||
if (await examCard.isVisible()) {
|
||||
await expect(examCard.getByText(/título/i)).toBeVisible();
|
||||
await expect(examCard.getByText(/prazo|deadline/i)).toBeVisible();
|
||||
await expect(examCard.getByText(/tempo|time/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve ter botão para iniciar prova', async ({ page }) => {
|
||||
await page.goto('/dashboard/student/assignments');
|
||||
|
||||
const startButton = page.getByRole('link', { name: /iniciar|começar|fazer/i }).first();
|
||||
if (await startButton.isVisible()) {
|
||||
await startButton.click();
|
||||
await expect(page).toHaveURL(/\/assignments\/\w+\/take/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Fazendo Prova', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// TODO: Navegar para uma prova específica
|
||||
// await page.goto('/dashboard/student/assignments/assignment-id/take');
|
||||
});
|
||||
|
||||
test('deve mostrar título e instruções da prova', async ({ page }) => {
|
||||
// await expect(page.getByText(/título/i)).toBeVisible();
|
||||
// await expect(page.getByText(/instruções/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar timer de contagem regressiva', async ({ page }) => {
|
||||
// await expect(page.getByText(/\d+:\d+|\d+ min/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar questões', async ({ page }) => {
|
||||
// await expect(page.getByText(/questão|pergunta/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve responder múltipla escolha', async ({ page }) => {
|
||||
// const firstOption = page.locator('input[type="radio"]').first();
|
||||
// await firstOption.check();
|
||||
// await expect(firstOption).toBeChecked();
|
||||
});
|
||||
|
||||
test('deve responder questão textual', async ({ page }) => {
|
||||
// const textAnswer = page.getByPlaceholder(/digite|escreva/i).first();
|
||||
// if (await textAnswer.isVisible()) {
|
||||
// await textAnswer.fill('Resposta de teste');
|
||||
// }
|
||||
});
|
||||
|
||||
test('deve navegar entre questões', async ({ page }) => {
|
||||
// const nextButton = page.getByRole('button', { name: /próxima|avançar/i });
|
||||
// if (await nextButton.isVisible()) {
|
||||
// await nextButton.click();
|
||||
// // Verificar se mudou de questão
|
||||
// }
|
||||
});
|
||||
|
||||
test('deve submeter prova com confirmação', async ({ page }) => {
|
||||
// Responder pelo menos uma questão
|
||||
// const firstOption = page.locator('input[type="radio"]').first();
|
||||
// if (await firstOption.isVisible()) {
|
||||
// await firstOption.check();
|
||||
// }
|
||||
|
||||
// Clicar em submeter
|
||||
// await page.getByRole('button', { name: /submeter|enviar|finalizar/i }).click();
|
||||
|
||||
// Confirmar
|
||||
// await expect(page.getByText(/tem certeza|confirma/i)).toBeVisible();
|
||||
// await page.getByRole('button', { name: /confirmar|sim/i }).click();
|
||||
|
||||
// Deve redirecionar
|
||||
// await expect(page).toHaveURL(/\/dashboard\/student/);
|
||||
});
|
||||
|
||||
test('não deve editar após submeter', async ({ page }) => {
|
||||
// Tentar acessar prova já submetida
|
||||
// await page.goto('/dashboard/student/assignments/submitted-id/take');
|
||||
|
||||
// Deve mostrar mensagem ou redirecionar
|
||||
// await expect(page.getByText(/já submetida|não disponível/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Resultados de Provas', () => {
|
||||
test('deve mostrar provas corrigidas', async ({ page }) => {
|
||||
// TODO: Navegar para resultados
|
||||
// await page.goto('/dashboard/student/results');
|
||||
|
||||
// await expect(page.getByText(/resultados|notas/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar nota obtida', async ({ page }) => {
|
||||
// const resultCard = page.locator('[data-testid="result-card"]').first();
|
||||
// await expect(resultCard.getByText(/\d+\/\d+/)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar porcentagem', async ({ page }) => {
|
||||
// const resultCard = page.locator('[data-testid="result-card"]').first();
|
||||
// await expect(resultCard.getByText(/\d+%/)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar correção do professor', async ({ page }) => {
|
||||
// const viewButton = page.getByRole('button', { name: /ver|detalhes/i }).first();
|
||||
// await viewButton.click();
|
||||
|
||||
// await expect(page.getByText(/correção|feedback/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar respostas corretas e erradas', async ({ page }) => {
|
||||
// await page.goto('/dashboard/student/results/result-id');
|
||||
|
||||
// const correctAnswer = page.locator('[data-testid="correct-answer"]').first();
|
||||
// if (await correctAnswer.isVisible()) {
|
||||
// await expect(correctAnswer).toHaveClass(/emerald|green|success/i);
|
||||
// }
|
||||
|
||||
// const wrongAnswer = page.locator('[data-testid="wrong-answer"]').first();
|
||||
// if (await wrongAnswer.isVisible()) {
|
||||
// await expect(wrongAnswer).toHaveClass(/red|error|danger/i);
|
||||
// }
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Timer da Prova', () => {
|
||||
test('deve contar regressivamente', async ({ page }) => {
|
||||
// await page.goto('/dashboard/student/assignments/assignment-id/take');
|
||||
|
||||
// const initialTime = await page.getByText(/\d+:\d+/).textContent();
|
||||
// await page.waitForTimeout(60000); // Esperar 1 minuto
|
||||
|
||||
// const newTime = await page.getByText(/\d+:\d+/).textContent();
|
||||
// expect(newTime).not.toBe(initialTime);
|
||||
});
|
||||
|
||||
test('deve submeter automaticamente quando timer acabar', async ({ page }) => {
|
||||
// TODO: Testar com prova curta
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Prova após prazo', () => {
|
||||
test('não deve permitir iniciar prova após prazo', async ({ page }) => {
|
||||
// TODO: Tentar acessar prova expirada
|
||||
// await page.goto('/dashboard/student/assignments/expired-id/take');
|
||||
|
||||
// await expect(page.getByText(/prazo expirado|não disponível/i)).toBeVisible();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,283 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Aluno - Materiais', () => {
|
||||
// TODO: Fazer login como aluno
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/dispatcher|\/dashboard\/student/);
|
||||
// });
|
||||
|
||||
test.describe('Dashboard do Aluno', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/student');
|
||||
});
|
||||
|
||||
test('deve mostrar turmas matriculadas', async ({ page }) => {
|
||||
await expect(page.getByText(/turmas|classes|minhas turmas/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar cards de turmas com informações', async ({ page }) => {
|
||||
const classCard = page.locator('[data-testid="class-card"], .card').first();
|
||||
if (await classCard.isVisible()) {
|
||||
await expect(classCard.getByText(/inglês|português/i, { ignoreCase: true })).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Materiais por Turma', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// TODO: Navegar para página de uma turma específica
|
||||
await page.goto('/dashboard/student/class/class-id');
|
||||
});
|
||||
|
||||
test('deve mostrar seção de materiais', async ({ page }) => {
|
||||
await expect(page.getByText(/materiais|arquivos|materials/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar materiais agrupados por categoria', async ({ page }) => {
|
||||
const materialsSection = page.getByText(/materiais/i).locator('..');
|
||||
|
||||
const categoryBadges = materialsSection.locator('[data-testid="category-badge"]');
|
||||
const count = await categoryBadges.count();
|
||||
|
||||
if (count > 0) {
|
||||
// Deve ter cor
|
||||
for (let i = 0; i < Math.min(count, 3); i++) {
|
||||
const badge = categoryBadges.nth(i);
|
||||
const classes = await badge.getAttribute('class') || '';
|
||||
expect(classes).toMatch(/bg-/);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('deve listar arquivos de cada categoria', async ({ page }) => {
|
||||
const materialsSection = page.getByText(/materiais/i).locator('..');
|
||||
|
||||
const fileLinks = materialsSection.getByRole('link', { name: /abrir|baixar/i });
|
||||
const count = await fileLinks.count();
|
||||
|
||||
if (count > 0) {
|
||||
await expect(fileLinks.nth(0)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar tamanho do arquivo', async ({ page }) => {
|
||||
const materialsSection = page.getByText(/materiais/i).locator('..');
|
||||
|
||||
const sizeText = page.getByText(/\d+\.?\d* KB|MB/i).first();
|
||||
if (await sizeText.isVisible()) {
|
||||
await expect(sizeText).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Download de Arquivo', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/student/class/class-id');
|
||||
});
|
||||
|
||||
test('deve baixar arquivo ao clicar no botão', async ({ page }) => {
|
||||
const downloadButton = page.getByRole('link', { name: /baixar|download/i }).first();
|
||||
if (await downloadButton.isVisible()) {
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
|
||||
await downloadButton.click();
|
||||
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve abrir PDF em nova aba', async ({ page }) => {
|
||||
const openButton = page.getByRole('link', { name: /abrir/i }).first();
|
||||
if (await openButton.isVisible()) {
|
||||
const newPagePromise = page.waitForEvent('popup');
|
||||
|
||||
await openButton.click();
|
||||
|
||||
const newPage = await newPagePromise;
|
||||
await expect(newPage).toHaveURL(/\.pdf/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar preview de imagem', async ({ page }) => {
|
||||
const imageLink = page.getByRole('link', { name: /abrir/i }).first();
|
||||
if (await imageLink.isVisible()) {
|
||||
await imageLink.click();
|
||||
|
||||
// Pode abrir modal com preview
|
||||
const preview = page.locator('img[src*="/uploads/"]').first();
|
||||
if (await preview.isVisible()) {
|
||||
await expect(preview).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Filtros de Categoria', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/student/class/class-id');
|
||||
});
|
||||
|
||||
test('deve ter botões de filtro por categoria', async ({ page }) => {
|
||||
const categoryBadges = page.locator('[data-testid="category-badge"]');
|
||||
|
||||
const count = await categoryBadges.count();
|
||||
if (count > 0) {
|
||||
// Ao clicar, deve filtrar materiais
|
||||
await categoryBadges.nth(0).click();
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Verificar se filtrou (pode mostrar apenas materiais da categoria)
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar todos ao clicar no filtro ativo', async ({ page }) => {
|
||||
// Primeiro clicar em uma categoria
|
||||
const categoryBadges = page.locator('[data-testid="category-badge"]');
|
||||
if ((await categoryBadges.count()) > 0) {
|
||||
await categoryBadges.nth(0).click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Clicar novamente para limpar filtro
|
||||
await categoryBadges.nth(0).click();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Deve mostrar todos novamente
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Histórico de Aulas', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/student/class/class-id/history');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de aulas registradas', async ({ page }) => {
|
||||
await expect(page.getByText(/histórico|lessons|aulas/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar data, tópico e presença', async ({ page }) => {
|
||||
const lessonRow = page.locator('[data-testid="lesson-row"], tr').nth(1);
|
||||
if (await lessonRow.isVisible()) {
|
||||
await expect(lessonRow.getByText(/\d{2}\/\d{2}/)).toBeVisible();
|
||||
|
||||
const topicCell = lessonRow.locator('td').nth(1);
|
||||
if (await topicCell.isVisible()) {
|
||||
await expect(topicCell.getByText(/tópico/i)).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar presença do aluno em cada aula', async ({ page }) => {
|
||||
const attendanceCell = page.locator('tbody tr td').nth(2);
|
||||
if (await attendanceCell.isVisible()) {
|
||||
const text = await attendanceCell.textContent();
|
||||
// Pode ser emoji ou texto
|
||||
expect(text).toMatch(/presente|ausente|atrasado|✓|✗|◑/i);
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar link para materiais da aula', async ({ page }) => {
|
||||
const lessonRow = page.locator('[data-testid="lesson-row"], tr').nth(1);
|
||||
if (await lessonRow.isVisible()) {
|
||||
const materialsLink = lessonRow.getByRole('link', { name: /materiais/i });
|
||||
if (await materialsLink.isVisible()) {
|
||||
await expect(materialsLink).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Presença Global', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/student/class/class-id');
|
||||
});
|
||||
|
||||
test('deve mostrar taxa de presença geral', async ({ page }) => {
|
||||
await expect(page.getByText(/presença|attendance/i)).toBeVisible();
|
||||
|
||||
const percentage = page.getByText(/\d+%/i).first();
|
||||
if (await percentage.isVisible()) {
|
||||
await expect(percentage).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar presença de forma visual (gráfico ou barra)', async ({ page }) => {
|
||||
// Pode ter gráfico de barras ou outra visualização
|
||||
const chart = page.locator('[data-testid="attendance-chart"], canvas, svg');
|
||||
if (await chart.isVisible()) {
|
||||
await expect(chart).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Responsividade - Mobile', () => {
|
||||
test('materiais devem ser usáveis em mobile', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.goto('/dashboard/student/class/class-id');
|
||||
|
||||
// Deve mostrar lista sem scroll horizontal
|
||||
const hasHorizontalScroll = await page.evaluate(() => {
|
||||
return document.body.scrollWidth > document.body.clientWidth;
|
||||
});
|
||||
expect(hasHorizontalScroll).toBe(false);
|
||||
});
|
||||
|
||||
test('cards de turma devem se ajustar em mobile', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.goto('/dashboard/student');
|
||||
|
||||
const classCards = page.locator('[data-testid="class-card"]');
|
||||
const count = await classCards.count();
|
||||
|
||||
if (count > 0) {
|
||||
// Em mobile, devem estar em coluna única
|
||||
const firstCard = classCards.nth(0);
|
||||
const secondCard = classCards.nth(1);
|
||||
|
||||
if (await secondCard.isVisible()) {
|
||||
const firstBox = await firstCard.boundingBox();
|
||||
const secondBox = await secondCard.boundingBox();
|
||||
|
||||
// Devem estar um abaixo do outro
|
||||
expect(secondBox.y).toBeGreaterThan(firstBox.y + firstBox.height);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Busca de Materiais', () => {
|
||||
test('deve ter campo de busca', async ({ page }) => {
|
||||
await page.goto('/dashboard/student/class/class-id');
|
||||
|
||||
const searchInput = page.getByPlaceholder(/buscar|pesquisar|search/i);
|
||||
if (await searchInput.isVisible()) {
|
||||
await expect(searchInput).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve filtrar materiais ao buscar', async ({ page }) => {
|
||||
const searchInput = page.getByPlaceholder(/buscar/i);
|
||||
if (await searchInput.isVisible()) {
|
||||
await searchInput.fill('PDF');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Deve mostrar apenas materiais com "PDF"
|
||||
const fileLinks = page.getByRole('link', { name: /abrir/i });
|
||||
const count = await fileLinks.count();
|
||||
|
||||
if (count > 0) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
const text = await fileLinks.nth(i).textContent();
|
||||
// Pode não filtrar estritamente, mas deve priorizar resultados
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,300 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Professor - Dashboard', () => {
|
||||
// TODO: Fazer login como professor
|
||||
// test.beforeEach(async ({ page }) => {
|
||||
// await page.goto('/auth/login');
|
||||
// await page.getByLabel(/email/i).fill('[email protected]');
|
||||
// await page.getByLabel(/senha/i).fill('123456');
|
||||
// await page.getByRole('button', { name: /entrar/i }).click();
|
||||
// await page.waitForURL(/\/dispatcher|\/dashboard\/teacher/);
|
||||
// });
|
||||
|
||||
test.describe('Página Inicial do Professor', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/teacher');
|
||||
});
|
||||
|
||||
test('deve mostrar turmas atribuídas', async ({ page }) => {
|
||||
await expect(page.getByText(/turmas|classes|minhas turmas/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar cards de turmas', async ({ page }) => {
|
||||
const classCard = page.locator('[data-testid="class-card"], .card').first();
|
||||
if (await classCard.isVisible()) {
|
||||
await expect(classCard.getByText(/turma|class/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar botão de ver detalhes', async ({ page }) => {
|
||||
const classCard = page.locator('[data-testid="class-card"], .card').first();
|
||||
if (await classCard.isVisible()) {
|
||||
const viewButton = classCard.getByRole('link', { name: /ver|detalhes/i });
|
||||
if (await viewButton.isVisible()) {
|
||||
await expect(viewButton).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Detalhes da Turma', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// TODO: Usar ID de turma de teste
|
||||
await page.goto('/dashboard/teacher/class/class-id');
|
||||
});
|
||||
|
||||
test('deve mostrar título da turma', async ({ page }) => {
|
||||
await expect(page.getByText(/inglês|português|matemática/i, { ignoreCase: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar informações básicas', async ({ page }) => {
|
||||
await expect(page.getByText(/professores|teachers/i)).toBeVisible();
|
||||
await expect(page.getByText(/dias|schedule/i)).toBeVisible();
|
||||
await expect(page.getByText(/horário|time/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar label de status', async ({ page }) => {
|
||||
const statusLabel = page.locator('[data-testid="status-label"]');
|
||||
if (await statusLabel.isVisible()) {
|
||||
// Status: Ativa, Arquivada, Inativa
|
||||
const classes = await statusLabel.getAttribute('class') || '';
|
||||
expect(classes).toMatch(/label-(emerald|gray|red)/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Materiais da Turma', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/teacher/class/class-id');
|
||||
});
|
||||
|
||||
test('deve mostrar seção de materiais', async ({ page }) => {
|
||||
await expect(page.getByText(/materiais|arquivos|materials/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar materiais agrupados por categoria', async ({ page }) => {
|
||||
const materialsSection = page.getByText(/materiais/i).locator('..');
|
||||
|
||||
const categoryBadges = materialsSection.locator('[data-testid="category-badge"]');
|
||||
const count = await categoryBadges.count();
|
||||
|
||||
if (count > 0) {
|
||||
// Cada categoria deve ter cor
|
||||
for (let i = 0; i < Math.min(count, 3); i++) {
|
||||
const badge = categoryBadges.nth(i);
|
||||
const classes = await badge.getAttribute('class') || '';
|
||||
expect(classes).toMatch(/bg-/); // Deve ter cor de fundo
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('deve ter botão de upload', async ({ page }) => {
|
||||
const uploadButton = page.getByRole('link', { name: /upload|adicionar/i });
|
||||
if (await uploadButton.isVisible()) {
|
||||
await expect(uploadButton).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve permitir download de arquivo', async ({ page }) => {
|
||||
const downloadLink = page.getByRole('link', { name: /baixar|download|abrir/i }).first();
|
||||
if (await downloadLink.isVisible()) {
|
||||
const downloadPromise = page.waitForEvent('download');
|
||||
|
||||
await downloadLink.click();
|
||||
|
||||
const download = await downloadPromise;
|
||||
expect(download.suggestedFilename()).toBeTruthy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Registrar Aula (Lesson)', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/teacher/class/class-id');
|
||||
});
|
||||
|
||||
test('deve abrir modal ao clicar em Registrar Aula', async ({ page }) => {
|
||||
const lessonButton = page.getByRole('button', { name: /registrar aula|lesson/i });
|
||||
if (await lessonButton.isVisible()) {
|
||||
await lessonButton.click();
|
||||
|
||||
await expect(page.locator('.fixed.inset-0')).toBeVisible();
|
||||
await expect(page.getByText(/registrar aula|nova aula/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve ter campos de data, tópico e notas', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /registrar aula/i }).click();
|
||||
|
||||
await expect(page.getByLabel(/data|date/i)).toBeVisible();
|
||||
await expect(page.getByLabel(/tópico|topic/i)).toBeVisible();
|
||||
await expect(page.getByPlaceholder(/notas|observations/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve permitir registrar presença dos alunos', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /registrar aula/i }).click();
|
||||
|
||||
// Deve mostrar lista de alunos
|
||||
const studentList = page.locator('[data-testid="student-list"]');
|
||||
if (await studentList.isVisible()) {
|
||||
// Deve ter opções de presença
|
||||
await expect(page.getByRole('combobox', { name: /presença/i })).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve salvar aula com sucesso', async ({ page }) => {
|
||||
await page.getByRole('button', { name: /registrar aula/i }).click();
|
||||
|
||||
const timestamp = Date.now();
|
||||
await page.getByLabel(/data/i).fill('2025-02-05');
|
||||
await page.getByLabel(/tópico/i).fill(`Aula Teste ${timestamp}`);
|
||||
await page.getByPlaceholder(/notas/i).fill('Notas da aula');
|
||||
|
||||
// Registrar presenças
|
||||
const attendanceSelects = page.getByRole('combobox', { name: /presença/i });
|
||||
const count = await attendanceSelects.count();
|
||||
|
||||
for (let i = 0; i < Math.min(count, 3); i++) {
|
||||
await attendanceSelects.nth(i).selectOption('present');
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: /salvar|registrar/i }).click();
|
||||
|
||||
// Deve fechar modal
|
||||
await expect(page.locator('.fixed.inset-0')).not.toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Histórico de Aulas', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/teacher/class/class-id/history');
|
||||
});
|
||||
|
||||
test('deve mostrar lista de aulas registradas', async ({ page }) => {
|
||||
await expect(page.getByText(/histórico|lessons|aulas/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar data e tópico de cada aula', async ({ page }) => {
|
||||
const lessonRow = page.locator('[data-testid="lesson-row"], tr').nth(1);
|
||||
if (await lessonRow.isVisible()) {
|
||||
await expect(lessonRow.getByText(/\d{2}\/\d{2}\/\d{4}/)).toBeVisible();
|
||||
await expect(lessonRow.getByText(/tópico|topic/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve permitir ver detalhes de uma aula', async ({ page }) => {
|
||||
const viewButton = page.getByRole('link', { name: /ver|detalhes/i }).first();
|
||||
if (await viewButton.isVisible()) {
|
||||
await viewButton.click();
|
||||
|
||||
await expect(page.getByText(/presença|attendance/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Provas da Turma', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/teacher/class/class-id');
|
||||
});
|
||||
|
||||
test('deve mostrar seção de provas', async ({ page }) => {
|
||||
await expect(page.getByText(/provas|avaliações|exams/i)).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve ter botão de atribuir prova', async ({ page }) => {
|
||||
const assignButton = page.getByRole('button', { name: /atribuir prova/i });
|
||||
if (await assignButton.isVisible()) {
|
||||
await expect(assignButton).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar lista de provas atribuídas', async ({ page }) => {
|
||||
const assignmentList = page.locator('[data-testid="assignment-list"]');
|
||||
if (await assignmentList.isVisible()) {
|
||||
await expect(assignmentList.getByText(/prova/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Visão Geral da Turma', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/teacher/class/class-id');
|
||||
});
|
||||
|
||||
test('deve mostrar número de alunos', async ({ page }) => {
|
||||
await expect(page.getByText(/\d+ alunos/i, { exact: false })).toBeVisible();
|
||||
});
|
||||
|
||||
test('deve mostrar cards de visão geral', async ({ page }) => {
|
||||
await expect(page.getByText(/visão geral/i)).toBeVisible();
|
||||
|
||||
const overviewCards = page.locator('[data-testid="overview-card"]');
|
||||
const count = await overviewCards.count();
|
||||
|
||||
if (count > 0) {
|
||||
// Deve ter estatísticas
|
||||
await expect(page.getByText(/alunos|students/i)).toBeVisible();
|
||||
await expect(page.getByText(/presença|attendance/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Lista de Alunos da Turma', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/dashboard/teacher/class/class-id');
|
||||
});
|
||||
|
||||
test('deve mostrar tabela de alunos', async ({ page }) => {
|
||||
const studentTable = page.locator('table');
|
||||
if (await studentTable.isVisible()) {
|
||||
await expect(studentTable.getByText(/nome|aluno/i)).toBeVisible();
|
||||
await expect(studentTable.getByText(/presença/i)).toBeVisible();
|
||||
await expect(studentTable.getByText(/média/i)).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar nome, presença e média', async ({ page }) => {
|
||||
const studentTable = page.locator('table').first();
|
||||
if (await studentTable.isVisible()) {
|
||||
const firstRow = studentTable.locator('tbody tr').first();
|
||||
|
||||
// Nome (primeiro nome)
|
||||
await expect(firstRow.locator('td').nth(0)).not.toBeEmpty();
|
||||
|
||||
// Presença em %
|
||||
const attendanceCell = firstRow.locator('td').nth(1);
|
||||
if (await attendanceCell.isVisible()) {
|
||||
const text = await attendanceCell.textContent();
|
||||
expect(text).match(/\d+%/);
|
||||
}
|
||||
|
||||
// Média
|
||||
const avgCell = firstRow.locator('td').nth(2);
|
||||
if (await avgCell.isVisible()) {
|
||||
const text = await avgCell.textContent();
|
||||
expect(text).toMatch(/\d+\.?\d*|-/); // Nota ou traço
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('deve mostrar cor baseada no desempenho', async ({ page }) => {
|
||||
const studentTable = page.locator('table').first();
|
||||
if (await studentTable.isVisible()) {
|
||||
// Presença >= 80%: verde, >= 60%: amarelo, < 60%: vermelho
|
||||
const attendanceCell = studentTable.locator('tbody tr td').nth(1);
|
||||
if (await attendanceCell.isVisible()) {
|
||||
const classes = await attendanceCell.getAttribute('class') || '';
|
||||
const text = await attendanceCell.textContent();
|
||||
|
||||
if (text.includes('80') || text.includes('90') || text.includes('100')) {
|
||||
expect(classes).toMatch(/text-emerald|green/);
|
||||
} else if (text.includes('60') || text.includes('70')) {
|
||||
expect(classes).toMatch(/text-amber|yellow/);
|
||||
} else {
|
||||
expect(classes).toMatch(/text-red/);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,275 @@
|
||||
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
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user