UNPKG

supapup

Version:

⚡ Lightning-fast MCP browser dev tool. Navigate → Get instant structured data. No screenshots needed! Puppeteer: 📸 → CSS selectors → JS eval. Supapup: semantic IDs ready to use. 10x faster, 90% fewer tokens.

216 lines (215 loc) 7.22 kB
// Responsive Testing Utilities for Supapup export const DEVICE_PRESETS = { // iPhones 'iphone-se': { name: 'iPhone SE', width: 375, height: 667, deviceScaleFactor: 2, isMobile: true, hasTouch: true, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15' }, 'iphone-12': { name: 'iPhone 12/13', width: 390, height: 844, deviceScaleFactor: 3, isMobile: true, hasTouch: true, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15' }, 'iphone-14-pro': { name: 'iPhone 14 Pro', width: 393, height: 852, deviceScaleFactor: 3, isMobile: true, hasTouch: true, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15' }, // Android 'pixel-5': { name: 'Pixel 5', width: 393, height: 851, deviceScaleFactor: 2.75, isMobile: true, hasTouch: true, userAgent: 'Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36' }, 'samsung-s21': { name: 'Samsung S21', width: 360, height: 800, deviceScaleFactor: 3, isMobile: true, hasTouch: true, userAgent: 'Mozilla/5.0 (Linux; Android 11; Samsung S21) AppleWebKit/537.36' }, // Tablets 'ipad': { name: 'iPad', width: 768, height: 1024, deviceScaleFactor: 2, isMobile: true, hasTouch: true, userAgent: 'Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15' }, 'ipad-pro': { name: 'iPad Pro 12.9"', width: 1024, height: 1366, deviceScaleFactor: 2, isMobile: true, hasTouch: true, userAgent: 'Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15' }, // Desktop 'desktop-1080': { name: 'Desktop HD', width: 1920, height: 1080, deviceScaleFactor: 1, isMobile: false, hasTouch: false }, 'desktop-1440': { name: 'Desktop 2K', width: 2560, height: 1440, deviceScaleFactor: 1, isMobile: false, hasTouch: false }, 'laptop': { name: 'Laptop', width: 1366, height: 768, deviceScaleFactor: 1, isMobile: false, hasTouch: false }, // Common breakpoints 'mobile-small': { name: 'Mobile Small', width: 320, height: 568, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, 'mobile-medium': { name: 'Mobile Medium', width: 375, height: 812, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, 'tablet-portrait': { name: 'Tablet Portrait', width: 768, height: 1024, deviceScaleFactor: 2, isMobile: true, hasTouch: true }, 'tablet-landscape': { name: 'Tablet Landscape', width: 1024, height: 768, deviceScaleFactor: 2, isMobile: true, hasTouch: true } }; export class ResponsiveTester { // Test for common responsive issues static async detectLayoutIssues(page) { return await page.evaluate(() => { const issues = []; // Check for horizontal scroll if (document.documentElement.scrollWidth > window.innerWidth) { issues.push('Horizontal scroll detected - content wider than viewport'); } // Check for elements outside viewport const elements = document.querySelectorAll('*'); let outOfBounds = 0; elements.forEach((el) => { const rect = el.getBoundingClientRect(); if (rect.right > window.innerWidth || rect.left < 0) { outOfBounds++; } }); if (outOfBounds > 0) { issues.push(`${outOfBounds} elements extend outside viewport`); } // Check for text overflow const textElements = document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, span, div'); let textOverflow = 0; textElements.forEach((el) => { const element = el; if (element.scrollWidth > element.clientWidth) { textOverflow++; } }); if (textOverflow > 0) { issues.push(`${textOverflow} elements have text overflow`); } // Check for images larger than viewport const images = document.querySelectorAll('img'); let oversizedImages = 0; images.forEach((img) => { if (img.naturalWidth > window.innerWidth) { oversizedImages++; } }); if (oversizedImages > 0) { issues.push(`${oversizedImages} images are larger than viewport`); } // Check for fixed elements that might overlap on mobile const fixedElements = Array.from(elements).filter((el) => { const style = window.getComputedStyle(el); return style.position === 'fixed' || style.position === 'sticky'; }); if (fixedElements.length > 2) { issues.push(`${fixedElements.length} fixed/sticky elements might cause mobile issues`); } return issues; }); } // Get layout metrics static async getLayoutMetrics(page) { return await page.evaluate(() => { const hasHorizontalScroll = document.documentElement.scrollWidth > window.innerWidth; const hasVerticalScroll = document.documentElement.scrollHeight > window.innerHeight; // Calculate viewport coverage const viewportArea = window.innerWidth * window.innerHeight; const contentArea = document.documentElement.scrollWidth * document.documentElement.scrollHeight; const viewportCoverage = Math.min(viewportArea / contentArea, 1) * 100; // Count out of bounds elements const elements = document.querySelectorAll('*'); let elementsOutOfBounds = 0; let textTruncated = 0; elements.forEach((el) => { const rect = el.getBoundingClientRect(); if (rect.right > window.innerWidth || rect.left < 0 || rect.bottom > window.innerHeight || rect.top < 0) { elementsOutOfBounds++; } const element = el; if (element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight) { textTruncated++; } }); return { horizontalScroll: hasHorizontalScroll, verticalScroll: hasVerticalScroll, viewportCoverage, elementsOutOfBounds, textTruncated }; }); } }