150 lines
5.7 KiB
TypeScript
150 lines
5.7 KiB
TypeScript
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 <html> 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<typeof original>) => {
|
|
(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);
|
|
});
|
|
}
|
|
});
|
|
}
|