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);
|
|||
|
|
});
|