Skip to content

performance-testing

<!-- Loaded by performance-optimization-engineer when task involves Lighthouse CI, performance budgets, regression testing, synthetic monitoring, or CI/CD performance gates -->

From plugin
vexjoy-agent
413198 skills198 agents10 commands86 hooks
Install
$ npx -y skills add notque/vexjoy-agent --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

<!-- Loaded by performance-optimization-engineer when task involves Lighthouse CI, performance budgets, regression testing, synthetic monitoring, or CI/CD performance gates -->

Agent definition

performance-testing.md

Performance Testing Reference

<!-- Loaded by performance-optimization-engineer when task involves Lighthouse CI, performance budgets, regression testing, synthetic monitoring, or CI/CD performance gates -->

> **Scope**: Automated performance testing in CI/CD, Lighthouse CI setup, performance budgets, and regression detection. Does NOT cover RUM/real-user monitoring (see `metrics-and-monitoring.md`). > **Version range**: Lighthouse CI 0.12+, @lhci/cli 0.12+ > **Generated**: 2026-04-09

---

Overview

Synthetic performance testing in CI/CD catches regressions before they reach production. The most common failure mode is running Lighthouse manually during development and never catching performance regressions in CI — so bundle size grows 20% over 6 months with no alerts. Performance budgets with automated gates prevent this category of problem entirely.

---

Pattern Table

| Tool | Purpose | When to Use | |------|---------|-------------| | `@lhci/cli` | Lighthouse CI — runs Lighthouse in CI, stores results, enforces assertions | Primary CI performance gate | | `bundlesize` / `size-limit` | Bundle size gates — fail CI if JS bundle exceeds limit | Add alongside @lhci when bundle size is the primary concern | | `playwright` + tracing | Real browser performance measurement with network control | Integration tests that need timing precision | | `web-vitals` + Vitest | Unit-level performance assertions | Catching regressions in specific components |

---

Correct Patterns

Lighthouse CI Configuration with Assertions

`.lighthouserc.js` at repo root is the standard config location.

// .lighthouserc.js
module.exports = {
  ci: {
    collect: {
      // Run against the built app served locally
      url: ['http://localhost:3000/', 'http://localhost:3000/products'],
      numberOfRuns: 3, // Average over 3 runs to reduce variance
      startServerCommand: 'npm run start', // Production build
      startServerReadyPattern: 'ready on',
    },
    assert: {
      // Fail CI if these thresholds aren't met
      assertions: {
        'categories:performance': ['error', { minScore: 0.9 }], // 90+ score
        'categories:accessibility': ['warn', { minScore: 0.9 }],
        'first-contentful-paint': ['error', { maxNumericValue: 2000 }], // 2s
        'largest-contentful-paint': ['error', { maxNumericValue: 2500 }], // 2.5s
        'total-blocking-time': ['error', { maxNumericValue: 300 }],
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
        // Bundle size gates via Lighthouse
        'total-byte-weight': ['error', { maxNumericValue: 1_600_000 }], // 1.6MB
        'uses-optimized-images': ['warn', {}],
        'unused-javascript': ['warn', { maxLength: 2 }], // Max 2 unused JS chunks
      },
    },
    upload: {
      target: 'temporary-public-storage', // Free LHCI storage for 30 days
      // Or: target: 'lhci', serverBaseUrl: 'https://lhci.yourcompany.com'
    },
  },
}

**Why**: `numberOfRuns: 3` is essential — single-run Lighthouse scores have 10-15 point variance on CI machines. Assertions use `'error'` to fail CI (not just warn). The upload step stores historical data so you can see score trends over time.

---

GitHub Actions Workflow for Lighthouse CI

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push, pull_request]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run build
      - name: Run Lighthouse CI
        run: |
          npm install -g @lhci/cli@0.14
          lhci autorun
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

**Why**: Running `lhci autorun` reads `.lighthouserc.js` automatically. The `LHCI_GITHUB_APP_TOKEN` enables PR status checks and comments. Without it, Lighthouse results don't appear inline in PRs.

---

size-limit for Bundle Size Gates

// package.json
{
  "size-limit": [
    {
      "path": ".next/static/chunks/main-*.js",
      "limit": "80 kB",
      "gzip": true
    },
    {
      "path": ".next/static/chunks/pages/**/*.js",
      "limit": "50 kB",
      "gzip": true,
      "ignore": ["node_modules"]
    }
  ],
  "scripts": {
    "size": "size-limit",
    "analyze": "ANALYZE=true next build"
  }
}
# Add to CI workflow
- name: Check bundle size
  run: npx size-limit --json > size-report.json
- name: Report size
  uses: andresz1/size-limit-action@v1
  with:
    github_token: ${{ secrets.GITHUB_TOKEN }}

**Why**: `size-limit` measures gzipped output, which is what the browser actually downloads. Failing CI on bundle size regressions prevents the incremental growth pattern where no single PR is "bad" but 6 months of PRs add 150KB.

---

Pattern Catalog

Average Multiple Lighthouse Runs

**Detection**:

grep -rn "numberOfRuns\|number-of-runs" .lighthouserc.* .lhci* 2>/dev/null
# If no output: numberOfRuns is not configured (defaults to 1)

**Signal**:

// .lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: ['http://localhost:3000'],
      // numberOfRuns missing — defaults to 1
    },
  },
}

**Why this matters**: A single Lighthouse run has 10-15 point score variance on CI machines (shared CPU, GC pauses, network jitter). A score of 85 one run, 72 the next — both from the same code. Single-run CI gates either false-positive (blocking good PRs) or miss real regressions.

**Preferred action**:

collect: {
  url: ['http://localhost:3000'],
  numberOfRuns: 3, // Minimum for stable averages
},

---

Use error Level for Core Performance Assertions

**Detection**:

grep -rn "largest-contentful-paint\|first-contentful-paint\|total-blocking-time" .lighthouserc.*
# Check if it uses 'warn' instead of 'error' for core metrics

**Signal**:

assertions:
Read more
Ships withvexjoy-agent

Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.

Get the whole plugin, auto-invoked