feat: 增加测试覆盖率并优化代码质量

test: 添加单元测试和端到端测试
refactor: 重构登录页面和上传模块
ci: 更新测试覆盖率阈值至42%
build: 添加测试相关依赖
docs: 更新测试文档
style: 修复代码格式问题
This commit is contained in:
张翔
2026-03-11 11:14:37 +08:00
parent 8fd7ed84ed
commit b207bfa7af
58 changed files with 14494 additions and 655 deletions
+81
View File
@@ -0,0 +1,81 @@
const fs = require('fs');
const path = require('path');
function generateSimpleReport() {
const coveragePath = path.join(__dirname, 'coverage/e2e/coverage-data.json');
if (!fs.existsSync(coveragePath)) {
console.error('Coverage data file not found!');
return;
}
const coverageData = JSON.parse(fs.readFileSync(coveragePath, 'utf-8'));
const outputDir = path.join(__dirname, 'coverage/e2e');
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
let totalEntries = 0;
let appEntries = 0;
const fileStats = {};
console.log('\n=== E2E Coverage Collection Report ===\n');
console.log('Pages covered:');
const pages = new Set();
const scripts = new Set();
for (const entry of coverageData) {
if (!entry.url) continue;
const url = entry.url;
if (url.includes('localhost:3000') || url.includes('_next')) {
totalEntries++;
const urlObj = new URL(url);
pages.add(urlObj.pathname);
const scriptUrl = entry.scriptId ? `script-${entry.scriptId}` : 'inline';
if (!fileStats[scriptUrl]) {
fileStats[scriptUrl] = { count: 0, sourceSize: 0 };
}
fileStats[scriptUrl].count++;
if (entry.source) {
fileStats[scriptUrl].sourceSize += entry.source.length;
}
if (url.includes('novalon-website') || url.includes('/_next/')) {
appEntries++;
}
}
}
console.log(`\nTotal JS bundles collected: ${totalEntries}`);
console.log(`App-specific bundles: ${appEntries}`);
console.log(`Unique pages visited: ${pages.size}`);
console.log(`\nPages:`);
pages.forEach(p => console.log(` - ${p}`));
const reportPath = path.join(outputDir, 'coverage-summary.json');
const report = {
timestamp: new Date().toISOString(),
totalEntries,
appEntries,
pagesVisited: Array.from(pages),
fileStats,
};
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
console.log(`\nReport saved to: ${reportPath}`);
console.log('\n=== Coverage Summary ===');
console.log(`✓ Playwright successfully collected JS coverage from ${totalEntries} bundles`);
console.log(`✓ Covered ${pages.size} unique pages`);
console.log(`\nNote: For Istanbul HTML report, run: npx playwright show-report`);
return report;
}
generateSimpleReport();
+35 -1
View File
@@ -19,8 +19,10 @@
"allure-playwright": "^3.5.0",
"chrome-launcher": "^1.2.1",
"glob": "^13.0.6",
"istanbul-lib-coverage": "^3.2.2",
"lighthouse": "^13.0.3",
"typescript": "^5.3.0"
"typescript": "^5.3.0",
"v8-to-istanbul": "^9.3.0"
}
},
"node_modules/@axe-core/playwright": {
@@ -3573,6 +3575,13 @@
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -5093,6 +5102,16 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/jest-worker": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
@@ -6566,6 +6585,21 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-to-istanbul": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
"integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
"dev": true,
"license": "ISC",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.12",
"@types/istanbul-lib-coverage": "^2.0.1",
"convert-source-map": "^2.0.0"
},
"engines": {
"node": ">=10.12.0"
}
},
"node_modules/watchpack": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz",
+4 -1
View File
@@ -19,6 +19,7 @@
"test:allure:open": "allure open allure-report",
"test:allure:serve": "allure serve allure-results",
"test:all-with-progress": "node run-tests-with-progress.js",
"test:coverage": "playwright test --config=playwright.coverage.config.ts && node coverage-reporter.js",
"install": "playwright install --with-deps"
},
"devDependencies": {
@@ -29,8 +30,10 @@
"allure-playwright": "^3.5.0",
"chrome-launcher": "^1.2.1",
"glob": "^13.0.6",
"istanbul-lib-coverage": "^3.2.2",
"lighthouse": "^13.0.3",
"typescript": "^5.3.0"
"typescript": "^5.3.0",
"v8-to-istanbul": "^9.3.0"
},
"dependencies": {
"@sentry/nextjs": "^10.42.0"
+9
View File
@@ -46,6 +46,15 @@ export default defineConfig({
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'chromium-coverage',
use: {
...devices['Desktop Chrome'],
browserName: 'chromium',
},
testMatch: /.*\.spec\.ts/,
globalSetup: undefined,
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
+25
View File
@@ -0,0 +1,25 @@
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './src/tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: 0,
timeout: 30000,
use: {
baseURL: 'http://localhost:3000',
trace: 'off',
screenshot: 'off',
video: 'off',
headless: true,
viewport: { width: 1280, height: 720 },
actionTimeout: 15000,
navigationTimeout: 30000,
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
+80
View File
@@ -0,0 +1,80 @@
import { test, expect, Page } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
let coverageData: any[] = [];
async function startCoverage(page: Page) {
await page.coverage.startJSCoverage({
resetOnNavigation: true,
reportAnonymousScripts: false,
});
}
async function stopCoverage(page: Page) {
const coverage = await page.coverage.stopJSCoverage();
coverageData = coverage;
console.log(`Collected ${coverage.length} JS coverage entries`);
}
async function saveCoverage() {
const outputPath = path.join(__dirname, '../../coverage/e2e/coverage-data.json');
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
fs.writeFileSync(outputPath, JSON.stringify(coverageData, null, 2));
console.log(`Coverage data saved to: ${outputPath}`);
}
test.describe('E2E覆盖率测试', () => {
test.beforeEach(async ({ page }) => {
await startCoverage(page);
});
test.afterEach(async ({ page }) => {
await stopCoverage(page);
});
test('首页应该正常加载', async ({ page }) => {
await page.goto('http://localhost:3000/');
await expect(page).toHaveURL(/localhost:3000\//);
await page.waitForLoadState('networkidle');
});
test('关于页面应该正常加载', async ({ page }) => {
await page.goto('http://localhost:3000/about');
await expect(page).toHaveURL(/about/);
await page.waitForLoadState('networkidle');
});
test('产品页面应该正常加载', async ({ page }) => {
await page.goto('http://localhost:3000/products');
await expect(page).toHaveURL(/products/);
await page.waitForLoadState('networkidle');
});
test('服务页面应该正常加载', async ({ page }) => {
await page.goto('http://localhost:3000/services');
await expect(page).toHaveURL(/services/);
await page.waitForLoadState('networkidle');
});
test('案例页面应该正常加载', async ({ page }) => {
await page.goto('http://localhost:3000/cases');
await expect(page).toHaveURL(/cases/);
await page.waitForLoadState('networkidle');
});
test('新闻页面应该正常加载', async ({ page }) => {
await page.goto('http://localhost:3000/news');
await expect(page).toHaveURL(/news/);
await page.waitForLoadState('networkidle');
});
test.afterAll(async () => {
await saveCoverage();
});
});