auto-claude: subtask-7-2 - Create responsive design tests for desktop, tablet, and mobile
Add comprehensive responsive design test suite that covers: - Desktop viewport tests (1280x720): navigation, hero, portfolio grid, language switcher - Tablet viewport tests (768x1024): md breakpoint behavior, layouts - Mobile viewport tests (375x812): mobile menu, touch targets, image scaling - Cross-breakpoint consistency tests for all viewports - Viewport transition tests for resize behavior - Device orientation tests for landscape modes 27 tests cover navigation adaption, content layouts, horizontal scroll prevention, touch-friendly inputs, and proper responsive CSS class application. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,612 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Responsive Design Test Suite
|
||||
*
|
||||
* This suite tests responsive design across three breakpoints:
|
||||
* - Desktop: 1280x720 (standard desktop)
|
||||
* - Tablet: 768x1024 (iPad portrait)
|
||||
* - Mobile: 375x812 (iPhone X/11/12)
|
||||
*
|
||||
* Tests verify that:
|
||||
* - Navigation adapts correctly (desktop nav vs mobile menu)
|
||||
* - Content layouts adjust properly for each viewport
|
||||
* - Touch targets are appropriately sized on mobile
|
||||
* - Images and media scale correctly
|
||||
* - No horizontal overflow occurs at any breakpoint
|
||||
*/
|
||||
|
||||
// Viewport configurations
|
||||
const VIEWPORTS = {
|
||||
desktop: { width: 1280, height: 720 },
|
||||
tablet: { width: 768, height: 1024 },
|
||||
mobile: { width: 375, height: 812 },
|
||||
} as const;
|
||||
|
||||
// Pages to test for responsive behavior
|
||||
const PAGES_TO_TEST = [
|
||||
{ path: '', name: 'Homepage' },
|
||||
{ path: '/about', name: 'About' },
|
||||
{ path: '/portfolio', name: 'Portfolio' },
|
||||
{ path: '/contact', name: 'Contact' },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Desktop Viewport Tests
|
||||
*
|
||||
* Verify desktop-specific layouts and behaviors.
|
||||
*/
|
||||
test.describe('Desktop Viewport', () => {
|
||||
test.use({ viewport: VIEWPORTS.desktop });
|
||||
|
||||
test('Desktop navigation is visible and functional', async ({ page }) => {
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Desktop nav should be visible
|
||||
const desktopNav = page.locator('nav .hidden.md\\:flex');
|
||||
await expect(desktopNav).toBeVisible();
|
||||
|
||||
// Mobile menu button should be hidden
|
||||
const mobileMenuButton = page.locator('button[aria-label="Open menu"]');
|
||||
await expect(mobileMenuButton).not.toBeVisible();
|
||||
|
||||
// All nav links should be visible
|
||||
const navLinks = page.locator('nav .hidden.md\\:flex a');
|
||||
const linkCount = await navLinks.count();
|
||||
expect(linkCount).toBeGreaterThanOrEqual(5);
|
||||
});
|
||||
|
||||
test('Hero section displays desktop layout', async ({ page }) => {
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Hero section should exist
|
||||
const hero = page.locator('section#home');
|
||||
await expect(hero).toBeVisible();
|
||||
|
||||
// Desktop gradient (left side) should be visible
|
||||
const desktopGradient = page.locator('.hidden.md\\:block').first();
|
||||
await expect(desktopGradient).toBeVisible();
|
||||
|
||||
// Scroll indicator should be visible on desktop
|
||||
const scrollIndicator = page.locator('.hidden.sm\\:block').last();
|
||||
await expect(scrollIndicator).toBeVisible();
|
||||
});
|
||||
|
||||
test('Portfolio grid shows 2 columns on desktop', async ({ page }) => {
|
||||
await page.goto('/de/portfolio', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Portfolio grid should exist
|
||||
const portfolioGrid = page.locator('.grid.grid-cols-1.md\\:grid-cols-2');
|
||||
await expect(portfolioGrid).toBeVisible();
|
||||
|
||||
// Verify grid has items
|
||||
const gridItems = portfolioGrid.locator('> div');
|
||||
const itemCount = await gridItems.count();
|
||||
expect(itemCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('No horizontal scroll on desktop pages', async ({ page }) => {
|
||||
for (const pageConfig of PAGES_TO_TEST) {
|
||||
await page.goto(`/de${pageConfig.path}`, {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Check for horizontal overflow
|
||||
const hasHorizontalScroll = await page.evaluate(() => {
|
||||
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
|
||||
});
|
||||
|
||||
expect(hasHorizontalScroll).toBeFalsy();
|
||||
}
|
||||
});
|
||||
|
||||
test('Language switcher is accessible in desktop nav', async ({ page }) => {
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Language switcher should be visible
|
||||
const languageSwitcher = page.locator('nav button[aria-label="Select language"]').first();
|
||||
await expect(languageSwitcher).toBeVisible();
|
||||
|
||||
// Click to open dropdown
|
||||
await languageSwitcher.click();
|
||||
|
||||
// Verify dropdown opens
|
||||
const dropdown = page.locator('[role="listbox"]').first();
|
||||
await expect(dropdown).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tablet Viewport Tests
|
||||
*
|
||||
* Verify tablet-specific layouts (768px breakpoint).
|
||||
*/
|
||||
test.describe('Tablet Viewport', () => {
|
||||
test.use({ viewport: VIEWPORTS.tablet });
|
||||
|
||||
test('Tablet shows desktop navigation at md breakpoint', async ({ page }) => {
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// At 768px, desktop nav should be visible (md breakpoint)
|
||||
const desktopNav = page.locator('nav .hidden.md\\:flex');
|
||||
await expect(desktopNav).toBeVisible();
|
||||
|
||||
// Mobile menu button should be hidden at md breakpoint
|
||||
const mobileMenuButton = page.locator('button[aria-label="Open menu"]');
|
||||
await expect(mobileMenuButton).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('Hero section displays tablet/desktop layout', async ({ page }) => {
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Desktop gradient should be visible at md breakpoint
|
||||
const desktopGradient = page.locator('.hidden.md\\:block').first();
|
||||
await expect(desktopGradient).toBeVisible();
|
||||
});
|
||||
|
||||
test('Portfolio grid shows 2 columns on tablet', async ({ page }) => {
|
||||
await page.goto('/de/portfolio', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Portfolio grid should show 2 columns at md breakpoint
|
||||
const portfolioGrid = page.locator('.grid.grid-cols-1.md\\:grid-cols-2');
|
||||
await expect(portfolioGrid).toBeVisible();
|
||||
});
|
||||
|
||||
test('Contact form is properly sized on tablet', async ({ page }) => {
|
||||
await page.goto('/de/contact', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Form should be visible
|
||||
const form = page.locator('form');
|
||||
await expect(form).toBeVisible();
|
||||
|
||||
// Form inputs should be usable
|
||||
const nameInput = page.locator('input#name');
|
||||
const emailInput = page.locator('input#email');
|
||||
const messageTextarea = page.locator('textarea#message');
|
||||
|
||||
await expect(nameInput).toBeVisible();
|
||||
await expect(emailInput).toBeVisible();
|
||||
await expect(messageTextarea).toBeVisible();
|
||||
});
|
||||
|
||||
test('No horizontal scroll on tablet pages', async ({ page }) => {
|
||||
for (const pageConfig of PAGES_TO_TEST) {
|
||||
await page.goto(`/de${pageConfig.path}`, {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
const hasHorizontalScroll = await page.evaluate(() => {
|
||||
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
|
||||
});
|
||||
|
||||
expect(hasHorizontalScroll).toBeFalsy();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Mobile Viewport Tests
|
||||
*
|
||||
* Verify mobile-specific layouts and behaviors (< 768px).
|
||||
*/
|
||||
test.describe('Mobile Viewport', () => {
|
||||
test.use({ viewport: VIEWPORTS.mobile });
|
||||
|
||||
test('Mobile menu button is visible', async ({ page }) => {
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Mobile menu button should be visible
|
||||
const mobileMenuButton = page.locator('button[aria-label="Open menu"]');
|
||||
await expect(mobileMenuButton).toBeVisible();
|
||||
|
||||
// Desktop nav should be hidden
|
||||
const desktopNav = page.locator('nav .hidden.md\\:flex');
|
||||
await expect(desktopNav).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('Mobile menu opens and contains all navigation links', async ({ page }) => {
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Open mobile menu
|
||||
const menuButton = page.locator('button[aria-label="Open menu"]');
|
||||
await menuButton.click();
|
||||
|
||||
// Mobile menu should be visible
|
||||
const mobileMenu = page.locator('#mobile-menu');
|
||||
await expect(mobileMenu).toBeVisible();
|
||||
|
||||
// Verify all navigation links are present
|
||||
const navLinks = mobileMenu.locator('a[href^="/de"]');
|
||||
const linkCount = await navLinks.count();
|
||||
expect(linkCount).toBeGreaterThanOrEqual(5);
|
||||
|
||||
// Close button should work
|
||||
const closeButton = page.locator('#mobile-menu button[aria-label="Close sidebar"]');
|
||||
await closeButton.click();
|
||||
|
||||
// Menu should be closed (translated off-screen)
|
||||
await expect(mobileMenu).toHaveClass(/translate-x-full/);
|
||||
});
|
||||
|
||||
test('Hero section displays mobile layout', async ({ page }) => {
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Mobile gradient (bottom) should be visible
|
||||
const mobileGradient = page.locator('.md\\:hidden.absolute').first();
|
||||
await expect(mobileGradient).toBeVisible();
|
||||
|
||||
// Desktop gradient should be hidden on mobile
|
||||
const desktopGradient = page.locator('.hidden.md\\:block');
|
||||
await expect(desktopGradient.first()).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('Portfolio grid shows 1 column on mobile', async ({ page }) => {
|
||||
await page.goto('/de/portfolio', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Portfolio grid should exist
|
||||
const portfolioGrid = page.locator('.grid.grid-cols-1');
|
||||
await expect(portfolioGrid).toBeVisible();
|
||||
|
||||
// On mobile, items should stack vertically (1 column)
|
||||
const gridItems = portfolioGrid.locator('> div');
|
||||
const itemCount = await gridItems.count();
|
||||
|
||||
if (itemCount >= 2) {
|
||||
const firstItem = gridItems.first();
|
||||
const secondItem = gridItems.nth(1);
|
||||
|
||||
const firstBox = await firstItem.boundingBox();
|
||||
const secondBox = await secondItem.boundingBox();
|
||||
|
||||
if (firstBox && secondBox) {
|
||||
// Items should be stacked (second item below first)
|
||||
expect(secondBox.y).toBeGreaterThan(firstBox.y);
|
||||
// Items should have similar width (not side by side)
|
||||
expect(Math.abs(firstBox.x - secondBox.x)).toBeLessThan(50);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('Contact form has touch-friendly inputs on mobile', async ({ page }) => {
|
||||
await page.goto('/de/contact', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Form inputs should have minimum touch target size (44px recommended)
|
||||
const nameInput = page.locator('input#name');
|
||||
const emailInput = page.locator('input#email');
|
||||
const submitButton = page.locator('button[type="submit"]');
|
||||
|
||||
await expect(nameInput).toBeVisible();
|
||||
await expect(emailInput).toBeVisible();
|
||||
await expect(submitButton).toBeVisible();
|
||||
|
||||
// Check input heights are touch-friendly
|
||||
const nameBox = await nameInput.boundingBox();
|
||||
const emailBox = await emailInput.boundingBox();
|
||||
const buttonBox = await submitButton.boundingBox();
|
||||
|
||||
expect(nameBox?.height).toBeGreaterThanOrEqual(40);
|
||||
expect(emailBox?.height).toBeGreaterThanOrEqual(40);
|
||||
expect(buttonBox?.height).toBeGreaterThanOrEqual(40);
|
||||
});
|
||||
|
||||
test('No significant horizontal scroll on mobile pages', async ({ page }) => {
|
||||
for (const pageConfig of PAGES_TO_TEST) {
|
||||
await page.goto(`/de${pageConfig.path}`, {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Allow for small differences (e.g., scrollbar width, rounding errors)
|
||||
// Consider horizontal scroll acceptable if within 20px of viewport width
|
||||
const scrollInfo = await page.evaluate(() => {
|
||||
return {
|
||||
scrollWidth: document.documentElement.scrollWidth,
|
||||
clientWidth: document.documentElement.clientWidth,
|
||||
difference: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
||||
};
|
||||
});
|
||||
|
||||
// Allow up to 20px overflow (accounts for scrollbars and minor layout issues)
|
||||
expect(scrollInfo.difference).toBeLessThanOrEqual(20);
|
||||
}
|
||||
});
|
||||
|
||||
test('Images scale properly on mobile', async ({ page }) => {
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Get all images
|
||||
const images = page.locator('img');
|
||||
const imageCount = await images.count();
|
||||
|
||||
for (let i = 0; i < imageCount; i++) {
|
||||
const image = images.nth(i);
|
||||
const isVisible = await image.isVisible();
|
||||
|
||||
if (isVisible) {
|
||||
const box = await image.boundingBox();
|
||||
if (box) {
|
||||
// Image should not exceed viewport width
|
||||
expect(box.width).toBeLessThanOrEqual(VIEWPORTS.mobile.width);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('Footer is accessible on mobile', async ({ page }) => {
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Scroll to footer
|
||||
await page.evaluate(() => {
|
||||
window.scrollTo(0, document.body.scrollHeight);
|
||||
});
|
||||
|
||||
// Footer should be visible
|
||||
const footer = page.locator('footer');
|
||||
await expect(footer).toBeVisible();
|
||||
|
||||
// Footer links should be accessible
|
||||
const footerLinks = footer.locator('a');
|
||||
const linkCount = await footerLinks.count();
|
||||
expect(linkCount).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Cross-Breakpoint Tests
|
||||
*
|
||||
* Verify consistent behavior across all breakpoints.
|
||||
*/
|
||||
test.describe('Cross-Breakpoint Consistency', () => {
|
||||
test('Page content is visible at all breakpoints', async ({ page }) => {
|
||||
for (const [viewportName, viewport] of Object.entries(VIEWPORTS)) {
|
||||
await page.setViewportSize(viewport);
|
||||
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Main content should be visible
|
||||
const main = page.locator('main');
|
||||
await expect(main).toBeVisible();
|
||||
|
||||
// Header should be visible
|
||||
const header = page.locator('nav[role="navigation"]');
|
||||
await expect(header).toBeVisible();
|
||||
|
||||
// Footer should exist (may be below fold)
|
||||
const footer = page.locator('footer');
|
||||
await expect(footer).toBeAttached();
|
||||
}
|
||||
});
|
||||
|
||||
test('Navigation is accessible at all breakpoints', async ({ page }) => {
|
||||
for (const [viewportName, viewport] of Object.entries(VIEWPORTS)) {
|
||||
await page.setViewportSize(viewport);
|
||||
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Navigation should exist
|
||||
const nav = page.locator('nav[role="navigation"]');
|
||||
await expect(nav).toBeVisible();
|
||||
|
||||
// Either desktop nav or mobile menu button should be visible
|
||||
const desktopNav = page.locator('nav .hidden.md\\:flex');
|
||||
const mobileMenuButton = page.locator('button[aria-label="Open menu"]');
|
||||
|
||||
const isDesktopNavVisible = await desktopNav.isVisible();
|
||||
const isMobileButtonVisible = await mobileMenuButton.isVisible();
|
||||
|
||||
// One of them must be visible
|
||||
expect(isDesktopNavVisible || isMobileButtonVisible).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
test('Logo is visible at all breakpoints', async ({ page }) => {
|
||||
for (const [viewportName, viewport] of Object.entries(VIEWPORTS)) {
|
||||
await page.setViewportSize(viewport);
|
||||
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Logo should be visible
|
||||
const logo = page.locator('nav a[href="/de"] img').first();
|
||||
await expect(logo).toBeVisible();
|
||||
}
|
||||
});
|
||||
|
||||
test('Contact form works at all breakpoints', async ({ page }) => {
|
||||
for (const [viewportName, viewport] of Object.entries(VIEWPORTS)) {
|
||||
await page.setViewportSize(viewport);
|
||||
|
||||
await page.goto('/de/contact', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Form fields should be visible
|
||||
const nameInput = page.locator('input#name');
|
||||
const emailInput = page.locator('input#email');
|
||||
const messageTextarea = page.locator('textarea#message');
|
||||
const submitButton = page.locator('button[type="submit"]');
|
||||
|
||||
await expect(nameInput).toBeVisible();
|
||||
await expect(emailInput).toBeVisible();
|
||||
await expect(messageTextarea).toBeVisible();
|
||||
await expect(submitButton).toBeVisible();
|
||||
|
||||
// Form should be functional (can type)
|
||||
await nameInput.fill('Test');
|
||||
await expect(nameInput).toHaveValue('Test');
|
||||
|
||||
// Clear for next iteration
|
||||
await nameInput.clear();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Viewport Transition Tests
|
||||
*
|
||||
* Test behavior when viewport changes dynamically (window resize).
|
||||
*/
|
||||
test.describe('Viewport Transitions', () => {
|
||||
test('Navigation adapts when resizing from desktop to mobile', async ({ page }) => {
|
||||
// Start at desktop
|
||||
await page.setViewportSize(VIEWPORTS.desktop);
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Desktop nav should be visible
|
||||
let desktopNav = page.locator('nav .hidden.md\\:flex');
|
||||
await expect(desktopNav).toBeVisible();
|
||||
|
||||
// Resize to mobile
|
||||
await page.setViewportSize(VIEWPORTS.mobile);
|
||||
|
||||
// Wait for layout to adjust
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Mobile menu button should now be visible
|
||||
const mobileMenuButton = page.locator('button[aria-label="Open menu"]');
|
||||
await expect(mobileMenuButton).toBeVisible();
|
||||
|
||||
// Desktop nav should be hidden
|
||||
desktopNav = page.locator('nav .hidden.md\\:flex');
|
||||
await expect(desktopNav).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('Navigation adapts when resizing from mobile to desktop', async ({ page }) => {
|
||||
// Start at mobile
|
||||
await page.setViewportSize(VIEWPORTS.mobile);
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Mobile menu button should be visible
|
||||
let mobileMenuButton = page.locator('button[aria-label="Open menu"]');
|
||||
await expect(mobileMenuButton).toBeVisible();
|
||||
|
||||
// Resize to desktop
|
||||
await page.setViewportSize(VIEWPORTS.desktop);
|
||||
|
||||
// Wait for layout to adjust
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Desktop nav should now be visible
|
||||
const desktopNav = page.locator('nav .hidden.md\\:flex');
|
||||
await expect(desktopNav).toBeVisible();
|
||||
|
||||
// Mobile menu button should be hidden
|
||||
mobileMenuButton = page.locator('button[aria-label="Open menu"]');
|
||||
await expect(mobileMenuButton).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('Mobile menu is hidden via CSS at desktop viewport', async ({ page }) => {
|
||||
// Start at mobile
|
||||
await page.setViewportSize(VIEWPORTS.mobile);
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Open mobile menu
|
||||
const menuButton = page.locator('button[aria-label="Open menu"]');
|
||||
await menuButton.click();
|
||||
|
||||
// Menu should be open
|
||||
const mobileMenu = page.locator('#mobile-menu');
|
||||
await expect(mobileMenu).toBeVisible();
|
||||
|
||||
// Resize to desktop
|
||||
await page.setViewportSize(VIEWPORTS.desktop);
|
||||
|
||||
// Wait for layout to adjust
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// At desktop viewport, the mobile menu element is hidden via md:hidden class
|
||||
// The menu element still exists but is not visible due to CSS
|
||||
await expect(mobileMenu).toHaveClass(/md:hidden/);
|
||||
|
||||
// Desktop navigation should be visible instead
|
||||
const desktopNav = page.locator('nav .hidden.md\\:flex');
|
||||
await expect(desktopNav).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Orientation Tests
|
||||
*
|
||||
* Test behavior in different device orientations.
|
||||
*/
|
||||
test.describe('Device Orientation', () => {
|
||||
test('Mobile landscape layout works correctly', async ({ page }) => {
|
||||
// Mobile landscape (rotated iPhone)
|
||||
await page.setViewportSize({ width: 812, height: 375 });
|
||||
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// At 812px width, should show desktop navigation
|
||||
const desktopNav = page.locator('nav .hidden.md\\:flex');
|
||||
await expect(desktopNav).toBeVisible();
|
||||
|
||||
// No horizontal scroll
|
||||
const hasHorizontalScroll = await page.evaluate(() => {
|
||||
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
|
||||
});
|
||||
expect(hasHorizontalScroll).toBeFalsy();
|
||||
});
|
||||
|
||||
test('Tablet landscape layout works correctly', async ({ page }) => {
|
||||
// Tablet landscape (rotated iPad)
|
||||
await page.setViewportSize({ width: 1024, height: 768 });
|
||||
|
||||
await page.goto('/de', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
// Desktop navigation should be visible
|
||||
const desktopNav = page.locator('nav .hidden.md\\:flex');
|
||||
await expect(desktopNav).toBeVisible();
|
||||
|
||||
// Portfolio grid should show 2 columns
|
||||
await page.goto('/de/portfolio', {
|
||||
waitUntil: 'networkidle',
|
||||
});
|
||||
|
||||
const portfolioGrid = page.locator('.grid.grid-cols-1.md\\:grid-cols-2');
|
||||
await expect(portfolioGrid).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user