¿Qué es Lighthouse?

Lighthouse es una herramienta automatizada de código abierto de Google para mejorar la calidad de páginas web. Proporciona auditorías integrales de rendimiento, accesibilidad, progressive web apps, SEO y mejores prácticas. Para ingenieros QA, Lighthouse es esencial para validar que las aplicaciones cumplan estándares modernos de rendimiento web, especialmente Core Web Vitals — las métricas clave de Google para experiencia de usuario.

Por qué Lighthouse Importa para QA

  • Core Web Vitals Impactan SEO - Google usa LCP, FID y CLS como factores de ranking
  • Validación de Experiencia de Usuario - Cuantifica el rendimiento percibido con métricas reales
  • Integración CI/CD - Automatiza pruebas de regresión de rendimiento en pipelines
  • Insights Accionables - Obtén recomendaciones específicas para mejoras de rendimiento
  • Rendimiento Móvil - Prueba diseño responsive y problemas específicos de móvil

Las pruebas de rendimiento con Lighthouse se complementan perfectamente con pruebas de rendimiento de APIs para validar tiempos de respuesta del backend. Integrar Lighthouse en tu pipeline CI/CD como parte de una estrategia de testing continuo asegura que las regresiones de rendimiento se detecten automáticamente antes de llegar a producción.

Core Web Vitals Explicados

Core Web Vitals son tres métricas clave que miden la experiencia de usuario en el mundo real:

1. Largest Contentful Paint (LCP)

Qué mide: Rendimiento de carga - cuando el elemento de contenido más grande se vuelve visible.

Umbrales:

  • Bueno: ≤ 2.5 segundos
  • Necesita Mejora: 2.5 - 4.0 segundos
  • Pobre: > 4.0 segundos

Problemas Comunes:

// Problema: JavaScript bloqueante de renderizado
<script src="analytics.js"></script>

// Solución: Diferir scripts no críticos
<script src="analytics.js" defer></script>

// Problema: Imágenes no optimizadas
<img src="hero-10mb.jpg" alt="Hero">

// Solución: Formatos modernos + imágenes responsive
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Hero" loading="lazy">
</picture>

Estrategias de Optimización:

  • Usar CDN para assets estáticos
  • Implementar lazy loading para imágenes below-fold
  • Precargar recursos críticos: <link rel="preload" href="hero.jpg" as="image">
  • Optimizar tiempos de respuesta del servidor (TTFB < 600ms)
  • Usar resource hints: dns-prefetch, preconnect

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

Qué mide: Interactividad - retraso entre interacción del usuario y respuesta del navegador.

Umbrales FID (siendo reemplazado por INP):

  • Bueno: ≤ 100ms
  • Necesita Mejora: 100 - 300ms
  • Pobre: > 300ms

Umbrales INP (nueva métrica desde 2024):

  • Bueno: ≤ 200ms
  • Necesita Mejora: 200 - 500ms
  • Pobre: > 500ms

Problemas Comunes:

// Problema: Tareas largas de JavaScript bloquean el hilo principal
function processHugeDataset() {
  // Loop síncrono procesando 100k items
  for (let i = 0; i < 100000; i++) {
    heavyCalculation(data[i]);
  }
}

// Solución: Dividir en chunks pequeños con 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));

    // Ceder al navegador cada chunk
    await new Promise(resolve => setTimeout(resolve, 0));
  }
}

// Solución: Usar Web Workers para cálculos pesados
// 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);
};

Estrategias de Optimización:

  • Dividir tareas largas en chunks pequeños (< 50ms cada uno)
  • Usar code splitting para reducir tamaño de bundle JavaScript
  • Diferir scripts de terceros
  • Implementar service workers para mejor caching
  • Usar requestIdleCallback para trabajo no urgente

3. Cumulative Layout Shift (CLS)

Qué mide: Estabilidad visual - cambios inesperados de layout durante carga de página.

Umbrales:

  • Bueno: ≤ 0.1
  • Necesita Mejora: 0.1 - 0.25
  • Pobre: > 0.25

Problemas Comunes:

<!-- Problema: Imágenes sin dimensiones -->
<img src="product.jpg" alt="Product">

