✨ feat: implement new feature for enhanced user experience
This commit is contained in:
113
tests/e2e-visual/fixtures.ts
Normal file
113
tests/e2e-visual/fixtures.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import type { Page, Locator } 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 VisualFixtures = {}
|
||||
|
||||
export const test = base.extend<VisualFixtures>({
|
||||
/**
|
||||
* 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 fully rendered and stable for visual comparison.
|
||||
* Waits for skeleton loaders to disappear and CSS to settle.
|
||||
*/
|
||||
export async function waitForVisualReady(page: Page, opts?: { timeout?: number }): Promise<void> {
|
||||
const timeout = opts?.timeout ?? 15000
|
||||
await page.waitForLoadState("domcontentloaded")
|
||||
await expect(page.locator("#appContainer")).not.toBeEmpty({ timeout })
|
||||
try {
|
||||
await page.waitForFunction(() => document.querySelectorAll(".animate-pulse").length === 0, { timeout: 10000 })
|
||||
} catch {
|
||||
// Skeleton loaders may not exist on every page
|
||||
}
|
||||
await page.waitForTimeout(800)
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate to a route with visual readiness wait.
|
||||
*/
|
||||
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 waitForVisualReady(page)
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide dynamic content that would cause screenshot flakiness:
|
||||
* - BrowserSync overlay
|
||||
* - All animations and transitions
|
||||
* - Blinking cursor
|
||||
*/
|
||||
export async function hideDynamicContent(page: Page): Promise<void> {
|
||||
await page.addStyleTag({
|
||||
content: `
|
||||
#__bs_notify__, #__bs_notify__:before, #__bs_notify__:after { display: none !important; }
|
||||
*, *::before, *::after {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
transition-duration: 0s !important;
|
||||
transition-delay: 0s !important;
|
||||
}
|
||||
* { caret-color: transparent !important; }
|
||||
`,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns locators for non-deterministic elements that should be masked
|
||||
* in screenshots (e.g. cart badges, timestamps).
|
||||
* Customize this for your project.
|
||||
*/
|
||||
export function getDynamicMasks(page: Page): Locator[] {
|
||||
return [
|
||||
// Add project-specific dynamic element selectors here:
|
||||
// page.locator('[data-testid="cart-badge"]'),
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the page for a screenshot: hide dynamic content and scroll to top.
|
||||
*/
|
||||
export async function prepareForScreenshot(page: Page): Promise<void> {
|
||||
await hideDynamicContent(page)
|
||||
await page.evaluate(() => window.scrollTo(0, 0))
|
||||
await page.waitForTimeout(300)
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a screenshot and compare against baseline.
|
||||
*/
|
||||
export async function expectScreenshot(
|
||||
page: Page,
|
||||
name: string,
|
||||
opts?: {
|
||||
fullPage?: boolean
|
||||
mask?: Locator[]
|
||||
maxDiffPixelRatio?: number
|
||||
}
|
||||
): Promise<void> {
|
||||
const masks = [...getDynamicMasks(page), ...(opts?.mask ?? [])]
|
||||
await pwExpect(page).toHaveScreenshot(name, {
|
||||
fullPage: opts?.fullPage ?? false,
|
||||
mask: masks,
|
||||
maxDiffPixelRatio: opts?.maxDiffPixelRatio ?? 0.02,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user