import { expect, test } from "@playwright/test"; import { DESKTOP_USE, PHONE_USE } from "./helpers"; /** * (d) Clicar em "Cases" no menu e depois num case navega sem recarregar o documento (um marcador em `window` * sobrevive) e cada troca de página dispara uma startViewTransition; a carga inicial, nenhuma. * (k) De uma página rolada para outra (/servicos -> /servicos/criacao-de-sites, /cases -> um case), a página nova abre em * scrollY === 0, sem animação: a cada quadro, a partir do clique, nunca se vê a página nova numa altura diferente de * 0 (o `data-scroll-behavior` do faz o Next desligar a rolagem suave só durante a troca de rota). No * computador com clique, no celular (`isMobile`, `hasTouch`) com toque. */ type Probe = { __marker?: string; __viewTransitions?: number }; test.use(DESKTOP_USE); test("menu Cases e depois um case: sem recarregar o documento, uma transição de página por navegação", async ({ page, }) => { // conta as chamadas de document.startViewTransition, desde antes do primeiro script da página await page.addInitScript(() => { const probe = window as unknown as Probe; probe.__viewTransitions = 0; const original = document.startViewTransition?.bind(document); if (!original) return; document.startViewTransition = ((...args: Parameters) => { (probe.__viewTransitions as number) += 1; return original(...args); }) as typeof document.startViewTransition; }); const transitions = () => page.evaluate(() => (window as unknown as Probe).__viewTransitions); await page.goto("/", { waitUntil: "load" }); await page.waitForTimeout(1500); await page.evaluate(() => { (window as unknown as Probe).__marker = "mesmo-documento"; }); expect( await page.evaluate(() => "startViewTransition" in document), "o navegador precisa ter startViewTransition", ).toBe(true); expect(await transitions(), "carga inicial").toBe(0); await page .getByRole("navigation", { name: "Navegação principal" }) .getByRole("link", { name: "Cases" }) .click(); await page.waitForURL("**/cases"); await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); await page.waitForTimeout(500); expect(await transitions(), "depois de Cases").toBe(1); await page .getByRole("link", { name: /^Ver case / }) .first() .click(); await page.waitForURL(/\/cases\/[^/]+$/); await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); await page.waitForTimeout(500); expect(await transitions(), "depois de abrir um case").toBe(2); expect( await page.evaluate(() => (window as unknown as Probe).__marker), "o documento foi recarregado (o marcador sumiu)", ).toBe("mesmo-documento"); }); const SCROLLED_NAVIGATIONS = [ { from: "/servicos", link: "a[href='/servicos/criacao-de-sites']", to: /\/servicos\/criacao-de-sites$/, }, { from: "/cases", link: "a[href^='/cases/']", to: /\/cases\/[^/]+$/ }, ] as const; const DEVICES = [ { name: "computador, clique", use: DESKTOP_USE, tap: false }, { name: "celular, toque", use: PHONE_USE, tap: true }, ] as const; for (const { name, use, tap } of DEVICES) { test.describe(`página nova abre no topo, ${name}`, () => { test.use(use); for (const { from, link, to } of SCROLLED_NAVIGATIONS) { test(`${from} rolado -> próxima página: scrollY 0 em todos os quadros, sem deslizar`, async ({ page, }) => { await page.goto(from, { waitUntil: "load" }); await page.waitForTimeout(1500); const target = page.locator(link).first(); await target.scrollIntoViewIfNeeded(); await page.evaluate(() => { const maxScroll = document.documentElement.scrollHeight - innerHeight; window.scrollTo({ top: Math.min(1200, maxScroll), behavior: "instant", }); }); await page.waitForTimeout(300); const before = await page.evaluate(() => Math.round(scrollY)); expect(before, `${from}: a página começa rolada`).toBeGreaterThan(300); await expect(target).toBeVisible(); // a cada quadro desenhado, o caminho e a rolagem que a pessoa vê, até a nova página assentar await page.evaluate(() => { const frames: Array<{ path: string; y: number }> = []; (window as unknown as { __frames: typeof frames }).__frames = frames; const startedAt = performance.now(); const sample = () => { frames.push({ path: location.pathname, y: Math.round(scrollY) }); if (performance.now() - startedAt < 4_000) requestAnimationFrame(sample); }; requestAnimationFrame(sample); }); if (tap) await target.tap(); else await target.click(); await page.waitForURL(to); await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); await page.waitForTimeout(4_200); const { frames, finalY, path } = await page.evaluate(() => ({ frames: ( window as unknown as { __frames: Array<{ path: string; y: number }>; } ).__frames, finalY: Math.round(scrollY), path: location.pathname, })); const onNewPage = frames.filter((frame) => frame.path === path); expect( onNewPage.length, "quadros vistos na página nova", ).toBeGreaterThan(5); const notAtTop = onNewPage.filter((frame) => frame.y !== 0); expect( notAtTop.slice(0, 8), `a página nova (${path}) foi vista fora do topo em ${notAtTop.length} de ${onNewPage.length} quadros (antes: ${before}px)`, ).toEqual([]); expect(finalY, "rolagem final da página nova").toBe(0); }); } }); }