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:
Metric | Weight | Description |
---|---|---|
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 Index | 10% | 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.