Etapa 1 inteira (logica com 100% de cobertura, cena 3D, toque, sons, bancada de desempenho, e2e) e a base da etapa 2 (mapa novo no Tiled, texturas, imagens dos itens, sons conferidos, golpe recortado e medido). Pagina /jogo fora do menu e do sitemap ate a medicao no celular.
580 lines
20 KiB
TypeScript
580 lines
20 KiB
TypeScript
import AxeBuilder from "@axe-core/playwright";
|
|
import {
|
|
type Browser,
|
|
type BrowserContext,
|
|
type BrowserContextOptions,
|
|
type CDPSession,
|
|
chromium,
|
|
expect,
|
|
type Locator,
|
|
type Page,
|
|
test,
|
|
} from "@playwright/test";
|
|
import { PROBLEMAS, problemaPorId } from "../lib/jogo/problemas";
|
|
import { DESKTOP, isDevServer, saveConsent } from "./helpers";
|
|
|
|
/**
|
|
* Ponta a ponta do Expediente dos Mortos (/jogo): CA1, CA4, CA8, CA9, CA11 e CA13 da spec
|
|
* specs/2026-11-10-jogo-zumbis-etapa-1.md.
|
|
*
|
|
* Os casos que desenham 3D abrem um Chromium próprio com os argumentos de GPU da bancada. Sem WebGL (CI sem GPU)
|
|
* eles conferem só o aviso "Este navegador não desenha 3D." e anotam que rodaram sem 3D.
|
|
* Falha num desses casos deixa a captura `falha-N.png` na pasta do teste em test-results/.
|
|
*/
|
|
|
|
const GPU = [
|
|
"--enable-gpu",
|
|
"--ignore-gpu-blocklist",
|
|
"--use-angle=vulkan",
|
|
"--enable-features=Vulkan",
|
|
];
|
|
// CA9: `gc()` na página e `performance.memory` sem arredondamento
|
|
const MEMORIA = ["--js-flags=--expose-gc", "--enable-precise-memory-info"];
|
|
const SEM_3D = "Este navegador não desenha 3D.";
|
|
// mesma exceção de e2e/routes.spec.ts: aviso que só o React de desenvolvimento emite
|
|
const AVISO_DO_DEV = /Each child in a list should have a unique "key" prop/;
|
|
const FIM = /^(Problemas resolvidos|Os problemas venceram desta vez)$/;
|
|
|
|
let navegador: Browser;
|
|
const contextos: BrowserContext[] = [];
|
|
|
|
test.beforeAll(async () => {
|
|
navegador = await chromium.launch({
|
|
executablePath: process.env.PW_CHROMIUM || undefined,
|
|
args: [...GPU, ...MEMORIA],
|
|
});
|
|
});
|
|
test.afterAll(async () => {
|
|
await navegador?.close();
|
|
});
|
|
test.afterEach(async () => {
|
|
const info = test.info();
|
|
const falhou = info.status !== info.expectedStatus;
|
|
const paginas = contextos.flatMap((c) => c.pages());
|
|
for (const [i, pagina] of paginas.entries())
|
|
if (falhou)
|
|
await pagina
|
|
.screenshot({ path: info.outputPath(`falha-${i}.png`) })
|
|
.catch(() => {});
|
|
for (const contexto of contextos.splice(0)) await contexto.close();
|
|
});
|
|
|
|
/** Página no Chromium com GPU, com o consentimento de cookies já salvo para a barra não cobrir o jogo. */
|
|
async function abrir(
|
|
opcoes: BrowserContextOptions,
|
|
baseURL: string | undefined,
|
|
): Promise<Page> {
|
|
const contexto = await navegador.newContext({ baseURL, ...opcoes });
|
|
contextos.push(contexto);
|
|
const page = await contexto.newPage();
|
|
await saveConsent(page);
|
|
return page;
|
|
}
|
|
|
|
/** Sem WebGL: confere o aviso, anota e devolve true para o teste terminar ali. */
|
|
async function sem3d(page: Page): Promise<boolean> {
|
|
const tem = await page.evaluate(() => {
|
|
const contexto = document.createElement("canvas").getContext("webgl2");
|
|
contexto?.getExtension("WEBGL_lose_context")?.loseContext();
|
|
return contexto !== null;
|
|
});
|
|
if (tem) return false;
|
|
await expect(page.getByText(SEM_3D)).toBeVisible();
|
|
test.info().annotations.push({
|
|
type: "sem 3D",
|
|
description: "WebGL indisponível: conferido só o aviso da página",
|
|
});
|
|
return true;
|
|
}
|
|
|
|
function vigiarConsole(page: Page, dev: boolean): string[] {
|
|
const erros: string[] = [];
|
|
page.on("console", (mensagem) => {
|
|
if (mensagem.type() !== "error") return;
|
|
if (dev && AVISO_DO_DEV.test(mensagem.text())) return;
|
|
erros.push(mensagem.text());
|
|
});
|
|
page.on("pageerror", (erro) => erros.push(erro.message));
|
|
return erros;
|
|
}
|
|
|
|
const estado = (page: Page) => page.evaluate(() => window.__jogo);
|
|
|
|
async function comecarPartida(page: Page, nome: string) {
|
|
await page
|
|
.locator("label", {
|
|
has: page.getByRole("radio", { name: nome, exact: true }),
|
|
})
|
|
.click();
|
|
await page
|
|
.getByRole("button", { name: /^(Começar|Jogar mesmo assim)$/ })
|
|
.click();
|
|
await page.waitForFunction(() => window.__jogo !== undefined, null, {
|
|
timeout: 90_000,
|
|
});
|
|
}
|
|
|
|
async function dentroDaTela(
|
|
alvo: Locator,
|
|
tela: { width: number; height: number },
|
|
) {
|
|
const caixa = await alvo.boundingBox();
|
|
expect(caixa, "elemento sem caixa").not.toBeNull();
|
|
if (!caixa) return;
|
|
const fora = `${JSON.stringify(caixa)} fora de ${tela.width}x${tela.height}`;
|
|
expect(caixa.x, fora).toBeGreaterThanOrEqual(0);
|
|
expect(caixa.y, fora).toBeGreaterThanOrEqual(0);
|
|
expect(caixa.x + caixa.width, fora).toBeLessThanOrEqual(tela.width + 0.5);
|
|
expect(caixa.y + caixa.height, fora).toBeLessThanOrEqual(tela.height + 0.5);
|
|
}
|
|
|
|
type Dedo = { id: number; x: number; y: number };
|
|
const tocar = (
|
|
cdp: CDPSession,
|
|
type: "touchStart" | "touchMove" | "touchEnd",
|
|
touchPoints: Dedo[],
|
|
) => cdp.send("Input.dispatchTouchEvent", { type, touchPoints });
|
|
|
|
/** O que passou pela tela durante a partida: cartões "Resolvido:" e linhas do feed. Roda dentro da página. */
|
|
type Vistos = { resolvidos: string[]; feed: string[] };
|
|
declare global {
|
|
interface Window {
|
|
__vistos?: Vistos;
|
|
}
|
|
}
|
|
function anotarTextos() {
|
|
const vistos: Vistos = { resolvidos: [], feed: [] };
|
|
window.__vistos = vistos;
|
|
const olhar = () => {
|
|
for (const p of document.querySelectorAll("p")) {
|
|
const texto = p.textContent ?? "";
|
|
const nome = texto.startsWith("Resolvido: ") ? texto.slice(11) : "";
|
|
if (nome && !vistos.resolvidos.includes(nome))
|
|
vistos.resolvidos.push(nome);
|
|
}
|
|
for (const li of document.querySelectorAll(
|
|
'[aria-label="Últimas eliminações"] li',
|
|
)) {
|
|
const texto = li.textContent ?? "";
|
|
if (!vistos.feed.includes(texto)) vistos.feed.push(texto);
|
|
}
|
|
};
|
|
new MutationObserver(olhar).observe(document, {
|
|
childList: true,
|
|
subtree: true,
|
|
characterData: true,
|
|
});
|
|
}
|
|
|
|
async function axeSerio(page: Page) {
|
|
const { violations } = await new AxeBuilder({ page }).analyze();
|
|
return violations
|
|
.filter(({ impact }) => impact === "serious" || impact === "critical")
|
|
.map(
|
|
({ id, impact, nodes }) =>
|
|
`${id} (${impact}): ${nodes.length} elemento(s), por exemplo ${nodes[0]?.target.join(" ")}`,
|
|
);
|
|
}
|
|
|
|
test("CA1: o piloto joga a partida inteira e o relatório liga cada problema ao serviço", async ({
|
|
baseURL,
|
|
request,
|
|
}) => {
|
|
test.setTimeout(300_000);
|
|
const dev = await isDevServer(request);
|
|
const page = await abrir({ viewport: { width: 960, height: 540 } }, baseURL);
|
|
const erros = vigiarConsole(page, dev);
|
|
await page.addInitScript(anotarTextos);
|
|
await page.goto(
|
|
"/jogo?piloto=1&semente=1&problema=site-lento&dificuldade=facil&vel=8",
|
|
);
|
|
if (await sem3d(page)) return;
|
|
|
|
const titulo = page.getByRole("heading", { level: 1, name: FIM });
|
|
await expect(titulo).toBeVisible({ timeout: 240_000 });
|
|
const vistos = await page.evaluate(() => window.__vistos);
|
|
if (!vistos) throw new Error("o observador de textos não rodou");
|
|
console.log(`CA1 vistos: ${JSON.stringify(vistos)}`);
|
|
expect(vistos.resolvidos.length, "cartão Resolvido:").toBeGreaterThan(0);
|
|
expect(
|
|
vistos.feed.filter((linha) => linha.includes("eliminou")).length,
|
|
"linha do feed com eliminou",
|
|
).toBeGreaterThan(0);
|
|
|
|
await expect(page.getByText(/^Você \d+ · Drones \d+$/)).toBeVisible();
|
|
|
|
// o nome acessível junta o texto e o trecho só para leitor de tela, com ou sem espaço antes dos dois-pontos
|
|
const links = page.getByRole("link", { name: /^Ver como eu resolvo isso/ });
|
|
const nomes: string[] = [];
|
|
for (const link of await links.all()) {
|
|
const nome = (await link.textContent())?.split(": ")[1] ?? "";
|
|
const problema = PROBLEMAS.find((p) => p.nome === nome);
|
|
expect(problema, `problema do link "${nome}"`).toBeDefined();
|
|
await expect(link).toHaveAttribute("href", problema?.destino ?? "");
|
|
nomes.push(nome);
|
|
}
|
|
console.log(`CA1 relatório: ${await titulo.textContent()} · ${nomes}`);
|
|
expect(new Set(nomes).size, "um link por problema").toBe(nomes.length);
|
|
for (const nome of vistos.resolvidos) expect(nomes).toContain(nome);
|
|
const chefao = problemaPorId("site-lento");
|
|
if ((await titulo.textContent()) === "Problemas resolvidos")
|
|
expect(nomes).toContain(chefao?.nome);
|
|
|
|
await expect(
|
|
// o rodapé do site tem outro "Chamar no WhatsApp", com a mensagem genérica
|
|
page
|
|
.getByRole("main")
|
|
.getByRole("link", { name: "Chamar no WhatsApp", exact: true }),
|
|
).toHaveAttribute(
|
|
"href",
|
|
new RegExp(encodeURIComponent(chefao?.nome ?? "?")),
|
|
);
|
|
expect(erros).toEqual([]);
|
|
});
|
|
|
|
for (const tela of [
|
|
{ width: 915, height: 412 },
|
|
{ width: 640, height: 360 },
|
|
]) {
|
|
test(`CA4: toque em ${tela.width}x${tela.height}: controles na tela, andar, mirar e acertar`, async ({
|
|
baseURL,
|
|
}) => {
|
|
test.setTimeout(200_000);
|
|
const page = await abrir(
|
|
{ viewport: tela, hasTouch: true, isMobile: true },
|
|
baseURL,
|
|
);
|
|
await page.goto("/jogo");
|
|
if (await sem3d(page)) return;
|
|
await comecarPartida(page, "Site lento");
|
|
const atirar = page.getByRole("button", { name: "Atirar", exact: true });
|
|
await expect(atirar).toBeVisible({ timeout: 30_000 });
|
|
const cdp = await page.context().newCDPSession(page);
|
|
const W = tela.width;
|
|
const H = tela.height;
|
|
|
|
// direcional na metade esquerda; empurrado para a frente, anda
|
|
const mover: Dedo = {
|
|
id: 1,
|
|
x: Math.round(W * 0.2),
|
|
y: Math.round(H * 0.6),
|
|
};
|
|
const parado = await estado(page);
|
|
await tocar(cdp, "touchStart", [mover]);
|
|
await expect(page.getByTestId("direcional")).toBeVisible();
|
|
await dentroDaTela(page.getByTestId("direcional"), tela);
|
|
await tocar(cdp, "touchMove", [{ ...mover, y: mover.y - 50 }]);
|
|
await page.waitForTimeout(1500);
|
|
const andou = await estado(page);
|
|
await tocar(cdp, "touchEnd", []);
|
|
expect(
|
|
Math.hypot(
|
|
(andou?.x ?? 0) - (parado?.x ?? 0),
|
|
(andou?.z ?? 0) - (parado?.z ?? 0),
|
|
),
|
|
`de ${JSON.stringify(parado)} para ${JSON.stringify(andou)}`,
|
|
).toBeGreaterThan(0.01);
|
|
|
|
await dentroDaTela(atirar, tela);
|
|
for (const arma of [/^Pistola$/, /^Automática/, /^Faca$/])
|
|
await dentroDaTela(page.getByRole("button", { name: arma }), tela);
|
|
|
|
// arrastar na metade direita gira
|
|
const mirar: Dedo = {
|
|
id: 2,
|
|
x: Math.round(W * 0.6),
|
|
y: Math.round(H * 0.3),
|
|
};
|
|
const rumo = (await estado(page))?.rumo;
|
|
await tocar(cdp, "touchStart", [mirar]);
|
|
for (let dx = 8; dx <= 80; dx += 8)
|
|
await tocar(cdp, "touchMove", [{ ...mirar, x: mirar.x + dx }]);
|
|
await tocar(cdp, "touchEnd", []);
|
|
await page.waitForTimeout(300);
|
|
expect((await estado(page))?.rumo, "rumo").not.toBe(rumo);
|
|
|
|
// segura Atirar e varre a mira em voltas
|
|
const caixa = await atirar.boundingBox();
|
|
if (!caixa) throw new Error("Atirar sem caixa");
|
|
const tiro: Dedo = {
|
|
id: 3,
|
|
x: Math.round(caixa.x + caixa.width / 2),
|
|
y: Math.round(caixa.y + caixa.height / 2),
|
|
};
|
|
const limite = Date.now() + 90_000;
|
|
let acertos = 0;
|
|
while (acertos < 1 && Date.now() < limite) {
|
|
const varrer: Dedo = { id: 4, x: Math.round(W * 0.55), y: mirar.y };
|
|
await tocar(cdp, "touchStart", [tiro]);
|
|
await tocar(cdp, "touchStart", [tiro, varrer]);
|
|
for (let dx = 6; dx <= W * 0.35 && acertos < 1; dx += 6) {
|
|
await tocar(cdp, "touchMove", [tiro, { ...varrer, x: varrer.x + dx }]);
|
|
await page.waitForTimeout(40);
|
|
acertos = (await estado(page))?.placar.jogador ?? 0;
|
|
}
|
|
await tocar(cdp, "touchEnd", []);
|
|
}
|
|
const fim = await estado(page);
|
|
expect(
|
|
fim?.placar.jogador,
|
|
`estado: ${JSON.stringify(fim)}`,
|
|
).toBeGreaterThanOrEqual(1);
|
|
});
|
|
}
|
|
|
|
test("CA4: em retrato, pede para virar o aparelho", async ({ baseURL }) => {
|
|
test.setTimeout(120_000);
|
|
const page = await abrir(
|
|
{ viewport: { width: 412, height: 915 }, hasTouch: true, isMobile: true },
|
|
baseURL,
|
|
);
|
|
await page.goto("/jogo");
|
|
if (await sem3d(page)) return;
|
|
await comecarPartida(page, "Site lento");
|
|
await expect(page.getByText("Vire o aparelho para jogar")).toBeVisible();
|
|
});
|
|
|
|
test("CA8: a home, rolada até o fim, não pede nada do jogo", async ({
|
|
browser,
|
|
baseURL,
|
|
request,
|
|
}) => {
|
|
test.setTimeout(180_000);
|
|
/** Caminhos dos scripts que a página pede até a rede parar. */
|
|
async function scripts(caminho: string) {
|
|
const contexto = await browser.newContext({ baseURL });
|
|
contextos.push(contexto);
|
|
const page = await contexto.newPage();
|
|
const pedidos = new Set<string>();
|
|
page.on("request", (pedido) => {
|
|
if (pedido.resourceType() === "script")
|
|
pedidos.add(new URL(pedido.url()).pathname);
|
|
});
|
|
await page.goto(caminho);
|
|
await page.waitForLoadState("networkidle");
|
|
return pedidos;
|
|
}
|
|
// Blocos da cena: dos scripts que a partida automática pede a mais que a tela inicial parada, os que trazem o
|
|
// código da cena (ela publica `window.__jogo`). O three em si fica de fora: o fundo 3D da home
|
|
// (HeroField → BlocosScene) já o carrega sob demanda, e é o mesmo arquivo.
|
|
const parada = await scripts("/jogo");
|
|
const novos = [
|
|
...(await scripts("/jogo?piloto=1&problema=site-lento")),
|
|
].filter((caminho) => !parada.has(caminho));
|
|
const blocos: string[] = [];
|
|
for (const caminho of novos)
|
|
if ((await (await request.get(caminho)).text()).includes("__jogo"))
|
|
blocos.push(caminho);
|
|
console.log(`CA8 pedidos da partida: ${novos.join(" ")}`);
|
|
console.log(`CA8 blocos da cena: ${blocos.join(" ")}`);
|
|
expect(blocos.length, "blocos da cena achados").toBeGreaterThan(0);
|
|
|
|
const contexto = await browser.newContext({ baseURL });
|
|
contextos.push(contexto);
|
|
const page = await contexto.newPage();
|
|
const pedidos: string[] = [];
|
|
page.on("request", (pedido) => {
|
|
pedidos.push(pedido.url());
|
|
});
|
|
await page.goto("/");
|
|
for (let i = 0; i < 100; i += 1) {
|
|
const noFim = await page.evaluate(() => {
|
|
window.scrollBy(0, window.innerHeight * 0.8);
|
|
return (
|
|
window.scrollY + window.innerHeight >=
|
|
document.documentElement.scrollHeight - 2
|
|
);
|
|
});
|
|
await page.waitForTimeout(150);
|
|
if (noFim) break;
|
|
}
|
|
await page.waitForLoadState("networkidle");
|
|
await page.waitForTimeout(1500);
|
|
const doJogo = pedidos.filter(
|
|
(url) => url.includes("/jogo/") || blocos.includes(new URL(url).pathname),
|
|
);
|
|
console.log(
|
|
`CA8 da partida também pedidos pela home: ${novos.filter((caminho) => pedidos.some((url) => new URL(url).pathname === caminho))}`,
|
|
);
|
|
expect(doJogo).toEqual([]);
|
|
});
|
|
|
|
test("CA9: três partidas seguidas sem acumular geometria, textura nem memória", async ({
|
|
baseURL,
|
|
}) => {
|
|
test.setTimeout(900_000);
|
|
const page = await abrir({ viewport: DESKTOP }, baseURL);
|
|
// semente fixa na 1ª partida; "Jogar de novo" sorteia outra
|
|
await page.goto("/jogo?piloto=1&semente=1&problema=site-lento&vel=16");
|
|
if (await sem3d(page)) return;
|
|
const cdp = await page.context().newCDPSession(page);
|
|
const jogarDeNovo = page.getByRole("button", { name: "Jogar de novo" });
|
|
const medidas: {
|
|
geometrias?: number;
|
|
texturas?: number;
|
|
heap: number | null;
|
|
sobras?: { geometrias: number; texturas: number };
|
|
}[] = [];
|
|
for (let i = 0; i < 3; i += 1) {
|
|
if (i > 0) await jogarDeNovo.click();
|
|
const pronto = await page.waitForFunction(
|
|
() => {
|
|
const j = window.__jogo;
|
|
return j?.horda === 1 && j.fase === "horda"
|
|
? { geometrias: j.geometrias, texturas: j.texturas }
|
|
: null;
|
|
},
|
|
null,
|
|
{ timeout: 180_000, polling: 50 },
|
|
);
|
|
const contagem = await pronto.jsonValue();
|
|
// coleta completa pelo protocolo do Chromium antes de medir
|
|
await cdp.send("HeapProfiler.collectGarbage");
|
|
const heap = await page.evaluate(() => {
|
|
// `performance.memory` só existe no Chromium (preciso com as flags de MEMORIA); os tipos do DOM não o trazem
|
|
const desempenho = performance as Performance & {
|
|
memory?: { usedJSHeapSize: number };
|
|
};
|
|
return desempenho.memory?.usedJSHeapSize ?? null;
|
|
});
|
|
// no fim da partida a cena é descartada e publica o que o renderer ainda contava
|
|
await expect(jogarDeNovo).toBeVisible({ timeout: 300_000 });
|
|
const sobras = await page.evaluate(() => window.__jogo?.sobras);
|
|
medidas.push({ ...contagem, heap, sobras });
|
|
console.log(`CA9 partida ${i + 1}: ${JSON.stringify(medidas[i])}`);
|
|
}
|
|
const [primeira, , terceira] = medidas;
|
|
expect(terceira.geometrias, "geometrias").toBe(primeira.geometrias);
|
|
expect(terceira.texturas, "texturas").toBe(primeira.texturas);
|
|
for (const [i, { sobras }] of medidas.entries())
|
|
expect(sobras, `sobras da partida ${i + 1}`).toEqual({
|
|
geometrias: 0,
|
|
texturas: 0,
|
|
});
|
|
if (primeira.heap !== null && terceira.heap !== null)
|
|
expect(terceira.heap, "heap JS").toBeLessThanOrEqual(primeira.heap * 1.1);
|
|
});
|
|
|
|
test.describe("CA11: acesso", () => {
|
|
test("sem JavaScript, mostra os 27 problemas com link", async ({
|
|
browser,
|
|
baseURL,
|
|
}) => {
|
|
const contexto = await browser.newContext({
|
|
baseURL,
|
|
javaScriptEnabled: false,
|
|
});
|
|
contextos.push(contexto);
|
|
const page = await contexto.newPage();
|
|
await page.goto("/jogo");
|
|
const lista = page.locator('section[aria-labelledby="lista-de-problemas"]');
|
|
await expect(lista.getByRole("link")).toHaveCount(27);
|
|
for (const problema of PROBLEMAS) {
|
|
const item = lista
|
|
.getByRole("listitem")
|
|
.filter({ hasText: problema.nome });
|
|
await expect(item.getByRole("link")).toHaveAttribute(
|
|
"href",
|
|
problema.destino,
|
|
);
|
|
}
|
|
});
|
|
|
|
test("com movimento reduzido, oferece Jogar mesmo assim, e o aviso de sangue vem antes", async ({
|
|
baseURL,
|
|
}) => {
|
|
const page = await abrir(
|
|
{ viewport: DESKTOP, reducedMotion: "reduce" },
|
|
baseURL,
|
|
);
|
|
await page.goto("/jogo");
|
|
await expect(
|
|
page.getByText("Tem sangue e sustos. É um jogo de tiro."),
|
|
).toBeVisible();
|
|
if (await sem3d(page)) return;
|
|
await expect(
|
|
page.getByRole("button", { name: "Jogar mesmo assim" }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("no computador, o mudo alterna e perder a trava do mouse pausa", async ({
|
|
baseURL,
|
|
}) => {
|
|
test.setTimeout(120_000);
|
|
const page = await abrir({ viewport: DESKTOP }, baseURL);
|
|
await page.goto("/jogo");
|
|
if (await sem3d(page)) return;
|
|
await comecarPartida(page, "Site lento");
|
|
|
|
const desligar = page.getByRole("button", { name: "Desligar o som" });
|
|
const ligar = page.getByRole("button", { name: "Ligar o som" });
|
|
await desligar.click();
|
|
await expect(ligar).toBeVisible();
|
|
await ligar.click();
|
|
await expect(desligar).toBeVisible();
|
|
|
|
const pausado = page.getByRole("heading", { name: "Pausado" });
|
|
await page.mouse.click(DESKTOP.width / 2, DESKTOP.height / 2);
|
|
const travou = await page
|
|
.waitForFunction(() => document.pointerLockElement !== null, null, {
|
|
timeout: 5_000,
|
|
})
|
|
.then(
|
|
() => true,
|
|
() => false,
|
|
);
|
|
test.info().annotations.push({
|
|
type: "trava do mouse",
|
|
description: travou ? "obtida ao clicar" : "não obtida no clique",
|
|
});
|
|
console.log(`CA11 trava do mouse obtida: ${travou}`);
|
|
await page.keyboard.press("Escape");
|
|
const pausouComEsc = await pausado.waitFor({ timeout: 3_000 }).then(
|
|
() => true,
|
|
() => false,
|
|
);
|
|
console.log(`CA11 Esc pausou: ${pausouComEsc}`);
|
|
if (!pausouComEsc) {
|
|
test.info().annotations.push({
|
|
type: "pausa",
|
|
description: "Esc não pausou; conferido saindo da trava do mouse",
|
|
});
|
|
await page.evaluate(() => document.exitPointerLock());
|
|
}
|
|
await expect(pausado).toBeVisible();
|
|
});
|
|
|
|
test("axe sem violação serious ou critical na tela inicial", async ({
|
|
baseURL,
|
|
}) => {
|
|
const page = await abrir({ viewport: DESKTOP }, baseURL);
|
|
await page.goto("/jogo", { waitUntil: "load" });
|
|
await page.waitForTimeout(800);
|
|
expect(await axeSerio(page)).toEqual([]);
|
|
});
|
|
|
|
test("axe sem violação serious ou critical no relatório", async ({
|
|
baseURL,
|
|
}) => {
|
|
test.setTimeout(300_000);
|
|
const page = await abrir({ viewport: DESKTOP }, baseURL);
|
|
await page.goto("/jogo?piloto=1&semente=1&problema=site-lento&vel=16");
|
|
if (await sem3d(page)) return;
|
|
await expect(
|
|
page.getByRole("heading", { level: 1, name: FIM }),
|
|
).toBeVisible({ timeout: 240_000 });
|
|
await page.waitForTimeout(800);
|
|
expect(await axeSerio(page)).toEqual([]);
|
|
});
|
|
});
|
|
|
|
test("CA13: /jogo e /jogo/bancada trazem noindex", async ({ page }) => {
|
|
for (const caminho of ["/jogo", "/jogo/bancada"]) {
|
|
await page.goto(caminho);
|
|
await expect(page.locator('meta[name="robots"]'), caminho).toHaveAttribute(
|
|
"content",
|
|
/noindex/,
|
|
);
|
|
}
|
|
});
|