From 9ee3bde19593eb8b1f061a5119c6073d88126b2a Mon Sep 17 00:00:00 2001 From: RickBarretto <78623871+RickBarretto@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:30:20 -0300 Subject: [PATCH 1/7] Configure E2E timeout --- playwright.config.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/playwright.config.ts b/playwright.config.ts index 74ce26ab..1e122c25 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -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', From 8314d54f3f2ee2fa8bb2f718f98d2c550f8af7c6 Mon Sep 17 00:00:00 2001 From: RickBarretto <78623871+RickBarretto@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:32:48 -0300 Subject: [PATCH 2/7] Create authenticated page's fixture --- e2e/fixtures/authenticated-page.ts | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 e2e/fixtures/authenticated-page.ts diff --git a/e2e/fixtures/authenticated-page.ts b/e2e/fixtures/authenticated-page.ts new file mode 100644 index 00000000..2458b8d7 --- /dev/null +++ b/e2e/fixtures/authenticated-page.ts @@ -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({ + authenticatedPage: async ({ page }, run) => { + await loginAs(page) + await run(page) + }, +}) + +export { expect } From 550fa0258068b4398333697578eb846e58b5b205 Mon Sep 17 00:00:00 2001 From: RickBarretto <78623871+RickBarretto@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:32:54 -0300 Subject: [PATCH 3/7] Check E2E's passenger's page * Check passenger data * Check session persist * Check trips * Check profile --- e2e/app/passenger.spec.ts | 90 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 e2e/app/passenger.spec.ts diff --git a/e2e/app/passenger.spec.ts b/e2e/app/passenger.spec.ts new file mode 100644 index 00000000..ed0eef45 --- /dev/null +++ b/e2e/app/passenger.spec.ts @@ -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() + }) +}) From 0689fedadd29759a0bc6a01cdc36042a67701b5b Mon Sep 17 00:00:00 2001 From: RickBarretto <78623871+RickBarretto@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:33:33 -0300 Subject: [PATCH 4/7] E2E: Check feedback for error on login --- e2e/auth/login.spec.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/e2e/auth/login.spec.ts b/e2e/auth/login.spec.ts index e60bcd20..14d7a98f 100644 --- a/e2e/auth/login.spec.ts +++ b/e2e/auth/login.spec.ts @@ -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() + }) + }) From 2b83cb1acb78dc5325afe802acac6addbac8d6ab Mon Sep 17 00:00:00 2001 From: RickBarretto <78623871+RickBarretto@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:33:51 -0300 Subject: [PATCH 5/7] E2E: Test password recovery --- e2e/auth/recovery.spec.ts | 46 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 e2e/auth/recovery.spec.ts diff --git a/e2e/auth/recovery.spec.ts b/e2e/auth/recovery.spec.ts new file mode 100644 index 00000000..68c5040b --- /dev/null +++ b/e2e/auth/recovery.spec.ts @@ -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() + }) +}) From 7f8089ccd7c659d1f67009b8a99135555fcf59ca Mon Sep 17 00:00:00 2001 From: RickBarretto <78623871+RickBarretto@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:35:20 -0300 Subject: [PATCH 6/7] E2E: test non-happy paths for signup --- e2e/auth/signup.spec.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/e2e/auth/signup.spec.ts b/e2e/auth/signup.spec.ts index 839c0506..e57593e5 100644 --- a/e2e/auth/signup.spec.ts +++ b/e2e/auth/signup.spec.ts @@ -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') + }) + }) From bfc4a2b5ac0f4bd0d0dca03dd1f294ee3b89aae9 Mon Sep 17 00:00:00 2001 From: RickBarretto <78623871+RickBarretto@users.noreply.github.com> Date: Mon, 29 Jun 2026 21:36:05 -0300 Subject: [PATCH 7/7] Test public pages --- e2e/public/pages.spec.ts | 49 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 e2e/public/pages.spec.ts diff --git a/e2e/public/pages.spec.ts b/e2e/public/pages.spec.ts new file mode 100644 index 00000000..f08c9f61 --- /dev/null +++ b/e2e/public/pages.spec.ts @@ -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') + }) +})