Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Testowanie cross-browser

Testowanie cross-browser to krytyczny proces w developmencie front-end, który zapewnia, że aplikacje webowe działają poprawnie we wszystkich obsługiwanych przeglądarkach i na różnych urządzeniach. Różnice w implementacji standardów webowych między przeglądarkami mogą prowadzić do niespójności w wyglądzie i funkcjonalności.

Podstawy testowania cross-browser

Główne przeglądarki do testowania

Współczesny landscape przeglądarek składa się z kilku głównych silników:

1// Statystyki użycia przeglądarek (2024)
2const browserStats = {
3  chrome: "65%",      // Blink engine
4  safari: "19%",      // WebKit engine  
5  edge: "5%",         // Blink engine (Chromium-based)
6  firefox: "3%",      // Gecko engine
7  opera: "2%",        // Blink engine
8  others: "6%"
9};
10
11// Mobilne przeglądarki
12const mobileBrowsers = {
13  chrome: "65%",
14  safari: "25%",
15  samsung: "4%",
16  opera: "2%",
17  firefox: "1%",
18  others: "3%"
19};

Kluczowe różnice między przeglądarkami

1. Wsparcie CSS

1/* Prefiks vendorów dla nowszych funkcji */
2.element {
3  -webkit-transform: rotate(45deg);  /* Safari, Chrome */
4  -moz-transform: rotate(45deg);     /* Firefox */
5  -ms-transform: rotate(45deg);      /* Internet Explorer */
6  transform: rotate(45deg);          /* Standard */
7}
8
9/* Różnice w implementacji Grid */
10.grid-container {
11  display: grid;
12  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
13  gap: 20px; /* IE nie wspiera gap w Grid */
14}
15
16/* Fallback dla IE */
17@supports not (gap: 20px) {
18  .grid-container > * {
19    margin: 10px;
20  }
21}

2. JavaScript API differences

1// Fetch API - nie wspierane w IE
2function fetchDataCompatible(url) {
3  if (window.fetch) {
4    // Nowoczesne przeglądarki
5    return fetch(url).then(response => response.json());
6  } else {
7    // Fallback dla starszych przeglądarek
8    return new Promise((resolve, reject) => {
9      const xhr = new XMLHttpRequest();
10      xhr.open('GET', url);
11      xhr.onload = () => {
12        if (xhr.status === 200) {
13          resolve(JSON.parse(xhr.responseText));
14        } else {
15          reject(new Error('Request failed'));
16        }
17      };
18      xhr.onerror = () => reject(new Error('Network error'));
19      xhr.send();
20    });
21  }
22}
23
24// Intersection Observer - wymaga polyfill w IE
25function observeElementsCompatible(elements, callback) {
26  if ('IntersectionObserver' in window) {
27    const observer = new IntersectionObserver(callback);
28    elements.forEach(el => observer.observe(el));
29  } else {
30    // Fallback - scroll listener
31    function checkVisibility() {
32      elements.forEach(element => {
33        const rect = element.getBoundingClientRect();
34        const isVisible = rect.top < window.innerHeight && rect.bottom > 0;
35        if (isVisible) callback([{ target: element, isIntersecting: true }]);
36      });
37    }
38    
39    window.addEventListener('scroll', checkVisibility);
40    window.addEventListener('resize', checkVisibility);
41    checkVisibility();
42  }
43}

Narzędzia do testowania cross-browser

1. BrowserStack

1// Konfiguracja BrowserStack dla automatycznych testów
2const browserStackConfig = {
3  user: process.env.BROWSERSTACK_USERNAME,
4  key: process.env.BROWSERSTACK_ACCESS_KEY,
5  
6  capabilities: [
7    {
8      browserName: 'Chrome',
9      browserVersion: 'latest',
10      os: 'Windows',
11      osVersion: '10'
12    },
13    {
14      browserName: 'Safari',
15      browserVersion: 'latest', 
16      os: 'OS X',
17      osVersion: 'Big Sur'
18    },
19    {
20      browserName: 'Firefox',
21      browserVersion: 'latest',
22      os: 'Windows',
23      osVersion: '10'
24    }
25  ]
26};

2. Sauce Labs

