Initial commit
This commit is contained in:
@@ -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/);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user