-
Notifications
You must be signed in to change notification settings - Fork 0
test(e2e): bateria abrangente + relatório auditoria (re-#85) #94
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,4 +68,71 @@ test.describe("Catalog & Filters", () => { | |
| expect(page.url()).toContain("page=2"); | ||
| } | ||
| }); | ||
|
|
||
| // ──────────────────────────────────────────────────────────────────── | ||
| // Regression — Fix #40 (commit 208e80a) | ||
| // mapLightweightToProduct() retornava "Sem categoria" hardcoded para | ||
| // 100% dos cards. Após o fix, a maioria carrega o nome real via map | ||
| // pré-fetch. O threshold aceita ≤5% para tolerar produtos sem | ||
| // category_id ou queries em paralelo. | ||
| // ──────────────────────────────────────────────────────────────────── | ||
| test("catalog cards exibem o nome real da categoria (não 'Sem categoria')", async ({ page }) => { | ||
| await gotoAndSettle(page, "/produtos"); | ||
| await expectVisibleByTestId(page, "product-grid"); | ||
|
|
||
| // Aguarda primeiro card hidratar | ||
| const firstCard = page.locator('[data-testid="product-card"]').first(); | ||
| await expect(firstCard).toBeVisible({ timeout: 15_000 }); | ||
| const totalCards = await page.locator('[data-testid="product-card"]').count(); | ||
| if (totalCards === 0) { | ||
| test.skip(true, "Sem cards renderizados — provavelmente sem dados."); | ||
| return; | ||
| } | ||
|
|
||
| // Conta cards cujo badge de categoria diz literalmente "Sem categoria". | ||
| // Antes do fix #40, isso era 100% dos cards. | ||
| const badgesSemCat = page.locator('[data-testid="product-card"]').locator("text=Sem categoria"); | ||
| const countSem = await badgesSemCat.count(); | ||
| const ratio = countSem / totalCards; | ||
| expect( | ||
| ratio, | ||
| `${countSem}/${totalCards} cards ainda exibem 'Sem categoria' (regressão do fix #40)`, | ||
| ).toBeLessThanOrEqual(0.05); | ||
|
Comment on lines
+96
to
+100
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The asserted threshold ( Useful? React with 👍 / 👎. |
||
| }); | ||
|
|
||
| // ──────────────────────────────────────────────────────────────────── | ||
| // Regression — Fix #41 (commit 0676f73) | ||
| // OptimizedImage perdia o onLoad interno quando o consumer passava o | ||
| // próprio. Resultado: opacity-0 permanente em todos os <img>. | ||
| // Aqui asseguramos que após carga, imagens estão visíveis | ||
| // (opacity-100 ou sem classe opacity-0). | ||
| // ──────────────────────────────────────────────────────────────────── | ||
| test("OptimizedImage transiciona para opacity-100 após carregar", async ({ page }) => { | ||
| await gotoAndSettle(page, "/produtos"); | ||
| await expectVisibleByTestId(page, "product-grid"); | ||
|
|
||
| // Aguarda primeira imagem do grid carregar (event 'load' real do browser) | ||
| const firstImg = page.locator('[data-testid="product-grid"] img').first(); | ||
| await expect(firstImg).toBeVisible({ timeout: 15_000 }); | ||
| await firstImg.evaluate((el: HTMLImageElement) => { | ||
| if (el.complete && el.naturalWidth > 0) return; | ||
| return new Promise<void>((resolve) => { | ||
| el.addEventListener("load", () => resolve(), { once: true }); | ||
|
Comment on lines
+116
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The same control-flow issue exists here: the test only calls Useful? React with 👍 / 👎. |
||
| el.addEventListener("error", () => resolve(), { once: true }); | ||
| }); | ||
| }); | ||
|
|
||
| // Conta quantas imagens permaneceram em opacity-0 (regressão) | ||
| const opacityZero = await page.locator('[data-testid="product-grid"] img.opacity-0').count(); | ||
| const totalImgs = await page.locator('[data-testid="product-grid"] img').count(); | ||
| if (totalImgs === 0) { | ||
| test.skip(true, "Sem <img> no grid — provavelmente sem dados."); | ||
| return; | ||
| } | ||
| // Permitimos até 20% ainda em opacity-0 (cards abaixo do viewport / lazy load). | ||
| expect( | ||
| opacityZero / totalImgs, | ||
| `${opacityZero}/${totalImgs} imgs ficaram com opacity-0 após carga`, | ||
| ).toBeLessThanOrEqual(0.2); | ||
|
Comment on lines
+133
to
+136
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This assertion divides by all images in the grid, but many images are expected to remain Useful? React with 👍 / 👎. |
||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| /** | ||
| * E2E: SPA Rewrite — Deep Routes (Fix #42 / commit 6b8a890) | ||
| * | ||
| * Sem o rewrite em vercel.json, qualquer GET direto em /admin/*, /orcamentos/*, | ||
| * /produtos/:id etc. retornava a página 404 NOT_FOUND da Vercel — quebrando | ||
| * refresh em rotas profundas e prefetch de chunks por <Link prefetch>. | ||
| * | ||
| * Aqui validamos no servidor de dev (Vite) que: | ||
| * 1. Rotas profundas servem o index.html (não geram 404). | ||
| * 2. App monta (header/sidebar/outlet) sem ficar preso em fallback. | ||
| * 3. Assets em /assets/* continuam sendo servidos diretamente (não interceptados). | ||
| * 4. Refresh em rota profunda preserva o caminho (router client-side reativa). | ||
| * | ||
| * Observação: vercel.json em si só age no deploy. O Vite dev tem fallback | ||
| * historyApi nativo, então este spec funciona como contrato de comportamento | ||
| * (qualquer regressão em vercel.json também regressaria a UX no dev). | ||
|
Comment on lines
+14
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This suite claims to validate the Useful? React with 👍 / 👎. |
||
| */ | ||
| import { test, expect } from "./fixtures/test-base"; | ||
|
|
||
| const DEEP_ROUTES = [ | ||
| "/admin/usuarios", | ||
| "/admin/conexoes", | ||
| "/admin/configuracoes", | ||
| "/admin/telemetria", | ||
| "/orcamentos", | ||
| "/orcamentos/novo", | ||
| "/produtos", | ||
| "/colecoes", | ||
| "/favoritos", | ||
| "/montar-kit", | ||
| ]; | ||
|
|
||
| test.describe("SPA rewrite — deep routes serve index.html", () => { | ||
| // Sem requerer auth: a validação aqui é do contrato HTTP/servidor (rewrite | ||
| // entrega index.html para qualquer caminho), independente de sessão. | ||
| // Rotas protegidas redirecionam para /login depois — mas isso é client-side | ||
| // e exige que o index.html tenha carregado primeiro. | ||
|
|
||
| for (const route of DEEP_ROUTES) { | ||
| test(`GET direto em ${route} monta a SPA (não 404)`, async ({ page }) => { | ||
| const response = await page.goto(route, { waitUntil: "domcontentloaded" }); | ||
| expect(response, `Resposta nula para ${route}`).not.toBeNull(); | ||
| expect(response!.status(), `Status HTTP para ${route}`).toBeLessThan(400); | ||
|
Comment on lines
+41
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Each route test only checks Useful? React with 👍 / 👎. |
||
|
|
||
| // O index.html sempre carrega #root — se o fallback historyApi (dev) ou | ||
| // o rewrite (prod) estiver quebrado, o body trará HTML do 404 do servidor | ||
| // e não terá esse elemento. | ||
| const root = page.locator("#root"); | ||
| await expect(root, `#root ausente em ${route} (fallback SPA quebrado)`).toBeVisible({ | ||
| timeout: 15_000, | ||
| }); | ||
| }); | ||
| } | ||
|
|
||
| test("refresh em rota profunda preserva o caminho", async ({ page }) => { | ||
| const target = "/orcamentos/novo"; | ||
| await page.goto(target, { waitUntil: "domcontentloaded" }); | ||
| await page.reload({ waitUntil: "domcontentloaded" }); | ||
| // O Router declarativo só consegue restaurar o path se o rewrite/fallback | ||
| // estiver entregando index.html para o caminho real. | ||
| expect(new URL(page.url()).pathname).toBe(target); | ||
| await expect(page.locator("#root")).toBeVisible(); | ||
| }); | ||
|
|
||
| test("/assets/* não são interceptados pelo rewrite", async ({ page }) => { | ||
| // Carrega a home, captura o primeiro asset do <link rel="modulepreload"> ou <script>. | ||
| await page.goto("/", { waitUntil: "domcontentloaded" }); | ||
| const assetHref = await page.evaluate(() => { | ||
| const link = document.querySelector<HTMLLinkElement>( | ||
| 'link[rel="modulepreload"][href^="/assets/"], link[rel="stylesheet"][href^="/assets/"]', | ||
| ); | ||
| const script = document.querySelector<HTMLScriptElement>('script[src^="/assets/"]'); | ||
| return link?.href ?? script?.src ?? null; | ||
|
Comment on lines
+70
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The asset assertion is effectively non-executing in the configured test environment: this code only looks for Useful? React with 👍 / 👎. |
||
| }); | ||
|
|
||
| if (!assetHref) { | ||
| test.skip(true, "Sem /assets/* — dev server inline (esperado em vite dev)."); | ||
| return; | ||
| } | ||
|
|
||
| const res = await page.request.get(assetHref); | ||
| expect(res.status(), `Asset ${assetHref} deveria responder 200`).toBe(200); | ||
| const ct = res.headers()["content-type"] ?? ""; | ||
| expect(ct).not.toContain("text/html"); // se virou index.html, é regressão do rewrite | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test tries to skip when there are no products, but the skip branch is unreachable because
expect(firstCard).toBeVisible()runs first and will timeout when the catalog is empty. In environments/tenants without seeded products, this turns an intended skip into a hard failure and can make the regression suite flaky. Count cards (or otherwise detect empty state) before the visibility assertion so the test can skip as designed.Useful? React with 👍 / 👎.