Zero telas sem texto no computador e no celular; atalho de teclado em até 4 Tabs e linear em até 36 com o inventário das paradas; ciclo de foco do menu com a barra de cookies; oferta e cabeçalho levando ao formulário; foco vindo de outra página; separador da faixa de clientes; contagem de pôsteres da SEV. .gitattributes com eol=lf.
81 lines
2.9 KiB
TypeScript
81 lines
2.9 KiB
TypeScript
import { expect, type Page, test } from "@playwright/test";
|
|
import { DESKTOP, PHONE } from "./helpers";
|
|
|
|
/**
|
|
* (j) O pôster da SEV Moda Íntima não se repete: em /servicos e em /cases ele aparece no máximo 2 vezes (meta da
|
|
* rodada 4), contando todo elemento que o mostra ao navegador (<img>, <video poster>, fundo CSS) e que não esteja
|
|
* dentro da própria página do case. Conta o que está no HTML, visível ou não, porque o que está no HTML é o que o
|
|
* navegador baixa. O mesmo vale nas duas larguras.
|
|
*/
|
|
|
|
const SEV_ASSETS = "/videos/sev-modaintima/";
|
|
const MAX_SEV_POSTERS = 2;
|
|
const PAGES = ["/servicos", "/cases"] as const;
|
|
const VIEWPORTS = [
|
|
{ name: "computador 1440x900", size: DESKTOP },
|
|
{ name: "celular 390x844", size: PHONE },
|
|
] as const;
|
|
|
|
/** Elementos que mostram um arquivo de /videos/sev-modaintima/, descritos em texto. */
|
|
function sevPosters(page: Page) {
|
|
return page.evaluate((assets) => {
|
|
const decoded = (value: string | null) => {
|
|
try {
|
|
return decodeURIComponent(value ?? "");
|
|
} catch {
|
|
return value ?? "";
|
|
}
|
|
};
|
|
const found: string[] = [];
|
|
for (const element of document.querySelectorAll("*")) {
|
|
const sources = [
|
|
element.getAttribute("src"),
|
|
element.getAttribute("srcset"),
|
|
element.getAttribute("poster"),
|
|
element instanceof HTMLElement ? element.style.backgroundImage : null,
|
|
];
|
|
if (element instanceof HTMLElement && element.tagName !== "HTML")
|
|
sources.push(getComputedStyle(element).backgroundImage);
|
|
const hit = sources.find((value) => decoded(value).includes(assets));
|
|
if (hit)
|
|
found.push(
|
|
`<${element.tagName.toLowerCase()}> ${decoded(hit).slice(0, 110)}`,
|
|
);
|
|
}
|
|
return found;
|
|
}, SEV_ASSETS);
|
|
}
|
|
|
|
for (const { name, size } of VIEWPORTS) {
|
|
test.describe(`pôster da SEV, ${name}`, () => {
|
|
test.use({ viewport: size });
|
|
|
|
// o detector enxerga o pôster onde ele certamente está (o hero da home e o próprio case): sem isso, "0 imagens"
|
|
// poderia ser só um seletor que não acha nada
|
|
for (const path of ["/", "/cases/sev-modaintima"]) {
|
|
test(`${path}: o detector acha o pôster da SEV`, async ({ page }) => {
|
|
await page.goto(path, { waitUntil: "load" });
|
|
await page.waitForTimeout(1000);
|
|
|
|
expect((await sevPosters(page)).length).toBeGreaterThan(0);
|
|
});
|
|
}
|
|
|
|
for (const path of PAGES) {
|
|
test(`${path}: no máximo ${MAX_SEV_POSTERS} imagens da SEV fora do próprio case`, async ({
|
|
page,
|
|
}) => {
|
|
await page.goto(path, { waitUntil: "load" });
|
|
await page.waitForTimeout(1000);
|
|
|
|
const posters = await sevPosters(page);
|
|
|
|
expect(
|
|
posters.length,
|
|
`${path}: ${posters.length} elemento(s) mostram arquivos da SEV (meta: no máximo ${MAX_SEV_POSTERS}):\n${posters.join("\n")}`,
|
|
).toBeLessThanOrEqual(MAX_SEV_POSTERS);
|
|
});
|
|
}
|
|
});
|
|
}
|