Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions e2e/app/passenger.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { expect, loginAs, test } from '../fixtures/authenticated-page'

test.describe('Passenger app', () => {
test('redirects unauthenticated visitors to login', async ({ page }) => {
await page.goto('/app')

await expect(page).toHaveURL('/login')
await expect(page.getByLabel('Email Institucional')).toBeVisible()
})

test('shows the passenger dashboard and persists the session', async ({
authenticatedPage: page,
}) => {
await expect(page.getByRole('heading', { name: 'Estudante E2E' })).toBeVisible()
await expect(page.getByText('Estudante', { exact: true })).toBeVisible()
await expect(page.getByText('student.e2e@discente.uefs.br')).toBeVisible()
await expect(page.getByText('Próxima viagem', { exact: true })).toBeVisible()
await expect(page.getByRole('heading', { name: 'Nenhuma viagem próxima' })).toBeVisible()
await expect(page.getByText('Ainda não existem reservas registradas para esta conta.')).toBeVisible()

await page.reload()

await expect(page).toHaveURL('/app')
await expect(page.getByRole('heading', { name: 'Estudante E2E' })).toBeVisible()
})

test('shows the available trip empty state', async ({
authenticatedPage: page,
}) => {
await expect(page.getByRole('link', { name: 'Ver todas as viagens' })).toHaveAttribute('href', '/app/viagens')
await page.goto('/app/viagens', { waitUntil: 'domcontentloaded' })

await expect(page).toHaveURL('/app/viagens')
await expect(page.getByRole('heading', { name: 'Selecione sua viagem' })).toBeVisible()
await expect(page.getByText('Nenhuma viagem cadastrada para os próximos horários.')).toBeVisible()
})

test('shows the current trip empty state', async ({
authenticatedPage: page,
}) => {
await expect(page.getByRole('link', { name: 'Ver viagem atual' })).toHaveAttribute('href', '/app/viagens/atual')

await page.goto('/app/viagens/atual', { waitUntil: 'domcontentloaded' })

await expect(page.getByRole('heading', { name: 'Viagem Atual' })).toBeVisible()
await expect(page.getByText('Você não tem nenhuma viagem próxima agendada.')).toBeVisible()
await expect(page.getByText('Nenhuma viagem para exibir no momento.')).toBeVisible()
})

test('shows profile details, settings, and logs out', async ({
authenticatedPage: page,
}) => {
await expect(page.getByRole('link', { name: 'Perfil' })).toHaveAttribute('href', '/app/perfil')
await page.goto('/app/perfil', { waitUntil: 'domcontentloaded' })

await expect(page).toHaveURL('/app/perfil')
await expect(page.getByRole('heading', { name: 'Estudante E2E' })).toBeVisible()
await expect(page.getByText('Perfil: Estudante')).toBeVisible()
await expect(page.getByText('Matrícula: E2E-STUDENT')).toBeVisible()
await expect(page.getByText('Reservas ativas')).toBeVisible()
await expect(page.getByText('Nenhuma viagem encontrada')).toBeVisible()
await expect(page.getByText('Nenhuma penalidade registrada no seu perfil.')).toBeVisible()

await page.getByRole('button', { name: 'Configurações' }).click()

const settings = page.getByRole('dialog', { name: 'Configurações' })
await expect(settings).toBeVisible()
await expect(settings.getByText('Receber notificações por email')).toBeVisible()
await expect(settings.getByText('Receber notificações push')).toBeVisible()
await settings.getByRole('button', { name: 'Sair da conta' }).click()

await expect(page).toHaveURL('/login')
})

test('shows trip requests only to civil servants', async ({ page }) => {
await loginAs(page, 'civilServant')

await expect(page.getByText('Funcionário Público', { exact: true })).toBeVisible()
await expect(page.getByText('Solicitações de Viagem', { exact: true })).toBeVisible()
await expect(page.getByText('Nenhuma solicitação encontrada.')).toBeVisible()

await page.getByRole('button', { name: 'Solicitar' }).click()

await expect(page.getByPlaceholder('Ex: UEFS')).toBeVisible()
await expect(page.getByPlaceholder('Ex: Salvador, BA')).toBeVisible()
await expect(page.getByPlaceholder('Ex: Aula de campo da disciplina X')).toBeVisible()
await page.getByRole('button', { name: 'Cancelar' }).click()
await expect(page.getByPlaceholder('Ex: UEFS')).toBeHidden()
})
})
12 changes: 12 additions & 0 deletions e2e/auth/login.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,16 @@ test.describe('Login Page', () => {
await expect(page).toHaveURL('/recuperar')
})

