Visual regression tests refined
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const breakpoints = [
|
||||
{ name: "xs", width: 320, height: 700 },
|
||||
{ name: "sm", width: 360, height: 700 },
|
||||
{ name: "md", width: 480, height: 700 },
|
||||
{ name: "lg", width: 640, height: 700 },
|
||||
{ name: "xl", width: 768, height: 700 },
|
||||
{ name: "2xl", width: 1024, height: 700 },
|
||||
{ name: "3xl", width: 1280, height: 700 },
|
||||
{ name: "4xl", width: 1440, height: 700 },
|
||||
{ name: "full", width: 1920, height: 700 },
|
||||
];
|
||||
|
||||
for (const bp of breakpoints) {
|
||||
test.describe(`Footer responsive behavior at ${bp.name} breakpoint`, () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.setViewportSize({ width: bp.width, height: bp.height });
|
||||
await page.goto("/");
|
||||
});
|
||||
|
||||
test(`footer layout at ${bp.name}`, async ({ page }) => {
|
||||
const footer = page.getByRole("contentinfo");
|
||||
await expect(footer).toBeVisible();
|
||||
|
||||
// Check that footer content is visible
|
||||
const footerContent = page.locator("footer");
|
||||
await expect(footerContent).toBeVisible();
|
||||
});
|
||||
|
||||
test(`footer navigation items visibility at ${bp.name}`, async ({
|
||||
page,
|
||||
}) => {
|
||||
// All breakpoints should have navigation items
|
||||
await expect(
|
||||
page.getByRole("link", { name: /use cases/i })
|
||||
).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: /learn/i })).toBeVisible();
|
||||
await expect(page.getByRole("link", { name: /about/i })).toBeVisible();
|
||||
});
|
||||
|
||||
test(`footer legal links visibility at ${bp.name}`, async ({ page }) => {
|
||||
// All breakpoints should have legal links
|
||||
await expect(
|
||||
page.getByRole("link", { name: /privacy policy/i })
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
page.getByRole("link", { name: /terms of service/i })
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
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(`footer logo visibility at ${bp.name}`, async ({ page }) => {
|
||||
// Logo should be visible at all breakpoints
|
||||
const logo = page.locator('[data-testid="logo-wrapper"]').first();
|
||||
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.locator("footer");
|
||||
await expect(footer).toBeVisible();
|
||||
|
||||
// Check that content is properly stacked
|
||||
const footerContent = page.locator("footer > 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.locator("footer");
|
||||
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.locator("footer");
|
||||
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.goto("/");
|
||||
|
||||
// Scroll to footer
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Take a screenshot for visual regression testing
|
||||
await expect(page.locator("footer").first()).toHaveScreenshot(
|
||||
`footer-${bp.name}.png`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("footer 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("/");
|
||||
|
||||
// Scroll to footer
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Test hover on navigation items
|
||||
const useCasesLink = page.getByRole("link", { name: /use cases/i });
|
||||
await useCasesLink.hover();
|
||||
await page.waitForTimeout(200);
|
||||
await expect(page.locator("footer").first()).toHaveScreenshot(
|
||||
`footer-${bp.name}-hover-nav.png`
|
||||
);
|
||||
|
||||
// Test hover on social links
|
||||
const blueskyLink = page.getByRole("link", {
|
||||
name: /follow us on bluesky/i,
|
||||
});
|
||||
await blueskyLink.hover();
|
||||
await page.waitForTimeout(200);
|
||||
await expect(page.locator("footer").first()).toHaveScreenshot(
|
||||
`footer-${bp.name}-hover-social.png`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("footer focus states visual consistency", async ({ page }) => {
|
||||
// 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("/");
|
||||
|
||||
// Scroll to footer
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Test focus on navigation items
|
||||
const useCasesLink = page.getByRole("link", { name: /use cases/i });
|
||||
await useCasesLink.focus();
|
||||
await page.waitForTimeout(200);
|
||||
await expect(page.locator("footer").first()).toHaveScreenshot(
|
||||
`footer-${bp.name}-focus-nav.png`
|
||||
);
|
||||
|
||||
// Test focus on social links
|
||||
const blueskyLink = page.getByRole("link", {
|
||||
name: /follow us on bluesky/i,
|
||||
});
|
||||
await blueskyLink.focus();
|
||||
await page.waitForTimeout(200);
|
||||
await expect(page.locator("footer").first()).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("link", { name: /use cases/i }),
|
||||
page.getByRole("link", { name: /learn/i }),
|
||||
page.getByRole("link", { name: /about/i }),
|
||||
page.getByRole("link", { name: /privacy policy/i }),
|
||||
page.getByRole("link", { name: /terms of service/i }),
|
||||
page.getByRole("link", { name: /follow us on bluesky/i }),
|
||||
page.getByRole("link", { name: /follow us on gitlab/i }),
|
||||
];
|
||||
|
||||
for (const element of interactiveElements) {
|
||||
await expect(element).toBeVisible();
|
||||
await expect(element).toBeEnabled();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,15 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
const breakpoints = [
|
||||
{ name: "xs", width: 360, height: 700 },
|
||||
{ name: "sm", width: 640, height: 700 },
|
||||
{ name: "md", width: 768, height: 700 },
|
||||
{ name: "lg", width: 1024, height: 700 },
|
||||
{ name: "xl", width: 1280, height: 700 },
|
||||
{ name: "xs", width: 320, height: 700 },
|
||||
{ name: "sm", width: 360, height: 700 },
|
||||
{ name: "md", width: 480, height: 700 },
|
||||
{ name: "lg", width: 640, height: 700 },
|
||||
{ name: "xl", width: 768, height: 700 },
|
||||
{ name: "2xl", width: 1024, height: 700 },
|
||||
{ name: "3xl", width: 1280, height: 700 },
|
||||
{ name: "4xl", width: 1440, height: 700 },
|
||||
{ name: "full", width: 1920, height: 700 },
|
||||
];
|
||||
|
||||
for (const bp of breakpoints) {
|
||||
@@ -132,6 +136,89 @@ for (const bp of breakpoints) {
|
||||
});
|
||||
}
|
||||
|
||||
// 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("/");
|
||||
|
||||
// Test hover on navigation items
|
||||
const useCasesLink = page.getByRole("link", { name: /use cases/i });
|
||||
await useCasesLink.hover();
|
||||
await page.waitForTimeout(200);
|
||||
await expect(page.locator("header").first()).toHaveScreenshot(
|
||||
`header-${bp.name}-hover-nav.png`
|
||||
);
|
||||
|
||||
// Test hover on create rule button
|
||||
const createRuleButton = page.getByRole("button", {
|
||||
name: /create a new rule with avatar decoration/i,
|
||||
});
|
||||
await createRuleButton.hover();
|
||||
await page.waitForTimeout(200);
|
||||
await expect(page.locator("header").first()).toHaveScreenshot(
|
||||
`header-${bp.name}-hover-button.png`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("header focus states visual consistency", async ({ page }) => {
|
||||
// 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("/");
|
||||
|
||||
// Test focus on navigation items
|
||||
const useCasesLink = page.getByRole("link", { name: /use cases/i });
|
||||
await useCasesLink.focus();
|
||||
await page.waitForTimeout(200);
|
||||
await expect(page.locator("header").first()).toHaveScreenshot(
|
||||
`header-${bp.name}-focus-nav.png`
|
||||
);
|
||||
|
||||
// Test focus on create rule button
|
||||
const createRuleButton = page.getByRole("button", {
|
||||
name: /create a new rule with avatar decoration/i,
|
||||
});
|
||||
await createRuleButton.focus();
|
||||
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 ({
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Visual Regression Testing Configuration
|
||||
*
|
||||
* This file defines the configuration for visual regression testing across
|
||||
* different breakpoints, components, and scenarios.
|
||||
*/
|
||||
|
||||
// Breakpoint definitions for responsive testing
|
||||
export const breakpoints = {
|
||||
// Mobile breakpoints
|
||||
xs: { width: 320, height: 700, name: "Extra Small" },
|
||||
sm: { width: 360, height: 700, name: "Small" },
|
||||
md: { width: 480, height: 700, name: "Medium" },
|
||||
|
||||
// Tablet breakpoints
|
||||
lg: { width: 640, height: 700, name: "Large" },
|
||||
xl: { width: 768, height: 700, name: "Extra Large" },
|
||||
|
||||
// Desktop breakpoints
|
||||
"2xl": { width: 1024, height: 700, name: "2XL" },
|
||||
"3xl": { width: 1280, height: 700, name: "3XL" },
|
||||
"4xl": { width: 1440, height: 700, name: "4XL" },
|
||||
full: { width: 1920, height: 700, name: "Full HD" },
|
||||
};
|
||||
|
||||
// Key breakpoints for focused testing
|
||||
export const keyBreakpoints = [
|
||||
breakpoints.xs, // Mobile
|
||||
breakpoints.md, // Tablet
|
||||
breakpoints.xl, // Desktop
|
||||
];
|
||||
|
||||
// Visual testing scenarios
|
||||
export const visualScenarios = {
|
||||
// Component states
|
||||
states: {
|
||||
default: "Default state",
|
||||
hover: "Hover state",
|
||||
focus: "Focus state",
|
||||
active: "Active/pressed state",
|
||||
disabled: "Disabled state",
|
||||
},
|
||||
|
||||
// Interactive states
|
||||
interactions: {
|
||||
hover: "Element hovered",
|
||||
focus: "Element focused",
|
||||
click: "Element clicked",
|
||||
loading: "Loading state",
|
||||
error: "Error state",
|
||||
},
|
||||
|
||||
// Content variations
|
||||
content: {
|
||||
short: "Short content",
|
||||
long: "Long content",
|
||||
empty: "Empty state",
|
||||
loading: "Loading content",
|
||||
error: "Error content",
|
||||
},
|
||||
|
||||
// Layout scenarios
|
||||
layout: {
|
||||
compact: "Compact layout",
|
||||
spacious: "Spacious layout",
|
||||
stacked: "Stacked layout",
|
||||
grid: "Grid layout",
|
||||
list: "List layout",
|
||||
},
|
||||
};
|
||||
|
||||
// Chromatic configuration
|
||||
export const chromaticConfig = {
|
||||
// Viewports for Chromatic screenshots
|
||||
viewports: Object.values(breakpoints).map((bp) => bp.width),
|
||||
|
||||
// Delay for layout stabilization
|
||||
delay: 200,
|
||||
|
||||
// Modes for different themes
|
||||
modes: {
|
||||
light: {},
|
||||
dark: {
|
||||
colorScheme: "dark",
|
||||
},
|
||||
},
|
||||
|
||||
// Storybook viewport configuration
|
||||
storybookViewports: Object.entries(breakpoints).reduce((acc, [key, bp]) => {
|
||||
acc[key] = {
|
||||
name: bp.name,
|
||||
styles: {
|
||||
width: `${bp.width}px`,
|
||||
height: `${bp.height}px`,
|
||||
},
|
||||
};
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
// Playwright visual testing configuration
|
||||
export const playwrightVisualConfig = {
|
||||
// Screenshot options
|
||||
screenshot: {
|
||||
fullPage: false,
|
||||
type: "png",
|
||||
quality: 90,
|
||||
},
|
||||
|
||||
// Visual comparison options
|
||||
visualComparison: {
|
||||
threshold: 0.1, // 10% difference threshold
|
||||
maxDiffPixels: 100,
|
||||
maxDiffPixelRatio: 0.1,
|
||||
},
|
||||
|
||||
// Test timeouts
|
||||
timeouts: {
|
||||
navigation: 30000,
|
||||
action: 5000,
|
||||
assertion: 10000,
|
||||
},
|
||||
};
|
||||
|
||||
// Component-specific visual testing configurations
|
||||
export const componentConfigs = {
|
||||
Header: {
|
||||
breakpoints: [breakpoints.xs, breakpoints.md, breakpoints.xl],
|
||||
states: ["default", "hover", "focus"],
|
||||
scenarios: ["navigation", "authentication", "responsive"],
|
||||
},
|
||||
|
||||
Footer: {
|
||||
breakpoints: [breakpoints.xs, breakpoints.md, breakpoints.xl],
|
||||
states: ["default", "hover", "focus"],
|
||||
scenarios: ["navigation", "social", "legal"],
|
||||
},
|
||||
|
||||
Button: {
|
||||
breakpoints: [breakpoints.sm, breakpoints.md, breakpoints.lg],
|
||||
states: ["default", "hover", "focus", "active", "disabled"],
|
||||
variants: ["default", "home"],
|
||||
sizes: ["xsmall", "small", "medium", "large", "xlarge"],
|
||||
},
|
||||
|
||||
Logo: {
|
||||
breakpoints: [breakpoints.xs, breakpoints.md, breakpoints.xl],
|
||||
states: ["default", "hover"],
|
||||
variants: ["with-text", "icon-only"],
|
||||
},
|
||||
|
||||
MenuBar: {
|
||||
breakpoints: [breakpoints.xs, breakpoints.md, breakpoints.xl],
|
||||
states: ["default", "hover", "focus"],
|
||||
scenarios: ["navigation", "dropdown"],
|
||||
},
|
||||
};
|
||||
|
||||
// Visual regression test patterns
|
||||
export const testPatterns = {
|
||||
// Basic component testing
|
||||
basic: {
|
||||
description: "Basic component rendering",
|
||||
steps: [
|
||||
"Navigate to component",
|
||||
"Wait for layout stabilization",
|
||||
"Take screenshot",
|
||||
],
|
||||
},
|
||||
|
||||
// Interactive state testing
|
||||
interactive: {
|
||||
description: "Interactive state testing",
|
||||
steps: [
|
||||
"Navigate to component",
|
||||
"Interact with element (hover/focus/click)",
|
||||
"Wait for state change",
|
||||
"Take screenshot",
|
||||
],
|
||||
},
|
||||
|
||||
// Responsive testing
|
||||
responsive: {
|
||||
description: "Responsive behavior testing",
|
||||
steps: [
|
||||
"Set viewport size",
|
||||
"Navigate to component",
|
||||
"Wait for layout stabilization",
|
||||
"Take screenshot",
|
||||
"Repeat for all breakpoints",
|
||||
],
|
||||
},
|
||||
|
||||
// Content variation testing
|
||||
contentVariation: {
|
||||
description: "Content variation testing",
|
||||
steps: [
|
||||
"Navigate to component with different content",
|
||||
"Wait for layout stabilization",
|
||||
"Take screenshot",
|
||||
"Compare with baseline",
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
// Export all configurations
|
||||
export default {
|
||||
breakpoints,
|
||||
keyBreakpoints,
|
||||
visualScenarios,
|
||||
chromaticConfig,
|
||||
playwrightVisualConfig,
|
||||
componentConfigs,
|
||||
testPatterns,
|
||||
};
|
||||
Reference in New Issue
Block a user