What is Lighthouse?

Lighthouse is an open-source, automated tool from Google for improving web page quality. It provides comprehensive audits for performance, accessibility, progressive web apps, SEO, and best practices. For QA engineers, Lighthouse is essential for validating that applications meet modern web performance standards, especially Core Web Vitals — Google’s key metrics for user experience.

Why Lighthouse Matters for QA

  • Core Web Vitals Impact SEO - Google uses LCP, FID, and CLS as ranking factors
  • User Experience Validation - Quantify perceived performance with real metrics
  • CI/CD Integration - Automate performance regression testing in pipelines
  • Actionable Insights - Get specific recommendations for performance improvements
  • Mobile Performance - Test responsive design and mobile-specific issues

Core Web Vitals Explained

Core Web Vitals are three key metrics that measure real-world user experience:

1. Largest Contentful Paint (LCP)

What it measures: Loading performance - when the largest content element becomes visible.

Thresholds:

  • Good: ≤ 2.5 seconds
  • Needs Improvement: 2.5 - 4.0 seconds
  • Poor: > 4.0 seconds

Common Issues:

// Problem: Render-blocking JavaScript
<script src="analytics.js"></script>

// Solution: Defer non-critical scripts
<script src="analytics.js" defer></script>

// Problem: Unoptimized images
<img src="hero-10mb.jpg" alt="Hero">

// Solution: Modern formats + responsive images
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Hero" loading="lazy">
</picture>

Optimization Strategies:

  • Use CDN for static assets
  • Implement lazy loading for below-fold images
  • Preload critical resources: <link rel="preload" href="hero.jpg" as="image">
  • Optimize server response times (TTFB < 600ms)
  • Use resource hints: dns-prefetch, preconnect

2. First Input Delay (FID) / Interaction to Next Paint (INP)

What it measures: Interactivity - delay between user interaction and browser response.

FID Thresholds (being replaced by INP):

  • Good: ≤ 100ms
  • Needs Improvement: 100 - 300ms
  • Poor: > 300ms

INP Thresholds (new metric from 2024):

  • Good: ≤ 200ms
  • Needs Improvement: 200 - 500ms
  • Poor: > 500ms

Common Issues:

// Problem: Long JavaScript tasks block main thread
function processHugeDataset() {
  // Synchronous loop processing 100k items
  for (let i = 0; i < 100000; i++) {
    heavyCalculation(data[i]);
  }
}

