import type { Page } from "@playwright/test" import { test as base, expect as pwExpect } from "@playwright/test" export { expect, type Page, API_BASE, clickSpaLink } from "../e2e/fixtures" import { expect } from "../e2e/fixtures" type MobileFixtures = {} export const test = base.extend({ /** * Override page fixture: BrowserSync domcontentloaded workaround. */ page: async ({ page }, use) => { const origGoto = page.goto.bind(page) const origReload = page.reload.bind(page) page.goto = ((url: string, opts?: any) => origGoto(url, { waitUntil: "domcontentloaded", ...opts })) as typeof page.goto page.reload = ((opts?: any) => origReload({ waitUntil: "domcontentloaded", ...opts })) as typeof page.reload await use(page) }, }) /** * Wait for the SPA to be ready in mobile viewport. */ export async function waitForSpaReady(page: Page): Promise { await page.waitForLoadState("domcontentloaded") await expect(page.locator("#appContainer")).not.toBeEmpty({ timeout: 15000 }) const url = page.url() const match = url.match(/\/([a-z]{2})(\/|$)/) return match?.[1] || "de" } /** * Navigate to a route with language prefix. */ export async function navigateToRoute(page: Page, routePath: string): Promise { const url = page.url() const match = url.match(/\/([a-z]{2})(\/|$)/) const lang = match?.[1] || "de" const fullPath = routePath === "/" ? `/${lang}` : `/${lang}${routePath}` await page.goto(fullPath, { waitUntil: "domcontentloaded" }) await expect(page.locator("#appContainer")).not.toBeEmpty({ timeout: 15000 }) } /** Check if viewport width is mobile (<768px) */ export function isMobileViewport(page: Page): boolean { return (page.viewportSize()?.width ?? 0) < 768 } /** Check if viewport width is tablet (768-1023px) */ export function isTabletViewport(page: Page): boolean { const w = page.viewportSize()?.width ?? 0 return w >= 768 && w < 1024 } /** Check if viewport width is below lg breakpoint (<1024px) */ export function isBelowLg(page: Page): boolean { return (page.viewportSize()?.width ?? 0) < 1024 } /** * Open the hamburger/mobile navigation menu. * Finds the first visible button with aria-expanded in the header. */ export async function openHamburgerMenu(page: Page): Promise { const hamburgers = page.locator("header button[aria-expanded]") const count = await hamburgers.count() let clicked = false for (let i = 0; i < count; i++) { const btn = hamburgers.nth(i) if (await btn.isVisible()) { await btn.click() clicked = true break } } if (!clicked) { throw new Error("No visible hamburger button found in header") } await page.waitForTimeout(500) await page .waitForFunction( () => { const btn = document.querySelector("header button[aria-expanded='true']") return btn !== null }, { timeout: 5000 } ) .catch(() => {}) } /** * Close the hamburger menu via Escape key. */ export async function closeHamburgerMenuViaEscape(page: Page): Promise { await page.keyboard.press("Escape") await page .waitForFunction( () => { const btn = document.querySelector("header button[aria-expanded='true']") return btn === null }, { timeout: 5000 } ) .catch(() => {}) }