1# .sauce/config.yml
2apiVersion: v1alpha
3kind: cypress
4sauce:
5  region: eu-central-1
6  
7suites:
8  - name: "Cross Browser Tests"
9    browser: "chrome"
10    browserVersion: "latest"
11    platformName: "Windows 10"
12    
13  - name: "Safari Tests"
14    browser: "safari"
15    browserVersion: "latest"
16    platformName: "macOS 11.00"
17    
18cypress:
19  configFile: "cypress.config.js"
20  version: "12.0.0"

3. Playwright

1// playwright.config.js - konfiguracja do testowania cross-browser
2const { defineConfig, devices } = require('@playwright/test');
3
4module.exports = defineConfig({
5  testDir: './tests',
6  
7  projects: [
8    {
9      name: 'chromium',
10      use: { ...devices['Desktop Chrome'] },
11    },
12    {
13      name: 'firefox',
14      use: { ...devices['Desktop Firefox'] },
15    },
16    {
17      name: 'webkit',
18      use: { ...devices['Desktop Safari'] },
19    },
20    {
21      name: 'Mobile Chrome',
22      use: { ...devices['Pixel 5'] },
23    },
24    {
25      name: 'Mobile Safari',
26      use: { ...devices['iPhone 12'] },
27    },
28  ],
29});
30
31// Przykład testu cross-browser
32// tests/cross-browser.spec.js
33const { test, expect } = require('@playwright/test');
34
35test.describe('Cross-browser compatibility', () => {
36  test('CSS Grid layout works correctly', async ({ page }) => {
37    await page.goto('/grid-layout');
38    
39    // Sprawdź czy grid jest poprawnie wyświetlany
40    const gridContainer = page.locator('.grid-container');
41    await expect(gridContainer).toBeVisible();
42    
43    // Sprawdź liczbę kolumn
44    const gridItems = page.locator('.grid-item');
45    const count = await gridItems.count();
46    expect(count).toBeGreaterThan(0);
47    
48    // Sprawdź responsywność
49    await page.setViewportSize({ width: 768, height: 1024 });
50    await expect(gridContainer).toBeVisible();
51  });
52  
53  test('JavaScript features work across browsers', async ({ page }) => {
54    await page.goto('/js-features');
55    
56    // Test Fetch API lub XHR fallback
57    const result = await page.evaluate(async () => {
58      const response = await fetchDataCompatible('/api/test');
59      return response.status === 'ok';
60    });
61    
62    expect(result).toBe(true);
63  });
64});

4. Lokalne środowiska testowe

1# Selenium Grid setup dla lokalnego testowania
2# docker-compose.yml
3version: '3'
4services:
5  selenium-hub:
6    image: selenium/hub:4.0.0
7    container_name: selenium-hub
8    ports:
9      - "4444:4444"
10      
11  chrome:
12    image: selenium/node-chrome:4.0.0
13    shm_size: 2gb
14    depends_on:
15      - selenium-hub
16    environment:
17      - HUB_HOST=selenium-hub
18      - HUB_PORT=4444
19      
20  firefox:
21    image: selenium/node-firefox:4.0.0
22    shm_size: 2gb
23    depends_on:
24      - selenium-hub
25    environment:
26      - HUB_HOST=selenium-hub
27      - HUB_PORT=4444

Strategie kompatybilności

1. Progressive Enhancement

