144 lines
4.5 KiB
TypeScript
144 lines
4.5 KiB
TypeScript
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
import puppeteer, { Browser, Page } from "puppeteer";
|
|
import * as net from "net";
|
|
import { build, preview } from "vite";
|
|
|
|
// Porta padrão do Vite
|
|
const PORT = 5173;
|
|
const ORIGIN = `http://127.0.0.1:${PORT}`;
|
|
|
|
function waitForPort(port: number, timeoutMs = 20000): Promise<void> {
|
|
const start = Date.now();
|
|
return new Promise((resolve, reject) => {
|
|
const tryOnce = () => {
|
|
const socket = net.connect(port, "127.0.0.1");
|
|
socket.on("connect", () => {
|
|
socket.end();
|
|
resolve();
|
|
});
|
|
socket.on("error", () => {
|
|
socket.destroy();
|
|
if (Date.now() - start > timeoutMs)
|
|
reject(new Error("Timeout aguardando Vite dev server"));
|
|
else setTimeout(tryOnce, 300);
|
|
});
|
|
};
|
|
tryOnce();
|
|
});
|
|
}
|
|
|
|
let browser: Browser;
|
|
let page: Page;
|
|
let previewServer: Awaited<ReturnType<typeof preview>> | undefined;
|
|
let built = false;
|
|
|
|
async function ensurePreviewServer() {
|
|
// Se já existe algo na porta (ex dev aberto manualmente), apenas usa
|
|
try {
|
|
await waitForPort(PORT, 800);
|
|
return;
|
|
} catch {
|
|
/* inicia preview */
|
|
}
|
|
|
|
if (!built) {
|
|
await build();
|
|
built = true;
|
|
}
|
|
previewServer = await preview({
|
|
preview: { port: PORT, host: "127.0.0.1" },
|
|
server: { middlewareMode: false },
|
|
} as unknown as Parameters<typeof preview>[0]);
|
|
await waitForPort(PORT);
|
|
}
|
|
|
|
describe("E2E Accessibility Menu", () => {
|
|
beforeAll(async () => {
|
|
await ensurePreviewServer();
|
|
browser = await puppeteer.launch({ headless: true });
|
|
page = await browser.newPage();
|
|
await page.goto(ORIGIN, { waitUntil: "domcontentloaded" });
|
|
}, 90000);
|
|
|
|
afterAll(async () => {
|
|
if (browser) await browser.close();
|
|
if (previewServer) {
|
|
// @ts-expect-error acesso interno não tipado
|
|
const httpServer =
|
|
previewServer.httpServer || previewServer.server?.httpServer;
|
|
if (httpServer) httpServer.close();
|
|
}
|
|
});
|
|
|
|
it("abre e fecha o diálogo de acessibilidade", async () => {
|
|
// Botão flutuante
|
|
await page.waitForSelector('button[aria-label="Menu de Acessibilidade"]', {
|
|
timeout: 10000,
|
|
});
|
|
await page.click('button[aria-label="Menu de Acessibilidade"]');
|
|
await page.waitForSelector('div[role="dialog"][aria-modal="true"]', {
|
|
timeout: 5000,
|
|
});
|
|
const exists = await page.$('div[role="dialog"][aria-modal="true"]');
|
|
expect(exists).not.toBeNull();
|
|
// Pressiona ESC para fechar
|
|
await page.keyboard.press("Escape");
|
|
// Pequeno delay
|
|
await new Promise((r) => setTimeout(r, 150));
|
|
const still = await page.$('div[role="dialog"][aria-modal="true"]');
|
|
expect(still).toBeNull();
|
|
}, 30000);
|
|
|
|
it("ativa dark mode e alto contraste e persiste após reload", async () => {
|
|
// Abre menu (caso esteja fechado)
|
|
const trigger = await page.$('button[aria-label="Menu de Acessibilidade"]');
|
|
if (trigger) {
|
|
await trigger.click();
|
|
await page.waitForSelector('div[role="dialog"][aria-modal="true"]', {
|
|
timeout: 5000,
|
|
});
|
|
}
|
|
|
|
// Helper para clicar botão pelo texto visível interno
|
|
async function clickToggleByAria(label: string) {
|
|
const selector = `button[aria-label="${label}"]`;
|
|
await page.waitForSelector(selector, { timeout: 5000 });
|
|
await page.click(selector);
|
|
}
|
|
|
|
// Ativa Modo Escuro e Alto Contraste (aria-label fica igual ao label)
|
|
await clickToggleByAria("Modo Escuro");
|
|
await clickToggleByAria("Alto Contraste");
|
|
|
|
// Verifica classes aplicadas
|
|
const classesBefore = await page.evaluate(() =>
|
|
Array.from(document.documentElement.classList)
|
|
);
|
|
expect(classesBefore).toContain("dark");
|
|
expect(classesBefore).toContain("high-contrast");
|
|
|
|
// Recarrega página para validar persistência (localStorage -> rehidratação)
|
|
await page.reload({ waitUntil: "domcontentloaded" });
|
|
|
|
const classesAfter = await page.evaluate(() =>
|
|
Array.from(document.documentElement.classList)
|
|
);
|
|
expect(classesAfter).toContain("dark");
|
|
expect(classesAfter).toContain("high-contrast");
|
|
|
|
// (Opcional) Reabre menu e desfaz para não impactar execuções subsequentes
|
|
const trigger2 = await page.$(
|
|
'button[aria-label="Menu de Acessibilidade"]'
|
|
);
|
|
if (trigger2) {
|
|
await trigger2.click();
|
|
await page.waitForSelector('div[role="dialog"][aria-modal="true"]', {
|
|
timeout: 5000,
|
|
});
|
|
await clickToggleByAria("Modo Escuro");
|
|
await clickToggleByAria("Alto Contraste");
|
|
await page.keyboard.press("Escape");
|
|
}
|
|
}, 45000);
|
|
});
|