Troubleshooting Playwright Lighthouse Integration

Fix common issues when running Lighthouse with Playwright: port conflicts, authentication problems, flaky scores, and Chrome version mismatches.
Harlan WiltonHarlan Wilton Published

Common issues and solutions when integrating Lighthouse with Playwright.

Port Already in Use

Error: Error: listen EADDRINUSE: address already in use :::9222

The debugging port is already occupied, usually from a previous run that didn't close properly.

Solution: Kill existing Chrome processes or use a different port:

# Kill process on port 9222
lsof -ti:9222 | xargs kill -9

# Or use a different port
const PORT = 9223

For CI environments, ensure proper cleanup:

async function audit() {
  let browser
  try {
    browser = await chromium.launch({
      args: [`--remote-debugging-port=${PORT}`],
    })
    // ... audit logic
  }
  finally {
    if (browser)
      await browser.close()
  }
}

Authentication State Lost

Symptom: Lighthouse audits a login page instead of the authenticated page.

Cause: Lighthouse opens a new page context, losing cookies/session.

Solution: Use disableStorageReset: true:

const result = await lighthouse(url, {
  port: PORT,
  disableStorageReset: true, // Prevents Lighthouse from clearing storage
})

See Authentication Guide for complete examples.

Flaky or Inconsistent Scores

Symptom: Performance scores vary significantly between runs (±10-20 points).

Causes:

  • Network variability
  • CPU load on the machine
  • Shared CI runners
  • Third-party script timing

Solutions:

  1. Run multiple audits and use median:
const runs = 3
const scores = []
for (let i = 0; i < runs; i++) {
  const result = await lighthouse(url, { port: PORT })
  scores.push(result.lhr.categories.performance.score)
}
scores.sort((a, b) => a - b)
const median = scores[Math.floor(scores.length / 2)]
  1. Disable throttling for consistent local results:
const result = await lighthouse(url, {
  port: PORT,
  throttling: {
    cpuSlowdownMultiplier: 1,
    throughputKbps: 0,
    requestLatencyMs: 0,
  },
})
  1. Use dedicated CI runners instead of shared ones.

Chrome/Chromium Version Mismatch

Error: Protocol error or Target closed or Cannot find context with specified id

Cause: Lighthouse and Playwright bundled Chromium versions are incompatible.

Solution: Use the same Chrome for both:

// Use Playwright's bundled Chromium path
import { chromium } from 'playwright'

const browserPath = chromium.executablePath()
console.log('Using Chrome at:', browserPath)

const browser = await chromium.launch({
  executablePath: browserPath,
  args: [`--remote-debugging-port=${PORT}`],
})

Or install a specific Chrome version:

npx playwright install chrome  # Installs stable Chrome, not Chromium

Timeout Errors

Error: Lighthouse timeout or Navigation timeout exceeded

Causes:

  • Page takes too long to load
  • waitUntil: 'networkidle' waiting for never-ending requests
  • Large pages with many resources

Solutions:

  1. Increase Lighthouse timeout:
const result = await lighthouse(url, {
  port: PORT,
  maxWaitForLoad: 60000, // 60 seconds (default is 45s)
})
  1. Use different wait strategy:
// Instead of networkidle, wait for specific element
await page.goto(url)
await page.waitForSelector('#main-content')
  1. Check for infinite polling requests (analytics, websockets).

Sandbox Errors

Error: No usable sandbox! or Running as root without --no-sandbox is not supported

Cause: Chrome sandboxing issues, common in Docker/CI.

Solution: Disable sandbox (only in trusted CI environments):

const browser = await chromium.launch({
  args: [
    `--remote-debugging-port=${PORT}`,
    '--no-sandbox',
    '--disable-setuid-sandbox',
  ],
})
Only disable sandbox in controlled CI environments, never in production or when processing untrusted URLs.

Multiple Concurrent Audits Fail

Error: Audits interfere with each other or return incorrect results.

Cause: Multiple Lighthouse instances trying to use the same debugging port.

Solution: Use different ports for parallel audits:

async function auditWithDynamicPort(url) {
  const port = 9222 + Math.floor(Math.random() * 1000)
  const browser = await chromium.launch({
    args: [`--remote-debugging-port=${port}`],
  })
  // ... use `port` variable
}

Or run audits sequentially with a single worker:

# Playwright Test
npx playwright test --workers=1

Lighthouse Reports Show Wrong Page

Symptom: Report shows homepage or login page instead of target URL.

Causes:

  • Redirects before audit runs
  • Authentication issues
  • URL mismatch between Playwright navigation and Lighthouse call

Solution: Verify URL after navigation:

await page.goto(targetUrl, { waitUntil: 'networkidle' })

// Check actual URL
const actualUrl = page.url()
if (actualUrl !== targetUrl) {
  console.warn(`Redirected from ${targetUrl} to ${actualUrl}`)
}

// Use actual URL for Lighthouse
const result = await lighthouse(actualUrl, { port: PORT })

Empty or Incomplete Reports

Symptom: Lighthouse report missing metrics or categories.

Causes:

  • Page not fully loaded
  • JavaScript errors on page
  • Missing viewport settings

Solution: Ensure page readiness:

await page.goto(url, { waitUntil: 'networkidle' })

// Wait for critical content
await page.waitForSelector('[data-testid="main-content"]')

// Check for console errors
page.on('console', (msg) => {
  if (msg.type() === 'error')
    console.warn('Page error:', msg.text())
})

CI-Specific Issues

GitHub Actions: Chrome Not Found

- name: Install Chrome
  run: npx playwright install chromium --with-deps

The --with-deps flag installs system dependencies required by Chromium.

Docker: Missing Dependencies

Add to Dockerfile:

RUN apt-get update && apt-get install -y \
    libnss3 \
    libatk1.0-0 \
    libatk-bridge2.0-0 \
    libcups2 \
    libdrm2 \
    libxkbcommon0 \
    libxcomposite1 \
    libxdamage1 \
    libxrandr2 \
    libgbm1 \
    libasound2

Or use Playwright's Docker image:

FROM mcr.microsoft.com/playwright:v1.40.0-jammy

Crash / Target Closed in Docker: If Chrome crashes with "Target closed" or "Page crashed", it's often due to small shared memory /dev/shm. Add this flag:

const browser = await chromium.launch({
  args: [
    `--remote-debugging-port=${PORT}`,
    '--disable-dev-shm-usage', // Critical for Docker
  ],
})

Visual Debugging

Use Playwright's Inspector to pause execution right before the Lighthouse audit to verify the page state.

# Run with Inspector
PWDEBUG=1 npx playwright test

When paused:

  1. Check if you are logged in (if expected).
  2. Verify the DOM is fully loaded.
  3. Check for any overlay/modals that might block Lighthouse.

Still Stuck?