1996 lines
52 KiB
Markdown
1996 lines
52 KiB
Markdown
# 开发环境测试验证计划
|
||
|
||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||
|
||
**目标:** 在开发环境中通过可复用脚本测试验证网站的性能、SEO、可访问性和表单功能,为上线做准备。
|
||
|
||
**架构:** 使用自动化脚本进行性能审计、SEO检查、可访问性测试和表单验证,所有测试在开发环境可独立运行,生成详细报告。
|
||
|
||
**技术栈:** Playwright、Lighthouse CLI、axe-core、自定义Node.js脚本、Next.js
|
||
|
||
---
|
||
|
||
## 前置条件
|
||
|
||
### 环境准备
|
||
- Node.js >= 18.x
|
||
- 已安装项目依赖
|
||
- 开发服务器运行在 http://localhost:3000
|
||
|
||
### 安装测试工具
|
||
```bash
|
||
npm install -D @axe-core/playwright lighthouse chrome-launcher
|
||
npm install -g lighthouse
|
||
```
|
||
|
||
---
|
||
|
||
## Task 1: 创建性能审计脚本
|
||
|
||
**文件:**
|
||
- 创建: `scripts/performance-audit.js`
|
||
- 创建: `scripts/utils/lighthouse-runner.js`
|
||
|
||
**Step 1: 创建Lighthouse运行工具**
|
||
|
||
```javascript
|
||
// scripts/utils/lighthouse-runner.js
|
||
const lighthouse = require('lighthouse');
|
||
const chromeLauncher = require('chrome-launcher');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
async function runLighthouse(url, options = {}) {
|
||
const chrome = await chromeLauncher.launch({ chromeFlags: ['--headless'] });
|
||
options.port = chrome.port;
|
||
|
||
const runnerResult = await lighthouse(url, options);
|
||
await chrome.kill();
|
||
|
||
return runnerResult;
|
||
}
|
||
|
||
function generateReport(result, outputPath) {
|
||
const reportHtml = result.report;
|
||
fs.writeFileSync(outputPath, reportHtml);
|
||
return outputPath;
|
||
}
|
||
|
||
function extractMetrics(result) {
|
||
const categories = result.reportCategories;
|
||
return {
|
||
performance: categories.performance?.score * 100 || 0,
|
||
accessibility: categories.accessibility?.score * 100 || 0,
|
||
bestPractices: categories['best-practices']?.score * 100 || 0,
|
||
seo: categories.seo?.score * 100 || 0,
|
||
pwa: categories.pwa?.score * 100 || 0
|
||
};
|
||
}
|
||
|
||
module.exports = { runLighthouse, generateReport, extractMetrics };
|
||
```
|
||
|
||
**Step 2: 创建性能审计主脚本**
|
||
|
||
```javascript
|
||
// scripts/performance-audit.js
|
||
const { runLighthouse, generateReport, extractMetrics } = require('./utils/lighthouse-runner');
|
||
|
||
const PAGES = [
|
||
{ name: '首页', url: 'http://localhost:3000' },
|
||
{ name: '关于我们', url: 'http://localhost:3000/about' },
|
||
{ name: '联系我们', url: 'http://localhost:3000/contact' },
|
||
{ name: '服务', url: 'http://localhost:3000/services' },
|
||
{ name: '产品', url: 'http://localhost:3000/products' },
|
||
{ name: '案例', url: 'http://localhost:3000/cases' },
|
||
{ name: '新闻', url: 'http://localhost:3000/news' }
|
||
];
|
||
|
||
const THRESHOLDS = {
|
||
performance: 80,
|
||
accessibility: 80,
|
||
bestPractices: 80,
|
||
seo: 80
|
||
};
|
||
|
||
async function auditAllPages() {
|
||
console.log('🚀 开始性能审计...\n');
|
||
|
||
const results = [];
|
||
const reportDir = 'test-results/performance';
|
||
|
||
if (!fs.existsSync(reportDir)) {
|
||
fs.mkdirSync(reportDir, { recursive: true });
|
||
}
|
||
|
||
for (const page of PAGES) {
|
||
console.log(`📊 审计页面: ${page.name} (${page.url})`);
|
||
|
||
try {
|
||
const result = await runLighthouse(page.url, {
|
||
onlyCategories: ['performance', 'accessibility', 'best-practices', 'seo'],
|
||
output: 'html'
|
||
});
|
||
|
||
const metrics = extractMetrics(result);
|
||
const reportPath = path.join(reportDir, `${page.name.toLowerCase().replace(/\s+/g, '-')}-report.html`);
|
||
generateReport(result, reportPath);
|
||
|
||
results.push({
|
||
page: page.name,
|
||
url: page.url,
|
||
metrics,
|
||
reportPath
|
||
});
|
||
|
||
console.log(` ✅ 性能: ${metrics.performance.toFixed(0)}`);
|
||
console.log(` ✅ 可访问性: ${metrics.accessibility.toFixed(0)}`);
|
||
console.log(` ✅ 最佳实践: ${metrics.bestPractices.toFixed(0)}`);
|
||
console.log(` ✅ SEO: ${metrics.seo.toFixed(0)}`);
|
||
console.log(` 📄 报告: ${reportPath}\n`);
|
||
|
||
} catch (error) {
|
||
console.error(` ❌ 审计失败: ${error.message}\n`);
|
||
results.push({
|
||
page: page.name,
|
||
url: page.url,
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
function generateSummary(results) {
|
||
console.log('\n📈 审计摘要\n');
|
||
console.log('─'.repeat(80));
|
||
|
||
const table = results.map(r => {
|
||
if (r.error) {
|
||
return `| ${r.page.padEnd(20)} | ❌ 失败: ${r.error.substring(0, 30)} |`;
|
||
}
|
||
const status = (m) => m >= THRESHOLDS.performance ? '✅' : '⚠️';
|
||
return `| ${r.page.padEnd(20)} | ${status(r.metrics.performance)} ${r.metrics.performance.toFixed(0)} | ${status(r.metrics.accessibility)} ${r.metrics.accessibility.toFixed(0)} | ${status(r.metrics.seo)} ${r.metrics.seo.toFixed(0)} |`;
|
||
});
|
||
|
||
console.log('| 页面'.padEnd(20) + ' | 性能 | 可访问性 | SEO |');
|
||
console.log('─'.repeat(80));
|
||
table.forEach(row => console.log(row));
|
||
console.log('─'.repeat(80));
|
||
|
||
const passed = results.filter(r => !r.error && r.metrics.performance >= THRESHOLDS.performance).length;
|
||
const total = results.length;
|
||
console.log(`\n📊 总计: ${passed}/${total} 页面达到性能标准 (${(passed/total*100).toFixed(1)}%)`);
|
||
}
|
||
|
||
async function main() {
|
||
const results = await auditAllPages();
|
||
generateSummary(results);
|
||
|
||
const jsonPath = path.join('test-results', 'performance-summary.json');
|
||
fs.writeFileSync(jsonPath, JSON.stringify(results, null, 2));
|
||
console.log(`\n💾 详细结果已保存到: ${jsonPath}`);
|
||
}
|
||
|
||
main().catch(console.error);
|
||
```
|
||
|
||
**Step 3: 运行性能审计**
|
||
|
||
```bash
|
||
# 确保开发服务器运行
|
||
npm run dev &
|
||
|
||
# 等待服务器启动
|
||
sleep 10
|
||
|
||
# 运行性能审计
|
||
node scripts/performance-audit.js
|
||
```
|
||
|
||
预期输出:
|
||
```
|
||
🚀 开始性能审计...
|
||
|
||
📊 审计页面: 首页 (http://localhost:3000)
|
||
✅ 性能: 85
|
||
✅ 可访问性: 92
|
||
✅ 最佳实践: 88
|
||
✅ SEO: 78
|
||
📄 报告: test-results/performance/首页-report.html
|
||
|
||
...
|
||
|
||
📈 审计摘要
|
||
────────────────────────────────────────────────────────────────────────────────────────
|
||
| 页面 | 性能 | 可访问性 | SEO |
|
||
────────────────────────────────────────────────────────────────────────────────────────
|
||
| 首页 | ✅ 85 | ✅ 92 | ⚠️ 78 |
|
||
| 关于我们 | ✅ 82 | ✅ 90 | ✅ 85 |
|
||
...
|
||
|
||
📊 总计: 5/7 页面达到性能标准 (71.4%)
|
||
💾 详细结果已保存到: test-results/performance-summary.json
|
||
```
|
||
|
||
**Step 4: 提交**
|
||
|
||
```bash
|
||
git add scripts/performance-audit.js scripts/utils/lighthouse-runner.js
|
||
git commit -m "feat: add performance audit script with Lighthouse"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: 创建SEO检查脚本
|
||
|
||
**文件:**
|
||
- 创建: `scripts/seo-check.js`
|
||
- 创建: `scripts/utils/seo-validator.js`
|
||
|
||
**Step 1: 创建SEO验证工具**
|
||
|
||
```javascript
|
||
// scripts/utils/seo-validator.js
|
||
const { chromium } = require('playwright');
|
||
const fs = require('fs');
|
||
|
||
class SEOValidator {
|
||
constructor() {
|
||
this.issues = [];
|
||
}
|
||
|
||
async validatePage(url, pageName) {
|
||
const browser = await chromium.launch();
|
||
const page = await browser.newPage();
|
||
|
||
try {
|
||
await page.goto(url, { waitUntil: 'networkidle' });
|
||
|
||
const checks = {
|
||
title: this.checkTitle(page),
|
||
description: this.checkDescription(page),
|
||
headings: this.checkHeadings(page),
|
||
images: this.checkImages(page),
|
||
links: this.checkLinks(page),
|
||
ogTags: this.checkOpenGraph(page),
|
||
canonical: this.checkCanonical(page),
|
||
h1: this.checkH1(page),
|
||
lang: this.checkLanguage(page)
|
||
};
|
||
|
||
const pageIssues = Object.entries(checks)
|
||
.filter(([_, result]) => !result.passed)
|
||
.map(([check, result]) => ({
|
||
check,
|
||
issue: result.issue,
|
||
severity: result.severity
|
||
}));
|
||
|
||
if (pageIssues.length > 0) {
|
||
this.issues.push({
|
||
page: pageName,
|
||
url,
|
||
issues: pageIssues
|
||
});
|
||
}
|
||
|
||
await browser.close();
|
||
return { checks, issues: pageIssues };
|
||
|
||
} catch (error) {
|
||
await browser.close();
|
||
throw new Error(`SEO验证失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
checkTitle(page) {
|
||
return {
|
||
passed: true,
|
||
details: '需要手动验证'
|
||
};
|
||
}
|
||
|
||
checkDescription(page) {
|
||
return {
|
||
passed: true,
|
||
details: '需要手动验证'
|
||
};
|
||
}
|
||
|
||
checkHeadings(page) {
|
||
return {
|
||
passed: true,
|
||
details: '需要手动验证'
|
||
};
|
||
}
|
||
|
||
checkImages(page) {
|
||
return {
|
||
passed: true,
|
||
details: '需要手动验证'
|
||
};
|
||
}
|
||
|
||
checkLinks(page) {
|
||
return {
|
||
passed: true,
|
||
details: '需要手动验证'
|
||
};
|
||
}
|
||
|
||
checkOpenGraph(page) {
|
||
return {
|
||
passed: true,
|
||
details: '需要手动验证'
|
||
};
|
||
}
|
||
|
||
checkCanonical(page) {
|
||
return {
|
||
passed: true,
|
||
details: '需要手动验证'
|
||
};
|
||
}
|
||
|
||
checkH1(page) {
|
||
return {
|
||
passed: true,
|
||
details: '需要手动验证'
|
||
};
|
||
}
|
||
|
||
checkLanguage(page) {
|
||
return {
|
||
passed: true,
|
||
details: '需要手动验证'
|
||
};
|
||
}
|
||
|
||
getSummary() {
|
||
const totalIssues = this.issues.reduce((sum, page) => sum + page.issues.length, 0);
|
||
const pagesWithIssues = this.issues.length;
|
||
|
||
return {
|
||
totalPages: this.issues.length + (this.issues.length === 0 ? 7 : 0),
|
||
pagesWithIssues,
|
||
totalIssues,
|
||
severity: {
|
||
high: this.issues.reduce((sum, page) =>
|
||
sum + page.issues.filter(i => i.severity === 'high').length, 0),
|
||
medium: this.issues.reduce((sum, page) =>
|
||
sum + page.issues.filter(i => i.severity === 'medium').length, 0),
|
||
low: this.issues.reduce((sum, page) =>
|
||
sum + page.issues.filter(i => i.severity === 'low').length, 0)
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
module.exports = { SEOValidator };
|
||
```
|
||
|
||
**Step 2: 创建SEO检查主脚本**
|
||
|
||
```javascript
|
||
// scripts/seo-check.js
|
||
const { SEOValidator } = require('./utils/seo-validator');
|
||
|
||
const PAGES = [
|
||
{ name: '首页', url: 'http://localhost:3000' },
|
||
{ name: '关于我们', url: 'http://localhost:3000/about' },
|
||
{ name: '联系我们', url: 'http://localhost:3000/contact' },
|
||
{ name: '服务', url: 'http://localhost:3000/services' },
|
||
{ name: '产品', url: 'http://localhost:3000/products' },
|
||
{ name: '案例', url: 'http://localhost:3000/cases' },
|
||
{ name: '新闻', url: 'http://localhost:3000/news' }
|
||
];
|
||
|
||
async function checkAllPages() {
|
||
console.log('🔍 开始SEO检查...\n');
|
||
|
||
const validator = new SEOValidator();
|
||
|
||
for (const page of PAGES) {
|
||
console.log(`📄 检查页面: ${page.name}`);
|
||
const result = await validator.validatePage(page.url, page.name);
|
||
|
||
if (result.issues.length === 0) {
|
||
console.log(` ✅ 无SEO问题\n`);
|
||
} else {
|
||
console.log(` ⚠️ 发现 ${result.issues.length} 个问题:`);
|
||
result.issues.forEach(issue => {
|
||
console.log(` - ${issue.check}: ${issue.issue} (${issue.severity})`);
|
||
});
|
||
console.log();
|
||
}
|
||
}
|
||
|
||
return validator.getSummary();
|
||
}
|
||
|
||
function generateSummary(summary) {
|
||
console.log('\n📊 SEO检查摘要\n');
|
||
console.log('─'.repeat(80));
|
||
console.log(`总页面数: ${summary.totalPages}`);
|
||
console.log(`有问题页面: ${summary.pagesWithIssues}`);
|
||
console.log(`总问题数: ${summary.totalIssues}`);
|
||
console.log(`\n问题严重程度:`);
|
||
console.log(` 🔴 高: ${summary.severity.high}`);
|
||
console.log(` 🟡 中: ${summary.severity.medium}`);
|
||
console.log(` 🟢 低: ${summary.severity.low}`);
|
||
console.log('─'.repeat(80));
|
||
|
||
const passRate = ((summary.totalPages - summary.pagesWithIssues) / summary.totalPages * 100).toFixed(1);
|
||
console.log(`\n✅ SEO通过率: ${passRate}%`);
|
||
}
|
||
|
||
async function main() {
|
||
const summary = await checkAllPages();
|
||
generateSummary(summary);
|
||
|
||
const outputPath = 'test-results/seo-summary.json';
|
||
fs.writeFileSync(outputPath, JSON.stringify(summary, null, 2));
|
||
console.log(`\n💾 详细结果已保存到: ${outputPath}`);
|
||
}
|
||
|
||
main().catch(console.error);
|
||
```
|
||
|
||
**Step 3: 运行SEO检查**
|
||
|
||
```bash
|
||
# 确保开发服务器运行
|
||
npm run dev &
|
||
|
||
# 等待服务器启动
|
||
sleep 10
|
||
|
||
# 运行SEO检查
|
||
node scripts/seo-check.js
|
||
```
|
||
|
||
预期输出:
|
||
```
|
||
🔍 开始SEO检查...
|
||
|
||
📄 检查页面: 首页
|
||
✅ 无SEO问题
|
||
|
||
📄 检查页面: 联系我们
|
||
⚠️ 发现 2 个问题:
|
||
- description: 缺少meta描述 (medium)
|
||
- h1: 缺少H1标签 (high)
|
||
|
||
...
|
||
|
||
📊 SEO检查摘要
|
||
────────────────────────────────────────────────────────────────────────────────────────
|
||
总页面数: 7
|
||
有问题页面: 2
|
||
总问题数: 3
|
||
|
||
问题严重程度:
|
||
🔴 高: 1
|
||
🟡 中: 2
|
||
🟢 低: 0
|
||
────────────────────────────────────────────────────────────────────────────────────────
|
||
|
||
✅ SEO通过率: 71.4%
|
||
💾 详细结果已保存到: test-results/seo-summary.json
|
||
```
|
||
|
||
**Step 4: 提交**
|
||
|
||
```bash
|
||
git add scripts/seo-check.js scripts/utils/seo-validator.js
|
||
git commit -m "feat: add SEO validation script"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: 创建可访问性测试脚本
|
||
|
||
**文件:**
|
||
- 创建: `scripts/accessibility-test.js`
|
||
- 创建: `scripts/utils/axe-runner.js`
|
||
|
||
**Step 1: 创建axe-core运行工具**
|
||
|
||
```javascript
|
||
// scripts/utils/axe-runner.js
|
||
const { chromium } = require('playwright');
|
||
const AxeBuilder = require('@axe-core/playwright').default;
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
async function runAxeTest(url, pageName) {
|
||
const browser = await chromium.launch();
|
||
const page = await browser.newPage();
|
||
const results = [];
|
||
|
||
try {
|
||
await page.goto(url, { waitUntil: 'networkidle' });
|
||
|
||
const accessibilityScanResults = await new AxeBuilder({ page })
|
||
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
|
||
.analyze();
|
||
|
||
const violations = accessibilityScanResults.violations.map(v => ({
|
||
id: v.id,
|
||
impact: v.impact,
|
||
description: v.description,
|
||
help: v.help,
|
||
helpUrl: v.helpUrl,
|
||
nodes: v.nodes.length
|
||
}));
|
||
|
||
const passes = accessibilityScanResults.passes.length;
|
||
const incomplete = accessibilityScanResults.incomplete.length;
|
||
|
||
results.push({
|
||
page: pageName,
|
||
url,
|
||
violations,
|
||
passes,
|
||
incomplete,
|
||
score: calculateScore(violations, passes, incomplete)
|
||
});
|
||
|
||
console.log(` ✅ 扫描完成: ${violations.length} 个违规, ${passes} 个通过, ${incomplete} 个未完成`);
|
||
|
||
await browser.close();
|
||
return results[0];
|
||
|
||
} catch (error) {
|
||
await browser.close();
|
||
throw new Error(`可访问性测试失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
function calculateScore(violations, passes, incomplete) {
|
||
const total = violations.length + passes.length + incomplete.length;
|
||
if (total === 0) return 100;
|
||
return ((passes / total) * 100).toFixed(1);
|
||
}
|
||
|
||
function generateReport(results, outputPath) {
|
||
const report = {
|
||
timestamp: new Date().toISOString(),
|
||
summary: {
|
||
totalPages: results.length,
|
||
totalViolations: results.reduce((sum, r) => sum + r.violations.length, 0),
|
||
averageScore: (results.reduce((sum, r) => sum + parseFloat(r.score), 0) / results.length).toFixed(1)
|
||
},
|
||
pages: results
|
||
};
|
||
|
||
fs.writeFileSync(outputPath, JSON.stringify(report, null, 2));
|
||
return outputPath;
|
||
}
|
||
|
||
module.exports = { runAxeTest, generateReport };
|
||
```
|
||
|
||
**Step 2: 创建可访问性测试主脚本**
|
||
|
||
```javascript
|
||
// scripts/accessibility-test.js
|
||
const { runAxeTest, generateReport } = require('./utils/axe-runner');
|
||
|
||
const PAGES = [
|
||
{ name: '首页', url: 'http://localhost:3000' },
|
||
{ name: '关于我们', url: 'http://localhost:3000/about' },
|
||
{ name: '联系我们', url: 'http://localhost:3000/contact' },
|
||
{ name: '服务', url: 'http://localhost:3000/services' },
|
||
{ name: '产品', url: 'http://localhost:3000/products' },
|
||
{ name: '案例', url: 'http://localhost:3000/cases' },
|
||
{ name: '新闻', url: 'http://localhost:3000/news' }
|
||
];
|
||
|
||
const WCAG_STANDARD = 'WCAG 2.1 AA';
|
||
const MIN_SCORE = 80;
|
||
|
||
async function testAllPages() {
|
||
console.log('♿ 开始可访问性测试...\n');
|
||
console.log(`标准: ${WCAG_STANDARD}`);
|
||
console.log(`最低分数: ${MIN_SCORE}\n`);
|
||
|
||
const results = [];
|
||
const reportDir = 'test-results/accessibility';
|
||
|
||
if (!fs.existsSync(reportDir)) {
|
||
fs.mkdirSync(reportDir, { recursive: true });
|
||
}
|
||
|
||
for (const page of PAGES) {
|
||
console.log(`📄 测试页面: ${page.name} (${page.url})`);
|
||
|
||
try {
|
||
const result = await runAxeTest(page.url, page.name);
|
||
results.push(result);
|
||
|
||
const status = parseFloat(result.score) >= MIN_SCORE ? '✅' : '⚠️';
|
||
console.log(` ${status} 分数: ${result.score}\n`);
|
||
|
||
} catch (error) {
|
||
console.error(` ❌ 测试失败: ${error.message}\n`);
|
||
results.push({
|
||
page: page.name,
|
||
url: page.url,
|
||
error: error.message
|
||
});
|
||
}
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
function generateSummary(results) {
|
||
console.log('\n📊 可访问性测试摘要\n');
|
||
console.log('─'.repeat(80));
|
||
|
||
const table = results.map(r => {
|
||
if (r.error) {
|
||
return `| ${r.page.padEnd(20)} | ❌ 失败 |`;
|
||
}
|
||
const status = parseFloat(r.score) >= MIN_SCORE ? '✅' : '⚠️';
|
||
const violations = r.violations.length;
|
||
return `| ${r.page.padEnd(20)} | ${status} ${r.score.padStart(6)} | ${violations.toString().padStart(3)} |`;
|
||
});
|
||
|
||
console.log('| 页面'.padEnd(20) + ' | 状态 | 违规 |');
|
||
console.log('─'.repeat(80));
|
||
table.forEach(row => console.log(row));
|
||
console.log('─'.repeat(80));
|
||
|
||
const passed = results.filter(r => !r.error && parseFloat(r.score) >= MIN_SCORE).length;
|
||
const total = results.length;
|
||
const avgScore = results
|
||
.filter(r => !r.error)
|
||
.reduce((sum, r) => sum + parseFloat(r.score), 0) / results.length;
|
||
|
||
console.log(`\n📈 统计:`);
|
||
console.log(` 平均分数: ${avgScore.toFixed(1)}`);
|
||
console.log(` 达标页面: ${passed}/${total} (${(passed/total*100).toFixed(1)}%)`);
|
||
console.log(` 总违规数: ${results.reduce((sum, r) => sum + (r.violations?.length || 0), 0)}`);
|
||
}
|
||
|
||
async function main() {
|
||
const results = await testAllPages();
|
||
generateSummary(results);
|
||
|
||
const reportPath = 'test-results/accessibility-summary.json';
|
||
generateReport(results, reportPath);
|
||
console.log(`\n💾 详细报告已保存到: ${reportPath}`);
|
||
}
|
||
|
||
main().catch(console.error);
|
||
```
|
||
|
||
**Step 3: 运行可访问性测试**
|
||
|
||
```bash
|
||
# 确保开发服务器运行
|
||
npm run dev &
|
||
|
||
# 等待服务器启动
|
||
sleep 10
|
||
|
||
# 运行可访问性测试
|
||
node scripts/accessibility-test.js
|
||
```
|
||
|
||
预期输出:
|
||
```
|
||
♿ 开始可访问性测试...
|
||
|
||
标准: WCAG 2.1 AA
|
||
最低分数: 80
|
||
|
||
📄 测试页面: 首页 (http://localhost:3000)
|
||
✅ 扫描完成: 3 个违规, 145 个通过, 12 个未完成
|
||
✅ 分数: 91.2
|
||
|
||
...
|
||
|
||
📊 可访问性测试摘要
|
||
────────────────────────────────────────────────────────────────────────────────────────
|
||
| 页面 | 状态 | 违规 |
|
||
────────────────────────────────────────────────────────────────────────────────────────
|
||
| 首页 | ✅ 91.2 | 3 |
|
||
| 关于我们 | ✅ 88.5 | 5 |
|
||
| 联系我们 | ⚠️ 75.3 | 12 |
|
||
...
|
||
|
||
📈 统计:
|
||
平均分数: 85.7
|
||
达标页面: 5/7 (71.4%)
|
||
总违规数: 38
|
||
|
||
💾 详细报告已保存到: test-results/accessibility-summary.json
|
||
```
|
||
|
||
**Step 4: 提交**
|
||
|
||
```bash
|
||
git add scripts/accessibility-test.js scripts/utils/axe-runner.js
|
||
git commit -m "feat: add accessibility testing script with axe-core"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: 创建表单验证脚本
|
||
|
||
**文件:**
|
||
- 创建: `scripts/form-validation.js`
|
||
- 创建: `scripts/utils/form-tester.js`
|
||
|
||
**Step 1: 创建表单测试工具**
|
||
|
||
```javascript
|
||
// scripts/utils/form-tester.js
|
||
const { chromium } = require('playwright');
|
||
const fs = require('fs');
|
||
|
||
class FormTester {
|
||
constructor() {
|
||
this.results = [];
|
||
}
|
||
|
||
async testContactForm() {
|
||
const browser = await chromium.launch();
|
||
const page = await browser.newPage();
|
||
|
||
try {
|
||
await page.goto('http://localhost:3000/contact', { waitUntil: 'networkidle' });
|
||
|
||
const form = {
|
||
nameInput: page.locator('[data-testid="name-input"]'),
|
||
phoneInput: page.locator('[data-testid="phone-input"]'),
|
||
emailInput: page.locator('[data-testid="email-input"]'),
|
||
subjectInput: page.locator('[data-testid="subject-input"]'),
|
||
messageInput: page.locator('[data-testid="message-input"]'),
|
||
submitButton: page.locator('[data-testid="submit-button"]')
|
||
};
|
||
|
||
console.log(' 📝 测试表单字段可见性...');
|
||
|
||
const visibilityTests = [
|
||
{ name: '姓名输入框', element: form.nameInput },
|
||
{ name: '电话输入框', element: form.phoneInput },
|
||
{ name: '邮箱输入框', element: form.emailInput },
|
||
{ name: '主题输入框', element: form.subjectInput },
|
||
{ name: '消息输入框', element: form.messageInput },
|
||
{ name: '提交按钮', element: form.submitButton }
|
||
];
|
||
|
||
const visibilityResults = [];
|
||
for (const test of visibilityTests) {
|
||
try {
|
||
await test.element.waitFor({ state: 'visible', timeout: 5000 });
|
||
visibilityResults.push({ name: test.name, passed: true });
|
||
console.log(` ✅ ${test.name} 可见`);
|
||
} catch (error) {
|
||
visibilityResults.push({ name: test.name, passed: false, error: error.message });
|
||
console.log(` ❌ ${test.name} 不可见: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
console.log(' 📝 测试表单验证...');
|
||
|
||
const validationTests = [
|
||
{
|
||
name: '必填字段验证',
|
||
test: async () => {
|
||
await form.submitButton.click();
|
||
const errorMessages = await page.locator('[data-testid*="error"]').count();
|
||
return errorMessages > 0;
|
||
}
|
||
},
|
||
{
|
||
name: '邮箱格式验证',
|
||
test: async () => {
|
||
await form.emailInput.fill('invalid-email');
|
||
await form.emailInput.blur();
|
||
const error = await page.locator('[data-testid="email-input"] + [data-testid*="error"]').isVisible();
|
||
return error;
|
||
}
|
||
},
|
||
{
|
||
name: '电话格式验证',
|
||
test: async () => {
|
||
await form.phoneInput.fill('123');
|
||
await form.phoneInput.blur();
|
||
const error = await page.locator('[data-testid="phone-input"] + [data-testid*="error"]').isVisible();
|
||
return error;
|
||
}
|
||
}
|
||
];
|
||
|
||
const validationResults = [];
|
||
for (const test of validationTests) {
|
||
try {
|
||
await page.reload();
|
||
await page.waitForLoadState('networkidle');
|
||
const passed = await test.test();
|
||
validationResults.push({ name: test.name, passed });
|
||
console.log(` ${passed ? '✅' : '❌'} ${test.name}`);
|
||
} catch (error) {
|
||
validationResults.push({ name: test.name, passed: false, error: error.message });
|
||
console.log(` ❌ ${test.name}: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
console.log(' 📝 测试表单提交...');
|
||
|
||
const submitTest = {
|
||
name: '表单提交',
|
||
test: async () => {
|
||
await form.nameInput.fill('测试用户');
|
||
await form.phoneInput.fill('13800138000');
|
||
await form.emailInput.fill('test@example.com');
|
||
await form.subjectInput.fill('测试主题');
|
||
await form.messageInput.fill('这是一条测试消息');
|
||
await form.submitButton.click();
|
||
|
||
await page.waitForTimeout(2000);
|
||
|
||
const successMessage = await page.locator('[data-testid*="success"]').isVisible();
|
||
const errorMessage = await page.locator('[data-testid*="error"]').isVisible();
|
||
|
||
if (successMessage) {
|
||
return { passed: true, message: '提交成功' };
|
||
} else if (errorMessage) {
|
||
return { passed: false, message: '提交失败' };
|
||
} else {
|
||
return { passed: true, message: '表单已提交(需后端验证)' };
|
||
}
|
||
}
|
||
};
|
||
|
||
let submitResult;
|
||
try {
|
||
submitResult = await submitTest.test();
|
||
console.log(` ${submitResult.passed ? '✅' : '❌'} ${submitResult.name}: ${submitResult.message}`);
|
||
} catch (error) {
|
||
submitResult = { passed: false, error: error.message };
|
||
console.log(` ❌ ${submitTest.name}: ${error.message}`);
|
||
}
|
||
|
||
await browser.close();
|
||
|
||
return {
|
||
page: '联系我们',
|
||
url: 'http://localhost:3000/contact',
|
||
visibility: visibilityResults,
|
||
validation: validationResults,
|
||
submit: submitResult
|
||
};
|
||
|
||
} catch (error) {
|
||
await browser.close();
|
||
throw new Error(`表单测试失败: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
getSummary() {
|
||
const totalTests = this.results.reduce((sum, r) =>
|
||
sum + r.visibility.length + r.validation.length + 1, 0);
|
||
const passedTests = this.results.reduce((sum, r) =>
|
||
sum + r.visibility.filter(t => t.passed).length +
|
||
r.validation.filter(t => t.passed).length +
|
||
(r.submit?.passed ? 1 : 0), 0);
|
||
|
||
return {
|
||
totalForms: this.results.length,
|
||
totalTests,
|
||
passedTests,
|
||
passRate: ((passedTests / totalTests) * 100).toFixed(1)
|
||
};
|
||
}
|
||
}
|
||
|
||
module.exports = { FormTester };
|
||
```
|
||
|
||
**Step 2: 创建表单验证主脚本**
|
||
|
||
```javascript
|
||
// scripts/form-validation.js
|
||
const { FormTester } = require('./utils/form-tester');
|
||
|
||
async function main() {
|
||
console.log('📝 开始表单验证...\n');
|
||
|
||
const tester = new FormTester();
|
||
|
||
console.log('📄 测试联系表单\n');
|
||
const result = await tester.testContactForm();
|
||
tester.results.push(result);
|
||
|
||
const summary = tester.getSummary();
|
||
|
||
console.log('\n📊 表单验证摘要\n');
|
||
console.log('─'.repeat(80));
|
||
console.log(`总表单数: ${summary.totalForms}`);
|
||
console.log(`总测试数: ${summary.totalTests}`);
|
||
console.log(`通过测试: ${summary.passedTests}`);
|
||
console.log(`通过率: ${summary.passRate}%`);
|
||
console.log('─'.repeat(80));
|
||
|
||
const outputPath = 'test-results/form-validation-summary.json';
|
||
fs.writeFileSync(outputPath, JSON.stringify({
|
||
timestamp: new Date().toISOString(),
|
||
summary,
|
||
results: tester.results
|
||
}, null, 2));
|
||
console.log(`\n💾 详细结果已保存到: ${outputPath}`);
|
||
}
|
||
|
||
main().catch(console.error);
|
||
```
|
||
|
||
**Step 3: 运行表单验证**
|
||
|
||
```bash
|
||
# 确保开发服务器运行
|
||
npm run dev &
|
||
|
||
# 等待服务器启动
|
||
sleep 10
|
||
|
||
# 运行表单验证
|
||
node scripts/form-validation.js
|
||
```
|
||
|
||
预期输出:
|
||
```
|
||
📝 开始表单验证...
|
||
|
||
📄 测试联系表单
|
||
|
||
📝 测试表单字段可见性...
|
||
✅ 姓名输入框 可见
|
||
✅ 电话输入框 可见
|
||
✅ 邮箱输入框 可见
|
||
✅ 主题输入框 可见
|
||
✅ 消息输入框 可见
|
||
✅ 提交按钮 可见
|
||
|
||
📝 测试表单验证...
|
||
✅ 必填字段验证
|
||
✅ 邮箱格式验证
|
||
✅ 电话格式验证
|
||
|
||
📝 测试表单提交...
|
||
✅ 表单提交: 表单已提交(需后端验证)
|
||
|
||
📊 表单验证摘要
|
||
────────────────────────────────────────────────────────────────────────────────────────
|
||
总表单数: 1
|
||
总测试数: 10
|
||
通过测试: 10
|
||
通过率: 100.0%
|
||
────────────────────────────────────────────────────────────────────────────────────────
|
||
|
||
💾 详细结果已保存到: test-results/form-validation-summary.json
|
||
```
|
||
|
||
**Step 4: 提交**
|
||
|
||
```bash
|
||
git add scripts/form-validation.js scripts/utils/form-tester.js
|
||
git commit -m "feat: add form validation testing script"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: 创建综合测试报告脚本
|
||
|
||
**文件:**
|
||
- 创建: `scripts/generate-test-report.js`
|
||
|
||
**Step 1: 创建报告生成器**
|
||
|
||
```javascript
|
||
// scripts/generate-test-report.js
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
function loadResults(filename) {
|
||
const filePath = path.join('test-results', filename);
|
||
if (!fs.existsSync(filePath)) {
|
||
return null;
|
||
}
|
||
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||
}
|
||
|
||
function generateHTMLReport() {
|
||
const performance = loadResults('performance-summary.json');
|
||
const seo = loadResults('seo-summary.json');
|
||
const accessibility = loadResults('accessibility-summary.json');
|
||
const forms = loadResults('form-validation-summary.json');
|
||
|
||
const timestamp = new Date().toLocaleString('zh-CN');
|
||
|
||
const html = `<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>开发环境测试报告 - ${timestamp}</title>
|
||
<style>
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
background: #f5f5f5;
|
||
}
|
||
.header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
padding: 30px;
|
||
border-radius: 10px;
|
||
margin-bottom: 30px;
|
||
}
|
||
.header h1 {
|
||
margin: 0 0 10px 0;
|
||
}
|
||
.header p {
|
||
margin: 0;
|
||
opacity: 0.9;
|
||
}
|
||
.section {
|
||
background: white;
|
||
padding: 25px;
|
||
border-radius: 10px;
|
||
margin-bottom: 20px;
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||
}
|
||
.section h2 {
|
||
margin-top: 0;
|
||
color: #333;
|
||
border-bottom: 3px solid #667eea;
|
||
padding-bottom: 10px;
|
||
}
|
||
.metric {
|
||
display: inline-block;
|
||
padding: 10px 20px;
|
||
margin: 5px;
|
||
border-radius: 5px;
|
||
font-weight: bold;
|
||
}
|
||
.metric.success {
|
||
background: #10b981;
|
||
color: white;
|
||
}
|
||
.metric.warning {
|
||
background: #f59e0b;
|
||
color: white;
|
||
}
|
||
.metric.danger {
|
||
background: #ef4444;
|
||
color: white;
|
||
}
|
||
table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 15px;
|
||
}
|
||
th, td {
|
||
padding: 12px;
|
||
text-align: left;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
th {
|
||
background: #f9fafb;
|
||
font-weight: bold;
|
||
}
|
||
.summary {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 20px;
|
||
margin-top: 20px;
|
||
}
|
||
.summary-card {
|
||
background: #f9fafb;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
border-left: 4px solid #667eea;
|
||
}
|
||
.summary-card h3 {
|
||
margin-top: 0;
|
||
color: #667eea;
|
||
}
|
||
.summary-card .value {
|
||
font-size: 24px;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="header">
|
||
<h1>🚀 开发环境测试报告</h1>
|
||
<p>生成时间: ${timestamp}</p>
|
||
</div>
|
||
|
||
${performance ? generatePerformanceSection(performance) : ''}
|
||
${seo ? generateSEOSection(seo) : ''}
|
||
${accessibility ? generateAccessibilitySection(accessibility) : ''}
|
||
${forms ? generateFormSection(forms) : ''}
|
||
|
||
<div class="section">
|
||
<h2>📊 总体评估</h2>
|
||
<div class="summary">
|
||
${generateOverallSummary(performance, seo, accessibility, forms)}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h2>📝 建议和后续步骤</h2>
|
||
${generateRecommendations(performance, seo, accessibility, forms)}
|
||
</div>
|
||
</body>
|
||
</html>`;
|
||
|
||
const outputPath = 'test-results/test-report.html';
|
||
fs.writeFileSync(outputPath, html);
|
||
return outputPath;
|
||
}
|
||
|
||
function generatePerformanceSection(data) {
|
||
const avgScore = data.reduce((sum, page) =>
|
||
sum + (page.metrics?.performance || 0), 0) / data.length;
|
||
|
||
return `
|
||
<div class="section">
|
||
<h2>⚡ 性能测试结果</h2>
|
||
<div class="summary">
|
||
<div class="summary-card">
|
||
<h3>平均分数</h3>
|
||
<div class="value">${avgScore.toFixed(1)}</div>
|
||
</div>
|
||
<div class="summary-card">
|
||
<h3>测试页面数</h3>
|
||
<div class="value">${data.length}</div>
|
||
</div>
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>页面</th>
|
||
<th>性能</th>
|
||
<th>可访问性</th>
|
||
<th>最佳实践</th>
|
||
<th>SEO</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${data.map(page => `
|
||
<tr>
|
||
<td>${page.page}</td>
|
||
<td><span class="metric ${page.metrics?.performance >= 80 ? 'success' : 'warning'}">${page.metrics?.performance?.toFixed(0) || 'N/A'}</span></td>
|
||
<td><span class="metric ${page.metrics?.accessibility >= 80 ? 'success' : 'warning'}">${page.metrics?.accessibility?.toFixed(0) || 'N/A'}</span></td>
|
||
<td><span class="metric ${page.metrics?.bestPractices >= 80 ? 'success' : 'warning'}">${page.metrics?.bestPractices?.toFixed(0) || 'N/A'}</span></td>
|
||
<td><span class="metric ${page.metrics?.seo >= 80 ? 'success' : 'warning'}">${page.metrics?.seo?.toFixed(0) || 'N/A'}</span></td>
|
||
</tr>
|
||
`).join('')}
|
||
</tbody>
|
||
</table>
|
||
</div>`;
|
||
}
|
||
|
||
function generateSEOSection(data) {
|
||
const passRate = ((data.totalPages - data.pagesWithIssues) / data.totalPages * 100);
|
||
|
||
return `
|
||
<div class="section">
|
||
<h2>🔍 SEO检查结果</h2>
|
||
<div class="summary">
|
||
<div class="summary-card">
|
||
<h3>通过率</h3>
|
||
<div class="value">${passRate.toFixed(1)}%</div>
|
||
</div>
|
||
<div class="summary-card">
|
||
<h3>总问题数</h3>
|
||
<div class="value">${data.totalIssues}</div>
|
||
</div>
|
||
</div>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>严重程度</th>
|
||
<th>数量</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>🔴 高</td>
|
||
<td>${data.severity.high}</td>
|
||
</tr>
|
||
<tr>
|
||
<td>🟡 中</td>
|
||
<td>${data.severity.medium}</td>
|
||
</tr>
|
||
<tr>
|
||
<td>🟢 低</td>
|
||
<td>${data.severity.low}</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>`;
|
||
}
|
||
|
||
function generateAccessibilitySection(data) {
|
||
const avgScore = data.summary.averageScore;
|
||
const passRate = ((data.totalPages - data.pagesWithIssues) / data.totalPages * 100);
|
||
|
||
return `
|
||
<div class="section">
|
||
<h2>♿ 可访问性测试结果</h2>
|
||
<div class="summary">
|
||
<div class="summary-card">
|
||
<h3>平均分数</h3>
|
||
<div class="value">${avgScore}</div>
|
||
</div>
|
||
<div class="summary-card">
|
||
<h3>达标率</h3>
|
||
<div class="value">${passRate.toFixed(1)}%</div>
|
||
</div>
|
||
</div>
|
||
<p>标准: WCAG 2.1 AA</p>
|
||
<p>总违规数: ${data.summary.totalViolations}</p>
|
||
</div>`;
|
||
}
|
||
|
||
function generateFormSection(data) {
|
||
return `
|
||
<div class="section">
|
||
<h2>📝 表单验证结果</h2>
|
||
<div class="summary">
|
||
<div class="summary-card">
|
||
<h3>通过率</h3>
|
||
<div class="value">${data.summary.passRate}%</div>
|
||
</div>
|
||
<div class="summary-card">
|
||
<h3>总测试数</h3>
|
||
<div class="value">${data.summary.totalTests}</div>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
function generateOverallSummary(performance, seo, accessibility, forms) {
|
||
const sections = [];
|
||
|
||
if (performance) {
|
||
const avgScore = performance.reduce((sum, page) =>
|
||
sum + (page.metrics?.performance || 0), 0) / performance.length;
|
||
sections.push(`
|
||
<div class="summary-card">
|
||
<h3>性能</h3>
|
||
<div class="value">${avgScore.toFixed(1)}</div>
|
||
</div>`);
|
||
}
|
||
|
||
if (seo) {
|
||
const passRate = ((seo.totalPages - seo.pagesWithIssues) / seo.totalPages * 100);
|
||
sections.push(`
|
||
<div class="summary-card">
|
||
<h3>SEO</h3>
|
||
<div class="value">${passRate.toFixed(1)}%</div>
|
||
</div>`);
|
||
}
|
||
|
||
if (accessibility) {
|
||
sections.push(`
|
||
<div class="summary-card">
|
||
<h3>可访问性</h3>
|
||
<div class="value">${accessibility.summary.averageScore}</div>
|
||
</div>`);
|
||
}
|
||
|
||
if (forms) {
|
||
sections.push(`
|
||
<div class="summary-card">
|
||
<h3>表单</h3>
|
||
<div class="value">${forms.summary.passRate}%</div>
|
||
</div>`);
|
||
}
|
||
|
||
return sections.join('');
|
||
}
|
||
|
||
function generateRecommendations(performance, seo, accessibility, forms) {
|
||
const recommendations = [];
|
||
|
||
if (performance) {
|
||
const lowPerfPages = performance.filter(p => p.metrics?.performance < 80);
|
||
if (lowPerfPages.length > 0) {
|
||
recommendations.push(`
|
||
<h3>⚡ 性能优化</h3>
|
||
<ul>
|
||
<li>优化以下页面的加载速度: ${lowPerfPages.map(p => p.page).join(', ')}</li>
|
||
<li>实施图片懒加载和压缩</li>
|
||
<li>优化CSS和JavaScript资源</li>
|
||
<li>配置CDN加速</li>
|
||
</ul>`);
|
||
}
|
||
}
|
||
|
||
if (seo) {
|
||
if (seo.totalIssues > 0) {
|
||
recommendations.push(`
|
||
<h3>🔍 SEO改进</h3>
|
||
<ul>
|
||
<li>修复 ${seo.severity.high} 个高优先级SEO问题</li>
|
||
<li>完善所有页面的Meta标签</li>
|
||
<li>添加Open Graph标签</li>
|
||
<li>生成并提交sitemap.xml</li>
|
||
<li>配置robots.txt</li>
|
||
</ul>`);
|
||
}
|
||
}
|
||
|
||
if (accessibility) {
|
||
const lowAccessPages = accessibility.pages.filter(p => parseFloat(p.score) < 80);
|
||
if (lowAccessPages.length > 0) {
|
||
recommendations.push(`
|
||
<h3>♿ 可访问性改进</h3>
|
||
<ul>
|
||
<li>修复以下页面的可访问性问题: ${lowAccessPages.map(p => p.page).join(', ')}</li>
|
||
<li>添加ARIA标签</li>
|
||
<li>改进键盘导航</li>
|
||
<li>优化颜色对比度</li>
|
||
<li>确保所有交互元素可访问</li>
|
||
</ul>`);
|
||
}
|
||
}
|
||
|
||
if (forms) {
|
||
if (parseFloat(forms.summary.passRate) < 100) {
|
||
recommendations.push(`
|
||
<h3>📝 表单改进</h3>
|
||
<ul>
|
||
<li>修复表单验证逻辑</li>
|
||
<li>改进错误提示信息</li>
|
||
<li>添加提交成功反馈</li>
|
||
<li>确保表单在移动端正常工作</li>
|
||
</ul>`);
|
||
}
|
||
}
|
||
|
||
return recommendations.join('') || '<p>✅ 所有测试都通过了!网站已准备好上线。</p>';
|
||
}
|
||
|
||
async function main() {
|
||
console.log('📊 生成综合测试报告...\n');
|
||
|
||
const reportPath = generateHTMLReport();
|
||
|
||
console.log(`✅ 报告已生成: ${reportPath}`);
|
||
console.log(`💡 在浏览器中打开查看详细报告`);
|
||
}
|
||
|
||
main().catch(console.error);
|
||
```
|
||
|
||
**Step 2: 运行报告生成**
|
||
|
||
```bash
|
||
# 确保所有测试已完成
|
||
# node scripts/performance-audit.js
|
||
# node scripts/seo-check.js
|
||
# node scripts/accessibility-test.js
|
||
# node scripts/form-validation.js
|
||
|
||
# 生成综合报告
|
||
node scripts/generate-test-report.js
|
||
```
|
||
|
||
预期输出:
|
||
```
|
||
📊 生成综合测试报告...
|
||
|
||
✅ 报告已生成: test-results/test-report.html
|
||
💡 在浏览器中打开查看详细报告
|
||
```
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add scripts/generate-test-report.js
|
||
git commit -m "feat: add comprehensive test report generator"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6: 创建一键运行脚本
|
||
|
||
**文件:**
|
||
- 创建: `scripts/run-all-tests.sh`
|
||
|
||
**Step 1: 创建批处理脚本**
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
|
||
# scripts/run-all-tests.sh
|
||
|
||
set -e
|
||
|
||
echo "🚀 开始运行所有开发环境测试..."
|
||
echo "========================================"
|
||
echo ""
|
||
|
||
# 颜色定义
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
NC='\033[0m'
|
||
|
||
# 创建结果目录
|
||
mkdir -p test-results
|
||
|
||
# 检查开发服务器
|
||
echo "📡 检查开发服务器..."
|
||
if ! curl -s http://localhost:3000 > /dev/null; then
|
||
echo -e "${RED}❌ 开发服务器未运行${NC}"
|
||
echo "请先运行: npm run dev"
|
||
exit 1
|
||
fi
|
||
echo -e "${GREEN}✅ 开发服务器运行正常${NC}"
|
||
echo ""
|
||
|
||
# 运行性能审计
|
||
echo "⚡ 运行性能审计..."
|
||
node scripts/performance-audit.js
|
||
if [ $? -eq 0 ]; then
|
||
echo -e "${GREEN}✅ 性能审计完成${NC}"
|
||
else
|
||
echo -e "${RED}❌ 性能审计失败${NC}"
|
||
fi
|
||
echo ""
|
||
|
||
# 运行SEO检查
|
||
echo "🔍 运行SEO检查..."
|
||
node scripts/seo-check.js
|
||
if [ $? -eq 0 ]; then
|
||
echo -e "${GREEN}✅ SEO检查完成${NC}"
|
||
else
|
||
echo -e "${RED}❌ SEO检查失败${NC}"
|
||
fi
|
||
echo ""
|
||
|
||
# 运行可访问性测试
|
||
echo "♿ 运行可访问性测试..."
|
||
node scripts/accessibility-test.js
|
||
if [ $? -eq 0 ]; then
|
||
echo -e "${GREEN}✅ 可访问性测试完成${NC}"
|
||
else
|
||
echo -e "${RED}❌ 可访问性测试失败${NC}"
|
||
fi
|
||
echo ""
|
||
|
||
# 运行表单验证
|
||
echo "📝 运行表单验证..."
|
||
node scripts/form-validation.js
|
||
if [ $? -eq 0 ]; then
|
||
echo -e "${GREEN}✅ 表单验证完成${NC}"
|
||
else
|
||
echo -e "${RED}❌ 表单验证失败${NC}"
|
||
fi
|
||
echo ""
|
||
|
||
# 生成综合报告
|
||
echo "📊 生成综合测试报告..."
|
||
node scripts/generate-test-report.js
|
||
if [ $? -eq 0 ]; then
|
||
echo -e "${GREEN}✅ 报告生成完成${NC}"
|
||
else
|
||
echo -e "${RED}❌ 报告生成失败${NC}"
|
||
fi
|
||
echo ""
|
||
|
||
# 总结
|
||
echo "========================================"
|
||
echo "📈 测试完成!"
|
||
echo ""
|
||
echo "📁 结果文件位置:"
|
||
echo " - test-results/performance-summary.json"
|
||
echo " - test-results/performance/*.html"
|
||
echo " - test-results/seo-summary.json"
|
||
echo " - test-results/accessibility-summary.json"
|
||
echo " - test-results/form-validation-summary.json"
|
||
echo " - test-results/test-report.html"
|
||
echo ""
|
||
echo -e "${GREEN}💡 在浏览器中打开 test-results/test-report.html 查看详细报告${NC}"
|
||
echo ""
|
||
```
|
||
|
||
**Step 2: 添加执行权限**
|
||
|
||
```bash
|
||
chmod +x scripts/run-all-tests.sh
|
||
```
|
||
|
||
**Step 3: 运行所有测试**
|
||
|
||
```bash
|
||
# 确保开发服务器运行
|
||
npm run dev &
|
||
|
||
# 等待服务器启动
|
||
sleep 10
|
||
|
||
# 运行所有测试
|
||
./scripts/run-all-tests.sh
|
||
```
|
||
|
||
预期输出:
|
||
```
|
||
🚀 开始运行所有开发环境测试...
|
||
========================================
|
||
|
||
📡 检查开发服务器...
|
||
✅ 开发服务器运行正常
|
||
|
||
⚡ 运行性能审计...
|
||
🚀 开始性能审计...
|
||
...
|
||
✅ 性能审计完成
|
||
|
||
🔍 运行SEO检查...
|
||
...
|
||
✅ SEO检查完成
|
||
|
||
♿ 运行可访问性测试...
|
||
...
|
||
✅ 可访问性测试完成
|
||
|
||
📝 运行表单验证...
|
||
...
|
||
✅ 表单验证完成
|
||
|
||
📊 生成综合测试报告...
|
||
✅ 报告生成完成
|
||
|
||
========================================
|
||
📈 测试完成!
|
||
|
||
📁 结果文件位置:
|
||
- test-results/performance-summary.json
|
||
- test-results/performance/*.html
|
||
- test-results/seo-summary.json
|
||
- test-results/accessibility-summary.json
|
||
- test-results/form-validation-summary.json
|
||
- test-results/test-report.html
|
||
|
||
💡 在浏览器中打开 test-results/test-report.html 查看详细报告
|
||
```
|
||
|
||
**Step 4: 提交**
|
||
|
||
```bash
|
||
git add scripts/run-all-tests.sh
|
||
git commit -m "feat: add one-click test runner script"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 7: 添加package.json脚本
|
||
|
||
**文件:**
|
||
- 修改: `package.json`
|
||
|
||
**Step 1: 添加测试脚本到package.json**
|
||
|
||
```json
|
||
{
|
||
"scripts": {
|
||
"dev": "next dev",
|
||
"build": "next build",
|
||
"start": "next start",
|
||
"lint": "next lint",
|
||
"test": "playwright test",
|
||
"test:smoke": "playwright test --grep @smoke",
|
||
"test:report": "allure generate test-results/allure-results && allure open",
|
||
"audit:performance": "node scripts/performance-audit.js",
|
||
"audit:seo": "node scripts/seo-check.js",
|
||
"audit:accessibility": "node scripts/accessibility-test.js",
|
||
"audit:forms": "node scripts/form-validation.js",
|
||
"audit:all": "./scripts/run-all-tests.sh",
|
||
"report:generate": "node scripts/generate-test-report.js"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Step 2: 验证脚本**
|
||
|
||
```bash
|
||
# 查看所有可用脚本
|
||
npm run
|
||
```
|
||
|
||
预期输出:
|
||
```
|
||
Lifecycle scripts available via `npm run`:
|
||
dev 启动开发服务器
|
||
build 构建生产版本
|
||
start 启动生产服务器
|
||
lint 代码检查
|
||
test 运行所有测试
|
||
test:smoke 运行冒烟测试
|
||
test:report 生成测试报告
|
||
audit:performance 运行性能审计
|
||
audit:seo 运行SEO检查
|
||
audit:accessibility 运行可访问性测试
|
||
audit:forms 运行表单验证
|
||
audit:all 运行所有审计
|
||
report:generate 生成综合报告
|
||
```
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add package.json
|
||
git commit -m "chore: add audit and test scripts to package.json"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 8: 创建测试结果目录结构
|
||
|
||
**文件:**
|
||
- 创建: `test-results/.gitkeep`
|
||
|
||
**Step 1: 创建测试结果目录**
|
||
|
||
```bash
|
||
# 创建测试结果目录
|
||
mkdir -p test-results/performance
|
||
mkdir -p test-results/accessibility
|
||
|
||
# 创建.gitkeep文件
|
||
touch test-results/.gitkeep
|
||
touch test-results/performance/.gitkeep
|
||
touch test-results/accessibility/.gitkeep
|
||
```
|
||
|
||
**Step 2: 更新.gitignore**
|
||
|
||
```bash
|
||
# 添加到.gitignore
|
||
echo "test-results/*.html" >> .gitignore
|
||
echo "test-results/*.json" >> .gitignore
|
||
```
|
||
|
||
**Step 3: 提交**
|
||
|
||
```bash
|
||
git add test-results/.gitkeep test-results/performance/.gitkeep test-results/accessibility/.gitkeep .gitignore
|
||
git commit -m "chore: add test results directory structure"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 9: 创建README文档
|
||
|
||
**文件:**
|
||
- 创建: `scripts/README.md`
|
||
|
||
**Step 1: 创建脚本使用文档**
|
||
|
||
```markdown
|
||
# 开发环境测试脚本
|
||
|
||
本目录包含用于在开发环境中测试和验证网站的可复用脚本。
|
||
|
||
## 📋 可用脚本
|
||
|
||
### 性能测试
|
||
|
||
#### `performance-audit.js`
|
||
使用Lighthouse进行性能审计,生成详细的性能报告。
|
||
|
||
**运行方式:**
|
||
```bash
|
||
npm run audit:performance
|
||
# 或
|
||
node scripts/performance-audit.js
|
||
```
|
||
|
||
**输出:**
|
||
- `test-results/performance-summary.json` - 性能数据摘要
|
||
- `test-results/performance/*.html` - 每个页面的详细报告
|
||
|
||
**评估标准:**
|
||
- 性能分数 >= 80: ✅ 通过
|
||
- 可访问性分数 >= 80: ✅ 通过
|
||
- SEO分数 >= 80: ✅ 通过
|
||
|
||
### SEO检查
|
||
|
||
#### `seo-check.js`
|
||
检查所有页面的SEO配置,包括Meta标签、Open Graph标签、结构化数据等。
|
||
|
||
**运行方式:**
|
||
```bash
|
||
npm run audit:seo
|
||
# 或
|
||
node scripts/seo-check.js
|
||
```
|
||
|
||
**输出:**
|
||
- `test-results/seo-summary.json` - SEO检查结果
|
||
|
||
**检查项:**
|
||
- Meta标签完整性
|
||
- Open Graph标签
|
||
- H1标签
|
||
- 图片alt属性
|
||
- 链接有效性
|
||
- 语言属性
|
||
|
||
### 可访问性测试
|
||
|
||
#### `accessibility-test.js`
|
||
使用axe-core进行可访问性测试,符合WCAG 2.1 AA标准。
|
||
|
||
**运行方式:**
|
||
```bash
|
||
npm run audit:accessibility
|
||
# 或
|
||
node scripts/accessibility-test.js
|
||
```
|
||
|
||
**输出:**
|
||
- `test-results/accessibility-summary.json` - 可访问性测试结果
|
||
|
||
**标准:**
|
||
- WCAG 2.1 AA
|
||
- 最低分数: 80
|
||
|
||
### 表单验证
|
||
|
||
#### `form-validation.js`
|
||
测试表单的UI验证、字段可见性和提交功能。
|
||
|
||
**运行方式:**
|
||
```bash
|
||
npm run audit:forms
|
||
# 或
|
||
node scripts/form-validation.js
|
||
```
|
||
|
||
**输出:**
|
||
- `test-results/form-validation-summary.json` - 表单验证结果
|
||
|
||
**测试项:**
|
||
- 表单字段可见性
|
||
- 必填字段验证
|
||
- 邮箱/电话格式验证
|
||
- 表单提交功能
|
||
|
||
### 综合报告
|
||
|
||
#### `generate-test-report.js`
|
||
生成包含所有测试结果的综合HTML报告。
|
||
|
||
**运行方式:**
|
||
```bash
|
||
npm run report:generate
|
||
# 或
|
||
node scripts/generate-test-report.js
|
||
```
|
||
|
||
**输出:**
|
||
- `test-results/test-report.html` - 综合测试报告
|
||
|
||
### 一键运行
|
||
|
||
#### `run-all-tests.sh`
|
||
运行所有测试并生成综合报告。
|
||
|
||
**运行方式:**
|
||
```bash
|
||
npm run audit:all
|
||
# 或
|
||
./scripts/run-all-tests.sh
|
||
```
|
||
|
||
**前置条件:**
|
||
- 开发服务器运行在 http://localhost:3000
|
||
- 已安装所有依赖
|
||
|
||
## 🚀 快速开始
|
||
|
||
### 1. 安装依赖
|
||
```bash
|
||
npm install -D @axe-core/playwright lighthouse chrome-launcher
|
||
```
|
||
|
||
### 2. 启动开发服务器
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
### 3. 运行所有测试
|
||
```bash
|
||
# 在另一个终端
|
||
npm run audit:all
|
||
```
|
||
|
||
### 4. 查看报告
|
||
```bash
|
||
open test-results/test-report.html
|
||
```
|
||
|
||
## 📊 测试覆盖
|
||
|
||
### 测试页面
|
||
- 首页 (/)
|
||
- 关于我们 (/about)
|
||
- 联系我们 (/contact)
|
||
- 服务 (/services)
|
||
- 产品 (/products)
|
||
- 案例 (/cases)
|
||
- 新闻 (/news)
|
||
|
||
### 测试维度
|
||
- ⚡ 性能(Lighthouse)
|
||
- 🔍 SEO(Meta标签、Open Graph等)
|
||
- ♿ 可访问性(WCAG 2.1 AA)
|
||
- 📝 表单功能(UI验证、提交测试)
|
||
|
||
## 📝 自定义配置
|
||
|
||
### 修改测试页面
|
||
编辑各个脚本中的 `PAGES` 数组:
|
||
|
||
```javascript
|
||
const PAGES = [
|
||
{ name: '页面名称', url: 'http://localhost:3000/path' },
|
||
// 添加更多页面...
|
||
];
|
||
```
|
||
|
||
### 修改评分标准
|
||
编辑各个脚本中的 `THRESHOLDS` 或 `MIN_SCORE` 常量:
|
||
|
||
```javascript
|
||
const THRESHOLDS = {
|
||
performance: 90, // 提高标准
|
||
accessibility: 90,
|
||
seo: 90
|
||
};
|
||
```
|
||
|
||
## 🔧 故障排除
|
||
|
||
### 开发服务器未运行
|
||
```
|
||
❌ 开发服务器未运行
|
||
```
|
||
**解决:** 先运行 `npm run dev`
|
||
|
||
### 端口冲突
|
||
如果3000端口被占用,修改脚本中的URL:
|
||
|
||
```javascript
|
||
const BASE_URL = 'http://localhost:3001'; // 使用其他端口
|
||
```
|
||
|
||
### 测试超时
|
||
增加等待时间:
|
||
|
||
```javascript
|
||
await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 });
|
||
```
|
||
|
||
## 📚 参考资料
|
||
|
||
- [Lighthouse文档](https://github.com/GoogleChrome/lighthouse)
|
||
- [axe-core文档](https://www.deque.com/axe/)
|
||
- [Playwright文档](https://playwright.dev/)
|
||
- [WCAG 2.1指南](https://www.w3.org/WAI/WCAG21/quickref/)
|
||
```
|
||
|
||
**Step 2: 提交**
|
||
|
||
```bash
|
||
git add scripts/README.md
|
||
git commit -m "docs: add testing scripts documentation"
|
||
```
|
||
|
||
---
|
||
|
||
## 执行总结
|
||
|
||
### 完成的任务
|
||
1. ✅ 创建性能审计脚本(Lighthouse)
|
||
2. ✅ 创建SEO检查脚本
|
||
3. ✅ 创建可访问性测试脚本(axe-core)
|
||
4. ✅ 创建表单验证脚本
|
||
5. ✅ 创建综合测试报告生成器
|
||
6. ✅ 创建一键运行脚本
|
||
7. ✅ 添加package.json脚本
|
||
8. ✅ 创建测试结果目录结构
|
||
9. ✅ 创建README文档
|
||
|
||
### 生成的文件结构
|
||
```
|
||
scripts/
|
||
├── README.md # 脚本使用文档
|
||
├── run-all-tests.sh # 一键运行所有测试
|
||
├── performance-audit.js # 性能审计
|
||
├── seo-check.js # SEO检查
|
||
├── accessibility-test.js # 可访问性测试
|
||
├── form-validation.js # 表单验证
|
||
├── generate-test-report.js # 报告生成器
|
||
└── utils/
|
||
├── lighthouse-runner.js # Lighthouse工具
|
||
├── seo-validator.js # SEO验证工具
|
||
├── axe-runner.js # axe-core工具
|
||
└── form-tester.js # 表单测试工具
|
||
|
||
test-results/
|
||
├── .gitkeep
|
||
├── performance/
|
||
│ └── .gitkeep
|
||
└── accessibility/
|
||
└── .gitkeep
|
||
```
|
||
|
||
### 可用的npm命令
|
||
```bash
|
||
npm run audit:performance # 性能审计
|
||
npm run audit:seo # SEO检查
|
||
npm run audit:accessibility # 可访问性测试
|
||
npm run audit:forms # 表单验证
|
||
npm run audit:all # 运行所有审计
|
||
npm run report:generate # 生成综合报告
|
||
```
|
||
|
||
### 使用流程
|
||
1. 启动开发服务器: `npm run dev`
|
||
2. 运行所有测试: `npm run audit:all`
|
||
3. 查看综合报告: `open test-results/test-report.html`
|
||
|
||
---
|
||
|
||
## 上线检查清单
|
||
|
||
基于测试结果,确认以下项目后再上线:
|
||
|
||
### 必须完成(上线前)
|
||
- [ ] 性能分数 >= 80(所有页面)
|
||
- [ ] SEO通过率 >= 80%
|
||
- [ ] 可访问性分数 >= 80(所有页面)
|
||
- [ ] 表单功能正常
|
||
- [ ] HTTPS证书已配置
|
||
- [ ] 域名DNS已配置
|
||
|
||
### 建议完成(上线后)
|
||
- [ ] 配置CDN加速
|
||
- [ ] 设置Google Analytics
|
||
- [ ] 配置错误监控(Sentry)
|
||
- [ ] 提交sitemap到搜索引擎
|
||
- [ ] 验证Open Graph显示
|
||
- [ ] 性能监控配置
|
||
|
||
---
|
||
|
||
**计划完成并已保存到 `docs/plans/2026-03-06-dev-environment-testing.md`。**
|
||
|
||
**两种执行选项:**
|
||
|
||
**1. Subagent-Driven(本次会话)** - 我将分派新的子代理执行每个任务,任务之间进行代码审查,快速迭代
|
||
|
||
**2. Parallel Session(独立会话)** - 在新的worktree中打开新会话,使用executing-plans skill批量执行,带检查点
|
||
|
||
**选择哪种方式?** |