E2E Testing setup
This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Edge Cases and Error Scenarios', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('handles slow network conditions', async ({ page }) => {
|
||||
// Simulate slow network
|
||||
await page.route('**/*', route => {
|
||||
// Add 2 second delay to all requests
|
||||
setTimeout(() => route.continue(), 2000);
|
||||
});
|
||||
|
||||
// Reload page with slow network
|
||||
await page.reload();
|
||||
|
||||
// Page should still load eventually
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('handles offline mode gracefully', async ({ page }) => {
|
||||
// Set offline mode
|
||||
await page.setOffline(true);
|
||||
|
||||
// Reload page
|
||||
await page.reload();
|
||||
|
||||
// Should show some content even offline
|
||||
await expect(page.locator('body')).toBeVisible();
|
||||
|
||||
// Restore online mode
|
||||
await page.setOffline(false);
|
||||
});
|
||||
|
||||
test('handles rapid user interactions', async ({ page }) => {
|
||||
// Rapidly click buttons
|
||||
const buttons = page.locator('button');
|
||||
const buttonCount = await buttons.count();
|
||||
|
||||
for (let i = 0; i < Math.min(buttonCount, 5); i++) {
|
||||
await buttons.nth(i).click();
|
||||
await page.waitForTimeout(100); // Very short delay
|
||||
}
|
||||
|
||||
// Page should remain stable
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
});
|
||||
|
||||
test('handles rapid scrolling', async ({ page }) => {
|
||||
// Rapid scroll to bottom
|
||||
await page.evaluate(() => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
window.scrollTo(0, document.body.scrollHeight * (i / 10));
|
||||
}
|
||||
});
|
||||
|
||||
// Should end up at bottom
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
});
|
||||
|
||||
test('handles viewport size changes', async ({ page }) => {
|
||||
// Rapidly change viewport sizes
|
||||
const viewports = [
|
||||
{ width: 375, height: 667 },
|
||||
{ width: 768, height: 1024 },
|
||||
{ width: 1440, height: 900 },
|
||||
{ width: 1920, height: 1080 }
|
||||
];
|
||||
|
||||
for (const viewport of viewports) {
|
||||
await page.setViewportSize(viewport);
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Content should remain visible
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('handles browser back/forward navigation', async ({ page }) => {
|
||||
// Navigate to a section
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
|
||||
// Go back
|
||||
await page.goBack();
|
||||
|
||||
// Should be back at homepage
|
||||
await expect(page).toHaveURL('/');
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
|
||||
// Go forward
|
||||
await page.goForward();
|
||||
|
||||
// Should be back to the section
|
||||
await expect(page.locator('h2:has-text("How CommunityRule works")')).toBeVisible();
|
||||
});
|
||||
|
||||
test('handles page refresh during interactions', async ({ page }) => {
|
||||
// Start an interaction
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
|
||||
// Refresh page during interaction
|
||||
await page.reload();
|
||||
|
||||
// Should reload successfully
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
});
|
||||
|
||||
test('handles multiple browser tabs', async ({ page, context }) => {
|
||||
// Open multiple tabs
|
||||
const page1 = await context.newPage();
|
||||
const page2 = await context.newPage();
|
||||
|
||||
// Navigate all tabs to homepage
|
||||
await page1.goto('/');
|
||||
await page2.goto('/');
|
||||
|
||||
// Interact with each tab
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
await page1.locator('text=Consensus clusters').click();
|
||||
await page2.locator('button:has-text("Ask an organizer")').click();
|
||||
|
||||
// All tabs should work independently
|
||||
await expect(page.locator('h2:has-text("How CommunityRule works")')).toBeVisible();
|
||||
await expect(page1.locator('text=Consensus clusters')).toBeVisible();
|
||||
await expect(page2.locator('text=Still have questions?')).toBeVisible();
|
||||
|
||||
// Close extra tabs
|
||||
await page1.close();
|
||||
await page2.close();
|
||||
});
|
||||
|
||||
test('handles JavaScript errors gracefully', async ({ page }) => {
|
||||
// Inject a JavaScript error
|
||||
await page.evaluate(() => {
|
||||
// Create a temporary error handler
|
||||
const originalError = console.error;
|
||||
console.error = () => {}; // Suppress error logging
|
||||
|
||||
// Trigger a harmless error
|
||||
try {
|
||||
throw new Error('Test error');
|
||||
} catch (e) {
|
||||
// Error handled
|
||||
}
|
||||
|
||||
console.error = originalError;
|
||||
});
|
||||
|
||||
// Page should continue to function
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
});
|
||||
|
||||
test('handles missing images gracefully', async ({ page }) => {
|
||||
// Block image requests
|
||||
await page.route('**/*.{png,jpg,jpeg,svg,webp}', route => {
|
||||
route.abort();
|
||||
});
|
||||
|
||||
// Reload page
|
||||
await page.reload();
|
||||
|
||||
// Page should still function without images
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
});
|
||||
|
||||
test('handles CSS loading failures', async ({ page }) => {
|
||||
// Block CSS requests
|
||||
await page.route('**/*.css', route => {
|
||||
route.abort();
|
||||
});
|
||||
|
||||
// Reload page
|
||||
await page.reload();
|
||||
|
||||
// Page should still function without styles
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
});
|
||||
|
||||
test('handles font loading failures', async ({ page }) => {
|
||||
// Block font requests
|
||||
await page.route('**/*.{woff,woff2,ttf,otf}', route => {
|
||||
route.abort();
|
||||
});
|
||||
|
||||
// Reload page
|
||||
await page.reload();
|
||||
|
||||
// Page should still function with fallback fonts
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
});
|
||||
|
||||
test('handles memory pressure', async ({ page }) => {
|
||||
// Simulate memory pressure by creating many elements
|
||||
await page.evaluate(() => {
|
||||
// Create temporary elements to simulate memory usage
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = `Test element ${i}`;
|
||||
document.body.appendChild(div);
|
||||
}
|
||||
|
||||
// Clean up
|
||||
setTimeout(() => {
|
||||
const testElements = document.querySelectorAll('div[textContent*="Test element"]');
|
||||
testElements.forEach(el => el.remove());
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Page should remain functional
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
});
|
||||
|
||||
test('handles long content gracefully', async ({ page }) => {
|
||||
// Add a lot of content to test scrolling performance
|
||||
await page.evaluate(() => {
|
||||
const container = document.createElement('div');
|
||||
container.style.height = '10000px';
|
||||
container.style.background = 'linear-gradient(red, blue)';
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
||||
// Scroll through the content
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
|
||||
// Should handle long content without issues
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
});
|
||||
|
||||
test('handles focus management', async ({ page }) => {
|
||||
// Test focus trapping and management
|
||||
await page.keyboard.press('Tab');
|
||||
await expect(page.locator(':focus')).toBeVisible();
|
||||
|
||||
// Navigate through focusable elements
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await page.keyboard.press('Tab');
|
||||
await expect(page.locator(':focus')).toBeVisible();
|
||||
}
|
||||
|
||||
// Test Shift+Tab for reverse navigation
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
await expect(page.locator(':focus')).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('handles keyboard shortcuts', async ({ page }) => {
|
||||
// Test common keyboard shortcuts
|
||||
await page.keyboard.press('Home');
|
||||
await page.keyboard.press('End');
|
||||
await page.keyboard.press('PageUp');
|
||||
await page.keyboard.press('PageDown');
|
||||
|
||||
// Page should handle shortcuts gracefully
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
});
|
||||
|
||||
test('handles copy/paste operations', async ({ page }) => {
|
||||
// Test text selection and copy
|
||||
await page.locator('text=Collaborate').selectText();
|
||||
await page.keyboard.press('Control+c');
|
||||
|
||||
// Test paste (should work in input fields if any)
|
||||
const inputs = page.locator('input, textarea');
|
||||
if (await inputs.count() > 0) {
|
||||
await inputs.first().click();
|
||||
await page.keyboard.press('Control+v');
|
||||
}
|
||||
});
|
||||
|
||||
test('handles right-click context menu', async ({ page }) => {
|
||||
// Test right-click on various elements
|
||||
await page.locator('text=Collaborate').click({ button: 'right' });
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click({ button: 'right' });
|
||||
await page.locator('img[alt="Hero illustration"]').click({ button: 'right' });
|
||||
|
||||
// Should handle right-clicks gracefully
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
});
|
||||
|
||||
test('handles drag and drop operations', async ({ page }) => {
|
||||
// Test drag and drop (if applicable)
|
||||
const draggableElements = page.locator('[draggable="true"]');
|
||||
const dropZones = page.locator('[data-testid*="drop"], [class*="drop"]');
|
||||
|
||||
if (await draggableElements.count() > 0 && await dropZones.count() > 0) {
|
||||
await draggableElements.first().dragTo(dropZones.first());
|
||||
}
|
||||
|
||||
// Page should handle drag operations gracefully
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
});
|
||||
|
||||
test('handles print functionality', async ({ page }) => {
|
||||
// Test print functionality
|
||||
await page.evaluate(() => {
|
||||
// Mock print function
|
||||
window.print = () => {};
|
||||
});
|
||||
|
||||
// Trigger print
|
||||
await page.keyboard.press('Control+p');
|
||||
|
||||
// Should handle print gracefully
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
});
|
||||
|
||||
test('handles browser zoom', async ({ page }) => {
|
||||
// Test different zoom levels
|
||||
await page.evaluate(() => {
|
||||
document.body.style.zoom = '0.5';
|
||||
});
|
||||
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
|
||||
await page.evaluate(() => {
|
||||
document.body.style.zoom = '2.0';
|
||||
});
|
||||
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
|
||||
// Reset zoom
|
||||
await page.evaluate(() => {
|
||||
document.body.style.zoom = '1.0';
|
||||
});
|
||||
});
|
||||
|
||||
test('handles high contrast mode', async ({ page }) => {
|
||||
// Simulate high contrast mode
|
||||
await page.evaluate(() => {
|
||||
document.body.style.filter = 'contrast(200%)';
|
||||
});
|
||||
|
||||
// Content should remain readable
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
|
||||
// Reset contrast
|
||||
await page.evaluate(() => {
|
||||
document.body.style.filter = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
test('handles reduced motion preferences', async ({ page }) => {
|
||||
// Simulate reduced motion preference
|
||||
await page.evaluate(() => {
|
||||
document.documentElement.style.setProperty('--prefers-reduced-motion', 'reduce');
|
||||
});
|
||||
|
||||
// Page should respect reduced motion
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
});
|
||||
});
|
||||
+294
-86
@@ -1,95 +1,303 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { runA11y } from './axe';
|
||||
|
||||
test('home loads, nav works, basic a11y passes', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Check that the page loads
|
||||
await expect(page).toHaveTitle(/Community Rule/);
|
||||
|
||||
// Check for main navigation elements
|
||||
await expect(page.getByRole('banner')).toBeVisible();
|
||||
|
||||
// Check for main content
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
|
||||
// Check for footer
|
||||
await expect(page.getByRole('contentinfo')).toBeVisible();
|
||||
});
|
||||
test.describe('Homepage', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('keyboard navigation works', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Tab through the page
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
// Check that focus is visible
|
||||
await expect(page.locator(':focus')).toBeVisible();
|
||||
|
||||
// Continue tabbing to test navigation
|
||||
for (let i = 0; i < 5; i++) {
|
||||
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('h2').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 ctaButton = page.locator('button:has-text("Learn how CommunityRule works")');
|
||||
await expect(ctaButton).toBeVisible();
|
||||
await expect(ctaButton).toBeEnabled();
|
||||
|
||||
// Test button interaction
|
||||
await ctaButton.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"]');
|
||||
await expect(logos).toHaveCount(6);
|
||||
|
||||
// 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
|
||||
await expect(page.locator('text=1')).toBeVisible();
|
||||
await expect(page.locator('text=2')).toBeVisible();
|
||||
await expect(page.locator('text=3')).toBeVisible();
|
||||
|
||||
// Check CTA buttons
|
||||
await expect(page.locator('button:has-text("Create CommunityRule")')).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')).toBeVisible();
|
||||
await expect(page.locator('text=Elected Board')).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('h2: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
|
||||
await expect(page.locator('text=Decision-making support')).toBeVisible();
|
||||
await expect(page.locator('text=Values alignment exercises')).toBeVisible();
|
||||
await expect(page.locator('text=Membership guidance')).toBeVisible();
|
||||
await expect(page.locator('text=Conflict resolution tools')).toBeVisible();
|
||||
|
||||
// Check feature links
|
||||
const featureLinks = page.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"]')).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
|
||||
const askButton = page.locator('button:has-text("Ask an organizer")');
|
||||
await expect(askButton).toBeVisible();
|
||||
await expect(askButton).toBeEnabled();
|
||||
|
||||
// Test button interaction
|
||||
await askButton.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')).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
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
|
||||
// Check footer content
|
||||
await expect(page.locator('footer')).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('accessibility standards are met', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Run automated a11y checks
|
||||
await runA11y(page, {
|
||||
axeOptions: {
|
||||
runOnly: ['wcag2a', 'wcag2aa']
|
||||
}
|
||||
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 }) => {
|
||||
await runA11y(page, {
|
||||
rules: {
|
||||
'color-contrast': { enabled: true },
|
||||
'heading-order': { enabled: true },
|
||||
'landmark-one-main': { enabled: true },
|
||||
'page-has-heading-one': { enabled: true },
|
||||
'region': { enabled: true }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('scroll behavior and smooth scrolling', async ({ page }) => {
|
||||
// Test smooth scrolling to sections
|
||||
const ctaButton = page.locator('button:has-text("Learn how CommunityRule works")');
|
||||
await ctaButton.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');
|
||||
await expect(images).toHaveCount.greaterThan(0);
|
||||
|
||||
// Wait for images to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
// Check for any broken images
|
||||
const brokenImages = await page.evaluate(() => {
|
||||
const imgs = document.querySelectorAll('img');
|
||||
return Array.from(imgs).filter(img => !img.complete || img.naturalWidth === 0);
|
||||
});
|
||||
|
||||
expect(brokenImages.length).toBe(0);
|
||||
});
|
||||
|
||||
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
|
||||
await page.setOffline(true);
|
||||
await page.reload();
|
||||
|
||||
// Should handle offline state gracefully
|
||||
await expect(page.locator('body')).toBeVisible();
|
||||
|
||||
// Restore online mode
|
||||
await page.setOffline(false);
|
||||
});
|
||||
});
|
||||
|
||||
test('responsive design works', async ({ page }) => {
|
||||
// Test mobile viewport
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
await page.goto('/');
|
||||
|
||||
// Check that content is visible on mobile
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
|
||||
// Test tablet viewport
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
|
||||
// Test desktop viewport
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await expect(page.getByRole('main')).toBeVisible();
|
||||
});
|
||||
|
||||
test('images have alt text', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Get all images
|
||||
const images = page.locator('img');
|
||||
const imageCount = await images.count();
|
||||
|
||||
// Check that all images have alt attributes
|
||||
for (let i = 0; i < imageCount; i++) {
|
||||
const image = images.nth(i);
|
||||
const alt = await image.getAttribute('alt');
|
||||
expect(alt).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
test('links are accessible', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Get all links
|
||||
const links = page.locator('a');
|
||||
const linkCount = await links.count();
|
||||
|
||||
// Check that all links have either text content or aria-label
|
||||
for (let i = 0; i < linkCount; i++) {
|
||||
const link = links.nth(i);
|
||||
const text = await link.textContent();
|
||||
const ariaLabel = await link.getAttribute('aria-label');
|
||||
|
||||
// Link should have either text content or aria-label
|
||||
expect(text?.trim() || ariaLabel).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('User Journeys', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('complete user journey: learn about CommunityRule', async ({ page }) => {
|
||||
// 1. User lands on homepage
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
|
||||
// 2. User reads hero section
|
||||
await expect(page.locator('text=Help your community make important decisions')).toBeVisible();
|
||||
|
||||
// 3. User clicks CTA to learn more
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
|
||||
// 4. User scrolls to numbered cards section
|
||||
await expect(page.locator('h2:has-text("How CommunityRule works")')).toBeVisible();
|
||||
|
||||
// 5. User reads the process steps
|
||||
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();
|
||||
|
||||
// 6. User explores rule templates
|
||||
await page.locator('text=Consensus clusters').click();
|
||||
await page.locator('text=Consensus').click();
|
||||
await page.locator('text=Elected Board').click();
|
||||
await page.locator('text=Petition').click();
|
||||
|
||||
// 7. User checks out features
|
||||
await page.locator('text=Decision-making support').click();
|
||||
await page.locator('text=Values alignment exercises').click();
|
||||
await page.locator('text=Membership guidance').click();
|
||||
await page.locator('text=Conflict resolution tools').click();
|
||||
|
||||
// 8. User reads testimonial
|
||||
await expect(page.locator('text=Jo Freeman')).toBeVisible();
|
||||
|
||||
// 9. User decides to contact organizer
|
||||
await page.locator('button:has-text("Ask an organizer")').click();
|
||||
|
||||
// 10. User creates CommunityRule
|
||||
await page.locator('button:has-text("Create CommunityRule")').click();
|
||||
});
|
||||
|
||||
test('user journey: explore rule templates', async ({ page }) => {
|
||||
// Scroll to rule stack section
|
||||
await page.evaluate(() => {
|
||||
const element = document.querySelector('text=Consensus clusters');
|
||||
element?.scrollIntoView();
|
||||
});
|
||||
|
||||
// Explore each rule template
|
||||
const ruleTemplates = [
|
||||
'Consensus clusters',
|
||||
'Consensus',
|
||||
'Elected Board',
|
||||
'Petition'
|
||||
];
|
||||
|
||||
for (const template of ruleTemplates) {
|
||||
await page.locator(`text=${template}`).click();
|
||||
// Should trigger analytics tracking
|
||||
await page.waitForTimeout(500); // Brief pause between clicks
|
||||
}
|
||||
|
||||
// Click "See all templates"
|
||||
await page.locator('button:has-text("See all templates")').click();
|
||||
});
|
||||
|
||||
test('user journey: explore feature tools', async ({ page }) => {
|
||||
// Scroll to feature grid section
|
||||
await page.evaluate(() => {
|
||||
const element = document.querySelector('text=We\'ve got your back');
|
||||
element?.scrollIntoView();
|
||||
});
|
||||
|
||||
// Explore each feature
|
||||
const features = [
|
||||
{ name: 'Decision-making support', href: '#decision-making' },
|
||||
{ name: 'Values alignment exercises', href: '#values-alignment' },
|
||||
{ name: 'Membership guidance', href: '#membership-guidance' },
|
||||
{ name: 'Conflict resolution tools', href: '#conflict-resolution' }
|
||||
];
|
||||
|
||||
for (const feature of features) {
|
||||
await page.locator(`a[href="${feature.href}"]`).click();
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
});
|
||||
|
||||
test('user journey: contact organizer', async ({ page }) => {
|
||||
// Scroll to ask organizer section
|
||||
await page.evaluate(() => {
|
||||
const element = document.querySelector('text=Still have questions?');
|
||||
element?.scrollIntoView();
|
||||
});
|
||||
|
||||
// Read the section
|
||||
await expect(page.locator('text=Get answers from an experienced organizer')).toBeVisible();
|
||||
|
||||
// Click contact button
|
||||
await page.locator('button:has-text("Ask an organizer")').click();
|
||||
|
||||
// Should trigger analytics tracking
|
||||
// In a real app, this might open a contact form or modal
|
||||
});
|
||||
|
||||
test('user journey: create CommunityRule', async ({ page }) => {
|
||||
// Scroll to numbered cards section
|
||||
await page.evaluate(() => {
|
||||
const element = document.querySelector('text=Create CommunityRule');
|
||||
element?.scrollIntoView();
|
||||
});
|
||||
|
||||
// Click create button
|
||||
await page.locator('button:has-text("Create CommunityRule")').click();
|
||||
|
||||
// Should navigate to creation flow
|
||||
// In a real app, this would go to a form or wizard
|
||||
});
|
||||
|
||||
test('user journey: learn how it works', async ({ page }) => {
|
||||
// Click "See how it works" button
|
||||
await page.locator('button:has-text("See how it works")').click();
|
||||
|
||||
// Should show more detailed information
|
||||
// In a real app, this might open a modal or navigate to a detailed page
|
||||
});
|
||||
|
||||
test('user journey: scroll through entire page', async ({ page }) => {
|
||||
// Start at top
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
|
||||
// Scroll through each section
|
||||
const sections = [
|
||||
'Trusted by leading cooperators',
|
||||
'How CommunityRule works',
|
||||
'Consensus clusters',
|
||||
"We've got your back",
|
||||
'Jo Freeman',
|
||||
'Still have questions?'
|
||||
];
|
||||
|
||||
for (const section of sections) {
|
||||
await page.evaluate((text) => {
|
||||
const element = document.querySelector(`text=${text}`);
|
||||
element?.scrollIntoView();
|
||||
}, section);
|
||||
|
||||
await page.waitForTimeout(1000); // Wait for scroll and animations
|
||||
await expect(page.locator(`text=${section}`)).toBeVisible();
|
||||
}
|
||||
|
||||
// End at footer
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
await expect(page.locator('footer')).toBeVisible();
|
||||
});
|
||||
|
||||
test('user journey: keyboard navigation through page', async ({ page }) => {
|
||||
// Start with tab navigation
|
||||
await page.keyboard.press('Tab');
|
||||
await expect(page.locator(':focus')).toBeVisible();
|
||||
|
||||
// Navigate through all interactive elements
|
||||
let tabCount = 0;
|
||||
const maxTabs = 50; // Prevent infinite loop
|
||||
|
||||
while (tabCount < maxTabs) {
|
||||
await page.keyboard.press('Tab');
|
||||
tabCount++;
|
||||
|
||||
// Check if we've cycled back to the beginning
|
||||
const focusedElement = page.locator(':focus');
|
||||
if (await focusedElement.count() === 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Test Enter key on focused elements
|
||||
await page.keyboard.press('Enter');
|
||||
});
|
||||
|
||||
test('user journey: mobile navigation', async ({ page }) => {
|
||||
// Set mobile viewport
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
|
||||
// Navigate through page on mobile
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
|
||||
// Scroll through sections
|
||||
await page.evaluate(() => {
|
||||
const sections = document.querySelectorAll('section');
|
||||
sections.forEach(section => section.scrollIntoView());
|
||||
});
|
||||
|
||||
// Test touch interactions
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
await page.locator('text=Consensus clusters').click();
|
||||
await page.locator('button:has-text("Ask an organizer")').click();
|
||||
});
|
||||
|
||||
test('user journey: tablet navigation', async ({ page }) => {
|
||||
// Set tablet viewport
|
||||
await page.setViewportSize({ width: 768, height: 1024 });
|
||||
|
||||
// Navigate through page on tablet
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
|
||||
// Test tablet-specific interactions
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
await page.locator('text=Consensus clusters').click();
|
||||
await page.locator('button:has-text("Ask an organizer")').click();
|
||||
});
|
||||
|
||||
test('user journey: desktop navigation', async ({ page }) => {
|
||||
// Set desktop viewport
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
|
||||
// Navigate through page on desktop
|
||||
await expect(page.locator('text=Collaborate')).toBeVisible();
|
||||
|
||||
// Test desktop-specific interactions
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
await page.locator('text=Consensus clusters').click();
|
||||
await page.locator('button:has-text("Ask an organizer")').click();
|
||||
});
|
||||
|
||||
test('user journey: accessibility navigation', async ({ page }) => {
|
||||
// Test screen reader navigation
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
// Navigate through landmarks
|
||||
await page.keyboard.press('Tab');
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
// Test heading navigation (if supported)
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
// Test form navigation
|
||||
await page.keyboard.press('Tab');
|
||||
|
||||
// Test button activation
|
||||
await page.keyboard.press('Enter');
|
||||
});
|
||||
|
||||
test('user journey: performance testing', async ({ page }) => {
|
||||
// Measure initial page load
|
||||
const startTime = Date.now();
|
||||
await page.goto('/');
|
||||
const loadTime = Date.now() - startTime;
|
||||
|
||||
expect(loadTime).toBeLessThan(3000); // Should load within 3 seconds
|
||||
|
||||
// Measure scroll performance
|
||||
const scrollStartTime = Date.now();
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
const scrollTime = Date.now() - scrollStartTime;
|
||||
|
||||
expect(scrollTime).toBeLessThan(1000); // Should scroll smoothly
|
||||
|
||||
// Measure interaction response time
|
||||
const clickStartTime = Date.now();
|
||||
await page.locator('button:has-text("Learn how CommunityRule works")').click();
|
||||
const clickTime = Date.now() - clickStartTime;
|
||||
|
||||
expect(clickTime).toBeLessThan(500); // Should respond quickly
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,354 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Visual Regression Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
// Wait for all content to load
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('homepage full page screenshot', async ({ page }) => {
|
||||
// Take full page screenshot
|
||||
await expect(page).toHaveScreenshot('homepage-full.png', {
|
||||
fullPage: true,
|
||||
animations: 'disabled'
|
||||
});
|
||||
});
|
||||
|
||||
test('homepage viewport screenshot', async ({ page }) => {
|
||||
// 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
|
||||
|
||||
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
|
||||
await page.locator('h2:has-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);
|
||||
|
||||
const footer = page.locator('footer');
|
||||
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);
|
||||
|
||||
await expect(page).toHaveScreenshot('homepage-mobile.png', {
|
||||
animations: 'disabled'
|
||||
});
|
||||
|
||||
// Test mobile hero section
|
||||
await page.locator('text=Collaborate').scrollIntoViewIfNeeded();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const heroSection = page.locator('section').first();
|
||||
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);
|
||||
|
||||
await expect(page).toHaveScreenshot('homepage-tablet.png', {
|
||||
animations: 'disabled'
|
||||
});
|
||||
|
||||
// Test tablet hero section
|
||||
await page.locator('text=Collaborate').scrollIntoViewIfNeeded();
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const heroSection = page.locator('section').first();
|
||||
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
|
||||
const ctaButton = page.locator('button:has-text("Learn how CommunityRule works")');
|
||||
|
||||
// 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('h2:has-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
|
||||
const ctaButton = page.locator('button:has-text("Learn how CommunityRule works")');
|
||||
|
||||
// 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('error states', async ({ page }) => {
|
||||
// Test error states by blocking critical resources
|
||||
await page.route('**/*.css', route => {
|
||||
route.abort();
|
||||
});
|
||||
|
||||
// Reload page to trigger error states
|
||||
await page.reload();
|
||||
|
||||
// Take screenshot of error state
|
||||
await expect(page).toHaveScreenshot('homepage-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 = '';
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user