test('Shows an error for invalid credentials', async ({ page }) => {
const loginPage = new LoginPage(page)
await loginPage.goto()

await loginPage.email.fill(loginCredentials.student.email)
await loginPage.password.fill('incorrect-password')
await loginPage.submitButton.click()

await expect(page).toHaveURL('/login')
await expect(page.getByText('Credenciais inválidas')).toBeVisible()
})

})
46 changes: 46 additions & 0 deletions e2e/auth/recovery.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect, test } from '@playwright/test'

import { loginCredentials } from '../utils/auth'

test.describe('Password recovery', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/recuperar')
})

test('returns to login when recovery is cancelled', async ({ page }) => {
await expect(page.getByText('Recuperar Acesso', { exact: true })).toBeVisible()
await expect(page.getByText('1. Solicitar recuperação')).toBeVisible()
await page.getByRole('button', { name: 'Cancelar' }).click()

await expect(page).toHaveURL('/login')
})

test('shows a useful error for an unknown account', async ({ page }) => {
await page.getByLabel('Email').fill('missing.e2e@uefs.br')
const responsePromise = page.waitForResponse((response) =>
response.url().endsWith('/api/auth/password-reset-request/'),
)
await page.getByRole('button', { name: 'Receber email' }).click()
const response = await responsePromise

expect(response.status()).toBe(400)
await expect(page.getByText('Ocorreu um erro ao processar sua solicitação.')).toBeVisible()
await expect(page.getByRole('button', { name: 'Receber email' })).toBeEnabled()
})

test('advances to PIN confirmation and handles an invalid code', async ({ page }) => {
await page.getByLabel('Email').fill(loginCredentials.student.email)
await page.getByRole('button', { name: 'Receber email' }).click()

await expect(page.getByText(loginCredentials.student.email)).toBeVisible()
await expect(page.getByText('2. Confirmar PIN')).toBeVisible()
await expect(page.getByRole('button', { name: 'Reenviar código' })).toBeVisible()

await page.getByLabel('Código de Confirmação').fill('000000')
await page.getByRole('button', { name: 'Confirmar' }).click()

await expect(page.getByText('Código inválido ou expirado. Tente reenviar o código.')).toBeVisible()
await page.getByRole('button', { name: 'Email errado?' }).click()
await expect(page.getByRole('button', { name: 'Receber email' })).toBeVisible()
})
})
32 changes: 32 additions & 0 deletions e2e/auth/signup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,36 @@ test.describe('Signup Page', () => {
await expect(studentTab).toHaveAttribute('aria-selected', 'false')
})

test('Validates mismatched passwords and legal consent', async ({ page }) => {
const signupPage = new SignupPage(page)
await signupPage.asStudent()

await signupPage.email.fill(uniqueStudentEmail())
await signupPage.fullName.fill('Estudante E2E Inválido')
await signupPage.registration.fill(uniqueRegistration())
await signupPage.password.fill('12345678')
await signupPage.confirmPassword.fill('87654321')
await signupPage.submitButton.click()

await expect(page.getByText('As senhas não correspondem.')).toBeVisible()
await expect(page.getByText('Você precisa aceitar os Termos de Uso e a Política de Privacidade.')).toBeVisible()
await expect(page).toHaveURL('/signup')
})

