用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tomevault-io/skills-registry --skill performance-testing命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
| Use when this capability is needed.
> Use when this capability is needed.
Review architecture and API design for the vfs-s3 project. Use when the user mentions @architect, asks to review an issue's design, discuss module boundaries, API shape, or architectural decisions for vfs-s3. Also trigger when the user wants to create an ADR (Architecture Decision Record) or evaluate a technical approach for the project. Intended for dispatch from Codex automation or Claude routines; GitHub trigger phrase: @vfs-s3-bot please prepare design doc Use when this capability is needed.
基于 SOC 职业分类
正在显示 SKILL.md
| name | performance-testing |
| description | > Use when this capability is needed. |
Best practices for performance testing with Playwright to ensure your application meets performance requirements.
import { test, expect } from '@playwright/test';
test('measure Core Web Vitals', async ({ page }) => {
// Enable performance observer before navigation
await page.addInitScript(() => {
window.performanceMetrics = {};
// Largest Contentful Paint (LCP)
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
window.performanceMetrics.lcp = lastEntry.startTime;
}).observe({ type: 'largest-contentful-paint', buffered: true });
// First Input Delay (FID) - measured via interaction
new PerformanceObserver((list) => {
const entries = list.getEntries();
window.performanceMetrics.fid = entries[0].processingStart - entries[0].startTime;
}).observe({ type: 'first-input', buffered: true });
// Cumulative Layout Shift (CLS)
let clsValue = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
window.performanceMetrics.cls = clsValue;
}).observe({ type: 'layout-shift', buffered: true });
});
await page.goto('/');
await page.waitForLoadState('networkidle');
// Wait a bit for metrics to be collected
await page.waitForTimeout(1000);
const metrics = await page.evaluate(() => window.performanceMetrics);
// Assert against thresholds
expect(metrics.lcp).toBeLessThan(2500); // LCP should be < 2.5s
expect(metrics.cls).toBeLessThan(0.1); // CLS should be < 0.1
});
test('measure page load timing', async ({ page }) => {
await page.goto('/products');
await page.waitForLoadState('load');
const timing = await page.evaluate(() => {
const perf = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
return {
// DNS lookup
dns: perf.domainLookupEnd - perf.domainLookupStart,
// TCP connection
tcp: perf.connectEnd - perf.connectStart,
// TLS negotiation
tls: perf.secureConnectionStart > 0 ? perf.connectEnd - perf.secureConnectionStart : 0,
// Time to First Byte
ttfb: perf.responseStart - perf.requestStart,
// Content download
download: perf.responseEnd - perf.responseStart,
// DOM processing
domProcessing: perf.domComplete - perf.domInteractive,
// Total page load
: perf. - perf.,
};
});
.(, timing);
(timing.).();
(timing.).();
});
test('measure First Contentful Paint', async ({ page }) => {
await page.goto('/');
const fcp = await page.evaluate(() => {
return new Promise<number>((resolve) => {
new PerformanceObserver((list) => {
const entries = list.getEntries();
const fcpEntry = entries.find(entry => entry.name === 'first-contentful-paint');
if (fcpEntry) {
resolve(fcpEntry.startTime);
}
}).observe({ type: 'paint', buffered: true });
// Fallback if already painted
const existingEntry = performance.getEntriesByName('first-contentful-paint')[0];
if (existingEntry) {
resolve(existingEntry.startTime);
}
});
});
expect(fcp).toBeLessThan(1800); // FCP should be < 1.8s
});
test('measure API response times', async ({ page }) => {
const apiTimings: { url: string; duration: number }[] = [];
// Intercept API requests
page.on('response', async (response) => {
const timing = response.request().timing();
if (response.url().includes('/api/')) {
apiTimings.push({
url: response.url(),
duration: timing.responseEnd - timing.requestStart,
});
}
});
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');
// Log all API timings
console.log('API Response Times:', apiTimings);
// Assert all API calls are fast
for (const timing of apiTimings) {
expect(timing.duration).toBeLessThan(1000); // All APIs < 1s
}
});
test('performance under slow network', async ({ page, context }) => {
// Simulate slow 3G
const client = await context.newCDPSession(page);
await client.send('Network.emulateNetworkConditions', {
offline: false,
downloadThroughput: (500 * 1024) / 8, // 500 Kbps
uploadThroughput: (500 * 1024) / 8,
latency: 400, // 400ms latency
});
const startTime = Date.now();
await page.goto('/');
await page.waitForLoadState('domcontentloaded');
const loadTime = Date.now() - startTime;
// Even on slow network, should load within 10s
expect(loadTime).toBeLessThan(10000);
// Verify critical content is visible
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
});
test('monitor resource sizes', async ({ page }) => {
const resources: { url: string; size: number; type: string }[] = [];
page.on('response', async (response) => {
const headers = response.headers();
const contentLength = headers['content-length'];
const contentType = headers['content-type'] || 'unknown';
if (contentLength) {
resources.push({
url: response.url(),
size: parseInt(contentLength),
type: contentType.split(';')[0],
});
}
});
await page.goto('/');
await page.waitForLoadState('networkidle');
// Calculate total by type
const byType = resources.reduce((acc, r) => {
const type = r.type;
acc[type] = (acc[type] || 0) + r.size;
return acc;
}, {} <, >);
.(, byType);
totalJS = byType[] || ;
totalCSS = byType[] || ;
(totalJS).( * );
(totalCSS).( * );
});
test('image loading performance', async ({ page }) => {
await page.goto('/products');
// Wait for all images to load
await page.waitForFunction(() => {
const images = Array.from(document.querySelectorAll('img'));
return images.every(img => img.complete && img.naturalHeight > 0);
});
// Get image performance data
const imageMetrics = await page.evaluate(() => {
const images = performance.getEntriesByType('resource')
.filter((r): r is PerformanceResourceTiming =>
r.initiatorType === 'img' || r.name.match(/\.(jpg|jpeg|png|webp|gif)$/i) !== null
);
return images.map(img => ({
url: img.name,
duration: img.responseEnd - img.startTime,
size: img.,
}));
});
.(, imageMetrics);
( img imageMetrics) {
(img.).();
}
});
test('measure JavaScript execution time', async ({ page }) => {
// Start JavaScript profiling
const client = await page.context().newCDPSession(page);
await client.send('Profiler.enable');
await client.send('Profiler.start');
await page.goto('/');
await page.waitForLoadState('networkidle');
// Stop profiling
const { profile } = await client.send('Profiler.stop');
// Calculate total JS execution time
const totalTime = profile.nodes.reduce((acc, node) => {
return acc + (node.hitCount || 0) * (profile.samplingInterval || 0);
}, 0);
console.log(`Total JS execution time: ${totalTime / 1000}ms`);
// JS execution should be reasonable
expect(totalTime / 1000).toBeLessThan(1000); // < 1s
});
// utils/performance-assertions.ts
import { Page, expect } from '@playwright/test';
interface PerformanceThresholds {
fcp?: number;
lcp?: number;
ttfb?: number;
totalLoad?: number;
}
export async function assertPerformance(
page: Page,
thresholds: PerformanceThresholds
): Promise<void> {
const metrics = await page.evaluate(() => {
const navEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
const paintEntries = performance.getEntriesByType('paint');
const fcpEntry = paintEntries.find(e => e.name === 'first-contentful-paint');
return {
fcp: fcpEntry?.startTime || 0,
ttfb: navEntry.responseStart - navEntry.,
: navEntry. - navEntry.,
};
});
(thresholds.) {
(metrics., ).(thresholds.);
}
(thresholds.) {
(metrics., ).(thresholds.);
}
(thresholds.) {
(metrics., ).(thresholds.);
}
}
import { assertPerformance } from '../utils/performance-assertions';
test('homepage performance', async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('load');
await assertPerformance(page, {
fcp: 1500,
ttfb: 500,
totalLoad: 3000,
});
});
// performance-budgets.ts
export const performanceBudgets = {
homepage: {
fcp: 1500,
lcp: 2500,
ttfb: 500,
totalLoad: 3000,
jsSize: 300 * 1024, // 300KB
cssSize: 50 * 1024, // 50KB
imageSize: 500 * 1024, // 500KB
},
productList: {
fcp: 1800,
lcp: 2800,
ttfb: 600,
totalLoad: 4000,
jsSize: 400 * 1024,
cssSize: 60 * 1024,
imageSize: 800 * 1024,
},
checkout: {
fcp: 1200,
lcp: 2000,
ttfb: 400,
totalLoad: 2500,
jsSize: 350 * 1024,
cssSize: 50 * 1024,
: * ,
},
};
import { performanceBudgets } from '../performance-budgets';
test.describe('Performance Budget Compliance', () => {
test('homepage stays within budget', async ({ page }) => {
const budget = performanceBudgets.homepage;
await page.goto('/');
await page.waitForLoadState('networkidle');
// Measure timing
const timing = await page.evaluate(() => {
const nav = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
const fcp = performance.getEntriesByName('first-contentful-paint')[0];
return {
fcp: fcp?.startTime || 0,
ttfb: nav.responseStart - nav.requestStart,
totalLoad: nav.loadEventEnd - nav.startTime,
};
});
// Measure resource sizes
const resources = await page.evaluate(() => {
return performance.().( ({
: r.,
: r.,
}));
});
jsSize = resources.( r. === ).( sum + r., );
cssSize = resources.( r. === ).( sum + r., );
imgSize = resources.( r. === ).( sum + r., );
(timing.).(budget.);
(timing.).(budget.);
(timing.).(budget.);
(jsSize).(budget.);
(cssSize).(budget.);
(imgSize).(budget.);
});
});
test('capture performance trace', async ({ page, browser }) => {
// Start tracing
await browser.startTracing(page, {
screenshots: true,
categories: ['devtools.timeline'],
});
await page.goto('/');
await page.waitForLoadState('networkidle');
// Stop tracing and save
const traceBuffer = await browser.stopTracing();
require('fs').writeFileSync('trace.json', traceBuffer);
// The trace can be analyzed in Chrome DevTools
});
test('check for memory leaks', async ({ page }) => {
await page.goto('/');
// Get initial memory
const initialMemory = await page.evaluate(() => {
if (performance.memory) {
return performance.memory.usedJSHeapSize;
}
return 0;
});
// Perform actions that might leak memory
for (let i = 0; i < 10; i++) {
await page.getByRole('button', { name: 'Open Modal' }).click();
await page.getByRole('button', { name: 'Close' }).click();
}
// Force garbage collection (if available)
await page.evaluate(() => {
if (window.gc) window.gc();
});
// Get final memory
const finalMemory = await page.evaluate(() => {
if (performance.) {
performance..;
}
;
});
memoryGrowth = finalMemory - initialMemory;
(memoryGrowth).( * * );
});
// playwright.config.ts
export default defineConfig({
projects: [
{
name: 'performance',
testMatch: '**/*.perf.spec.ts',
use: {
// Disable features that might affect performance
video: 'off',
trace: 'off',
screenshot: 'off',
},
// Run serially to avoid resource contention
fullyParallel: false,
},
],
});
test.beforeEach(async ({ page }) => {
// Clear cache and cookies
await page.context().clearCookies();
// Disable service workers
await page.route('**/*', route => {
if (route.request().url().includes('sw.js')) {
route.abort();
} else {
route.continue();
}
});
});
test.describe('Critical Path Performance', () => {
test('homepage to checkout', async ({ page }) => {
const timings: Record<string, number> = {};
// Homepage
let start = Date.now();
await page.goto('/');
await page.waitForLoadState('networkidle');
timings.homepage = Date.now() - start;
// Product page
start = Date.now();
await page.getByRole('link', { name: 'Featured Product' }).click();
await page.waitForLoadState('networkidle');
timings.productPage = Date.now() - start;
// Add to cart
start = Date.now();
await page.getByRole('button', { name: 'Add to Cart' }).click();
await page.();
timings. = .() - start;
start = .();
page.(, { : }).();
page.();
timings. = .() - start;
.(, timings);
(timings.).();
(timings.).();
(timings.).();
(timings.).();
});
});
| Metric | Good | Needs Improvement | Poor |
|---|---|---|---|
| LCP | < 2.5s | 2.5s - 4s | > 4s |
| FID | < 100ms | 100ms - 300ms | > 300ms |
| CLS | < 0.1 | 0.1 - 0.25 | > 0.25 |
| FCP | < 1.8s | 1.8s - 3s | > 3s |
| TTFB | < 600ms | 600ms - 1.5s | > 1.5s |
// Navigation timing
performance.getEntriesByType('navigation')
// Resource timing
performance.getEntriesByType('resource')
// Paint timing
performance.getEntriesByType('paint')
// Long tasks
PerformanceObserver with type: 'longtask'
// Layout shifts
PerformanceObserver with type: 'layout-shift'
Converted and distributed by TomeVault — claim your Tome and manage your conversions.