<!-- Solución: Siempre especificar width/height -->
<img src="product.jpg" alt="Product" width="800" height="600">

<!-- Problema: Inserción dinámica de contenido -->
<div id="banner"></div>
<script>
  // Carga ad asíncronamente, empujando contenido hacia abajo
  loadAdvertisement('#banner');
</script>

<!-- Solución: Reservar espacio para contenido dinámico -->
<div id="banner" style="min-height: 250px;">
  <!-- Ad se carga aquí sin layout shift -->
</div>
/* Problema: Web fonts causan layout shift (FOIT/FOUT) */
@font-face {
  font-family: 'CustomFont';
  src: url('custom-font.woff2') format('woff2');
}

/* Solución: Usar font-display para controlar comportamiento de carga */
@font-face {
  font-family: 'CustomFont';
  src: url('custom-font.woff2') format('woff2');
  font-display: swap; /* Mostrar fallback inmediatamente, intercambiar cuando cargue */
}

/* Mejor: Precargar fuentes críticas */
/* En HTML: <link rel="preload" href="custom-font.woff2" as="font" crossorigin> */

Estrategias de Optimización:

  • Siempre incluir atributos de tamaño en imágenes y videos
  • Reservar espacio para ads y embeds con min-height
  • Evitar insertar contenido arriba de contenido existente
  • Usar animaciones transform en lugar de propiedades que disparan layout
  • Precargar fuentes personalizadas y usar font-display: swap

Ejecutar Pruebas Lighthouse

1. Chrome DevTools (Pruebas Manuales)

# Abrir Chrome DevTools
# 1. Presionar F12 o Cmd+Option+I
# 2. Ir a pestaña "Lighthouse"
# 3. Seleccionar categorías (Performance, Accessibility, etc.)
# 4. Elegir dispositivo (Mobile/Desktop)
# 5. Hacer clic en "Analyze page load"

Mejores Prácticas para Pruebas Manuales:

  • Probar en modo Incógnito (sin interferencia de extensiones)
  • Usar throttling para simular condiciones del mundo real (Slow 4G, 4x CPU slowdown)
  • Ejecutar múltiples pruebas y promediar puntuaciones (resultados pueden variar ±5 puntos)
  • Probar configuraciones móvil y desktop

2. Lighthouse CLI (Pruebas Automatizadas)

# Instalar Lighthouse
npm install -g lighthouse

# Prueba básica
lighthouse https://example.com

# Prueba móvil con formatos de salida
lighthouse https://example.com \
  --output html,json,csv \
  --output-path ./reports/report \
  --view \
  --chrome-flags="--headless"

# Prueba con throttling específico
lighthouse https://example.com \
  --throttling.rttMs=150 \
  --throttling.throughputKbps=1600 \
  --throttling.cpuSlowdownMultiplier=4

# Probar solo categorías específicas
lighthouse https://example.com \
  --only-categories=performance,accessibility

# Archivo de config personalizado
lighthouse https://example.com \
  --config-path=./lighthouse-config.js

3. Lighthouse CI (Integración Continua)

# .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/

Configuración lighthouserc.json:

{
  "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"
    }
  }
}

Interpretar Puntuaciones Lighthouse

Desglose de Puntuación de Rendimiento

La puntuación de Performance (0-100) se pondera entre múltiples métricas:

MétricaPesoDescripción
First Contentful Paint (FCP)10%Primer texto/imagen renderizado
Largest Contentful Paint (LCP)25%Elemento de contenido más grande visible
Total Blocking Time (TBT)30%Tiempo total que el hilo principal está bloqueado
Cumulative Layout Shift (CLS)25%Puntuación de estabilidad visual
Speed Index10%Qué tan rápido se muestra visualmente el contenido

Rangos de Puntuación:

  • 90-100 (Verde): Bueno - cumple mejores prácticas de rendimiento
  • 50-89 (Naranja): Necesita mejora - algo de optimización requerida
  • 0-49 (Rojo): Pobre - problemas significativos de rendimiento

Estrategia de Presupuesto de Rendimiento

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

        // Presupuestos de recursos
        "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}],

        // Presupuestos de cantidad de solicitudes
        "resource-summary:script:count": ["error", {"maxNumericValue": 15}],
        "resource-summary:third-party:count": ["error", {"maxNumericValue": 10}]
      }
    }
  }
}

