Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Test across all major browsers

Website works correctly across major browsers (Chrome, Firefox, Safari, Edge).

Utilities
Quick take
Typical fix time 60 min
  • Test on Chrome, Firefox, Safari, and Edge at minimum
  • Use automated tools like Playwright or BrowserStack for CI testing
  • Check CSS features with caniuse.com before using
  • Test on both desktop and mobile versions of browsers
  • Document and gracefully handle unsupported features
Why it matters: Users access your site from different browsers, each with unique rendering engines. Without cross-browser testing, a significant portion of users may experience broken layouts, missing features, or complete failures.

Rule Details

Cross-browser testing ensures your website works correctly for all users regardless of their browser choice.

Code Example

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
 
export default defineConfig({
  testDir: './e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
 
  projects: [
    // Desktop browsers
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
    {
      name: 'webkit',
      use: { ...devices['Desktop Safari'] },
    },
    {
      name: 'edge',
      use: { ...devices['Desktop Edge'], channel: 'msedge' },
    },
 
    // Mobile browsers
    {
      name: 'mobile-chrome',
      use: { ...devices['Pixel 5'] },
    },
    {
      name: 'mobile-safari',
      use: { ...devices['iPhone 12'] },
    },
  ],
 
  webServer: {
    command: 'pnpm dev',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
})

Why It Matters

Users access your site from different browsers, each with unique rendering engines. Without cross-browser testing, a significant portion of users may experience broken layouts, missing features, or complete failures.

Browser Market Share (Target Browsers)

BrowserEnginePriorityNotes
ChromeBlinkCritical~65% market share
SafariWebKitCriticalDefault on iOS/macOS
FirefoxGeckoHighPrivacy-focused users
EdgeBlinkHighWindows default
Samsung InternetBlinkMediumPopular on Samsung devices

Cross-Browser Test Example

// e2e/cross-browser.spec.ts
import { test, expect } from '@playwright/test'
 
test.describe('Cross-browser compatibility', () => {
  test('homepage renders correctly', async ({ page, browserName }) => {
    await page.goto('/')
 
    // Check main elements render
    await expect(page.locator('header')).toBeVisible()
    await expect(page.locator('main')).toBeVisible()
    await expect(page.locator('footer')).toBeVisible()
 
    // Check no layout shifts (CLS)
    const clsValue = await page.evaluate(() => {
      return new Promise<number>((resolve) => {
        let cls = 0
        new PerformanceObserver((list) => {
          for (const entry of list.getEntries()) {
            if (!(entry as any).hadRecentInput) {
              cls += (entry as any).value
            }
          }
        }).observe({ type: 'layout-shift', buffered: true })
 
        setTimeout(() => resolve(cls), 2000)
      })
    })
 
    expect(clsValue).toBeLessThan(0.1)
 
    // Screenshot for visual comparison
    await expect(page).toHaveScreenshot(`homepage-${browserName}.png`)
  })
 
  test('forms work correctly', async ({ page }) => {
    await page.goto('/contact')
 
    // Fill form
    await page.fill('[name="email"]', 'test@example.com')
    await page.fill('[name="message"]', 'Test message')
 
    // Check validation works
    await page.click('button[type="submit"]')
    await expect(page.locator('[role="alert"]')).not.toBeVisible()
  })
 
  test('CSS features degrade gracefully', async ({ page, browserName }) => {
    await page.goto('/')
 
    // Check CSS Grid layout
    const grid = page.locator('.grid-container')
    const boundingBox = await grid.boundingBox()
 
    expect(boundingBox?.width).toBeGreaterThan(0)
    expect(boundingBox?.height).toBeGreaterThan(0)
 
    // Check no overflow issues
    const overflow = await page.evaluate(() => {
      return document.documentElement.scrollWidth > window.innerWidth
    })
    expect(overflow).toBe(false)
  })
 
  test('JavaScript features work', async ({ page }) => {
    await page.goto('/')
 
    // Test interactive elements
    const menuButton = page.locator('[aria-label="Open menu"]')
    if (await menuButton.isVisible()) {
      await menuButton.click()
      await expect(page.locator('[role="menu"]')).toBeVisible()
    }
 
    // Test dynamic content loading
    const lazyContent = page.locator('[data-lazy]')
    if (await lazyContent.count() > 0) {
      await lazyContent.first().scrollIntoViewIfNeeded()
      await expect(lazyContent.first()).toBeVisible()
    }
  })
})

Visual Regression Testing

// e2e/visual.spec.ts
import { test, expect } from '@playwright/test'
 
test.describe('Visual regression', () => {
  const viewports = [
    { width: 375, height: 667, name: 'mobile' },
    { width: 768, height: 1024, name: 'tablet' },
    { width: 1280, height: 720, name: 'desktop' },
    { width: 1920, height: 1080, name: 'wide' },
  ]
 
  for (const viewport of viewports) {
    test(`homepage at ${viewport.name}`, async ({ page, browserName }) => {
      await page.setViewportSize({
        width: viewport.width,
        height: viewport.height,
      })
 
      await page.goto('/')
      await page.waitForLoadState('networkidle')
 
      await expect(page).toHaveScreenshot(
        `homepage-${browserName}-${viewport.name}.png`,
        { maxDiffPixels: 100 }
      )
    })
  }
})

CI/CD Integration

# .github/workflows/cross-browser-tests.yml
name: Cross-Browser Tests
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
 
      - uses: pnpm/action-setup@v2
        with:
          version: 8
 
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
 
      - name: Install dependencies
        run: pnpm install
 
      - name: Install Playwright browsers
        run: pnpm exec playwright install --with-deps
 
      - name: Build
        run: pnpm build
 
      - name: Run Playwright tests
        run: pnpm exec playwright test
 
      - uses: actions/upload-artifact@v4
        if: always()
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

BrowserStack Integration

// browserstack.config.ts
import { defineConfig } from '@playwright/test'
 
export default defineConfig({
  use: {
    connectOptions: {
      wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(
        JSON.stringify({
          browser: 'chrome',
          os: 'Windows',
          os_version: '11',
          'browserstack.username': process.env.BROWSERSTACK_USERNAME,
          'browserstack.accessKey': process.env.BROWSERSTACK_ACCESS_KEY,
        })
      )}`,
    },
  },
})

Feature Detection

// utils/feature-detection.ts
export const features = {
  // CSS features
  cssGrid: CSS.supports('display', 'grid'),
  cssSubgrid: CSS.supports('grid-template-columns', 'subgrid'),
  containerQueries: CSS.supports('container-type', 'inline-size'),
  hasSelector: CSS.supports('selector(:has(*))'),
 
  // JavaScript features
  intersectionObserver: 'IntersectionObserver' in window,
  resizeObserver: 'ResizeObserver' in window,
  webComponents: 'customElements' in window,
 
  // API features
  serviceWorker: 'serviceWorker' in navigator,
  webShare: 'share' in navigator,
  clipboard: 'clipboard' in navigator,
}
 
// Use in components
function FeatureBasedComponent() {
  if (!features.intersectionObserver) {
    // Fallback for older browsers
    return <BasicComponent />
  }
 
  return <EnhancedComponent />
}

CSS Feature Queries

/* Progressive enhancement with @supports */
.card {
  /* Fallback for all browsers */
  display: flex;
  flex-direction: column;
}
 
@supports (display: grid) {
  .card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 1rem;
  }
}
 
@supports (container-type: inline-size) {
  .card-container {
    container-type: inline-size;
  }
 
  @container (min-width: 400px) {
    .card {
      flex-direction: row;
    }
  }
}
 
/* Safari-specific fixes */
@supports (-webkit-touch-callout: none) {
  .sticky-element {
    position: -webkit-sticky;
    position: sticky;
  }
}

Common Browser Issues

IssueBrowsersSolution
Flexbox gapSafari < 14.1Use margins as fallback
CSS Grid subgridFirefox onlyUse nested grids
:has() selectorSafari only (old)JavaScript fallback
Smooth scrollSafariscroll-behavior polyfill
Date inputSafariUse date picker library
Form validation UIAllCustom validation UI

Polyfill Strategy

// polyfills.ts (load conditionally)
export async function loadPolyfills() {
  const polyfills: Promise<void>[] = []
 
  if (!('IntersectionObserver' in window)) {
    polyfills.push(import('intersection-observer'))
  }
 
  if (!('ResizeObserver' in window)) {
    polyfills.push(
      import('@juggle/resize-observer').then((module) => {
        window.ResizeObserver = module.ResizeObserver
      })
    )
  }
 
  if (!Element.prototype.scrollIntoView) {
    polyfills.push(import('scroll-into-view-if-needed'))
  }
 
  await Promise.all(polyfills)
}

Browser Testing Checklist

  1. Layout - No broken layouts or overflow issues
  2. Typography - Fonts render correctly
  3. Forms - Inputs, validation, submission work
  4. Navigation - Links, routing, history work
  5. Media - Images, videos, audio play correctly
  6. Animations - CSS/JS animations perform well
  7. Touch - Mobile gestures work (Safari iOS)
  8. Printing - Print stylesheets render correctly

Testing Tools

ToolPurposeCost
PlaywrightAutomated testingFree
BrowserStackReal device testingPaid
LambdaTestCross-browser testingPaid
Sauce LabsCI integrationPaid
caniuse.comFeature support checkFree
Test on Real Safari

Safari on iOS cannot be emulated perfectly. Use real iOS devices or BrowserStack for accurate Safari testing, especially for touch interactions and PWA features.

Support Notes

  • Tooling, browser-automation behavior, and CI environments can vary across platforms, so verify the intended workflow in the environments the team actually ships and tests against.
  • Document any fallback when a browser-specific testing capability is unavailable in part of the supported matrix.

Verification

Automated Checks

  • Test one primary path and one edge case affected by the change.
  • Use browser or CI tooling where applicable to verify the fix.

Manual Checks

  • Confirm the rule in the final rendered output or runtime behavior.
  • Re-check shared abstractions so the fix is applied consistently.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Verify that this website works correctly across major browsers (Chrome, Firefox, Safari, Edge).

Fix

Auto-fix issues

Fix browser-specific issues and ensure consistent experience across different browsers.

Explain

Learn more

Explain the importance of testing across browsers to ensure all users have a good experience.

Review

Code review

Review tests, CI workflows, and enforcement points related to Test across all major browsers. Flag exact gaps where the rule is not automatically verified or where failures do not block regressions.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

Playwright
playwright.devTool

Rules that often go hand-in-hand with this one.

Implement end-to-end testing

Use E2E testing frameworks like Playwright or Cypress to test critical user journeys.

Testing
Test on real mobile devices and viewports

Verify your application on real mobile devices and browser DevTools device emulation to catch touch interaction issues, viewport bugs, and mobile-specific rendering problems.

Testing
Include accessibility testing

Automate accessibility testing with tools like axe-core, jest-axe, or Playwright's accessibility testing.

Testing
Test with screen readers

Pages must be tested with actual screen reader software (NVDA, JAWS, VoiceOver, TalkBack) to verify announcements, focus order, and widget behavior beyond what automated tools can detect.

Accessibility

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385