完整的前后端图片分析应用,包含: - 后端:Express + Prisma + SQLite,101个单元测试全部通过 - 前端:React + TypeScript + Vite,47个单元测试,89.73%覆盖率 - E2E测试:Playwright 测试套件 - MCP集成:Playwright MCP配置完成并测试通过 功能模块: - 用户认证(JWT) - 文档管理(CRUD) - 待办管理(三态工作流) - 图片管理(上传、截图、OCR) 测试覆盖: - 后端单元测试:101/101 ✅ - 前端单元测试:47/47 ✅ - E2E测试:通过 ✅ - MCP Playwright测试:通过 ✅ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
321 lines
8.8 KiB
JavaScript
321 lines
8.8 KiB
JavaScript
/**
|
||
* MCP Playwright 完整测试
|
||
* 测试所有页面并检查:
|
||
* 1. 页面加载正常
|
||
* 2. 控制台无错误
|
||
* 3. UI 展示正常
|
||
* 4. 基本功能可用
|
||
*/
|
||
|
||
const { chromium } = require('playwright');
|
||
const fs = require('fs');
|
||
|
||
// 测试配置
|
||
const BASE_URL = 'http://localhost:3000';
|
||
const SCREENSHOT_DIR = 'screenshots/mcp-full-test';
|
||
const TEST_USER = {
|
||
username: 'testuser',
|
||
password: 'Password123@'
|
||
};
|
||
|
||
// 创建截图目录
|
||
if (!fs.existsSync(SCREENSHOT_DIR)) {
|
||
fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });
|
||
}
|
||
|
||
// 测试结果收集
|
||
const testResults = {
|
||
timestamp: new Date().toISOString(),
|
||
pages: [],
|
||
summary: {
|
||
total: 0,
|
||
passed: 0,
|
||
failed: 0,
|
||
errors: []
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 测试单个页面
|
||
*/
|
||
async function testPage(page, pageInfo) {
|
||
const pageResult = {
|
||
name: pageInfo.name,
|
||
url: pageInfo.url,
|
||
passed: true,
|
||
errors: [],
|
||
warnings: [],
|
||
screenshots: []
|
||
};
|
||
|
||
console.log(`\n📄 测试页面: ${pageInfo.name}`);
|
||
console.log(` URL: ${pageInfo.url}`);
|
||
|
||
try {
|
||
// 监听控制台消息
|
||
const consoleMessages = [];
|
||
page.on('console', msg => {
|
||
const type = msg.type();
|
||
const text = msg.text();
|
||
|
||
if (type === 'error') {
|
||
consoleMessages.push({ type, text });
|
||
pageResult.errors.push(`[Console Error] ${text}`);
|
||
} else if (type === 'warning') {
|
||
pageResult.warnings.push(`[Console Warning] ${text}`);
|
||
}
|
||
});
|
||
|
||
// 监听页面错误
|
||
const pageErrors = [];
|
||
page.on('pageerror', error => {
|
||
pageErrors.push(error.toString());
|
||
pageResult.errors.push(`[Page Error] ${error.message}`);
|
||
});
|
||
|
||
// 导航到页面
|
||
console.log(` ⏳ 正在加载页面...`);
|
||
await page.goto(pageInfo.url, {
|
||
waitUntil: 'networkidle',
|
||
timeout: 30000
|
||
});
|
||
|
||
// 等待页面稳定
|
||
await page.waitForTimeout(2000);
|
||
|
||
// 检查页面标题
|
||
const title = await page.title();
|
||
console.log(` 📋 页面标题: ${title}`);
|
||
|
||
// 截图 - 完整页面
|
||
const fullScreenshot = `${SCREENSHOT_DIR}/${pageInfo.name}-full.png`;
|
||
await page.screenshot({
|
||
path: fullScreenshot,
|
||
fullPage: true
|
||
});
|
||
pageResult.screenshots.push(fullScreenshot);
|
||
console.log(` 📸 完整截图: ${fullScreenshot}`);
|
||
|
||
// 截图 - 视口
|
||
const viewportScreenshot = `${SCREENSHOT_DIR}/${pageInfo.name}-viewport.png`;
|
||
await page.screenshot({
|
||
path: viewportScreenshot,
|
||
fullPage: false
|
||
});
|
||
pageResult.screenshots.push(viewportScreenshot);
|
||
|
||
// 检查页面基本信息
|
||
const bodyText = await page.evaluate(() => document.body.innerText);
|
||
const hasContent = bodyText.length > 100;
|
||
console.log(` 📝 内容长度: ${bodyText.length} 字符`);
|
||
|
||
if (!hasContent) {
|
||
pageResult.errors.push('页面内容过少,可能未正常加载');
|
||
pageResult.passed = false;
|
||
}
|
||
|
||
// 检查是否有死链
|
||
const brokenLinks = await page.evaluate(() => {
|
||
const links = Array.from(document.querySelectorAll('a[href]'));
|
||
return links.filter(link => {
|
||
const href = link.getAttribute('href');
|
||
return href && href.startsWith('http') && !href.includes(window.location.hostname);
|
||
}).length;
|
||
});
|
||
|
||
if (brokenLinks > 0) {
|
||
pageResult.warnings.push(`发现 ${brokenLinks} 个外部链接`);
|
||
}
|
||
|
||
// 检查响应式设计
|
||
const mobileViewport = { width: 375, height: 667 };
|
||
await page.setViewportSize(mobileViewport);
|
||
await page.waitForTimeout(500);
|
||
const mobileScreenshot = `${SCREENSHOT_DIR}/${pageInfo.name}-mobile.png`;
|
||
await page.screenshot({ path: mobileScreenshot });
|
||
pageResult.screenshots.push(mobileScreenshot);
|
||
console.log(` 📱 移动端截图: ${mobileScreenshot}`);
|
||
|
||
// 恢复桌面视口
|
||
await page.setViewportSize({ width: 1280, height: 720 });
|
||
|
||
// 执行页面特定测试
|
||
if (pageInfo.test) {
|
||
console.log(` 🔧 执行页面特定测试...`);
|
||
await pageInfo.test(page, pageResult);
|
||
}
|
||
|
||
// 统计错误
|
||
if (pageResult.errors.length > 0) {
|
||
pageResult.passed = false;
|
||
console.log(` ❌ 发现 ${pageResult.errors.length} 个错误:`);
|
||
pageResult.errors.forEach(err => console.log(` - ${err}`));
|
||
}
|
||
|
||
if (pageResult.warnings.length > 0) {
|
||
console.log(` ⚠️ 发现 ${pageResult.warnings.length} 个警告:`);
|
||
pageResult.warnings.slice(0, 3).forEach(warn => console.log(` - ${warn}`));
|
||
}
|
||
|
||
if (pageResult.passed) {
|
||
console.log(` ✅ 页面测试通过`);
|
||
} else {
|
||
console.log(` ❌ 页面测试失败`);
|
||
}
|
||
|
||
} catch (error) {
|
||
pageResult.passed = false;
|
||
pageResult.errors.push(`测试异常: ${error.message}`);
|
||
console.log(` ❌ 测试失败: ${error.message}`);
|
||
}
|
||
|
||
return pageResult;
|
||
}
|
||
|
||
/**
|
||
* 主测试流程
|
||
*/
|
||
async function runTests() {
|
||
console.log('🎭 MCP Playwright 完整测试开始');
|
||
console.log('='.repeat(60));
|
||
console.log(`基础URL: ${BASE_URL}`);
|
||
console.log(`截图目录: ${SCREENSHOT_DIR}`);
|
||
console.log('='.repeat(60));
|
||
|
||
const browser = await chromium.launch({
|
||
headless: false,
|
||
channel: 'chrome'
|
||
});
|
||
|
||
const context = await browser.newContext({
|
||
viewport: { width: 1280, height: 720 }
|
||
});
|
||
|
||
const page = await context.newPage();
|
||
|
||
// 定义要测试的页面
|
||
const pagesToTest = [
|
||
{
|
||
name: '01-homepage',
|
||
url: `${BASE_URL}/`,
|
||
description: '首页/登录页'
|
||
},
|
||
{
|
||
name: '02-dashboard',
|
||
url: `${BASE_URL}/dashboard`,
|
||
description: '仪表盘'
|
||
},
|
||
{
|
||
name: '03-documents',
|
||
url: `${BASE_URL}/documents`,
|
||
description: '文档管理'
|
||
},
|
||
{
|
||
name: '04-todos',
|
||
url: `${BASE_URL}/todos`,
|
||
description: '待办事项'
|
||
},
|
||
{
|
||
name: '05-images',
|
||
url: `${BASE_URL}/images`,
|
||
description: '图片管理'
|
||
}
|
||
];
|
||
|
||
// 首先尝试登录
|
||
console.log('\n🔐 尝试登录...');
|
||
try {
|
||
await page.goto(`${BASE_URL}/`, { waitUntil: 'networkidle' });
|
||
await page.waitForTimeout(1000);
|
||
|
||
// 查找并填写登录表单
|
||
const usernameInput = page.locator('input[type="text"]').first();
|
||
const passwordInput = page.locator('input[type="password"]').first();
|
||
const loginButton = page.locator('button').filter({ hasText: /登录|Login/i }).first();
|
||
|
||
if (await usernameInput.count() > 0 && await passwordInput.count() > 0) {
|
||
await usernameInput.fill(TEST_USER.username);
|
||
await passwordInput.fill(TEST_USER.password);
|
||
|
||
if (await loginButton.count() > 0) {
|
||
await loginButton.click();
|
||
console.log(' ✅ 登录表单已提交');
|
||
await page.waitForTimeout(2000);
|
||
} else {
|
||
console.log(' ⚠️ 未找到登录按钮');
|
||
}
|
||
} else {
|
||
console.log(' ℹ️ 未找到登录表单,可能已经登录');
|
||
}
|
||
} catch (error) {
|
||
console.log(` ⚠️ 登录过程异常: ${error.message}`);
|
||
}
|
||
|
||
// 测试每个页面
|
||
for (const pageInfo of pagesToTest) {
|
||
const result = await testPage(page, pageInfo);
|
||
testResults.pages.push(result);
|
||
testResults.summary.total++;
|
||
|
||
if (result.passed) {
|
||
testResults.summary.passed++;
|
||
} else {
|
||
testResults.summary.failed++;
|
||
testResults.summary.errors.push(...result.errors);
|
||
}
|
||
|
||
// 页面间等待
|
||
await page.waitForTimeout(1000);
|
||
}
|
||
|
||
// 生成测试报告
|
||
console.log('\n' + '='.repeat(60));
|
||
console.log('📊 测试汇总');
|
||
console.log('='.repeat(60));
|
||
|
||
testResults.pages.forEach(page => {
|
||
const status = page.passed ? '✅' : '❌';
|
||
console.log(`${status} ${page.name} - ${page.errors.length} 错误, ${page.warnings.length} 警告`);
|
||
});
|
||
|
||
console.log('\n总计:');
|
||
console.log(` 总页面数: ${testResults.summary.total}`);
|
||
console.log(` 通过: ${testResults.summary.passed} ✅`);
|
||
console.log(` 失败: ${testResults.summary.failed} ❌`);
|
||
console.log(` 通过率: ${((testResults.summary.passed / testResults.summary.total) * 100).toFixed(1)}%`);
|
||
|
||
if (testResults.summary.errors.length > 0) {
|
||
console.log('\n⚠️ 所有错误:');
|
||
testResults.summary.errors.forEach((err, i) => {
|
||
console.log(` ${i + 1}. ${err}`);
|
||
});
|
||
}
|
||
|
||
console.log('\n📸 所有截图保存在:', SCREENSHOT_DIR);
|
||
|
||
// 保存测试结果
|
||
const resultsPath = 'test-results.json';
|
||
fs.writeFileSync(resultsPath, JSON.stringify(testResults, null, 2));
|
||
console.log('📄 测试结果已保存到:', resultsPath);
|
||
|
||
// 等待用户查看
|
||
console.log('\n⏳ 5秒后关闭浏览器...');
|
||
await page.waitForTimeout(5000);
|
||
|
||
await browser.close();
|
||
console.log('\n🎉 测试完成!');
|
||
|
||
return testResults;
|
||
}
|
||
|
||
// 运行测试
|
||
runTests()
|
||
.then(results => {
|
||
const exitCode = results.summary.failed > 0 ? 1 : 0;
|
||
process.exit(exitCode);
|
||
})
|
||
.catch(error => {
|
||
console.error('❌ 测试运行失败:', error);
|
||
process.exit(1);
|
||
});
|