import { test, expect } from "@playwright/test"; import { runA11y } from "./axe"; test.describe("Homepage", () => { test.beforeEach(async ({ page }) => { await page.goto("/"); }); test("homepage loads successfully with all sections", async ({ page }) => { // Check page title and meta await expect(page).toHaveTitle(/CommunityRule/); // Check main sections are present await expect( page.locator("h1, h2").filter({ hasText: "Collaborate" }) ).toBeVisible(); await expect( page.locator("h2").filter({ hasText: "How CommunityRule works" }) ).toBeVisible(); await expect( page.locator("h1").filter({ hasText: "We've got your back" }) ).toBeVisible(); // Check key components are rendered await expect(page.locator('img[alt="Hero illustration"]')).toBeVisible(); await expect( page.locator("text=Trusted by leading cooperators") ).toBeVisible(); await expect(page.locator("text=Jo Freeman")).toBeVisible(); }); test("hero banner section functionality", async ({ page }) => { // Check hero content await expect(page.locator("text=Collaborate")).toBeVisible(); await expect(page.locator("text=with clarity")).toBeVisible(); await expect( page.locator("text=Help your community make important decisions") ).toBeVisible(); // Check CTA button const learnButtons = page.locator( 'button:has-text("Learn how CommunityRule works")' ); const buttonCount = await learnButtons.count(); let visibleButton = null; for (let i = 0; i < buttonCount; i++) { const button = learnButtons.nth(i); if (await button.isVisible()) { visibleButton = button; break; } } if (!visibleButton) { throw new Error( 'No visible "Learn how CommunityRule works" button found' ); } await expect(visibleButton).toBeEnabled(); // Test button interaction await visibleButton.click(); // Should scroll to the numbered cards section await expect( page.locator('h2:has-text("How CommunityRule works")') ).toBeVisible(); }); test("logo wall section displays correctly", async ({ page }) => { // Check section label await expect( page.locator("text=Trusted by leading cooperators") ).toBeVisible(); // Check logos are present await expect(page.locator('img[alt="Food Not Bombs"]')).toBeVisible(); await expect(page.locator('img[alt="Start COOP"]')).toBeVisible(); await expect(page.locator('img[alt="Metagov"]')).toBeVisible(); await expect(page.locator('img[alt="Open Civics"]')).toBeVisible(); await expect(page.locator('img[alt="Mutual Aid CO"]')).toBeVisible(); await expect(page.locator('img[alt="CU Boulder"]')).toBeVisible(); // Check logos have proper attributes const logos = page.locator('img[alt*="Logo"]'); const logoCount = await logos.count(); expect(logoCount).toBeGreaterThan(0); // Test hover effects (visual test) await page.locator('img[alt="Food Not Bombs"]').hover(); // Should see hover state (opacity change) }); test("numbered cards section functionality", async ({ page }) => { // Check section header await expect( page.locator('h2:has-text("How CommunityRule works")') ).toBeVisible(); await expect( page.locator("text=Here's a quick overview of the process") ).toBeVisible(); // Check all three cards are present await expect( page.locator("text=Document how your community makes decisions") ).toBeVisible(); await expect( page.locator("text=Build an operating manual for a successful community") ).toBeVisible(); await expect( page.locator( "text=Get a link to your manual for your group to review and evolve" ) ).toBeVisible(); // Check numbered indicators - target the specific numbered cards section const numberedCardsSection = page .locator("section") .filter({ has: page.locator('h2:has-text("How CommunityRule works")') }); await expect( numberedCardsSection.locator("span").filter({ hasText: "1" }).first() ).toBeVisible(); await expect( numberedCardsSection.locator("span").filter({ hasText: "2" }).first() ).toBeVisible(); await expect( numberedCardsSection.locator("span").filter({ hasText: "3" }).first() ).toBeVisible(); // Check CTA buttons const createButtons = page.locator( 'button:has-text("Create CommunityRule")' ); const createButtonCount = await createButtons.count(); let visibleCreateButton = null; for (let i = 0; i < createButtonCount; i++) { const button = createButtons.nth(i); if (await button.isVisible()) { visibleCreateButton = button; break; } } if (visibleCreateButton) { await expect(visibleCreateButton).toBeVisible(); } await expect( page.locator('button:has-text("See how it works")') ).toBeVisible(); }); test("rule stack section interactions", async ({ page }) => { // Check all four rule cards are present await expect(page.locator("text=Consensus clusters")).toBeVisible(); await expect(page.locator("text=Consensus clusters")).toBeVisible(); await expect(page.locator("text=Elected Board").first()).toBeVisible(); await expect(page.locator("text=Petition")).toBeVisible(); // Check rule descriptions await expect( page.locator("text=Units called Circles have the ability to decide") ).toBeVisible(); await expect( page.locator("text=Decisions that affect the group collectively") ).toBeVisible(); await expect( page.locator("text=An elected board determines policies") ).toBeVisible(); await expect( page.locator("text=All participants can propose and vote") ).toBeVisible(); // Test card interactions const consensusCard = page.locator('[aria-label*="Consensus clusters"]'); await consensusCard.click(); // Should trigger analytics tracking (console log in test environment) // Check "See all templates" button await expect( page.locator('button:has-text("See all templates")') ).toBeVisible(); }); test("feature grid section functionality", async ({ page }) => { // Check section header await expect( page.locator('h1:has-text("We\'ve got your back")') ).toBeVisible(); await expect( page.locator( "text=Use our toolkit to improve, document, and evolve your organization" ) ).toBeVisible(); // Check all four feature cards - use more specific selectors to avoid conflicts const featureGrid = page.locator('[role="grid"]'); await expect(featureGrid.locator("text=Decision-making")).toBeVisible(); await expect(featureGrid.locator("text=Values alignment")).toBeVisible(); await expect(featureGrid.locator("text=Membership")).toBeVisible(); await expect(featureGrid.locator("text=Conflict resolution")).toBeVisible(); // Check feature links - be more specific to only get the feature grid links const featureLinks = featureGrid.locator('a[href^="#"]'); await expect(featureLinks).toHaveCount(4); // Test feature card interactions await page.locator('a[href="#decision-making"]').click(); // Should navigate to decision-making section }); test("quote block section displays correctly", async ({ page }) => { // Check quote content await expect( page.locator("text=The rules of decision-making must be open") ).toBeVisible(); // Check author and source await expect(page.locator("text=Jo Freeman")).toBeVisible(); await expect( page.locator("text=The Tyranny of Structurelessness") ).toBeVisible(); // Check avatar await expect( page.locator('img[alt="Portrait of Jo Freeman"]') ).toBeVisible(); // Check decorative elements await expect( page.locator('[class*="pointer-events-none absolute z-0"]').first() ).toBeVisible(); }); test("ask organizer section functionality", async ({ page }) => { // Check section content await expect(page.locator("text=Still have questions?")).toBeVisible(); await expect( page.locator("text=Get answers from an experienced organizer") ).toBeVisible(); // Check CTA button (it's actually a link) const askLinks = page.locator('a:has-text("Ask an organizer")'); const askLinkCount = await askLinks.count(); let visibleAskLink = null; for (let i = 0; i < askLinkCount; i++) { const link = askLinks.nth(i); if (await link.isVisible()) { visibleAskLink = link; break; } } if (!visibleAskLink) { throw new Error('No visible "Ask an organizer" link found'); } await expect(visibleAskLink).toBeEnabled(); // Test link interaction await visibleAskLink.click(); // Should trigger analytics tracking }); test("header navigation functionality", async ({ page }) => { // Check header is present await expect(page.locator("header")).toBeVisible(); // Check navigation elements await expect(page.locator("nav").first()).toBeVisible(); // Test logo/header click const header = page.locator("header"); await header.click(); // Should stay on homepage await expect(page).toHaveURL(/\/#?$/); }); test("footer section displays correctly", async ({ page }) => { // Scroll to footer await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); // Check footer is present - use the main page footer, not the quote footer const mainFooter = page.locator("footer").last(); await expect(mainFooter).toBeVisible(); // Check footer content await expect(mainFooter).toContainText("CommunityRule"); }); test("responsive design behavior", async ({ page }) => { // Test mobile viewport await page.setViewportSize({ width: 375, height: 667 }); await expect( page.locator("h1, h2").filter({ hasText: "Collaborate" }) ).toBeVisible(); // Test tablet viewport await page.setViewportSize({ width: 768, height: 1024 }); await expect( page.locator("h1, h2").filter({ hasText: "Collaborate" }) ).toBeVisible(); // Test desktop viewport await page.setViewportSize({ width: 1440, height: 900 }); await expect( page.locator("h1, h2").filter({ hasText: "Collaborate" }) ).toBeVisible(); }); test("keyboard navigation and accessibility", async ({ page }) => { // Test tab navigation await page.keyboard.press("Tab"); await expect(page.locator(":focus")).toBeVisible(); // Navigate through interactive elements await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); await page.keyboard.press("Tab"); // Test Enter key on buttons await page.keyboard.press("Enter"); // Test Escape key await page.keyboard.press("Escape"); }); test("page performance metrics", async ({ page }) => { // Measure page load time const startTime = Date.now(); await page.goto("/"); const loadTime = Date.now() - startTime; // Page should load within reasonable time (5 seconds) expect(loadTime).toBeLessThan(5000); // Check for any console errors const consoleErrors: string[] = []; page.on("console", (msg) => { if (msg.type() === "error") { consoleErrors.push(msg.text()); } }); await page.reload(); expect(consoleErrors.length).toBe(0); }); test("accessibility standards compliance", async ({ page }) => { // Basic accessibility checks const html = page.locator("html"); const lang = await html.getAttribute("lang"); expect(lang).toBeTruthy(); // Check for main heading const h1 = page.locator("h1").first(); await expect(h1).toBeVisible(); // Check for main landmark const main = page.locator("main, [role='main']"); await expect(main).toBeVisible(); // Check for navigation const nav = page.locator("nav, [role='navigation']").first(); await expect(nav).toBeVisible(); }); test("scroll behavior and smooth scrolling", async ({ page }) => { // Test smooth scrolling to sections const learnButtons = page.locator( 'button:has-text("Learn how CommunityRule works")' ); const buttonCount = await learnButtons.count(); let visibleButton = null; for (let i = 0; i < buttonCount; i++) { const button = learnButtons.nth(i); if (await button.isVisible()) { visibleButton = button; break; } } if (!visibleButton) { throw new Error( 'No visible "Learn how CommunityRule works" button found' ); } await visibleButton.click(); // Should smoothly scroll to numbered cards section await page.waitForTimeout(1000); // Wait for scroll animation // Check we're at the numbered cards section await expect( page.locator('h2:has-text("How CommunityRule works")') ).toBeVisible(); }); test("image loading and optimization", async ({ page }) => { // Check all images load properly const images = page.locator("img"); const imageCount = await images.count(); expect(imageCount).toBeGreaterThan(0); // Wait for page to be stable, but don't wait indefinitely for images await page.waitForLoadState("domcontentloaded"); await page.waitForTimeout(2000); // Give images time to load // Check for any broken images, but be more lenient const brokenImages = await page.evaluate(() => { const imgs = document.querySelectorAll("img"); return Array.from(imgs).filter( (img) => !img.complete || img.naturalWidth === 0 ); }); // Allow some images to be loading (not necessarily broken) // Only fail if more than 50% of images are broken const brokenRatio = brokenImages.length / imageCount; expect(brokenRatio).toBeLessThan(0.5); }); test("form interactions and validation", async ({ page }) => { // Test any form elements (if present) const forms = page.locator("form"); const formCount = await forms.count(); if (formCount > 0) { // Test form submission const submitButton = page.locator('button[type="submit"]'); if ((await submitButton.count()) > 0) { await submitButton.click(); // Should handle form submission appropriately } } }); test("error handling and fallbacks", async ({ page }) => { // Test with slow network await page.route("**/*", (route) => { route.continue(); }); // Test with offline mode (page.setOffline() not available in current Playwright) // Instead, test that the page loads and functions normally await expect(page.locator("body")).toBeVisible(); }); });