Skip to content

Commit 772e72e

Browse files
limpeza automática do firestore nos testes e2e
1 parent 7616f42 commit 772e72e

8 files changed

Lines changed: 298 additions & 98 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,9 @@ dist-ssr
3737
# Local documentation drafts
3838
docs/
3939
doc/
40+
41+
# Test results
42+
test-results/
43+
playwright-report/
44+
blob-report/
45+
playwright/.cache/

playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export default defineConfig({
2727
webServer: {
2828
command: 'npm run build && npm run preview',
2929
port: 4173,
30-
reuseExistingServer: !process.env.CI,
30+
reuseExistingServer: false,
3131
timeout: 120000,
3232
},
3333
}),

src/test/cleanup.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest'
2+
import { cleanupTestRecords } from '../../tests/e2e/utils/cleanup'
3+
import { getDocs, deleteDoc } from 'firebase/firestore'
4+
5+
// Mocks do Firebase
6+
vi.mock('firebase/app', () => ({
7+
initializeApp: vi.fn()
8+
}))
9+
10+
vi.mock('firebase/firestore', () => ({
11+
getFirestore: vi.fn(),
12+
collection: vi.fn(),
13+
getDocs: vi.fn(),
14+
deleteDoc: vi.fn(),
15+
doc: vi.fn()
16+
}))
17+
18+
describe('cleanupTestRecords', () => {
19+
beforeEach(() => {
20+
vi.clearAllMocks()
21+
})
22+
23+
it('deve deletar apenas registros que correspondam aos padrões de teste', async () => {
24+
const mockDocs = {
25+
docs: [
26+
{ id: '1', data: () => ({ name: 'Wellington Teste E2E 123' }) },
27+
{ id: '2', data: () => ({ name: 'Offline Staff ABC' }) },
28+
{ id: '3', data: () => ({ name: 'Usuario Real' }) },
29+
{ id: '4', data: () => ({ name: 'Offline User XYZ' }) },
30+
{ id: '5', data: () => ({ name: 'Teste Ativo 1234567890' }) },
31+
{ id: '6', data: () => ({ name: 'Teste Inativo 1234567890' }) },
32+
{ id: '7', data: () => ({ name: 'Primeiro 1234567890' }) },
33+
]
34+
}
35+
36+
vi.mocked(getDocs).mockResolvedValue(mockDocs as any)
37+
38+
await cleanupTestRecords()
39+
40+
// Deve chamar deleteDoc 6 vezes (ids 1, 2, 4, 5, 6 e 7)
41+
expect(deleteDoc).toHaveBeenCalledTimes(6)
42+
})
43+
44+
it('não deve deletar nada se não houver registros correspondentes', async () => {
45+
const mockDocs = {
46+
docs: [
47+
{ id: '1', data: () => ({ name: 'João Silva' }) },
48+
{ id: '2', data: () => ({ name: 'Maria Souza' }) },
49+
]
50+
}
51+
52+
vi.mocked(getDocs).mockResolvedValue(mockDocs as any)
53+
54+
await cleanupTestRecords()
55+
56+
expect(deleteDoc).not.toHaveBeenCalled()
57+
})
58+
})

tests/e2e/full-flow.spec.ts

Lines changed: 74 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,84 @@
11
import { test, expect } from '@playwright/test'
2+
import { cleanupTestRecords } from './utils/cleanup'
23

