Claude-skill-registry cross-browser-testing
Test cross-browser compatibility on Chrome, Firefox, Safari, and Edge using Playwright. Use when ensuring browser compatibility or fixing browser-specific issues.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/cross-browser-testing" ~/.claude/skills/majiayu000-claude-skill-registry-cross-browser-testing && rm -rf "$T"
skills/data/cross-browser-testing/SKILL.mdYou implement cross-browser testing for the QA Team Portal using Playwright.
Requirements from PROJECT_PLAN.md
- Cross-browser compatibility (Chrome, Firefox, Safari, Edge)
- Mobile browser support (iOS Safari, Chrome Android)
- Test on different browser versions
- Identify and fix browser-specific issues
- Generate compatibility report
Browsers to Test
- Chrome (Chromium) - Latest + Previous version
- Firefox - Latest + Previous version
- Safari (WebKit) - Latest version (macOS only)
- Edge (Chromium) - Latest version
- Mobile Safari (iOS) - Latest version
- Chrome Android - Latest version
Implementation
1. Playwright Installation
cd frontend npm install -D @playwright/test npx playwright install npx playwright install-deps
2. Playwright Configuration
Location:
frontend/playwright.config.ts
import { defineConfig, devices } from '@playwright/test' export default defineConfig({ testDir: './tests/e2e', fullyParallel: true, forbidOnly: !!process.env.CI, retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: [ ['html', { outputFolder: 'playwright-report' }], ['json', { outputFile: 'test-results/results.json' }], ['junit', { outputFile: 'test-results/junit.xml' }] ], use: { baseURL: 'http://localhost:5173', trace: 'on-first-retry', screenshot: 'only-on-failure', video: 'retain-on-failure', }, projects: [ // Desktop Browsers { name: 'chromium', use: { ...devices['Desktop Chrome'], viewport: { width: 1920, height: 1080 } }, }, { name: 'firefox', use: { ...devices['Desktop Firefox'], viewport: { width: 1920, height: 1080 } }, }, { name: 'webkit', use: { ...devices['Desktop Safari'], viewport: { width: 1920, height: 1080 } }, }, { name: 'edge', use: { ...devices['Desktop Edge'], viewport: { width: 1920, height: 1080 } }, }, // Mobile Browsers { name: 'Mobile Chrome', use: { ...devices['Pixel 5'] }, }, { name: 'Mobile Safari', use: { ...devices['iPhone 13'] }, }, // Tablets { name: 'iPad', use: { ...devices['iPad Pro'] }, }, // Different Viewport Sizes { name: 'Desktop 1366x768', use: { ...devices['Desktop Chrome'], viewport: { width: 1366, height: 768 } }, }, { name: 'Desktop 1440x900', use: { ...devices['Desktop Chrome'], viewport: { width: 1440, height: 900 } }, }, ], webServer: { command: 'npm run dev', url: 'http://localhost:5173', reuseExistingServer: !process.env.CI, }, })
3. Cross-Browser Test Suite
Location:
frontend/tests/e2e/cross-browser.spec.ts
import { test, expect } from '@playwright/test' test.describe('Cross-Browser Compatibility', () => { test('homepage loads correctly', async ({ page, browserName }) => { await page.goto('/') // Check page title await expect(page).toHaveTitle(/QA Team Portal/) // Check hero section visible await expect(page.locator('h1')).toBeVisible() // Check navigation menu await expect(page.locator('nav')).toBeVisible() // Check footer await expect(page.locator('footer')).toBeVisible() // Browser-specific checks if (browserName === 'webkit') { // Safari-specific checks console.log('Running Safari-specific tests') } }) test('navigation works across browsers', async ({ page }) => { await page.goto('/') // Click team link await page.click('a[href="#team"]') await page.waitForURL('/#team') // Click tools link await page.click('a[href="#tools"]') await page.waitForURL('/#tools') // All navigations should work smoothly }) test('forms work correctly', async ({ page }) => { await page.goto('/admin/login') // Fill form await page.fill('input[name="email"]', 'admin@test.com') await page.fill('input[name="password"]', 'Test123!@#') // Submit form await page.click('button[type="submit"]') // Check for expected behavior // (adjust based on your app's behavior) }) test('responsive layout adjusts correctly', async ({ page, viewport }) => { await page.goto('/') if (viewport && viewport.width < 768) { // Mobile: hamburger menu should be visible await expect(page.locator('[data-testid="mobile-menu-button"]')).toBeVisible() // Desktop menu should be hidden await expect(page.locator('[data-testid="desktop-menu"]')).toBeHidden() } else { // Desktop: desktop menu should be visible await expect(page.locator('[data-testid="desktop-menu"]')).toBeVisible() // Mobile menu button should be hidden await expect(page.locator('[data-testid="mobile-menu-button"]')).toBeHidden() } }) test('CSS grid and flexbox layouts work', async ({ page }) => { await page.goto('/') // Check team grid const teamGrid = page.locator('[data-testid="team-grid"]') await expect(teamGrid).toBeVisible() // Check grid has correct display property const display = await teamGrid.evaluate((el) => window.getComputedStyle(el).getPropertyValue('display') ) expect(display).toBe('grid') // Check grid columns const gridTemplateColumns = await teamGrid.evaluate((el) => window.getComputedStyle(el).getPropertyValue('grid-template-columns') ) expect(gridTemplateColumns).toBeTruthy() }) test('images load correctly', async ({ page }) => { await page.goto('/') // Wait for images to load await page.waitForLoadState('networkidle') // Check all images are loaded const images = page.locator('img') const count = await images.count() for (let i = 0; i < count; i++) { const img = images.nth(i) const loaded = await img.evaluate((el: HTMLImageElement) => el.complete) expect(loaded).toBe(true) } }) test('fonts render correctly', async ({ page }) => { await page.goto('/') // Check font family is applied const heading = page.locator('h1') const fontFamily = await heading.evaluate((el) => window.getComputedStyle(el).getPropertyValue('font-family') ) // Should include expected font expect(fontFamily).toContain('Poppins') }) test('animations and transitions work', async ({ page }) => { await page.goto('/') // Check element has transition const button = page.locator('button').first() const transition = await button.evaluate((el) => window.getComputedStyle(el).getPropertyValue('transition') ) expect(transition).toBeTruthy() }) test('local storage works', async ({ page, context }) => { await page.goto('/admin/login') // Set local storage await page.evaluate(() => { localStorage.setItem('test_key', 'test_value') }) // Get local storage const value = await page.evaluate(() => { return localStorage.getItem('test_key') }) expect(value).toBe('test_value') // Clear local storage await page.evaluate(() => { localStorage.removeItem('test_key') }) }) test('date/time inputs work', async ({ page, browserName }) => { await page.goto('/admin/team-members/create') // Skip for older browsers that don't support date input if (browserName === 'webkit') { // Safari handles dates differently console.log('Skipping date input test for Safari') test.skip() } await page.fill('input[type="date"]', '2025-11-01') const value = await page.inputValue('input[type="date"]') expect(value).toBe('2025-11-01') }) }) test.describe('Browser-Specific Features', () => { test('WebP images work with fallback', async ({ page, browserName }) => { await page.goto('/') // Modern browsers should use WebP const img = page.locator('picture img').first() const src = await img.getAttribute('src') if (['chromium', 'firefox', 'webkit'].includes(browserName)) { // Should support WebP expect(src).toContain('.webp') } else { // Fallback to JPG/PNG expect(src).toMatch(/\.(jpg|jpeg|png)$/) } }) test('modern CSS features work', async ({ page }) => { await page.goto('/') // Check CSS Grid support const supportsGrid = await page.evaluate(() => { return CSS.supports('display', 'grid') }) expect(supportsGrid).toBe(true) // Check CSS Flexbox support const supportsFlex = await page.evaluate(() => { return CSS.supports('display', 'flex') }) expect(supportsFlex).toBe(true) // Check CSS Custom Properties support const supportsCustomProps = await page.evaluate(() => { return CSS.supports('--test', '0') }) expect(supportsCustomProps).toBe(true) }) })
4. Browser-Specific Polyfills
Location:
frontend/src/polyfills.ts
// Polyfills for older browsers // Promise polyfill if (typeof Promise === 'undefined') { // @ts-ignore window.Promise = import('promise-polyfill').then(m => m.default) } // Fetch polyfill if (typeof fetch === 'undefined') { import('whatwg-fetch') } // IntersectionObserver polyfill if (typeof IntersectionObserver === 'undefined') { import('intersection-observer') } // ResizeObserver polyfill if (typeof ResizeObserver === 'undefined') { import('@juggle/resize-observer').then(({ ResizeObserver }) => { window.ResizeObserver = ResizeObserver }) } // Array.from polyfill if (!Array.from) { Array.from = (function () { const toStr = Object.prototype.toString const isCallable = function (fn: any) { return typeof fn === 'function' || toStr.call(fn) === '[object Function]' } return function from(arrayLike: any, mapFn?: any, thisArg?: any) { const C = this const items = Object(arrayLike) if (arrayLike == null) { throw new TypeError('Array.from requires an array-like object') } const len = items.length >>> 0 const A = isCallable(C) ? Object(new C(len)) : new Array(len) let k = 0 let kValue while (k < len) { kValue = items[k] if (mapFn) { A[k] = typeof thisArg === 'undefined' ? mapFn(kValue, k) : mapFn.call(thisArg, kValue, k) } else { A[k] = kValue } k += 1 } A.length = len return A } })() }
5. CSS Vendor Prefixes (Auto with PostCSS)
npm install -D autoprefixer
Location:
frontend/postcss.config.js
export default { plugins: { autoprefixer: { overrideBrowserslist: [ '> 1%', 'last 2 versions', 'not dead', 'not ie 11' ] }, tailwindcss: {}, }, }
6. Browser Detection Utility
Location:
frontend/src/utils/browserDetect.ts
export const getBrowserInfo = () => { const ua = navigator.userAgent let browserName = 'Unknown' let version = 'Unknown' // Chrome if (ua.indexOf('Chrome') > -1 && ua.indexOf('Edg') === -1) { browserName = 'Chrome' version = ua.match(/Chrome\/(\d+)/)?.[1] || 'Unknown' } // Edge else if (ua.indexOf('Edg') > -1) { browserName = 'Edge' version = ua.match(/Edg\/(\d+)/)?.[1] || 'Unknown' } // Firefox else if (ua.indexOf('Firefox') > -1) { browserName = 'Firefox' version = ua.match(/Firefox\/(\d+)/)?.[1] || 'Unknown' } // Safari else if (ua.indexOf('Safari') > -1 && ua.indexOf('Chrome') === -1) { browserName = 'Safari' version = ua.match(/Version\/(\d+)/)?.[1] || 'Unknown' } return { browserName, version, userAgent: ua, isMobile: /Mobile|Android|iPhone|iPad/i.test(ua) } } export const isModernBrowser = () => { const info = getBrowserInfo() const version = parseInt(info.version) // Define minimum versions const minVersions: Record<string, number> = { Chrome: 90, Edge: 90, Firefox: 88, Safari: 14 } return version >= (minVersions[info.browserName] || 0) } // Usage if (!isModernBrowser()) { console.warn('You are using an outdated browser. Some features may not work correctly.') }
7. Run Cross-Browser Tests
# Run tests on all browsers npx playwright test # Run on specific browser npx playwright test --project=chromium npx playwright test --project=firefox npx playwright test --project=webkit # Run on mobile npx playwright test --project="Mobile Chrome" npx playwright test --project="Mobile Safari" # Run with UI npx playwright test --ui # Generate HTML report npx playwright show-report
8. CI/CD Integration
Location:
.github/workflows/cross-browser-tests.yml
name: Cross-Browser Tests on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest strategy: matrix: browser: [chromium, firefox, webkit] steps: - uses: actions/checkout@v3 - name: Setup Node.js uses: actions/setup-node@v3 with: node-version: '20' - name: Install dependencies run: | cd frontend npm ci - name: Install Playwright Browsers run: npx playwright install --with-deps ${{ matrix.browser }} - name: Run Playwright tests run: npx playwright test --project=${{ matrix.browser }} - name: Upload test results if: always() uses: actions/upload-artifact@v3 with: name: playwright-report-${{ matrix.browser }} path: playwright-report/ retention-days: 30
Browser Compatibility Checklist
Chrome/Edge (Chromium)
- Latest version tested
- Responsive design works
- All features functional
- CSS Grid/Flexbox working
- WebP images loading
- Animations smooth
Firefox
- Latest version tested
- Responsive design works
- All features functional
- CSS Grid/Flexbox working
- Fonts rendering correctly
- Form inputs working
Safari (WebKit)
- Latest macOS version tested
- iOS Safari tested
- Responsive design works
- Date inputs working (or fallback)
- Flexbox gap property supported or polyfilled
- Backdrop-filter working or fallback provided
- -webkit- prefixes added where needed
Mobile Browsers
- iOS Safari tested
- Chrome Android tested
- Touch interactions work
- Viewport meta tag configured
- Mobile menu functional
- Touch targets >= 44x44px
Common Browser Issues & Solutions
Safari-Specific Issues
1. Flexbox Gap Not Supported (< Safari 14.1)
/* Instead of gap */ .container { display: flex; gap: 1rem; /* Not supported in old Safari */ } /* Use margin fallback */ .container > * { margin-right: 1rem; margin-bottom: 1rem; } .container > *:last-child { margin-right: 0; }
2. Date Input Not Supported
// Provide fallback for Safari const DateInput = ({ value, onChange }) => { const isDateSupported = () => { const input = document.createElement('input') input.setAttribute('type', 'date') return input.type === 'date' } if (!isDateSupported()) { // Use text input with placeholder return <input type="text" placeholder="YYYY-MM-DD" /> } return <input type="date" value={value} onChange={onChange} /> }
Firefox-Specific Issues
1. Scrollbar Styling
/* Firefox uses different properties */ * { scrollbar-width: thin; scrollbar-color: #888 #f1f1f1; } /* Chrome/Edge */ *::-webkit-scrollbar { width: 8px; }
Browser Testing Report Template
# Cross-Browser Testing Report **Date:** 2025-11-01 **Tested By:** QA Team **App Version:** 1.0.0 ## Browsers Tested ### Chrome 120 (Desktop) - ✅ All features working - ✅ Responsive design correct - ✅ Performance good (Lighthouse 95) ### Firefox 119 (Desktop) - ✅ All features working - ✅ Responsive design correct - ⚠️ Minor font rendering difference (acceptable) ### Safari 17 (macOS) - ✅ All features working - ✅ Responsive design correct - ⚠️ Backdrop-filter not working (fallback applied) ### Safari (iOS 17) - ✅ Mobile layout works - ✅ Touch interactions smooth - ✅ Forms functional ### Edge 120 (Desktop) - ✅ All features working (Chromium-based) ## Issues Found 1. **Safari: Date input fallback needed** - Severity: Low - Status: Fixed - Solution: Added text input fallback 2. **Firefox: Scrollbar styling different** - Severity: Low - Status: Accepted - Note: Minor visual difference, acceptable ## Recommendations - Continue testing on Safari when new features added - Monitor browser release notes for breaking changes - Keep polyfills updated
Report
✅ Playwright configured for cross-browser testing ✅ All major browsers tested (Chrome, Firefox, Safari, Edge) ✅ Mobile browsers tested (iOS Safari, Chrome Android) ✅ Responsive layouts verified across browsers ✅ Browser-specific issues identified and fixed ✅ Polyfills added for older browsers ✅ Vendor prefixes auto-added (autoprefixer) ✅ CI/CD integration configured ✅ Test report generated ✅ 100% compatibility achieved