Técnicas Avanzadas de Lighthouse

1. Auditorías Personalizadas

// custom-audit.js
class CustomPerformanceAudit {
  static get meta() {
    return {
      id: 'custom-api-performance',
      title: 'Tiempo de Respuesta API',
      failureTitle: 'Respuestas API son lentas',
      description: 'Mide tiempos de respuesta de endpoints API',
      requiredArtifacts: ['devtoolsLogs']
    };
  }

  static async audit(artifacts) {
    const devtools = artifacts.devtoolsLogs.defaultPass;
    const networkRecords = /* extraer registros de red */;

    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; // umbral 500ms

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

module.exports = CustomPerformanceAudit;

2. Pruebas de Flujo de Usuario (Nuevo en 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: 'Flujo checkout e-commerce'});

  // Paso 1: Navegar a homepage
  await flow.navigate('https://example.com', {
    stepName: 'Homepage'
  });

  // Paso 2: Buscar producto
  await flow.startTimespan({stepName: 'Interacción búsqueda'});
  await page.type('#search', 'laptop');
  await page.click('#search-button');
  await page.waitForSelector('.search-results');
  await flow.endTimespan();

  // Paso 3: Página detalles producto
  await flow.navigate('https://example.com/product/123', {
    stepName: 'Página producto'
  });

  // Paso 4: Interacción añadir al carrito
  await flow.startTimespan({stepName: 'Añadir al carrito'});
  await page.click('#add-to-cart');
  await page.waitForSelector('.cart-notification');
  await flow.endTimespan();

  // Paso 5: Snapshot checkout
  await flow.snapshot({stepName: 'Estado página carrito'});

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

  await browser.close();
}

runUserFlow();

3. Dashboard de Monitoreo de Rendimiento

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

def run_lighthouse(url):
    """Ejecutar Lighthouse y retornar métricas"""
    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):
    """Extraer métricas clave del reporte Lighthouse"""
    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):
    """Enviar métricas a InfluxDB para visualización Grafana"""
    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])

# Monitorear páginas cada hora
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}: Puntuación Rendimiento {metrics['performance_score']}")

Cuellos de Botella Comunes y Correcciones

Problema: Bundles JavaScript Grandes

# Analizar tamaño de bundle
npm install -g webpack-bundle-analyzer

# Añadir a webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

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

# Build y analizar
npm run build

Soluciones:

  • Code splitting: import('./heavy-component.js').then(module => ...)
  • Tree shaking: Asegurar "sideEffects": false en package.json
  • Eliminar dependencias no usadas
  • Usar imports dinámicos para code splitting basado en rutas

Problema: Recursos Bloqueantes de Renderizado

<!-- Problema: CSS bloqueante -->
<link rel="stylesheet" href="styles.css">

<!-- Solución: CSS crítico inline, diferir no crítico -->
<style>
  /* CSS crítico above-fold 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>

Problema: Imágenes No Optimizadas

# Instalar herramientas de optimización de imágenes
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})
    ]
  });
})();

Conclusión

Lighthouse es una herramienta indispensable para pruebas modernas de rendimiento web. Al integrar Lighthouse en tu flujo de trabajo QA — desde auditorías manuales DevTools hasta pipelines CI/CD (como se discute en Load Testing with JMeter: Complete Guide) automatizados — aseguras que las aplicaciones cumplan estándares Core Web Vitals y entreguen experiencias excepcionales de usuario.

Conclusiones Clave:

  • Enfócate en Core Web Vitals: LCP, INP/FID, CLS
  • Automatiza pruebas Lighthouse en pipelines CI/CD
  • (como se discute en Burp Suite for QA Engineers: Complete Security Testing Guide) Establece presupuestos de rendimiento y aplícalos
  • Usa pruebas User Flow para interacciones complejas
  • Monitorea rendimiento en el tiempo con dashboards
  • Optimiza imágenes, JavaScript y rutas de renderizado

El rendimiento es una característica, no una reflexión tardía. Haz de las pruebas Lighthouse una parte central de tu proceso QA.

Documentacion Relacionada