// Solution: Break into smaller chunks with setTimeout
async function processHugeDataset() {
  const chunkSize = 1000;
  for (let i = 0; i < data.length; i += chunkSize) {
    const chunk = data.slice(i, i + chunkSize);
    chunk.forEach(item => heavyCalculation(item));

    // Yield to browser every chunk
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

// Solution: Use Web Workers for heavy computation
// main.js
const worker = new Worker('calculator.worker.js');
worker.postMessage({ data: largeDataset });
worker.onmessage = (e) => {
  displayResults(e.data);
};

// calculator.worker.js
self.onmessage = (e) => {
  const results = heavyCalculation(e.data);
  self.postMessage(results);
};

Optimization Strategies:

  • Split long tasks into smaller chunks (< 50ms each)
  • Use code splitting to reduce JavaScript bundle size
  • Defer third-party scripts
  • Implement service workers for better caching
  • Use requestIdleCallback for non-urgent work

3. Cumulative Layout Shift (CLS)

What it measures: Visual stability - unexpected layout shifts during page load.

Thresholds:

  • Good: ≤ 0.1
  • Needs Improvement: 0.1 - 0.25
  • Poor: > 0.25

Common Issues:

<!-- Problem: Images without dimensions -->
<img src="product.jpg" alt="Product">

<!-- Solution: Always specify width/height -->
<img src="product.jpg" alt="Product" width="800" height="600">

<!-- Problem: Dynamic content insertion -->
<div id="banner"></div>
<script>
  // Loads ad asynchronously, pushing content down
  loadAdvertisement('#banner');
</script>

<!-- Solution: Reserve space for dynamic content -->
<div id="banner" style="min-height: 250px;">
  <!-- Ad loads here without layout shift -->
</div>
/* Problem: Web fonts cause layout shift (FOIT/FOUT) */
@font-face {
  font-family: 'CustomFont';
  src: url('custom-font.woff2') format('woff2');
}

/* Solution: Use font-display to control loading behavior */
@font-face {
  font-family: 'CustomFont';
  src: url('custom-font.woff2') format('woff2');
  font-display: swap; /* Show fallback immediately, swap when loaded */
}

/* Better: Preload critical fonts */
/* In HTML: <link rel="preload" href="custom-font.woff2" as="font" crossorigin> */

Optimization Strategies:

  • Always include size attributes on images and videos
  • Reserve space for ads and embeds with min-height
  • Avoid inserting content above existing content
  • Use transform animations instead of layout-triggering properties
  • Preload custom fonts and use font-display: swap

Running Lighthouse Tests

1. Chrome DevTools (Manual Testing)

# Open Chrome DevTools
# 1. Press F12 or Cmd+Option+I
# 2. Go to "Lighthouse" tab
# 3. Select categories (Performance, Accessibility, etc.)
# 4. Choose device (Mobile/Desktop)
# 5. Click "Analyze page load"

Best Practices for Manual Tests:

  • Test in Incognito mode (no extensions interference)
  • Use throttling to simulate real-world conditions (Slow 4G, 4x CPU slowdown)
  • Run multiple tests and average scores (results can vary ±5 points)
  • Test both mobile and desktop configurations

2. Lighthouse CLI (Automated Testing)

# Install Lighthouse
npm install -g lighthouse

# Basic test
lighthouse https://example.com

# Mobile test with output formats
lighthouse https://example.com \
  --output html,json,csv \
  --output-path ./reports/report \
  --view \
  --chrome-flags="--headless"

# Test with specific throttling
lighthouse https://example.com \
  --throttling.rttMs=150 \
  --throttling.throughputKbps=1600 \
  --throttling.cpuSlowdownMultiplier=4

# Test specific categories only
lighthouse https://example.com \
  --only-categories=performance,accessibility

# Custom config file
lighthouse https://example.com \
  --config-path=./lighthouse-config.js

3. Lighthouse CI (Continuous Integration)

# .github/workflows/lighthouse.yml
name: Lighthouse CI

on:
  pull_request:
    branches: [main]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Build application
        run: npm run build

      - name: Start server
        run: npm start &

      - name: Wait for server
        run: npx wait-on http://localhost:3000

      - name: Run Lighthouse CI
        run: |
          npm install -g @lhci/cli
          lhci autorun
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

      - name: Upload results
        uses: actions/upload-artifact@v3
        with:
          name: lighthouse-reports
          path: .lighthouseci/

lighthouserc.json Configuration:

{
  "ci": {
    "collect": {
      "url": ["http://localhost:3000"],
      "numberOfRuns": 3,
      "settings": {
        "preset": "desktop",
        "throttling": {
          "rttMs": 40,
          "throughputKbps": 10240,
          "cpuSlowdownMultiplier": 1
        }
      }
    },
    "assert": {
      "preset": "lighthouse:recommended",
      "assertions": {
        "categories:performance": ["error", {"minScore": 0.9}],
        "categories:accessibility": ["error", {"minScore": 0.9}],
        "first-contentful-paint": ["error", {"maxNumericValue": 2000}],
        "largest-contentful-paint": ["error", {"maxNumericValue": 2500}],
        "cumulative-layout-shift": ["error", {"maxNumericValue": 0.1}],
        "total-blocking-time": ["error", {"maxNumericValue": 300}]
      }
    },
    "upload": {
      "target": "temporary-public-storage"
    }
  }
}

Interpreting Lighthouse Scores

Performance Score Breakdown

The Performance score (0-100) is weighted across multiple metrics:

MetricWeightDescription
First Contentful Paint (FCP)10%First text/image rendered
Largest Contentful Paint (LCP)25%Largest content element visible
Total Blocking Time (TBT)30%Total time main thread is blocked
Cumulative Layout Shift (CLS)25%Visual stability score
Speed Index10%How quickly content is visually displayed

Score Ranges:

  • 90-100 (Green): Good - meets performance best practices
  • 50-89 (Orange): Needs improvement - some optimization required
  • 0-49 (Red): Poor - significant performance issues

Performance Budget Strategy

// lighthouserc-budget.json
{
  "ci": {
    "assert": {
      "assertions": {
        // Core Web Vitals thresholds
        "largest-contentful-paint": ["error", {"maxNumericValue": 2500}],
        "cumulative-layout-shift": ["error", {"maxNumericValue": 0.1}],
        "total-blocking-time": ["error", {"maxNumericValue": 300}],

        // Resource budgets
        "resource-summary:script:size": ["error", {"maxNumericValue": 300000}],
        "resource-summary:image:size": ["error", {"maxNumericValue": 500000}],
        "resource-summary:stylesheet:size": ["error", {"maxNumericValue": 50000}],
        "resource-summary:document:size": ["error", {"maxNumericValue": 50000}],
        "resource-summary:font:size": ["error", {"maxNumericValue": 100000}],
        "resource-summary:third-party:size": ["error", {"maxNumericValue": 200000}],

        // Request count budgets
        "resource-summary:script:count": ["error", {"maxNumericValue": 15}],
        "resource-summary:third-party:count": ["error", {"maxNumericValue": 10}]
      }
    }
  }
}

Advanced Lighthouse Techniques

1. Custom Audits

// custom-audit.js
class CustomPerformanceAudit {
  static get meta() {
    return {
      id: 'custom-api-performance',
      title: 'API Response Time',
      failureTitle: 'API responses are slow',
      description: 'Measures API endpoint response times',
      requiredArtifacts: ['devtoolsLogs']
    };
  }

  static async audit(artifacts) {
    const devtools = artifacts.devtoolsLogs.defaultPass;
    const networkRecords = /* extract network records */;

    const apiCalls = networkRecords.filter(r =>
      r.url.includes('/api/') && r.finished
    );

    const avgResponseTime = apiCalls.reduce((sum, call) =>
      sum + call.endTime - call.startTime, 0
    ) / apiCalls.length;

    const passed = avgResponseTime < 500; // 500ms threshold

    return {
      score: passed ? 1 : 0,
      numericValue: avgResponseTime,
      displayValue: `${Math.round(avgResponseTime)}ms average`,
      details: {
        type: 'table',
        headings: [
          {key: 'url', text: 'URL'},
          {key: 'responseTime', text: 'Response Time'}
        ],
        items: apiCalls.map(call => ({
          url: call.url,
          responseTime: `${Math.round(call.endTime - call.startTime)}ms`
        }))
      }
    };
  }
}

module.exports = CustomPerformanceAudit;

2. User Flow Testing (New in Lighthouse 9+)

// user-flow-test.js
import {startFlow} from 'lighthouse/lighthouse-core/fraggle-rock/api.js';
import puppeteer from 'puppeteer';

async function runUserFlow() {
  const browser = await puppeteer.launch({headless: 'new'});
  const page = await browser.newPage();

  const flow = await startFlow(page, {name: 'E-commerce checkout flow'});

  // Step 1: Navigate to homepage
  await flow.navigate('https://example.com', {
    stepName: 'Homepage'
  });

  // Step 2: Search for product
  await flow.startTimespan({stepName: 'Search interaction'});
  await page.type('#search', 'laptop');
  await page.click('#search-button');
  await page.waitForSelector('.search-results');
  await flow.endTimespan();

  // Step 3: Product details page
  await flow.navigate('https://example.com/product/123', {
    stepName: 'Product page'
  });

  // Step 4: Add to cart interaction
  await flow.startTimespan({stepName: 'Add to cart'});
  await page.click('#add-to-cart');
  await page.waitForSelector('.cart-notification');
  await flow.endTimespan();

  // Step 5: Checkout snapshot
  await flow.snapshot({stepName: 'Cart page state'});

  // Generate report
  const report = await flow.generateReport();
  fs.writeFileSync('user-flow-report.html', report);

  await browser.close();
}

runUserFlow();

3. Performance Monitoring Dashboard

# lighthouse-monitor.py
import subprocess
import json
import datetime
from influxdb import InfluxDBClient

def run_lighthouse(url):
    """Run Lighthouse and return metrics"""
    result = subprocess.run([
        'lighthouse',
        url,
        '--output=json',
        '--output-path=stdout',
        '--chrome-flags="--headless"'
    ], capture_output=True, text=True)

    return json.loads(result.stdout)

def extract_metrics(report):
    """Extract key metrics from Lighthouse report"""
    audits = report['audits']

    return {
        'performance_score': report['categories']['performance']['score'] * 100,
        'fcp': audits['first-contentful-paint']['numericValue'],
        'lcp': audits['largest-contentful-paint']['numericValue'],
        'tbt': audits['total-blocking-time']['numericValue'],
        'cls': audits['cumulative-layout-shift']['numericValue'],
        'speed_index': audits['speed-index']['numericValue'],
    }

def send_to_influxdb(metrics, url):
    """Send metrics to InfluxDB for Grafana visualization"""
    client = InfluxDBClient(host='localhost', port=8086, database='lighthouse')

    point = {
        'measurement': 'performance',
        'tags': {
            'url': url,
            'device': 'mobile'
        },
        'time': datetime.datetime.utcnow().isoformat(),
        'fields': metrics
    }

    client.write_points([point])

# Monitor pages every hour
urls = [
    'https://example.com',
    'https://example.com/products',
    'https://example.com/checkout'
]

for url in urls:
    report = run_lighthouse(url)
    metrics = extract_metrics(report)
    send_to_influxdb(metrics, url)
    print(f"✓ {url}: Performance Score {metrics['performance_score']}")

Common Performance Bottlenecks and Fixes

Issue: Large JavaScript Bundles

# Analyze bundle size
npm install -g webpack-bundle-analyzer

# Add to webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
};

# Build and analyze
npm run build

Solutions:

  • Code splitting: import('./heavy-component.js').then(module => ...)
  • Tree shaking: Ensure "sideEffects": false in package.json
  • Remove unused dependencies
  • Use dynamic imports for route-based code splitting

Issue: Render-Blocking Resources

<!-- Problem: Blocking CSS -->
<link rel="stylesheet" href="styles.css">

<!-- Solution: Critical CSS inline, defer non-critical -->
<style>
  /* Critical above-fold CSS inline */
  .header { /* ... */ }
  .hero { /* ... */ }
</style>
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="styles.css"></noscript>

Issue: Unoptimized Images

# Install image optimization tools
npm install -D imagemin imagemin-webp imagemin-avif

# optimize-images.js
const imagemin = require('imagemin');
const imageminWebp = require('imagemin-webp');
const imageminAvif = require('imagemin-avif');

(async () => {
  await imagemin(['images/*.{jpg,png}'], {
    destination: 'images/optimized',
    plugins: [
      imageminWebp({quality: 80}),
      imageminAvif({quality: 65})
    ]
  });
})();

Conclusion

Lighthouse is an indispensable tool for modern web performance testing. By integrating Lighthouse into your QA workflow — from manual DevTools audits to automated CI/CD (as discussed in Load Testing with JMeter: Complete Guide) pipelines — you ensure applications meet Core Web Vitals standards and deliver exceptional user experiences.

Key Takeaways:

  • Focus on Core Web Vitals: LCP, INP/FID, CLS
  • Automate Lighthouse tests in CI/CD (as discussed in Burp Suite for QA Engineers: Complete Security Testing Guide) pipelines
  • Set performance budgets and enforce them
  • Use User Flow testing for complex interactions
  • Monitor performance over time with dashboards
  • Optimize images, JavaScript, and rendering paths

Performance is a feature, not an afterthought. Make Lighthouse testing a core part of your QA process.