mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
- Sessão por arquivo: coordinator/leader compartilhados por worker (ownership própria, cleanup no afterAll); beforeEach semeia o cookie campaign-token no context fresh de cada teste; advisors permanecem por teste (OPS29/B176) - Premissa da intenção corrigida por medição: getPayload é memoizado por worker (364 ms cold, 0 ms warm); o overhead real era login de browser (3.6 s quente / 30.3 s 1º hit vs 112-134 ms de seed) + criação de usuário - Sem mode: 'serial' (divergência declarada): seed por cookie dispensa o reuso de contexto e evita skips em cascata no relatório - Endurece 7 interações client-side com toPass (classe B13/B17 exposta pela remoção do warm-up do login) - Medição antes/depois registrada no CHANGELOG; débito OPS39 (prewarm de home-search/mudar-tendencia) registrado com depends no OPS30
201 lines
8 KiB
TypeScript
201 lines
8 KiB
TypeScript
import {
|
|
createCampaignOwnership,
|
|
expect,
|
|
mintCampaignSession,
|
|
seedCampaignSession,
|
|
test,
|
|
type CampaignE2EOwnership,
|
|
type CampaignSessionUser,
|
|
} from './fixtures/campaignE2EFixtures.js'
|
|
|
|
test.use({ viewport: { width: 390, height: 844 } })
|
|
|
|
/**
|
|
* OPS36 — one shared staff session per worker (see the sibling comment in
|
|
* campaignHomeActions.e2e.spec.ts): coordinator + leader are created once,
|
|
* each test's fresh context receives the matching `campaign-token` cookie,
|
|
* and the group rows are cleaned up in `afterAll`.
|
|
*/
|
|
let sharedFixtures: CampaignE2EOwnership
|
|
let sharedCoordinator: CampaignSessionUser
|
|
let coordinatorToken: string
|
|
let sharedLeader: CampaignSessionUser
|
|
let leaderToken: string
|
|
|
|
test.beforeAll(async () => {
|
|
sharedFixtures = await createCampaignOwnership()
|
|
sharedCoordinator = await sharedFixtures.createCampaignUser('coordinator', {
|
|
name: sharedFixtures.value('Coordenadora Compartilhada'),
|
|
})
|
|
coordinatorToken = await mintCampaignSession(sharedFixtures.payload, sharedCoordinator)
|
|
sharedLeader = await sharedFixtures.createCampaignUser('leader', {
|
|
name: sharedFixtures.value('Liderança Compartilhada'),
|
|
})
|
|
leaderToken = await mintCampaignSession(sharedFixtures.payload, sharedLeader)
|
|
})
|
|
|
|
test.beforeEach(async ({ campaign, context }) => {
|
|
await seedCampaignSession(context, campaign.baseURL, coordinatorToken)
|
|
})
|
|
|
|
test.afterAll(async () => {
|
|
if (!sharedFixtures) return
|
|
await sharedFixtures.cleanup()
|
|
await sharedFixtures.expectNoOwnedRows()
|
|
})
|
|
|
|
test.describe('Mobile bottom nav (B164)', () => {
|
|
test('staff sees five-item bottom nav', async ({ page }) => {
|
|
await page.goto('/campanha')
|
|
|
|
const bottomNav = page.locator('[aria-label="Navegação principal"]')
|
|
await expect(bottomNav).toBeVisible()
|
|
await expect(bottomNav.getByRole('link', { name: 'Início' })).toBeVisible()
|
|
await expect(bottomNav.getByRole('link', { name: 'Municípios' })).toBeVisible()
|
|
await expect(bottomNav.getByRole('link', { name: 'Atualizações' })).toBeVisible()
|
|
await expect(bottomNav.getByRole('link', { name: 'Agenda' })).toBeVisible()
|
|
await expect(bottomNav.getByRole('button', { name: 'Mais' })).toBeVisible()
|
|
})
|
|
|
|
test('highlights active item and navigates on tap', async ({ page }) => {
|
|
await page.goto('/campanha')
|
|
|
|
const bottomNav = page.locator('[aria-label="Navegação principal"]')
|
|
|
|
// Início is active on /campanha
|
|
await expect(bottomNav.getByRole('link', { name: 'Início' })).toHaveAttribute(
|
|
'aria-current',
|
|
'page',
|
|
)
|
|
|
|
// Navigate to Municípios — active state moves
|
|
await bottomNav.getByRole('link', { name: 'Municípios' }).click()
|
|
await expect(page).toHaveURL(/\/campanha\/municipios\/?$/)
|
|
await expect(bottomNav.getByRole('link', { name: 'Municípios' })).toHaveAttribute(
|
|
'aria-current',
|
|
'page',
|
|
)
|
|
await expect(bottomNav.getByRole('link', { name: 'Início' })).not.toHaveAttribute(
|
|
'aria-current',
|
|
'page',
|
|
)
|
|
})
|
|
|
|
test('Mais opens overflow drawer with secondary destinations', async ({ page }) => {
|
|
await page.goto('/campanha')
|
|
|
|
const bottomNav = page.locator('[aria-label="Navegação principal"]')
|
|
// Client-side drawer state — a click that lands before hydration is a
|
|
// silent no-op (the B13/B17 flake class; with the shared-session seed the
|
|
// page is often the worker's first load, so guard the transition).
|
|
await expect(async () => {
|
|
await bottomNav.getByRole('button', { name: 'Mais' }).click({ timeout: 1_000 })
|
|
await expect(page.locator('[data-slot="drawer-content"]')).toBeVisible({ timeout: 5_000 })
|
|
}).toPass({ timeout: 15_000 })
|
|
|
|
const drawer = page.locator('[data-slot="drawer-content"]')
|
|
await expect(drawer.getByRole('link', { name: 'Quadro', exact: true })).toBeVisible()
|
|
await expect(drawer.getByRole('link', { name: 'Conceitos' })).toBeVisible()
|
|
await expect(drawer.getByRole('link', { name: 'Perfil' })).toBeVisible()
|
|
await expect(drawer.getByRole('button', { name: 'Sair' })).toBeVisible()
|
|
})
|
|
|
|
test('leader does not see the bottom nav', async ({ campaign, context, page }) => {
|
|
await seedCampaignSession(context, campaign.baseURL, leaderToken)
|
|
await page.goto('/campanha/contatos')
|
|
|
|
await expect(page.locator('[aria-label="Navegação principal"]')).toHaveCount(0)
|
|
})
|
|
|
|
test('staff mobile has no sidebar trigger and no nav sheet (C102)', async ({ page }) => {
|
|
await page.goto('/campanha')
|
|
|
|
const topBar = page.locator('[data-slot="campaign-mobile-top-bar"]')
|
|
await expect(topBar).toBeVisible()
|
|
await expect(topBar.locator('[data-slot="sidebar-trigger"]')).toHaveCount(0)
|
|
// The sheet itself is unmounted for staff — no "Sidebar" dialog exists.
|
|
await expect(page.getByRole('dialog', { name: 'Sidebar' })).toHaveCount(0)
|
|
// Every destination still arrives from below.
|
|
await expect(page.locator('[aria-label="Navegação principal"]')).toBeVisible()
|
|
})
|
|
|
|
test('leader keeps the sidebar sheet on mobile (C102)', async ({ campaign, context, page }) => {
|
|
await seedCampaignSession(context, campaign.baseURL, leaderToken)
|
|
await page.goto('/campanha/contatos')
|
|
|
|
const topBar = page.locator('[data-slot="campaign-mobile-top-bar"]')
|
|
const trigger = topBar.locator('[data-slot="sidebar-trigger"]')
|
|
await expect(trigger).toBeVisible()
|
|
|
|
await trigger.click()
|
|
const sheet = page.getByRole('dialog', { name: 'Sidebar' })
|
|
await expect(sheet).toBeVisible()
|
|
await expect(sheet.getByRole('link', { name: 'Meus contatos' })).toBeVisible()
|
|
})
|
|
|
|
test('hidden on desktop viewport', async ({ page }) => {
|
|
await page.setViewportSize({ width: 1280, height: 800 })
|
|
await page.goto('/campanha')
|
|
|
|
// CSS-hidden, but still in DOM
|
|
await expect(page.locator('[aria-label="Navegação principal"]')).toBeHidden()
|
|
// Sidebar trigger is visible on desktop
|
|
await expect(page.getByRole('button', { name: /abrir ou fechar/i })).toBeVisible()
|
|
})
|
|
|
|
test('FAB sits above the bottom nav on mobile', async ({ page }) => {
|
|
await page.goto('/campanha/municipios')
|
|
|
|
const fab = page.getByRole('button', { name: 'Ações rápidas' })
|
|
await expect(fab).toBeVisible()
|
|
await expect(page.locator('[aria-label="Navegação principal"]')).toBeVisible()
|
|
|
|
const fabBox = await fab.boundingBox()
|
|
const navBox = await (await page.locator('[aria-label="Navegação principal"]')).boundingBox()
|
|
if (fabBox && navBox) {
|
|
expect(fabBox.y).toBeLessThan(navBox.y)
|
|
}
|
|
})
|
|
|
|
test('items breathe below the top edge and labels never overlap (B171)', async ({ page }) => {
|
|
await page.goto('/campanha')
|
|
|
|
const bottomNav = page.locator('[aria-label="Navegação principal"]')
|
|
|
|
const expectLabelsLegible = async () => {
|
|
await expect(bottomNav).toBeVisible()
|
|
|
|
// A small breathing gap between the item content and the bar's top edge
|
|
// (pt-2.5 computes to 10px).
|
|
const paddingTop = await bottomNav.evaluate((el) =>
|
|
parseFloat(getComputedStyle(el).paddingTop),
|
|
)
|
|
expect(paddingTop).toBeGreaterThanOrEqual(10)
|
|
|
|
// Labels render smaller than the original text-xs (12px).
|
|
const labels = bottomNav.locator('a > span, button > span')
|
|
await expect(labels).toHaveCount(5)
|
|
const sizes = await labels.evaluateAll((els) =>
|
|
els.map((el) => parseFloat(getComputedStyle(el).fontSize)),
|
|
)
|
|
for (const fontSize of sizes) expect(fontSize).toBeLessThan(12)
|
|
|
|
// Adjacent labels must not overlap horizontally on a mobile viewport.
|
|
const boxes = await labels.evaluateAll((els) =>
|
|
els.map((el) => {
|
|
const rect = el.getBoundingClientRect()
|
|
return { left: rect.left, right: rect.right }
|
|
}),
|
|
)
|
|
for (let i = 1; i < boxes.length; i++) {
|
|
expect(boxes[i].left).toBeGreaterThanOrEqual(boxes[i - 1].right - 0.5)
|
|
}
|
|
}
|
|
|
|
// Default narrow phone (390px) and an extra-narrow one — the acceptance
|
|
// promises readable labels on "any mobile viewport", so cover both.
|
|
await expectLabelsLegible()
|
|
await page.setViewportSize({ width: 320, height: 640 })
|
|
await expectLabelsLegible()
|
|
})
|
|
})
|