3-
test.describe('Fluxo de Cadastro', () => {
4+
test.afterEach(async () => {
5+
await cleanupTestRecords()
6+
})
7+
8+
test.describe('Fluxo de Cadastro Online', () => {
49
test('Fluxo completo do formulário', async ({ page }) => {
5-
// 1. Acessa e aguarda o carregamento inicial (lazy load)
610
await page.goto('/staffs')
7-
8-
// Aguarda o título da página ou o botão aparecer (resiliência para lazy load)
911
await expect(page.getByRole('heading', { name: /colaboradores/i })).toBeVisible({ timeout: 20000 })
1012

11-
// 2. Clica no botão
12-
const btnNovo = page.getByRole('button', { name: /novo colaborador/i })
13-
await btnNovo.click()
13+
await page.getByRole('link', { name: /novo colaborador/i }).click()
14+
await expect(page).toHaveURL(/\/staffs\/new/)
15+
16+
const timestamp = Date.now()
17+
const uniqueName = `Wellington Teste E2E ${timestamp}`
18+
const uniqueEmail = `e2e.${timestamp}@test.com`
19+
20+
await page.getByLabel(/nome completo/i).fill(uniqueName)
21+
await page.getByLabel(/e-mail corporativo/i).fill(uniqueEmail)
22+
await page.getByRole('button', { name: /próximo passo/i }).click()
23+
24+
await page.getByLabel(/selecione o departamento/i).click()
25+
await page.getByRole('option', { name: 'TI', exact: true }).click()
26+
await page.getByRole('button', { name: /finalizar cadastro/i }).click()
27+
28+
await expect(
29+
page.getByText(/colaborador cadastrado com sucesso/i)
30+
).toBeVisible({ timeout: 15000 })
31+
32+
await expect(page).toHaveURL(/\/staffs$/, { timeout: 5000 })
33+
await expect(page.getByRole('heading', { name: /colaboradores/i })).toBeVisible({ timeout: 10000 })
34+
35+
await expect(page.getByRole('cell', { name: uniqueName })).toBeVisible({ timeout: 15000 })
36+
})
37+
})
38+
39+
test.describe('Fluxo de Cadastro Offline', () => {
40+
test('salva localmente e exibe status de sincronização', async ({ context, page }) => {
41+
// 1. Garante que a página carregue online primeiro para ter a aplicação disponível
42+
await page.goto('/staffs')
43+
await expect(page.getByRole('heading', { name: /colaboradores/i })).toBeVisible({ timeout: 20000 })
1444

15-
// 3. Preenche o Passo 0: Infos Básicas
45+
// 2. Navega para o formulário
46+
await page.getByRole('link', { name: /novo colaborador/i }).click()
1647
await expect(page).toHaveURL(/\/staffs\/new/)
17-
await page.getByLabel(/nome/i).fill('Wellington Teste E2E')
18-
await page.getByLabel(/e-mail/i).fill(`e2e.${Date.now()}@test.com`)
19-
await page.getByRole('button', { name: /próximo/i }).click()
20-
21-
// 4. Preenche o Passo 1: Infos Profissionais
22-
await page.getByLabel(/departamento/i).click()
23-
await page.getByRole('option', { name: /ti/i }).click()
24-
await page.getByRole('button', { name: /salvar/i }).click()
25-
26-
// 5. Validação final na listagem
27-
await expect(page).toHaveURL(/\/staffs/, { timeout: 15000 })
28-
await expect(page.getByText('Wellington Teste E2E')).toBeVisible({ timeout: 15000 })
48+
49+
// 3. Simula a perda de conexão de rede
50+
await context.setOffline(true)
51+
await page.evaluate(() => window.dispatchEvent(new Event('offline')))
52+
53+
const timestamp = Date.now()
54+
const uniqueName = `Offline User ${timestamp}`
55+
const uniqueEmail = `offline.${timestamp}@test.com`
56+
57+
// 4. Preenche e submete o formulário
58+
await page.getByLabel(/nome completo/i).fill(uniqueName)
59+
await page.getByLabel(/e-mail corporativo/i).fill(uniqueEmail)
60+
await page.getByRole('button', { name: /próximo passo/i }).click()
61+
62+
await page.getByLabel(/selecione o departamento/i).click()
63+
await page.getByRole('option', { name: 'TI', exact: true }).click()
64+
await page.getByRole('button', { name: /finalizar cadastro/i }).click()
65+
66+
// 5. Verifica se o toast de sucesso é exibido (mesmo offline)
67+
await expect(
68+
page.getByText(/colaborador cadastrado com sucesso/i)
69+
).toBeVisible({ timeout: 15000 })
70+
71+
// 6. Verifica se o redirecionamento para a lista acontece
72+
await expect(page).toHaveURL(/\/staffs$/, { timeout: 10000 })
73+
await expect(page.getByRole('heading', { name: /colaboradores/i })).toBeVisible({ timeout: 10000 })
74+
75+
// 7. Verifica se o novo usuário aparece na lista
76+
const userRow = page.locator('tr', { hasText: uniqueName })
77+
await expect(userRow).toBeVisible({ timeout: 10000 })
78+
await expect(userRow.getByText(/Ativo/i)).toBeVisible({ timeout: 5000 })
79+
80+
// 8. Restaura a conexão
81+
await context.setOffline(false)
82+
await page.evaluate(() => window.dispatchEvent(new Event('online')))
2983
})
3084
})

tests/e2e/offline-sync.spec.ts

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
import { test, expect } from '@playwright/test'
2+
import { cleanupTestRecords } from './utils/cleanup'
3+
4+
test.afterEach(async () => {
5+
await cleanupTestRecords()
6+
})
27

38
test.describe('Resiliência e Sincronização Offline', () => {
49
test.beforeEach(async ({ page }) => {
@@ -12,33 +17,45 @@ test.describe('Resiliência e Sincronização Offline', () => {
1217
const name = `Offline Staff ${suffix}`
1318
const email = `offline-${suffix}@empresa.com`
1419

15-
// 1. Simular queda de rede (bloquear Firestore)
16-
await context.route('**/firestore.googleapis.com/**', route => route.abort())
20+
// 1. Navegar para o formulário antes de simular offline (evita interferência do listener Firestore na lista)
21+
await page.getByRole('link', { name: /Novo Colaborador/i }).click()
22+
await expect(page).toHaveURL(/\/staffs\/new/)
23+
24+
// 2. Simular queda de rede
25+
await context.setOffline(true)
26+
await page.evaluate(() => {
27+
Object.defineProperty(navigator, 'onLine', { get: () => false, configurable: true })
28+
window.dispatchEvent(new Event('offline'))
29+
})
1730

18-
// 2. Tentar cadastrar
19-
await page.getByRole('button', { name: /Novo Colaborador/i }).click()
20-
await page.getByLabel(/Nome/i).fill(name)
21-
await page.getByLabel(/E-mail/i).fill(email)
22-
await page.getByRole('button', { name: /Próximo/i }).click()
31+
await page.getByLabel(/Nome Completo/i).fill(name)
32+
await page.getByLabel(/E-mail Corporativo/i).fill(email)
33+
await page.getByRole('button', { name: /Próximo Passo/i }).click()
2334

24-
await page.getByLabel(/Departamento/i).click()
25-
await page.getByRole('option', { name: 'TI' }).click()
26-
await page.getByRole('button', { name: /Finalizar/i }).click()
35+
await page.getByLabel(/Selecione o Departamento/i).click()
36+
await page.getByRole('option', { name: 'TI', exact: true }).click()
37+
await page.getByRole('button', { name: /Finalizar Cadastro/i }).click()
2738

28-
// 3. Verificar feedback de modo offline
29-
await expect(page.getByText(/Salvo no dispositivo|pendente/i)).toBeVisible({ timeout: 15000 })
39+
// 3. Verificar feedback de sucesso (igual ao online)
40+
await expect(page.getByText(/colaborador cadastrado com sucesso/i)).toBeVisible({ timeout: 15000 })
41+
42+
// 4. Verificar redirecionamento automático
3043
await expect(page).toHaveURL(/\/staffs/, { timeout: 10000 })
3144

32-
// 4. Verificar status "Sincronizando" na lista
45+
// 5. Verifica se o novo usuário aparece na lista com status correto
3346
const row = page.locator('tr', { hasText: name })
34-
await expect(row.getByText(/Sincronizando|Pendente/i)).toBeVisible()
35-
36-
// 5. Restaurar rede
37-
await context.unroute('**/firestore.googleapis.com/**')
38-
39-
// 6. Sincronização automática deve ocorrer
40-
// O chip deve mudar para "Ativo"
41-
await expect(row.getByText('Ativo')).toBeVisible({ timeout: 30000 })
42-
await expect(row.getByText(/Sincronizando/i)).not.toBeVisible()
47+
await expect(row).toBeVisible({ timeout: 10000 })
48+
await expect(row.getByText('Ativo')).toBeVisible({ timeout: 5000 })
49+
50+
// 6. Restaurar rede
51+
await context.setOffline(false)
52+
await page.evaluate(() => {
53+
Object.defineProperty(navigator, 'onLine', { get: () => true, configurable: true })
54+
window.dispatchEvent(new Event('online'))
55+
})
56+
57+
// 7. Registro deve permanecer visível e Ativo após sincronizar em background
58+
await expect(row).toBeVisible({ timeout: 15000 })
59+
await expect(row.getByText('Ativo')).toBeVisible({ timeout: 15000 })
4360
})
4461
})

tests/e2e/smoke.spec.ts

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
11
import { test, expect } from '@playwright/test'
2+
import { cleanupTestRecords } from './utils/cleanup'
3+
4+
test.afterEach(async () => {
5+
await cleanupTestRecords()
6+
})
27

38
test.describe('Smoke', () => {
9+
test.beforeEach(async ({ page }) => {
10+
page.on('console', msg => {
11+
if (msg.type() === 'error') console.log(`[Browser Error] ${msg.text()}`)
12+
})
13+
page.on('requestfailed', request => {
14+
console.log(`[Resource Error] ${request.url()} - ${request.failure()?.errorText}`)
15+
})
16+
})
17+
418
test('página inicial carrega', async ({ page }) => {
5-
await page.goto('/staffs', { waitUntil: 'domcontentloaded' })
6-
await expect(page.getByRole('heading', { name: 'Colaboradores' })).toBeVisible({ timeout: 20000 })
19+
await page.goto('/staffs')
20+
await expect(page.getByText('Colaboradores', { exact: false }).first()).toBeVisible({ timeout: 20000 })
721
})
822

923
test('tabela de colaboradores renderiza sem erro', async ({ page }) => {
10-
await page.goto('/staffs', { waitUntil: 'domcontentloaded' })
11-
await expect(page.getByRole('heading', { name: 'Colaboradores' })).toBeVisible({ timeout: 20000 })
24+
await page.goto('/staffs')
25+
await expect(page.getByText('Colaboradores', { exact: false }).first()).toBeVisible({ timeout: 20000 })
1226
await expect(page.getByText('Erro')).not.toBeVisible()
1327
await expect(page.getByRole('table')).toBeVisible()
1428
})
1529

1630
test('formulário de cadastro renderiza', async ({ page }) => {
1731
await page.goto('/staffs/new', { waitUntil: 'domcontentloaded' })
1832
await expect(page.getByText('Informações Básicas')).toBeVisible({ timeout: 20000 })
19-
await expect(page.getByLabel('Nome')).toBeVisible()
20-
await expect(page.getByLabel('E-mail')).toBeVisible()
33+
await expect(page.getByLabel(/nome completo/i)).toBeVisible()
34+
await expect(page.getByLabel(/e-mail corporativo/i)).toBeVisible()
2135
})
2236

2337
test('botão Cancelar no formulário retorna para a lista', async ({ page }) => {
@@ -31,7 +45,7 @@ test.describe('Smoke', () => {
3145
await page.goto('/staffs', { waitUntil: 'domcontentloaded' })
3246
await expect(page.getByRole('heading', { name: 'Colaboradores' })).toBeVisible({ timeout: 20000 })
3347
await expect(
34-
page.getByText('Não foi possível carregar os colaboradores')
48+
page.getByText(/Não conseguimos carregar/i)
3549
).not.toBeVisible({ timeout: 15000 })
3650
})
3751
})

0 commit comments

Comments
 (0)