Compare commits

..

4 Commits

Author SHA1 Message Date
adilallo 147dd013ab Fix visual regression test snapshot naming and add missing snapshots
CI Pipeline / test (20) (pull_request) Successful in 4m0s
CI Pipeline / test (18) (pull_request) Successful in 4m5s
CI Pipeline / e2e (chromium) (pull_request) Failing after 3m8s
CI Pipeline / visual-regression (pull_request) Has been cancelled
CI Pipeline / performance (pull_request) Has been cancelled
CI Pipeline / storybook (pull_request) Has been cancelled
CI Pipeline / lint (pull_request) Has been cancelled
CI Pipeline / build (pull_request) Has been cancelled
CI Pipeline / e2e (firefox) (pull_request) Has been cancelled
CI Pipeline / e2e (webkit) (pull_request) Has been cancelled
2025-09-02 22:52:33 -06:00
adilallo ce4a5efdda Update playwright snapshot names
CI Pipeline / test (20) (pull_request) Successful in 1m52s
CI Pipeline / test (18) (pull_request) Successful in 2m5s
CI Pipeline / e2e (chromium) (pull_request) Failing after 3m0s
CI Pipeline / visual-regression (pull_request) Has been cancelled
CI Pipeline / performance (pull_request) Has been cancelled
CI Pipeline / storybook (pull_request) Has been cancelled
CI Pipeline / lint (pull_request) Has been cancelled
CI Pipeline / build (pull_request) Has been cancelled
CI Pipeline / e2e (webkit) (pull_request) Has been cancelled
CI Pipeline / e2e (firefox) (pull_request) Has been cancelled
2025-09-02 22:43:44 -06:00
adilallo 07faeb2460 Seed screenshots for E2E tests
CI Pipeline / test (20) (pull_request) Successful in 1m46s
CI Pipeline / test (18) (pull_request) Successful in 2m2s
CI Pipeline / e2e (chromium) (pull_request) Failing after 4m38s
CI Pipeline / e2e (firefox) (pull_request) Failing after 4m24s
CI Pipeline / performance (pull_request) Has been cancelled
CI Pipeline / storybook (pull_request) Has been cancelled
CI Pipeline / lint (pull_request) Has been cancelled
CI Pipeline / build (pull_request) Has been cancelled
CI Pipeline / e2e (webkit) (pull_request) Has been cancelled
CI Pipeline / visual-regression (pull_request) Has been cancelled
2025-09-02 22:04:22 -06:00
adilallo c2de9e4788 Seed snapshots on branch
CI Pipeline / test (20) (pull_request) Successful in 1m51s
CI Pipeline / test (18) (pull_request) Successful in 2m9s
CI Pipeline / e2e (chromium) (pull_request) Failing after 3m10s
CI Pipeline / e2e (firefox) (pull_request) Failing after 3m29s
CI Pipeline / performance (pull_request) Has been cancelled
CI Pipeline / visual-regression (pull_request) Has been cancelled
CI Pipeline / storybook (pull_request) Has been cancelled
CI Pipeline / lint (pull_request) Has been cancelled
CI Pipeline / build (pull_request) Has been cancelled
CI Pipeline / e2e (webkit) (pull_request) Has been cancelled
2025-09-02 21:57:58 -06:00
47 changed files with 447 additions and 103 deletions
+2
View File
@@ -76,6 +76,8 @@ jobs:
curl -fsS "http://$HOST:$PORT" >/dev/null curl -fsS "http://$HOST:$PORT" >/dev/null
echo "✅ App is responding at http://$HOST:$PORT" echo "✅ App is responding at http://$HOST:$PORT"
# Run tests # Run tests
echo "🧪 Running E2E tests for ${{ matrix.browser }}..." echo "🧪 Running E2E tests for ${{ matrix.browser }}..."
BASE_URL="http://$HOST:$PORT" npx playwright test --project=${{ matrix.browser }} --reporter=list BASE_URL="http://$HOST:$PORT" npx playwright test --project=${{ matrix.browser }} --reporter=list
+5
View File
@@ -19,6 +19,11 @@
# Visual regression snapshots (allow these) # Visual regression snapshots (allow these)
!tests/e2e/visual-regression.spec.ts-snapshots/ !tests/e2e/visual-regression.spec.ts-snapshots/
!tests/e2e/visual-regression.spec.ts-snapshots/*.png !tests/e2e/visual-regression.spec.ts-snapshots/*.png
# Footer and header responsive snapshots (allow these)
!tests/e2e/footer.responsive.spec.js-snapshots/
!tests/e2e/footer.responsive.spec.js-snapshots/*.png
!tests/e2e/header.responsive.spec.js-snapshots/
!tests/e2e/header.responsive.spec.js-snapshots/*.png
# Ignore other image files # Ignore other image files
*.png *.png
*.jpg *.jpg
+1 -2
View File
@@ -35,8 +35,7 @@ export default defineConfig({
}, },
}), }),
// OS-agnostic snapshot path template (removes platform-specific suffixes) // OS-agnostic snapshot path template (removes platform-specific suffixes)
snapshotPathTemplate: snapshotPathTemplate: "{testDir}/{testFileName}-snapshots/{arg}.png",
"{testDir}/{testFileName}-snapshots/{arg}-{projectName}.png",
projects: [ projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } }, { name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } }, { name: "firefox", use: { ...devices["Desktop Firefox"] } },
+187 -60
View File
@@ -1,73 +1,114 @@
import { test, expect } from "@playwright/test"; import { test, expect } from "@playwright/test";
const breakpoints = [ const breakpoints = [
{ name: "xs", width: 320, height: 568 }, { name: "xs", width: 320, height: 700 },
{ name: "sm", width: 640, height: 720 }, { name: "sm", width: 360, height: 700 },
{ name: "md", width: 768, height: 1024 }, { name: "md", width: 480, height: 700 },
{ name: "lg", width: 1024, height: 768 }, { name: "lg", width: 640, height: 700 },
{ name: "xl", width: 1280, height: 800 }, { name: "xl", width: 768, height: 700 },
{ name: "2xl", width: 1536, height: 864 }, { name: "2xl", width: 1024, height: 700 },
{ name: "3xl", width: 1920, height: 1080 }, { name: "3xl", width: 1280, height: 700 },
{ name: "4xl", width: 2560, height: 1440 }, { name: "4xl", width: 1440, height: 700 },
{ name: "full", width: 3840, height: 2160 }, { name: "full", width: 1920, height: 700 },
]; ];
test.describe("Footer responsive behavior", () => { for (const bp of breakpoints) {
for (const bp of breakpoints) { test.describe(`Footer responsive behavior at ${bp.name} breakpoint`, () => {
test(`footer content visibility at ${bp.name}`, async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: bp.width, height: bp.height }); await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/"); await page.goto("/");
});
// Scroll to footer test(`footer layout at ${bp.name}`, async ({ page }) => {
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(500);
// Test that footer is visible
const footer = page.getByRole("contentinfo"); const footer = page.getByRole("contentinfo");
await expect(footer).toBeVisible(); await expect(footer).toBeVisible();
// Test navigation links // Check that footer content is visible
const footerContent = page.getByRole("contentinfo");
await expect(footerContent).toBeVisible();
});
test(`footer navigation items visibility at ${bp.name}`, async ({
page,
}) => {
// All breakpoints should have navigation items
await expect( await expect(
page.getByRole("contentinfo").getByRole("link", { name: /use cases/i }) page.getByRole("link", { name: /use cases/i })
).toBeVisible(); ).toBeVisible();
// Look for the "Learn" link specifically in the footer (not in other components)
await expect( await expect(
page.getByRole("contentinfo").getByRole("link", { name: /learn/i }) page.getByRole("contentinfo").getByRole("link", { name: /learn/i })
).toBeVisible(); ).toBeVisible();
await expect( await expect(page.getByRole("link", { name: /about/i })).toBeVisible();
page.getByRole("contentinfo").getByRole("link", { name: /about/i }) });
).toBeVisible();
// Test legal links test(`footer legal links visibility at ${bp.name}`, async ({ page }) => {
// All breakpoints should have legal links
await expect( await expect(
page page.getByRole("link", { name: /privacy policy/i })
.getByRole("contentinfo")
.getByRole("link", { name: /privacy policy/i })
).toBeVisible(); ).toBeVisible();
await expect( await expect(
page page.getByRole("link", { name: /terms of service/i })
.getByRole("contentinfo")
.getByRole("link", { name: /terms of service/i })
).toBeVisible(); ).toBeVisible();
await expect( await expect(
page page.getByRole("link", { name: /cookies settings/i })
.getByRole("contentinfo")
.getByRole("link", { name: /cookies settings/i })
).toBeVisible();
// Test social links
await expect(
page
.getByRole("contentinfo")
.getByRole("link", { name: /follow us on bluesky/i })
).toBeVisible();
await expect(
page
.getByRole("contentinfo")
.getByRole("link", { name: /follow us on gitlab/i })
).toBeVisible(); ).toBeVisible();
}); });
test(`footer layout consistency at ${bp.name}`, async ({ page }) => { test(`footer social links visibility at ${bp.name}`, async ({ page }) => {
// All breakpoints should have social links
await expect(
page.getByRole("link", { name: /follow us on bluesky/i })
).toBeVisible();
await expect(
page.getByRole("link", { name: /follow us on gitlab/i })
).toBeVisible();
});
test.skip(`footer logo visibility at ${bp.name}`, async ({ page }) => {
// TODO: Fix logo visibility test - currently finds multiple logo variants
// Logo should be visible at all breakpoints
// Look for the logo specifically in the footer
const logo = page.getByRole("contentinfo").getByText("CommunityRule");
await expect(logo).toBeVisible();
});
// Breakpoint-specific tests
if (bp.name === "xs") {
test("xs breakpoint specific behavior", async ({ page }) => {
// At xs, footer should stack vertically
const footer = page.getByRole("contentinfo");
await expect(footer).toBeVisible();
// Check that content is properly stacked
const footerContent = page.getByRole("contentinfo").locator("> div");
await expect(footerContent).toBeVisible();
});
}
if (bp.name === "md") {
test("md breakpoint specific behavior", async ({ page }) => {
// At md, footer should have proper spacing
const footer = page.getByRole("contentinfo");
await expect(footer).toBeVisible();
});
}
if (bp.name === "xl") {
test("xl breakpoint specific behavior", async ({ page }) => {
// At xl, footer should have full layout
const footer = page.getByRole("contentinfo");
await expect(footer).toBeVisible();
});
}
});
}
// Visual regression tests
test.describe("Footer visual regression", () => {
test("footer visual consistency across breakpoints", async ({ page }) => {
// Test visual consistency at all breakpoints
for (const bp of breakpoints) {
await page.setViewportSize({ width: bp.width, height: bp.height }); await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/"); await page.goto("/");
@@ -75,20 +116,24 @@ test.describe("Footer responsive behavior", () => {
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight)); await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(500); await page.waitForTimeout(500);
// Test that footer has proper structure // Take a screenshot for visual regression testing
const footer = page.getByRole("contentinfo"); // Note: Playwright automatically appends the browser name (e.g., -chromium, -firefox)
await expect(footer).toBeVisible(); await expect(page.getByRole("contentinfo")).toHaveScreenshot(
`footer-${bp.name}.png`
// Test that footer contains expected sections );
// Note: Logo visibility can vary by breakpoint due to responsive design
// We'll skip this test to avoid flakiness
// await expect(footer.getByText("CommunityRule")).toBeVisible();
});
} }
});
test.describe("Footer interaction states", () => { test("footer hover states visual consistency", async ({ page }) => {
test("hover states work correctly", async ({ page }) => { // Test hover states at key breakpoints
await page.setViewportSize({ width: 1280, height: 800 }); const keyBreakpoints = [
{ name: "xs", width: 320, height: 700 },
{ name: "md", width: 768, height: 700 },
{ name: "xl", width: 1280, height: 700 },
];
for (const bp of keyBreakpoints) {
await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/"); await page.goto("/");
// Scroll to footer // Scroll to footer
@@ -101,6 +146,9 @@ test.describe("Footer responsive behavior", () => {
.getByRole("link", { name: /use cases/i }); .getByRole("link", { name: /use cases/i });
await useCasesLink.hover(); await useCasesLink.hover();
await page.waitForTimeout(200); await page.waitForTimeout(200);
await expect(page.getByRole("contentinfo")).toHaveScreenshot(
`footer-${bp.name}-hover-nav.png`
);
// Test hover on social links // Test hover on social links
const blueskyLink = page.getByRole("contentinfo").getByRole("link", { const blueskyLink = page.getByRole("contentinfo").getByRole("link", {
@@ -108,10 +156,22 @@ test.describe("Footer responsive behavior", () => {
}); });
await blueskyLink.hover(); await blueskyLink.hover();
await page.waitForTimeout(200); await page.waitForTimeout(200);
await expect(page.getByRole("contentinfo")).toHaveScreenshot(
`footer-${bp.name}-hover-social.png`
);
}
}); });
test("focus states work correctly", async ({ page }) => { test("footer focus states visual consistency", async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 }); // Test focus states at key breakpoints
const keyBreakpoints = [
{ name: "xs", width: 320, height: 700 },
{ name: "md", width: 768, height: 700 },
{ name: "xl", width: 1280, height: 700 },
];
for (const bp of keyBreakpoints) {
await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/"); await page.goto("/");
// Scroll to footer // Scroll to footer
@@ -124,6 +184,9 @@ test.describe("Footer responsive behavior", () => {
.getByRole("link", { name: /use cases/i }); .getByRole("link", { name: /use cases/i });
await useCasesLink.focus(); await useCasesLink.focus();
await page.waitForTimeout(200); await page.waitForTimeout(200);
await expect(page.getByRole("contentinfo")).toHaveScreenshot(
`footer-${bp.name}-focus-nav.png`
);
// Test focus on social links // Test focus on social links
const blueskyLink = page.getByRole("contentinfo").getByRole("link", { const blueskyLink = page.getByRole("contentinfo").getByRole("link", {
@@ -131,6 +194,70 @@ test.describe("Footer responsive behavior", () => {
}); });
await blueskyLink.focus(); await blueskyLink.focus();
await page.waitForTimeout(200); await page.waitForTimeout(200);
}); await expect(page.getByRole("contentinfo")).toHaveScreenshot(
`footer-${bp.name}-focus-social.png`
);
}
});
});
// Additional responsive behavior tests
test.describe("Footer responsive behavior", () => {
test("footer maintains proper layout across breakpoints", async ({
page,
}) => {
// Test that footer doesn't break at edge cases
const edgeCases = [
{ width: 320, height: 700 }, // Very small
{ width: 1920, height: 700 }, // Very large
];
for (const viewport of edgeCases) {
await page.setViewportSize(viewport);
await page.goto("/");
// Scroll to footer
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
const footer = page.getByRole("contentinfo");
await expect(footer).toBeVisible();
}
});
test("footer elements are properly accessible across breakpoints", async ({
page,
}) => {
// Test accessibility at different breakpoints
for (const bp of breakpoints) {
await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/");
// Scroll to footer
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
// Check that all interactive elements are accessible
const interactiveElements = [
page.getByRole("contentinfo").getByRole("link", { name: /use cases/i }),
page.getByRole("contentinfo").getByRole("link", { name: /learn/i }),
page.getByRole("contentinfo").getByRole("link", { name: /about/i }),
page
.getByRole("contentinfo")
.getByRole("link", { name: /privacy policy/i }),
page
.getByRole("contentinfo")
.getByRole("link", { name: /terms of service/i }),
page
.getByRole("contentinfo")
.getByRole("link", { name: /follow us on bluesky/i }),
page
.getByRole("contentinfo")
.getByRole("link", { name: /follow us on gitlab/i }),
];
for (const element of interactiveElements) {
await expect(element).toBeVisible();
await expect(element).toBeEnabled();
}
}
}); });
}); });
Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+250 -39
View File
@@ -1,81 +1,222 @@
import { test, expect } from "@playwright/test"; import { test, expect } from "@playwright/test";
const breakpoints = [ const breakpoints = [
{ name: "xs", width: 320, height: 568 }, { name: "xs", width: 320, height: 700 },
{ name: "sm", width: 640, height: 720 }, { name: "sm", width: 360, height: 700 },
{ name: "md", width: 768, height: 1024 }, { name: "md", width: 480, height: 700 },
{ name: "lg", width: 1024, height: 768 }, { name: "lg", width: 640, height: 700 },
{ name: "xl", width: 1280, height: 800 }, { name: "xl", width: 768, height: 700 },
{ name: "2xl", width: 1536, height: 864 }, { name: "2xl", width: 1024, height: 700 },
{ name: "3xl", width: 1920, height: 1080 }, { name: "3xl", width: 1280, height: 700 },
{ name: "4xl", width: 2560, height: 1440 }, { name: "4xl", width: 1440, height: 700 },
{ name: "full", width: 3840, height: 2160 }, { name: "full", width: 1920, height: 700 },
]; ];
test.describe("Header responsive behavior", () => { for (const bp of breakpoints) {
for (const bp of breakpoints) { test.describe(`Header responsive behavior at ${bp.name} breakpoint`, () => {
test(`navigation items visibility at ${bp.name}`, async ({ page }) => { test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: bp.width, height: bp.height }); await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/"); await page.goto("/");
});
test(`header layout at ${bp.name}`, async ({ page }) => {
const nav = page.getByRole("navigation", { name: /main navigation/i });
await expect(nav).toBeVisible();
// Check that header banner is visible
const header = page.getByRole("banner", {
name: /home page navigation header/i,
});
await expect(header).toBeVisible();
});
test(`navigation items visibility at ${bp.name}`, async ({ page }) => {
// All breakpoints should have navigation items // All breakpoints should have navigation items
await expect( await expect(
page.getByRole("menuitem", { name: /use cases/i }) page.getByRole("menuitem", { name: /use cases/i }),
).toBeVisible(); ).toBeVisible();
await expect( await expect(
page.getByRole("menuitem", { name: /learn/i }) page.getByRole("menuitem", { name: /learn/i }),
).toBeVisible(); ).toBeVisible();
await expect( await expect(
page.getByRole("menuitem", { name: /about/i }) page.getByRole("menuitem", { name: /about/i }),
).toBeVisible(); ).toBeVisible();
}); });
test(`login and create rule button visibility at ${bp.name}`, async ({ test(`authentication elements visibility at ${bp.name}`, async ({
page, page,
}) => { }) => {
await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/");
// All breakpoints should have login button // All breakpoints should have login button
await expect( await expect(
page.getByRole("menuitem", { name: /log in to your account/i }) page.getByRole("menuitem", { name: /log in to your account/i }),
).toBeVisible(); ).toBeVisible();
// All breakpoints should have create rule button // All breakpoints should have create rule button
await expect( await expect(
page.getByRole("button", { page.getByRole("button", {
name: /create a new rule with avatar decoration/i, name: /create a new rule with avatar decoration/i,
}) }),
).toBeVisible(); ).toBeVisible();
}); });
test(`header layout consistency at ${bp.name}`, async ({ page }) => { test.skip(`logo visibility at ${bp.name}`, async ({ page }) => {
await page.setViewportSize({ width: bp.width, height: bp.height }); // TODO: Fix logo visibility test - currently all logos are hidden at xs breakpoint
await page.goto("/"); // Logo should be visible at all breakpoints
// Look for any visible logo text in the header navigation
const logos = page
.getByRole("navigation", { name: /main navigation/i })
.getByText("CommunityRule");
const logoCount = await logos.count();
// Test that header is visible and has proper structure // At least one logo should be visible
const header = page.locator("header").first(); expect(logoCount).toBeGreaterThan(0);
await expect(header).toBeVisible();
// Test that header contains navigation // Check that at least one logo is visible (not all are hidden)
await expect(header.getByRole("navigation")).toBeVisible(); let hasVisibleLogo = false;
for (let i = 0; i < logoCount; i++) {
const logo = logos.nth(i);
if (await logo.isVisible()) {
hasVisibleLogo = true;
break;
}
}
expect(hasVisibleLogo).toBe(true);
});
// Test that header contains logo/brand // Breakpoint-specific tests
// Note: Logo visibility can vary by breakpoint due to responsive design if (bp.name === "xs") {
// We'll skip this test to avoid flakiness test("xs breakpoint specific behavior", async ({ page }) => {
// await expect(header.getByText("CommunityRule")).toBeVisible(); // At xs, navigation items should be in the right section
// (removed data-testid dependency since it doesn't exist)
// Navigation items should be in the auth section at xs
const useCasesLink = page.getByRole("menuitem", { name: /use cases/i });
await expect(useCasesLink).toBeVisible();
// Login button should be in the auth section
const loginButton = page.getByRole("menuitem", {
name: /log in to your account/i,
});
await expect(loginButton).toBeVisible();
// Create rule button should be visible
const createRuleButton = page.getByRole("button", {
name: /create a new rule with avatar decoration/i,
});
await expect(createRuleButton).toBeVisible();
}); });
} }
test.describe("Header interaction states", () => { if (bp.name === "sm") {
test("hover states work correctly", async ({ page }) => { test("sm breakpoint specific behavior", async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 }); // At sm, navigation items should be visible
const useCasesLink = page.getByRole("menuitem", { name: /use cases/i });
await expect(useCasesLink).toBeVisible();
// Create rule button should be visible
const createRuleButton = page.getByRole("button", {
name: /create a new rule with avatar decoration/i,
});
await expect(createRuleButton).toBeVisible();
});
}
if (bp.name === "md") {
test("md breakpoint specific behavior", async ({ page }) => {
// At md, navigation items should be visible
const useCasesLink = page.getByRole("menuitem", { name: /use cases/i });
await expect(useCasesLink).toBeVisible();
// Login and create rule buttons should be visible
const loginButton = page.getByRole("menuitem", {
name: /log in to your account/i,
});
await expect(loginButton).toBeVisible();
const createRuleButton = page.getByRole("button", {
name: /create a new rule with avatar decoration/i,
});
await expect(createRuleButton).toBeVisible();
});
}
if (bp.name === "lg") {
test("lg breakpoint specific behavior", async ({ page }) => {
// At lg, navigation items should be visible
const useCasesLink = page.getByRole("menuitem", { name: /use cases/i });
await expect(useCasesLink).toBeVisible();
// Login and create rule buttons should be visible
const loginButton = page.getByRole("menuitem", {
name: /log in to your account/i,
});
await expect(loginButton).toBeVisible();
const createRuleButton = page.getByRole("button", {
name: /create a new rule with avatar decoration/i,
});
await expect(createRuleButton).toBeVisible();
});
}
if (bp.name === "xl") {
test("xl breakpoint specific behavior", async ({ page }) => {
// At xl, navigation items should be visible
const useCasesLink = page.getByRole("menuitem", { name: /use cases/i });
await expect(useCasesLink).toBeVisible();
// Login and create rule buttons should be visible
const loginButton = page.getByRole("menuitem", {
name: /log in to your account/i,
});
await expect(loginButton).toBeVisible();
const createRuleButton = page.getByRole("button", {
name: /create a new rule with avatar decoration/i,
});
await expect(createRuleButton).toBeVisible();
});
}
});
}
// Visual regression tests
test.describe("Header visual regression", () => {
test("header visual consistency across breakpoints", async ({ page }) => {
// Test visual consistency at all breakpoints
for (const bp of breakpoints) {
await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/");
// Wait for layout to stabilize
await page.waitForTimeout(500);
// Take a screenshot for visual regression testing
await expect(page.locator("header").first()).toHaveScreenshot(
`header-${bp.name}.png`,
);
}
});
test("header hover states visual consistency", async ({ page }) => {
// Test hover states at key breakpoints
const keyBreakpoints = [
{ name: "xs", width: 320, height: 700 },
{ name: "md", width: 768, height: 700 },
{ name: "xl", width: 1280, height: 700 },
];
for (const bp of keyBreakpoints) {
await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/"); await page.goto("/");
// Test hover on navigation items // Test hover on navigation items
const useCasesLink = page.getByRole("menuitem", { name: /use cases/i }); const useCasesLink = page.getByRole("menuitem", { name: /use cases/i });
await useCasesLink.hover(); await useCasesLink.hover();
await page.waitForTimeout(200); await page.waitForTimeout(200);
await expect(page.locator("header").first()).toHaveScreenshot(
`header-${bp.name}-hover-nav.png`,
);
// Test hover on create rule button // Test hover on create rule button
const createRuleButton = page.getByRole("button", { const createRuleButton = page.getByRole("button", {
@@ -83,16 +224,31 @@ test.describe("Header responsive behavior", () => {
}); });
await createRuleButton.hover(); await createRuleButton.hover();
await page.waitForTimeout(200); await page.waitForTimeout(200);
await expect(page.locator("header").first()).toHaveScreenshot(
`header-${bp.name}-hover-button.png`,
);
}
}); });
test("focus states work correctly", async ({ page }) => { test("header focus states visual consistency", async ({ page }) => {
await page.setViewportSize({ width: 1280, height: 800 }); // Test focus states at key breakpoints
const keyBreakpoints = [
{ name: "xs", width: 320, height: 700 },
{ name: "md", width: 768, height: 700 },
{ name: "xl", width: 1280, height: 700 },
];
for (const bp of keyBreakpoints) {
await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/"); await page.goto("/");
// Test focus on navigation items // Test focus on navigation items
const useCasesLink = page.getByRole("menuitem", { name: /use cases/i }); const useCasesLink = page.getByRole("menuitem", { name: /use cases/i });
await useCasesLink.focus(); await useCasesLink.focus();
await page.waitForTimeout(200); await page.waitForTimeout(200);
await expect(page.locator("header").first()).toHaveScreenshot(
`header-${bp.name}-focus-nav.png`,
);
// Test focus on create rule button // Test focus on create rule button
const createRuleButton = page.getByRole("button", { const createRuleButton = page.getByRole("button", {
@@ -100,6 +256,61 @@ test.describe("Header responsive behavior", () => {
}); });
await createRuleButton.focus(); await createRuleButton.focus();
await page.waitForTimeout(200); await page.waitForTimeout(200);
}); await expect(page.locator("header").first()).toHaveScreenshot(
`header-${bp.name}-focus-button.png`,
);
}
});
});
// Additional responsive behavior tests
test.describe("Header responsive behavior", () => {
test("header maintains proper layout across breakpoints", async ({
page,
}) => {
// Test that header doesn't break at edge cases
const edgeCases = [
{ width: 320, height: 700 }, // Very small
{ width: 1920, height: 700 }, // Very large
];
for (const viewport of edgeCases) {
await page.setViewportSize(viewport);
await page.goto("/");
const header = page.getByRole("banner", {
name: /home page navigation header/i,
});
await expect(header).toBeVisible();
const nav = page.getByRole("navigation", { name: /main navigation/i });
await expect(nav).toBeVisible();
}
});
test("header elements are properly accessible across breakpoints", async ({
page,
}) => {
// Test accessibility at different breakpoints
for (const bp of breakpoints) {
await page.setViewportSize({ width: bp.width, height: bp.height });
await page.goto("/");
// Check that all interactive elements are accessible
const interactiveElements = [
page.getByRole("menuitem", { name: /use cases/i }),
page.getByRole("menuitem", { name: /learn/i }),
page.getByRole("menuitem", { name: /about/i }),
page.getByRole("menuitem", { name: /log in to your account/i }),
page.getByRole("button", {
name: /create a new rule with avatar decoration/i,
}),
];
for (const element of interactiveElements) {
await expect(element).toBeVisible();
await expect(element).toBeEnabled();
}
}
}); });
}); });
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB