- Raise use-swipe-gesture mutation score to 66.13% (target 65%+) - Maintain use-reduced-motion mutation score at 76.32% (target 50%+) - Fix use-reduced-motion.ts ESLint set-state-in-effect warning - Add UJ-10 deep searcher journey (category browse → article read → content discovery) - Add 40 new test files (analytics, detail, sections, ui, lib components) - Update test-strategy-plan.md to v2.0 (sync test count to 1591) - Sync README.md with final release metrics Quality gates: TS 0 errors, ESLint 0 errors, 121 suites / 1591 tests passed
121 lines
4.0 KiB
JavaScript
121 lines
4.0 KiB
JavaScript
// @ts-nocheck
|
|
import http from 'k6/http';
|
|
import { check, sleep } from 'k6';
|
|
import { Rate, Trend } from 'k6/metrics';
|
|
|
|
const errorRate = new Rate('errors');
|
|
const responseTime = new Trend('response_time');
|
|
|
|
export const options = {
|
|
stages: [
|
|
{ duration: '1m', target: 50 }, // 1分钟内增加到50用户
|
|
{ duration: '2m', target: 100 }, // 2分钟内增加到100用户
|
|
{ duration: '3m', target: 200 }, // 3分钟内增加到200用户
|
|
{ duration: '5m', target: 300 }, // 5分钟内增加到300用户(压力峰值)
|
|
{ duration: '2m', target: 100 }, // 2分钟内减少到100用户
|
|
{ duration: '1m', target: 0 }, // 1分钟内减少到0
|
|
],
|
|
thresholds: {
|
|
// 压力测试允许比负载测试更高的延迟;峰值 300 并发下 p95<2s、p99<3s 视为可接受
|
|
http_req_duration: ['p(95)<2000', 'p(99)<3000'],
|
|
http_req_failed: ['rate<0.05'], // 错误率<5%
|
|
errors: ['rate<0.05'],
|
|
},
|
|
};
|
|
|
|
const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000';
|
|
|
|
const pages = [
|
|
'/',
|
|
'/about',
|
|
'/services',
|
|
'/products',
|
|
'/news',
|
|
'/contact',
|
|
];
|
|
|
|
export default function () {
|
|
const page = pages[Math.floor(Math.random() * pages.length)];
|
|
const res = http.get(`${BASE_URL}${page}`, {
|
|
tags: { name: page },
|
|
});
|
|
|
|
// 记录非200响应的详细信息以便诊断混合渲染模式下的异常
|
|
if (res.status !== 200) {
|
|
const bodyLength = res.body ? res.body.length : 0;
|
|
console.warn(`[WARN] ${page} returned status ${res.status} (body length: ${bodyLength})`);
|
|
}
|
|
|
|
const success = check(res, {
|
|
'status is 200': (r) => r.status === 200,
|
|
'response time < 2000ms': (r) => r.timings.duration < 2000,
|
|
'body is not empty': (r) => r.body && r.body.length > 0,
|
|
});
|
|
|
|
errorRate.add(!success);
|
|
responseTime.add(res.timings.duration);
|
|
|
|
sleep(Math.random() * 2 + 0.5); // 0.5-2.5秒随机等待
|
|
}
|
|
|
|
export function handleSummary(data) {
|
|
const metrics = data.metrics;
|
|
const httpReqs = metrics?.http_reqs?.values || {};
|
|
const httpDuration = metrics?.http_req_duration?.values || {};
|
|
const httpFailed = metrics?.http_req_failed?.values || {};
|
|
|
|
// 构建按页面分组的统计(利用 k6 按 name 标签自动聚合的子指标)
|
|
const perPage = {};
|
|
for (const page of pages) {
|
|
const pageDur = metrics?.[`http_req_duration{name:${page}}`]?.values;
|
|
const pageFail = metrics?.[`http_req_failed{name:${page}}`]?.values;
|
|
perPage[page] = {
|
|
avg_response_time: pageDur?.avg ?? null,
|
|
p95_response_time: pageDur?.['p(95)'] ?? null,
|
|
p99_response_time: pageDur?.['p(99)'] ?? null,
|
|
max_response_time: pageDur?.max ?? null,
|
|
error_rate: pageFail?.rate ?? null,
|
|
total_requests: pageFail ? (pageFail.fails + pageFail.passes) : null,
|
|
error_count: pageFail?.fails ?? null,
|
|
};
|
|
}
|
|
|
|
const summary = {
|
|
type: 'stress-test',
|
|
stages: [
|
|
{ duration: '1m', target: 50, desc: 'Ramp up to 50 users' },
|
|
{ duration: '2m', target: 100, desc: 'Ramp up to 100 users' },
|
|
{ duration: '3m', target: 200, desc: 'Ramp up to 200 users' },
|
|
{ duration: '5m', target: 300, desc: 'Peak load at 300 users' },
|
|
{ duration: '2m', target: 100, desc: 'Scale down to 100 users' },
|
|
{ duration: '1m', target: 0, desc: 'Scale down to 0' },
|
|
],
|
|
thresholds: {
|
|
http_req_duration: ['p(95)<2000ms', 'p(99)<3000ms'],
|
|
http_req_failed: ['rate<0.05'],
|
|
errors: ['rate<0.05'],
|
|
},
|
|
results: {
|
|
overall: {
|
|
total_requests: httpReqs.count || 0,
|
|
error_rate: httpFailed.rate || 0,
|
|
avg_response_time: httpDuration.avg || 0,
|
|
p95_response_time: httpDuration['p(95)'] || 0,
|
|
p99_response_time: httpDuration['p(99)'] || 0,
|
|
max_response_time: httpDuration.max || 0,
|
|
},
|
|
per_page: perPage,
|
|
pages_tested: pages,
|
|
},
|
|
checks_passed: data.root_group?.checks?.map((c) => ({
|
|
name: c.name,
|
|
passes: c.passes,
|
|
fails: c.fails,
|
|
})),
|
|
};
|
|
|
|
return {
|
|
'performance/stress-test-summary.json': JSON.stringify(summary, null, 2),
|
|
};
|
|
}
|