Set up synthetic monitoring with Lighthouse CI and Playwright. Use when implementing automated performance testing, CI/CD performance gates, or proactive monitoring.
Automates performance testing with Lighthouse CI and Playwright. Use this when setting up CI/CD performance gates, implementing synthetic monitoring, or validating Core Web Vitals budgets in automated test suites.
/plugin marketplace add nexus-labs-automation/web-observability/plugin install nexus-labs-automation-web-observability@nexus-labs-automation/web-observabilityThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Automated testing for consistent, proactive performance monitoring.
| Aspect | RUM (Real User) | Synthetic (Lab) |
|---|---|---|
| Data source | Actual users | Controlled tests |
| Variability | High | Low |
| Coverage | What users do | What you test |
| Issues | Finds unknown | Validates known |
| Timing | Reactive | Proactive |
Use both: Synthetic catches regressions before users; RUM shows real-world impact.
npm install -D @lhci/cli
// lighthouserc.json
{
"ci": {
"collect": {
"url": [
"http://localhost:3000/",
"http://localhost:3000/products",
"http://localhost:3000/checkout"
],
"numberOfRuns": 3
},
"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 }],
"interactive": ["error", { "maxNumericValue": 3500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }]
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install & Build
run: |
npm ci
npm run build
- name: Start server
run: npm run start &
- name: Run Lighthouse
run: |
npm install -g @lhci/cli
lhci autorun
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
npm install -D @playwright/test
// tests/performance.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Performance Tests', () => {
test('homepage loads within budget', async ({ page }) => {
// Start tracing
await page.context().tracing.start({ screenshots: true, snapshots: true });
const startTime = Date.now();
await page.goto('/');
await page.waitForLoadState('networkidle');
const loadTime = Date.now() - startTime;
// Assert load time
expect(loadTime).toBeLessThan(3000);
// Get Core Web Vitals
const vitals = await page.evaluate(() => {
return new Promise((resolve) => {
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcp = entries.find((e) => e.entryType === 'largest-contentful-paint');
resolve({
lcp: lcp?.startTime,
cls: entries
.filter((e) => e.entryType === 'layout-shift')
.reduce((sum: number, e: any) => sum + e.value, 0),
});
}).observe({ type: 'largest-contentful-paint', buffered: true });
// Fallback timeout
setTimeout(() => resolve({ lcp: null, cls: null }), 5000);
});
});
console.log('Vitals:', vitals);
// Stop tracing
await page.context().tracing.stop({ path: 'trace.zip' });
});
test('checkout flow performance', async ({ page }) => {
const metrics: Record<string, number> = {};
// Navigate through checkout
await page.goto('/products/1');
metrics.productPageLoad = await measurePageLoad(page);
await page.click('[data-testid="add-to-cart"]');
await page.goto('/cart');
metrics.cartPageLoad = await measurePageLoad(page);
await page.click('[data-testid="checkout"]');
metrics.checkoutPageLoad = await measurePageLoad(page);
// Assert all pages load within budget
expect(metrics.productPageLoad).toBeLessThan(2000);
expect(metrics.cartPageLoad).toBeLessThan(1500);
expect(metrics.checkoutPageLoad).toBeLessThan(2000);
});
});
async function measurePageLoad(page: any): Promise<number> {
const timing = await page.evaluate(() => {
const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
return nav.loadEventEnd - nav.fetchStart;
});
return timing;
}
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
trace: 'retain-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'Desktop Chrome',
use: {
browserName: 'chromium',
viewport: { width: 1280, height: 720 },
},
},
{
name: 'Mobile Chrome',
use: {
browserName: 'chromium',
...devices['Pixel 5'],
},
},
{
name: 'Slow 3G',
use: {
browserName: 'chromium',
// Simulate slow network
launchOptions: {
args: ['--force-effective-connection-type=3g'],
},
},
},
],
});
// __checks__/homepage.check.ts
import { check, group } from 'checkly';
import { Playwright } from 'checkly/constructs';
const homepage = new Playwright('homepage-check', {
name: 'Homepage Performance',
frequency: 5, // Every 5 minutes
locations: ['us-east-1', 'eu-west-1'],
runtimeId: '2024.02',
script: `
const { expect } = require('@playwright/test');
async function run({ page }) {
const response = await page.goto('https://yoursite.com');
expect(response.status()).toBe(200);
const timing = await page.evaluate(() => {
const nav = performance.getEntriesByType('navigation')[0];
return nav.loadEventEnd - nav.fetchStart;
});
expect(timing).toBeLessThan(3000);
}
module.exports = { run };
`,
alertChannels: ['email-oncall'],
});
| Metric | Threshold | Priority |
|---|---|---|
| LCP | <2.5s | P0 |
| TTI | <3.5s | P0 |
| Total Blocking Time | <200ms | P0 |
| CLS | <0.1 | P0 |
| Speed Index | <3.4s | P1 |
| Time to First Byte | <600ms | P1 |
# Performance gate in CI
performance:
runs-on: ubuntu-latest
steps:
- name: Run Lighthouse
run: lhci autorun
- name: Run Playwright
run: npx playwright test tests/performance/
- name: Upload results
run: |
# Send to your observability platform
curl -X POST \
-H "Authorization: Bearer $TOKEN" \
-d @lighthouse-results.json \
https://api.yourplatform.com/synthetic-results
skills/core-web-vitals for RUM comparisonskills/bundle-performance for what to optimizeskills/route-transition-tracking for SPA performancereferences/performance.md - Performance budgetsreferences/platforms/*.md - Vendor integrationsUse when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.