Claude Code Plugins

Community-maintained marketplace

Feedback

cross-browser-testing

@PrasadTelasula/EvokeQOne
0
0

Test cross-browser compatibility on Chrome, Firefox, Safari, and Edge using Playwright. Use when ensuring browser compatibility or fixing browser-specific issues.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name cross-browser-testing
description Test cross-browser compatibility on Chrome, Firefox, Safari, and Edge using Playwright. Use when ensuring browser compatibility or fixing browser-specific issues.
allowed-tools Read, Write, Edit, Bash

You 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