1/* Podstawowe style dla wszystkich przeglądarek */
2.card {
3  border: 1px solid #ddd;
4  padding: 16px;
5  margin-bottom: 16px;
6  background: white;
7}
8
9/* Ulepszenia dla nowoczesnych przeglądarek */
10@supports (display: grid) {
11  .card-container {
12    display: grid;
13    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
14    gap: 20px;
15  }
16  
17  .card {
18    margin-bottom: 0;
19  }
20}
21
22/* Dalsze ulepszenia */
23@supports (backdrop-filter: blur(10px)) {
24  .card--glass {
25    background: rgba(255, 255, 255, 0.8);
26    backdrop-filter: blur(10px);
27  }
28}
1// JavaScript Progressive Enhancement
2class FeatureDetector {
3  static supportsIntersectionObserver() {
4    return 'IntersectionObserver' in window;
5  }
6  
7  static supportsWebP() {
8    const canvas = document.createElement('canvas');
9    canvas.width = 1;
10    canvas.height = 1;
11    return canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0;
12  }
13  
14  static supportsCustomProperties() {
15    return CSS.supports && CSS.supports('(--foo: red)');
16  }
17  
18  static init() {
19    // Lazy loading z fallbackiem
20    if (this.supportsIntersectionObserver()) {
21      this.initLazyLoadingModern();
22    } else {
23      this.initLazyLoadingFallback();
24    }
25    
26    // WebP support detection
27    if (this.supportsWebP()) {
28      document.documentElement.classList.add('webp');
29    } else {
30      document.documentElement.classList.add('no-webp');
31    }
32  }
33  
34  static initLazyLoadingModern() {
35    const images = document.querySelectorAll('img[data-src]');
36    const imageObserver = new IntersectionObserver((entries) => {
37      entries.forEach(entry => {
38        if (entry.isIntersecting) {
39          const img = entry.target;
40          img.src = img.dataset.src;
41          img.classList.remove('lazy');
42          imageObserver.unobserve(img);
43        }
44      });
45    });
46    
47    images.forEach(img => imageObserver.observe(img));
48  }
49  
50  static initLazyLoadingFallback() {
51    const images = document.querySelectorAll('img[data-src]');
52    
53    function loadImagesInViewport() {
54      images.forEach(img => {
55        if (img.dataset.src) {
56          const rect = img.getBoundingClientRect();
57          if (rect.top < window.innerHeight + 100) {
58            img.src = img.dataset.src;
59            delete img.dataset.src;
60            img.classList.remove('lazy');
61          }
62        }
63      });
64    }
65    
66    window.addEventListener('scroll', loadImagesInViewport);
67    window.addEventListener('resize', loadImagesInViewport);
68    loadImagesInViewport();
69  }
70}
71
72// Inicjalizacja przy ładowaniu strony
73document.addEventListener('DOMContentLoaded', () => {
74  FeatureDetector.init();
75});

2. Graceful Degradation

