import { test, expect } from "@playwright/test"; test.describe("Visual Regression Tests", () => { async function settle(page: any) { await page.evaluate(() => { window.scrollTo(0, window.scrollY); // ensure a frame boundary void document.body.getBoundingClientRect(); }); await page.waitForTimeout(50); } test.beforeEach(async ({ page }) => { // Add deterministic CSS to normalize rendering await page.addStyleTag({ content: ` /* stop caret and selection flicker */ * { caret-color: transparent !important; } ::selection { background: transparent !important; } /* hide scrollbars */ ::-webkit-scrollbar { display: none !important; } html { scrollbar-width: none !important; } /* stabilize font rasterization */ * { text-rendering: geometricPrecision !important; -webkit-font-smoothing: antialiased !important; -moz-osx-font-smoothing: grayscale !important; } `, }); await page.goto("/"); // Wait for all content to load await page.waitForLoadState("networkidle"); // Make sure we've really got the webfonts before shots await page.evaluate(async () => { // @ts-ignore if (document.fonts && document.fonts.status !== "loaded") { // @ts-ignore await document.fonts.ready; } }); }); test("homepage full page screenshot", async ({ page }) => { // Stabilize layout before screenshot await settle(page); // Take full page screenshot await expect(page).toHaveScreenshot("homepage-full.png", { fullPage: true, animations: "disabled", scale: "css", }); }); test("homepage viewport screenshot", async ({ page }) => { // Stabilize layout before screenshot await page.evaluate(() => { window.scrollTo(0, 0); // Force layout & a frame boundary void document.body.getBoundingClientRect(); }); await page.waitForTimeout(50); // give the compositor one tick // Take viewport screenshot await expect(page).toHaveScreenshot("homepage-viewport.png", { animations: "disabled", }); }); test("hero banner section screenshot", async ({ page }) => { // Scroll to hero section and take screenshot await page.locator("text=Collaborate").scrollIntoViewIfNeeded(); await page.waitForTimeout(500); // Wait for animations // Stabilize layout before screenshot await page.evaluate(() => { // Force layout & a frame boundary void document.body.getBoundingClientRect(); }); await page.waitForTimeout(50); // give the compositor one tick const heroSection = page.locator("section").first(); await expect(heroSection).toHaveScreenshot("hero-banner.png", { animations: "disabled", }); }); test("logo wall section screenshot", async ({ page }) => { // Scroll to logo wall section await page .locator("text=Trusted by leading cooperators") .scrollIntoViewIfNeeded(); await page.waitForTimeout(500); const logoSection = page.locator("section").nth(1); await expect(logoSection).toHaveScreenshot("logo-wall.png", { animations: "disabled", }); }); test("numbered cards section screenshot", async ({ page }) => { // Scroll to numbered cards section await page .locator('h2:has-text("How CommunityRule works")') .scrollIntoViewIfNeeded(); await page.waitForTimeout(500); const cardsSection = page.locator("section").nth(2); await expect(cardsSection).toHaveScreenshot("numbered-cards.png", { animations: "disabled", }); }); test("rule stack section screenshot", async ({ page }) => { // Scroll to rule stack section await page.locator("text=Consensus clusters").scrollIntoViewIfNeeded(); await page.waitForTimeout(500); const ruleSection = page.locator("section").nth(3); await expect(ruleSection).toHaveScreenshot("rule-stack.png", { animations: "disabled", }); }); test("feature grid section screenshot", async ({ page }) => { // Scroll to feature grid section - use a more reliable selector await page.locator("text=We've got your back").scrollIntoViewIfNeeded(); await page.waitForTimeout(500); const featureSection = page.locator("section").nth(4); await expect(featureSection).toHaveScreenshot("feature-grid.png", { animations: "disabled", }); }); test("quote block section screenshot", async ({ page }) => { // Scroll to quote block section await page.locator("text=Jo Freeman").scrollIntoViewIfNeeded(); await page.waitForTimeout(500); const quoteSection = page.locator("section").nth(5); await expect(quoteSection).toHaveScreenshot("quote-block.png", { animations: "disabled", }); }); test("ask organizer section screenshot", async ({ page }) => { // Scroll to ask organizer section await page.locator("text=Still have questions?").scrollIntoViewIfNeeded(); await page.waitForTimeout(500); const askSection = page.locator("section").nth(6); await expect(askSection).toHaveScreenshot("ask-organizer.png", { animations: "disabled", }); }); test("header component screenshot", async ({ page }) => { const header = page.locator("header"); await expect(header).toHaveScreenshot("header.png", { animations: "disabled", }); }); test("footer component screenshot", async ({ page }) => { // Scroll to footer await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); await page.waitForTimeout(500); // Use a more specific selector for the main footer const footer = page.locator("footer").last(); await expect(footer).toHaveScreenshot("footer.png", { animations: "disabled", }); }); test("mobile viewport screenshots", async ({ page }) => { // Test mobile viewport await page.setViewportSize({ width: 375, height: 667 }); await page.waitForTimeout(1000); // Wait for page to be stable await page.waitForLoadState("networkidle"); await expect(page).toHaveScreenshot("homepage-mobile.png", { animations: "disabled", }); // Test mobile hero section - use a more reliable selector const heroSection = page.locator("section").first(); if ((await heroSection.count()) > 0) { await heroSection.scrollIntoViewIfNeeded(); await page.waitForTimeout(500); await expect(heroSection).toHaveScreenshot("hero-banner-mobile.png", { animations: "disabled", }); } }); test("tablet viewport screenshots", async ({ page }) => { // Test tablet viewport await page.setViewportSize({ width: 768, height: 1024 }); await page.waitForTimeout(1000); // Wait for page to be stable await page.waitForLoadState("networkidle"); await expect(page).toHaveScreenshot("homepage-tablet.png", { animations: "disabled", }); // Test tablet hero section - use a more reliable selector const heroSection = page.locator("section").first(); if ((await heroSection.count()) > 0) { await heroSection.scrollIntoViewIfNeeded(); await page.waitForTimeout(500); await expect(heroSection).toHaveScreenshot("hero-banner-tablet.png", { animations: "disabled", }); } }); test("desktop viewport screenshots", async ({ page }) => { // Test desktop viewport await page.setViewportSize({ width: 1440, height: 900 }); await page.waitForTimeout(1000); await expect(page).toHaveScreenshot("homepage-desktop.png", { animations: "disabled", }); // Test desktop hero section await page.locator("text=Collaborate").scrollIntoViewIfNeeded(); await page.waitForTimeout(500); const heroSection = page.locator("section").first(); await expect(heroSection).toHaveScreenshot("hero-banner-desktop.png", { animations: "disabled", }); }); test("large desktop viewport screenshots", async ({ page }) => { // Test large desktop viewport await page.setViewportSize({ width: 1920, height: 1080 }); await page.waitForTimeout(1000); await expect(page).toHaveScreenshot("homepage-large-desktop.png", { animations: "disabled", }); }); // test('button hover states', async ({ page }) => { // // Test button hover states - scroll to hero section first to ensure button is visible // await page.locator('text=Collaborate').scrollIntoViewIfNeeded(); // await page.waitForTimeout(500); // // // Use a more specific selector for the visible button // const ctaButton = page.locator('button:has-text("Learn how CommunityRule works")').first(); // // // Ensure button is visible // await ctaButton.scrollIntoViewIfNeeded(); // await page.waitForTimeout(500); // // // Normal state // await expect(ctaButton).toHaveScreenshot('button-normal.png', { // animations: 'disabled' // }); // // // Hover state // await ctaButton.hover(); // await page.waitForTimeout(500); // await expect(ctaButton).toHaveScreenshot('button-hover.png', { // animations: 'disabled' // }); // }); test("rule card hover states", async ({ page }) => { // Scroll to rule stack section await page.locator("text=Consensus clusters").scrollIntoViewIfNeeded(); await page.waitForTimeout(500); const consensusCard = page.locator('[aria-label*="Consensus clusters"]'); // Normal state await expect(consensusCard).toHaveScreenshot("rule-card-normal.png", { animations: "disabled", }); // Hover state await consensusCard.hover(); await page.waitForTimeout(500); await expect(consensusCard).toHaveScreenshot("rule-card-hover.png", { animations: "disabled", }); }); test("feature card hover states", async ({ page }) => { // Scroll to feature grid section await page.locator("text=We've got your back").scrollIntoViewIfNeeded(); await page.waitForTimeout(500); const featureCard = page.locator('a[href="#decision-making"]'); // Normal state await expect(featureCard).toHaveScreenshot("feature-card-normal.png", { animations: "disabled", }); // Hover state await featureCard.hover(); await page.waitForTimeout(500); await expect(featureCard).toHaveScreenshot("feature-card-hover.png", { animations: "disabled", }); }); test("logo hover states", async ({ page }) => { // Scroll to logo wall section await page .locator("text=Trusted by leading cooperators") .scrollIntoViewIfNeeded(); await page.waitForTimeout(500); const logo = page.locator('img[alt="Food Not Bombs"]'); // Normal state await expect(logo).toHaveScreenshot("logo-normal.png", { animations: "disabled", }); // Hover state await logo.hover(); await page.waitForTimeout(500); await expect(logo).toHaveScreenshot("logo-hover.png", { animations: "disabled", }); }); // test('focus states', async ({ page }) => { // // Test focus states for interactive elements - scroll to hero section first // await page.locator('text=Collaborate').scrollIntoViewIfNeeded(); // await page.waitForTimeout(500); // // // Use first button and ensure it's visible // const ctaButton = page.locator('button:has-text("Learn how CommunityRule works")').first(); // // // Ensure button is visible // await ctaButton.scrollIntoViewIfNeeded(); // await page.waitForTimeout(500); // // // Focus the button // await ctaButton.focus(); // await page.waitForTimeout(500); // // await expect(ctaButton).toHaveScreenshot('button-focus.png', { // animations: 'disabled' // }); // }); test("loading states", async ({ page }) => { // Test loading states by blocking resources await page.route("**/*", (route) => { // Delay all requests to simulate loading setTimeout(() => route.continue(), 1000); }); // Reload page to trigger loading states await page.reload(); // Take screenshot during loading await expect(page).toHaveScreenshot("homepage-loading.png", { animations: "disabled", }); }); test("blog listing page", async ({ page }) => { // Navigate to blog listing page await page.goto("/blog"); await page.waitForLoadState("networkidle"); await settle(page); // Take full page screenshot of blog listing await expect(page).toHaveScreenshot("blog-listing.png", { fullPage: true, animations: "disabled", }); }); test("blog post page", async ({ page }) => { // Navigate to a specific blog post await page.goto("/blog/resolving-active-conflicts"); await page.waitForLoadState("networkidle"); await settle(page); // Take full page screenshot of blog post await expect(page).toHaveScreenshot("blog-post.png", { fullPage: true, animations: "disabled", }); }); test("404 error page", async ({ page }) => { // Navigate to a non-existent route to trigger 404 await page.goto("/non-existent-page"); await page.waitForLoadState("networkidle"); await settle(page); // Take screenshot of 404 page await expect(page).toHaveScreenshot("404-error.png", { animations: "disabled", }); }); test("high contrast mode", async ({ page }) => { // Simulate high contrast mode await page.evaluate(() => { document.body.style.filter = "contrast(200%)"; }); await expect(page).toHaveScreenshot("homepage-high-contrast.png", { animations: "disabled", }); // Reset contrast await page.evaluate(() => { document.body.style.filter = "none"; }); }); test("reduced motion mode", async ({ page }) => { // Simulate reduced motion preference await page.evaluate(() => { document.documentElement.style.setProperty( "--prefers-reduced-motion", "reduce" ); }); await expect(page).toHaveScreenshot("homepage-reduced-motion.png", { animations: "disabled", }); }); test("dark mode simulation", async ({ page }) => { // Simulate dark mode (if supported) await page.evaluate(() => { document.documentElement.classList.add("dark"); document.body.style.backgroundColor = "#000"; document.body.style.color = "#fff"; }); await expect(page).toHaveScreenshot("homepage-dark-mode.png", { animations: "disabled", }); // Reset to light mode await page.evaluate(() => { document.documentElement.classList.remove("dark"); document.body.style.backgroundColor = ""; document.body.style.color = ""; }); }); });