test('Rejects a non-institutional student email', async ({ page }) => {
const signupPage = new SignupPage(page)
await signupPage.asStudent()

await signupPage.email.fill(`e2e.${uniqueRegistration()}@example.com`)
await signupPage.fullName.fill('Estudante E2E Inválido')
await signupPage.registration.fill(uniqueRegistration())
await signupPage.password.fill('12345678')
await signupPage.confirmPassword.fill('12345678')
await signupPage.legalConsent.check()
await signupPage.submitButton.click()

await expect(page.getByText('E-mail institucional de aluno deve terminar com @discente.uefs.br.')).toBeVisible()
await expect(page).toHaveURL('/signup')
})

})
31 changes: 31 additions & 0 deletions e2e/fixtures/authenticated-page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect, test as base, type Page } from '@playwright/test'

import { LoginPage } from '../pages/login'
import { loginCredentials } from '../utils/auth'

type AuthenticatedFixtures = {
authenticatedPage: Page
}

export async function loginAs(
page: Page,
role: keyof typeof loginCredentials = 'student',
) {
const loginPage = new LoginPage(page)
const credentials = loginCredentials[role]

await loginPage.goto()
await loginPage.email.fill(credentials.email)
await loginPage.password.fill(credentials.password)
await loginPage.submitButton.click()
await expect(page).toHaveURL('/app')
}

export const test = base.extend<AuthenticatedFixtures>({
authenticatedPage: async ({ page }, run) => {
await loginAs(page)
await run(page)
},
})

export { expect }
49 changes: 49 additions & 0 deletions e2e/public/pages.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { expect, test } from '@playwright/test'

test.describe('Public pages', () => {
test('navigates from the landing page to login', async ({ page }) => {
await page.goto('/')

await expect(page.getByRole('heading', { name: /De onde/ })).toBeVisible()
await expect(page.getByRole('heading', { name: 'Como funciona' })).toBeVisible()
await expect(page.getByRole('navigation', { name: 'Navegacao principal' })).toBeVisible()

await page.getByRole('link', { name: 'ENTRAR' }).click()

await expect(page).toHaveURL('/login')
})

test('renders the project and team pages', async ({ page }) => {
await page.goto('/sobre-projeto')
await expect(page.getByRole('heading', { name: 'Mobilidade universitária com organização digital' })).toBeVisible()
await expect(page.getByRole('heading', { name: 'UEFS' })).toBeVisible()
await expect(page.getByRole('heading', { name: 'Uninfra' })).toBeVisible()

await page.goto('/equipe')
await expect(page.getByRole('heading', { name: 'As pessoas por trás da EasyRota' })).toBeVisible()
await expect(page.getByRole('heading', { name: 'Núcleo principal' })).toBeVisible()
await expect(page.getByRole('heading', { name: 'Base da plataforma' })).toBeVisible()
})

test('renders both legal documents', async ({ page }) => {
await page.goto('/termos-de-uso')
await expect(page.getByRole('heading', { name: 'Termos de Uso' })).toBeVisible()
await expect(page.getByText('26 de Junho de 2026')).toBeVisible()
await expect(page.getByRole('heading', { name: '14. Uso adequado do sistema' })).toBeVisible()

await page.goto('/politica-de-privacidade')
await expect(page.getByRole('heading', { name: 'Política de Privacidade' })).toBeVisible()
await expect(page.getByRole('heading', { name: 'Ausência de telemetria e rastreamento' })).toBeVisible()
await expect(page.getByRole('link', { name: 'easyrota0@gmail.com' }).first()).toHaveAttribute('href', 'mailto:easyrota0@gmail.com')
})

test('offers working recovery actions on the not-found page', async ({ page }) => {
await page.goto('/pagina-inexistente')

await expect(page.getByRole('heading', { name: 'Página não encontrada' })).toBeVisible()
await expect(page.getByRole('progressbar', { name: 'Progresso do redirecionamento' })).toHaveAttribute('aria-valuemax', '7')
await page.getByRole('button', { name: 'Ir para o app agora' }).click()

await expect(page).toHaveURL('/login')
})
})
6 changes: 5 additions & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './e2e',
timeout: 45 * 1000,
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
maxFailures: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
workers: process.env.CI ? 1 : 4,
reporter: 'html',
expect: {
timeout: 15 * 1000,
},
use: {
baseURL: 'http://127.0.0.1:4173',
trace: 'on-first-retry',
Expand Down
Loading