import { readFileSync } from "node:fs"; import { join } from "node:path"; import { type APIRequestContext, expect, type Page } from "@playwright/test"; /** Caminhos (`/`, `/cases/…`) de todas as rotas do /sitemap.xml, sem o domínio de produção. */ export async function sitemapPaths(request: APIRequestContext) { const response = await request.get("/sitemap.xml"); if (response.status() !== 200) throw new Error(`/sitemap.xml respondeu ${response.status()}`); const xml = await response.text(); const paths = [...xml.matchAll(/\s*([^<\s]+)\s*<\/loc>/g)].map( ([, loc]) => { const { pathname } = new URL(loc); return pathname === "" ? "/" : pathname; }, ); return [...new Set(paths)]; } let devServer: Promise | undefined; /** * O `BASE_URL` é um servidor de desenvolvimento (`next dev`)? O HTML dele traz o cliente de recarga a quente (HMR) e o * React Refresh, que o build de produção não tem. Serve para a única coisa que só existe em um dos dois: avisos do * React que só o desenvolvimento emite. (A barra de cookies é a mesma nos dois, e o gtag só carrega em produção.) */ export function isDevServer(request: APIRequestContext) { devServer ??= request .get("/") .then((response) => response.text()) .then((html) => /hmr-client|react-refresh/.test(html)) .catch((error) => { devServer = undefined; throw error; }); return devServer; } /** Nomes dos projetos de cliente, como a home os lista (public/portfolio-data.json, kind "cliente"). */ export function clientProjectNames() { const data = JSON.parse( readFileSync( join(__dirname, "..", "public", "portfolio-data.json"), "utf8", ), ) as { projects: Array<{ title: string; kind?: string }> }; return data.projects .filter((project) => project.kind === "cliente") .map((project) => project.title); } /** Texto do cabeçalho da seção de contato da home. */ export const CONTACT_HEADING = "Vamos conversar sobre o que sua empresa precisa."; export const DESKTOP = { width: 1440, height: 900 } as const; export const PHONE = { width: 390, height: 844 } as const; /** Opções de `test.use` do computador. */ export const DESKTOP_USE = { viewport: DESKTOP } as const; /** * Opções de `test.use` de um celular de verdade: a janela de 390x844 mais `isMobile` (viewport de celular, `` valendo) e `hasTouch` (toque, `tap()`), e não só uma janela estreita. */ export const PHONE_USE = { viewport: PHONE, isMobile: true, hasTouch: true, deviceScaleFactor: 2, } as const; /** Chave do consentimento no localStorage (`CONSENT_STORAGE_KEY` em lib/analytics.ts). */ export const CONSENT_STORAGE_KEY = "evandro.analytics-consent.v1"; /** Os controles da barra de cookies, na ordem do Tab. */ export const COOKIE_BAR_CONTROLS = [ "Política de privacidade", "Aceitar", "Recusar", ] as const; /** * Deixa o consentimento já salvo no navegador (como quem aceitou ou recusou numa visita anterior): chamar antes do * `page.goto`. Com ele a barra de cookies não abre. "denied" por padrão, para o gtag não carregar em produção. */ export async function saveConsent( page: Page, value: "granted" | "denied" = "denied", ) { await page.addInitScript( ([key, stored]) => window.localStorage.setItem(key, stored), [CONSENT_STORAGE_KEY, value], ); } /** * A barra de cookies da primeira visita (id `cookie-bar`, `COOKIE_BAR_ID` em lib/analytics.ts), fixa na base da tela, * antes do cabeçalho no documento. Ela é a mesma em qualquer servidor, de desenvolvimento ou de produção (só o * carregamento do gtag depende do ambiente): se não aparecer, o teste reprova. Devolve o localizador da barra. */ export async function cookieBar(page: Page) { const bar = page.getByRole("dialog", { name: "Cookies e privacidade" }); await expect( bar, "a barra de cookies não apareceu na primeira visita", ).toBeVisible({ timeout: 10_000 }); return bar; }