1/* Nowoczesne style z fallbackami */
2.hero-section {
3  /* Fallback dla starych przeglądarek */
4  background: linear-gradient(to right, #667eea, #764ba2);
5  
6  /* Nowoczesne style */
7  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
8  min-height: 100vh;
9  display: flex;
10  align-items: center;
11  justify-content: center;
12}
13
14/* Fallback dla flexbox */
15.hero-section {
16  text-align: center;
17  padding-top: 20vh;
18}
19
20@supports (display: flex) {
21  .hero-section {
22    padding-top: 0;
23    text-align: left;
24  }
25}
26
27/* CSS Variables z fallbackiem */
28.theme-primary {
29  color: #007bff; /* Fallback */
30  color: var(--primary-color, #007bff);
31}

3. Polyfills i shims

1// Polyfill dla Array.includes (IE)
2if (!Array.prototype.includes) {
3  Array.prototype.includes = function(searchElement, fromIndex) {
4    return this.indexOf(searchElement, fromIndex) !== -1;
5  };
6}
7
8// Polyfill dla Object.assign
9if (!Object.assign) {
10  Object.assign = function(target, ...sources) {
11    if (target == null) {
12      throw new TypeError('Cannot convert undefined or null to object');
13    }
14    
15    const to = Object(target);
16    
17    for (let index = 0; index < sources.length; index++) {
18      const nextSource = sources[index];
19      if (nextSource != null) {
20        for (const nextKey in nextSource) {
21          if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
22            to[nextKey] = nextSource[nextKey];
23          }
24        }
25      }
26    }
27    
28    return to;
29  };
30}
31
32// Intersection Observer Polyfill
33(function() {
34  if ('IntersectionObserver' in window) return;
35  
36  // Implementacja podstawowego polyfill
37  window.IntersectionObserver = class {
38    constructor(callback, options = {}) {
39      this.callback = callback;
40      this.options = options;
41      this.observedElements = new Set();
42      this.checkIntersections = this.checkIntersections.bind(this);
43      
44      this.startListening();
45    }
46    
47    observe(element) {
48      this.observedElements.add(element);
49      this.checkIntersections();
50    }
51    
52    unobserve(element) {
53      this.observedElements.delete(element);
54    }
55    
56    disconnect() {
57      this.observedElements.clear();
58      this.stopListening();
59    }
60    
61    startListening() {
62      window.addEventListener('scroll', this.checkIntersections);
63      window.addEventListener('resize', this.checkIntersections);
64    }
65    
66    stopListening() {
67      window.removeEventListener('scroll', this.checkIntersections);
68      window.removeEventListener('resize', this.checkIntersections);
69    }
70    
71    checkIntersections() {
72      const entries = [];
73      
74      this.observedElements.forEach(element => {
75        const rect = element.getBoundingClientRect();
76        const isIntersecting = (
77          rect.top < window.innerHeight &&
78          rect.bottom > 0 &&
79          rect.left < window.innerWidth &&
80          rect.right > 0
81        );
82        
83        entries.push({
84          target: element,
85          isIntersecting,
86          boundingClientRect: rect,
87          intersectionRatio: isIntersecting ? 1 : 0
88        });
89      });
90      
91      if (entries.length > 0) {
92        this.callback(entries);
93      }
94    }
95  };
96})();

Automatyzacja testów cross-browser

1. GitHub Actions workflow

1# .github/workflows/cross-browser-tests.yml
2name: Cross-Browser Tests
3
4on:
5  push:
6    branches: [ main, develop ]
7  pull_request:
8    branches: [ main ]
9
10jobs:
11  test:
12    runs-on: ubuntu-latest
13    
14    strategy:
15      matrix:
16        browser: ['chrome', 'firefox', 'safari']
17        
18    steps:
19    - uses: actions/checkout@v3
20    
21    - name: Setup Node.js
22      uses: actions/setup-node@v3
23      with:
24        node-version: '18'
25        cache: 'npm'
26        
27    - name: Install dependencies
28      run: npm ci
29      
30    - name: Install Playwright
31      run: npx playwright install --with-deps
32      
33    - name: Run tests
34      run: npx playwright test --project=${{ matrix.browser }}
35      
36    - name: Upload test results
37      uses: actions/upload-artifact@v3
38      if: failure()
39      with:
40        name: test-results-${{ matrix.browser }}
41        path: test-results/

2. Jest configuration dla różnych środowisk

1// jest.config.js
2module.exports = {
3  projects: [
4    {
5      displayName: 'jsdom',
6      testEnvironment: 'jsdom',
7      testMatch: ['<rootDir>/src/**/*.test.js'],
8      setupFilesAfterEnv: ['<rootDir>/src/setupTests.js']
9    },
10    {
11      displayName: 'node',
12      testEnvironment: 'node',
13      testMatch: ['<rootDir>/server/**/*.test.js']
14    }
15  ],
16  
17  // Global setup dla cross-browser testów
18  globalSetup: '<rootDir>/test/global-setup.js',
19  globalTeardown: '<rootDir>/test/global-teardown.js'
20};
21
22// setupTests.js - polyfills dla testów
23import 'intersection-observer';
24import 'whatwg-fetch';
25
26// Mock dla matchMedia (nie wspierane w jsdom)
27Object.defineProperty(window, 'matchMedia', {
28  writable: true,
29  value: jest.fn().mockImplementation(query => ({
30    matches: false,
31    media: query,
32    onchange: null,
33    addListener: jest.fn(),
34    removeListener: jest.fn(),
35    addEventListener: jest.fn(),
36    removeEventListener: jest.fn(),
37    dispatchEvent: jest.fn(),
38  })),
39});

Debugowanie problemów cross-browser

1. DevTools differences

1// Uniwersalne debugowanie
2const Logger = {
3  log: (...args) => {
4    if (console && console.log) {
5      console.log(...args);
6    }
7  },
8  
9  warn: (...args) => {
10    if (console && console.warn) {
11      console.warn(...args);
12    } else if (console && console.log) {
13      console.log('WARNING:', ...args);
14    }
15  },
16  
17  error: (...args) => {
18    if (console && console.error) {
19      console.error(...args);
20    } else if (console && console.log) {
21      console.log('ERROR:', ...args);
22    }
23  },
24  
25  table: (data) => {
26    if (console && console.table) {
27      console.table(data);
28    } else {
29      Logger.log('TABLE:', JSON.stringify(data, null, 2));
30    }
31  }
32};
33
34// Browser detection dla debugowania
35const BrowserDetector = {
36  isIE: () => navigator.userAgent.indexOf('MSIE') !== -1 || navigator.userAgent.indexOf('Trident') !== -1,
37  isEdge: () => navigator.userAgent.indexOf('Edge') !== -1,
38  isChrome: () => navigator.userAgent.indexOf('Chrome') !== -1 && navigator.userAgent.indexOf('Edge') === -1,
39  isFirefox: () => navigator.userAgent.indexOf('Firefox') !== -1,
40  isSafari: () => navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1,
41  
42  getInfo: () => ({
43    userAgent: navigator.userAgent,
44    vendor: navigator.vendor,
45    platform: navigator.platform,
46    language: navigator.language,
47    cookieEnabled: navigator.cookieEnabled,
48    onLine: navigator.onLine
49  })
50};
51
52// Debugging w różnych przeglądarkach
53function debugCompatibility() {
54  Logger.log('Browser Info:', BrowserDetector.getInfo());
55  
56  // Test CSS features
57  const cssFeatures = {
58    flexbox: CSS.supports('display', 'flex'),
59    grid: CSS.supports('display', 'grid'),
60    customProperties: CSS.supports('--foo', 'red'),
61    backdropFilter: CSS.supports('backdrop-filter', 'blur(10px)'),
62    objectFit: CSS.supports('object-fit', 'cover')
63  };
64  
65  Logger.table(cssFeatures);
66  
67  // Test JavaScript features
68  const jsFeatures = {
69    fetch: typeof fetch !== 'undefined',
70    intersectionObserver: 'IntersectionObserver' in window,
71    customElements: 'customElements' in window,
72    serviceWorker: 'serviceWorker' in navigator,
73    webp: (() => {
74      const canvas = document.createElement('canvas');
75      return canvas.toDataURL('image/webp').indexOf('data:image/webp') === 0;
76    })()
77  };
78  
79  Logger.table(jsFeatures);
80}
81
82// Wywołaj debugging w development mode
83if (process.env.NODE_ENV === 'development') {
84  debugCompatibility();
85}

2. Remote debugging

1// Remote debugging setup dla urządzeń mobilnych
2class RemoteDebugger {
3  constructor() {
4    this.logs = [];
5    this.errors = [];
6    this.init();
7  }
8  
9  init() {
10    // Przechwytywanie błędów JavaScript
11    window.addEventListener('error', (event) => {
12      const error = {
13        message: event.message,
14        filename: event.filename,
15        lineno: event.lineno,
16        colno: event.colno,
17        stack: event.error ? event.error.stack : null,
18        timestamp: new Date().toISOString(),
19        userAgent: navigator.userAgent
20      };
21      
22      this.errors.push(error);
23      this.sendToRemoteDebugger('error', error);
24    });
25    
26    // Przechwytywanie rejected promises
27    window.addEventListener('unhandledrejection', (event) => {
28      const error = {
29        reason: event.reason.toString(),
30        stack: event.reason.stack,
31        timestamp: new Date().toISOString(),
32        userAgent: navigator.userAgent
33      };
34      
35      this.errors.push(error);
36      this.sendToRemoteDebugger('rejection', error);
37    });
38    
39    // Override console methods
40    this.interceptConsole();
41  }
42  
43  interceptConsole() {
44    const originalLog = console.log;
45    const originalError = console.error;
46    const originalWarn = console.warn;
47    
48    console.log = (...args) => {
49      originalLog.apply(console, args);
50      this.sendToRemoteDebugger('log', args);
51    };
52    
53    console.error = (...args) => {
54      originalError.apply(console, args);
55      this.sendToRemoteDebugger('error', args);
56    };
57    
58    console.warn = (...args) => {
59      originalWarn.apply(console, args);
60      this.sendToRemoteDebugger('warn', args);
61    };
62  }
63  
64  sendToRemoteDebugger(type, data) {
65    // Wyślij dane do remote debugging service
66    if (navigator.sendBeacon) {
67      navigator.sendBeacon('/api/debug', JSON.stringify({
68        type,
69        data,
70        timestamp: new Date().toISOString(),
71        url: window.location.href,
72        userAgent: navigator.userAgent
73      }));
74    }
75  }
76  
77  getDeviceInfo() {
78    return {
79      screen: {
80        width: screen.width,
81        height: screen.height,
82        availWidth: screen.availWidth,
83        availHeight: screen.availHeight,
84        pixelRatio: window.devicePixelRatio
85      },
86      viewport: {
87        width: window.innerWidth,
88        height: window.innerHeight
89      },
90      browser: navigator.userAgent,
91      language: navigator.language,
92      platform: navigator.platform,
93      touchSupport: 'ontouchstart' in window,
94      orientation: screen.orientation ? screen.orientation.angle : 'unknown'
95    };
96  }
97}
98
99// Inicjalizacja tylko w development
100if (process.env.NODE_ENV === 'development') {
101  new RemoteDebugger();
102}

Best practices

1. Testowanie na prawdziwych urządzeniach

1// Device farm setup
2const testDevices = [
3  { name: 'iPhone 13', browser: 'Safari', os: 'iOS 15' },
4  { name: 'Samsung Galaxy S21', browser: 'Chrome', os: 'Android 11' },
5  { name: 'iPad Pro', browser: 'Safari', os: 'iPadOS 15' },
6  { name: 'Desktop', browser: 'Chrome', os: 'Windows 10' },
7  { name: 'Desktop', browser: 'Firefox', os: 'Windows 10' },
8  { name: 'Desktop', browser: 'Safari', os: 'macOS Big Sur' }
9];
10
11// Automatyczne testowanie na device farm
12async function runDeviceFarmTests() {
13  for (const device of testDevices) {
14    try {
15      await runTestOnDevice(device);
16      console.log(`✅ Tests passed on ${device.name} - ${device.browser}`);
17    } catch (error) {
18      console.error(`❌ Tests failed on ${device.name} - ${device.browser}:`, error);
19    }
20  }
21}

2. Performance considerations

1// Monitoring wydajności cross-browser
2class PerformanceMonitor {
3  constructor() {
4    this.metrics = {};
5    this.init();
6  }
7  
8  init() {
9    if ('performance' in window) {
10      this.collectNavigationMetrics();
11      this.collectResourceMetrics();
12      this.observeLargestContentfulPaint();
13      this.observeFirstInputDelay();
14    }
15  }
16  
17  collectNavigationMetrics() {
18    if (performance.getEntriesByType) {
19      const navigation = performance.getEntriesByType('navigation')[0];
20      if (navigation) {
21        this.metrics.navigation = {
22          domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
23          loadComplete: navigation.loadEventEnd - navigation.loadEventStart,
24          domInteractive: navigation.domInteractive - navigation.fetchStart,
25          firstByte: navigation.responseStart - navigation.requestStart
26        };
27      }
28    }
29  }
30  
31  collectResourceMetrics() {
32    if (performance.getEntriesByType) {
33      const resources = performance.getEntriesByType('resource');
34      this.metrics.resources = resources.map(resource => ({
35        name: resource.name,
36        duration: resource.duration,
37        size: resource.transferSize,
38        type: resource.initiatorType
39      }));
40    }
41  }
42  
43  observeLargestContentfulPaint() {
44    if ('PerformanceObserver' in window) {
45      try {
46        const observer = new PerformanceObserver((list) => {
47          const entries = list.getEntries();
48          const lastEntry = entries[entries.length - 1];
49          this.metrics.lcp = lastEntry.startTime;
50        });
51        observer.observe({ entryTypes: ['largest-contentful-paint'] });
52      } catch (e) {
53        // Fallback for browsers that don't support LCP
54        console.log('LCP not supported');
55      }
56    }
57  }
58  
59  observeFirstInputDelay() {
60    if ('PerformanceObserver' in window) {
61      try {
62        const observer = new PerformanceObserver((list) => {
63          const entries = list.getEntries();
64          entries.forEach(entry => {
65            this.metrics.fid = entry.processingStart - entry.startTime;
66          });
67        });
68        observer.observe({ entryTypes: ['first-input'] });
69      } catch (e) {
70        // Fallback for browsers that don't support FID
71        console.log('FID not supported');
72      }
73    }
74  }
75  
76  report() {
77    return {
78      ...this.metrics,
79      browser: navigator.userAgent,
80      timestamp: new Date().toISOString()
81    };
82  }
83}
84
85// Użycie
86const monitor = new PerformanceMonitor();
87
88window.addEventListener('load', () => {
89  setTimeout(() => {
90    const report = monitor.report();
91    // Wyślij raport do analytics
92    console.log('Performance Report:', report);
93  }, 1000);
94});

Podsumowanie

Testowanie cross-browser to:

  • Systematyczny proces zapewniający kompatybilność aplikacji
  • Kombinacja narzędzi automatycznych i manualnych testów
  • Strategia progressive enhancement dla lepszego UX
  • Monitoring rzeczywistych użytkowników na różnych platformach
  • Ciągły proces wymagający regularnych aktualizacji

Kluczem do sukcesu jest wczesne planowanie wsparcia przeglądarek, używanie nowoczesnych narzędzi do automatyzacji testów oraz systematyczne monitorowanie wydajności w różnych środowiskach.

Przejdź do CodeWorlds