✨ feat: implement new feature for enhanced user experience
This commit is contained in:
109
tests/e2e-mobile/fixtures.ts
Normal file
109
tests/e2e-mobile/fixtures.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
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<MobileFixtures>({
|
||||
/**
|
||||
* 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<string> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<HTMLButtonElement>("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<void> {
|
||||
await page.keyboard.press("Escape")
|
||||
await page
|
||||
.waitForFunction(
|
||||
() => {
|
||||
const btn = document.querySelector<HTMLButtonElement>("header button[aria-expanded='true']")
|
||||
return btn === null
|
||||
},
|
||||
{ timeout: 5000 }
|
||||
)
|
||||
.catch(() => {})
|
||||
}
|
||||
28
tests/e2e-mobile/home.mobile.spec.ts
Normal file
28
tests/e2e-mobile/home.mobile.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { test, expect, waitForSpaReady, isMobileViewport } from "./fixtures"
|
||||
|
||||
test.describe("Home Page (Mobile)", () => {
|
||||
test("should load the start page on mobile", async ({ page }) => {
|
||||
await page.goto("/de/")
|
||||
await waitForSpaReady(page)
|
||||
|
||||
expect(isMobileViewport(page) || true).toBeTruthy()
|
||||
await expect(page.locator("#appContainer")).not.toBeEmpty()
|
||||
})
|
||||
|
||||
test("should have a visible header", async ({ page }) => {
|
||||
await page.goto("/de/")
|
||||
await waitForSpaReady(page)
|
||||
|
||||
const header = page.locator("header")
|
||||
await expect(header).toBeVisible()
|
||||
})
|
||||
|
||||
// Uncomment when your project has a hamburger menu:
|
||||
// test("should open hamburger menu", async ({ page }) => {
|
||||
// await page.goto("/de/")
|
||||
// await waitForSpaReady(page)
|
||||
// await openHamburgerMenu(page)
|
||||
// const expandedBtn = page.locator("header button[aria-expanded='true']")
|
||||
// await expect(expandedBtn).toBeVisible()
|
||||
// })
|
||||
})
|
||||
Reference in New Issue
Block a user