Files
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

663 lines
26 KiB
JavaScript

const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
// 所有俄罗斯方块版本
const ALL_VERSIONS = [
// Canvas 2D 版本
{ file: 'tetris_final_canvas2d.html', name: '最终Canvas 2D版', type: 'canvas2d', priority: 1 },
{ file: 'tetris_simple_fall_test.html', name: '简单下落测试版', type: 'canvas2d', priority: 2 },
{ file: 'tetris_simple_fixed.html', name: '简单修复版', type: 'canvas2d', priority: 3 },
{ file: 'tetris_working.html', name: '工作版', type: 'canvas2d', priority: 4 },
{ file: 'tetris_mobile.html', name: '手机优化版', type: 'canvas2d', priority: 5 },
{ file: 'tetris_ultimate.html', name: '终极版', type: 'canvas2d', priority: 6 },
{ file: 'tetris_simple_canvas.html', name: 'Canvas 2D简化版', type: 'canvas2d', priority: 7 },
// PlayCanvas 版本
{ file: 'tetris_playcanvas_working.html', name: 'PlayCanvas工作版', type: 'playcanvas', priority: 1 },
{ file: 'tetris_playcanvas_super_debug.html', name: 'PlayCanvas超级调试版', type: 'playcanvas', priority: 2 },
{ file: 'tetris_playcanvas_render_fixed.html', name: 'PlayCanvas渲染修复版', type: 'playcanvas', priority: 3 },
{ file: 'tetris_playcanvas_simple.html', name: 'PlayCanvas简化版', type: 'playcanvas', priority: 4 },
{ file: 'tetris_playcanvas_fixed.html', name: 'PlayCanvas修复版', type: 'playcanvas', priority: 5 },
{ file: 'tetris_pure_playcanvas.html', name: '纯PlayCanvas版', type: 'playcanvas', priority: 6 },
// 其他版本
{ file: 'tetris_final.html', name: '最终版', type: 'mixed', priority: 1 },
{ file: 'tetris_exact_snake_copy.html', name: '贪吃蛇精确复制版', type: 'mixed', priority: 2 },
{ file: 'tetris_snake_style.html', name: '贪吃蛇风格版', type: 'mixed', priority: 3 },
{ file: 'tetris_no_poki.html', name: '无Poki SDK版', type: 'mixed', priority: 4 },
// 默认版本
{ file: 'index.html', name: '默认版', type: 'default', priority: 1 },
];
const BASE_URL = 'http://129.211.173.87:8895/games/tetris/';
const OUTPUT_DIR = '/tmp/tetris_auto_test';
// 创建输出目录
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
async function testVersion(browser, version, index, total) {
console.log(`\n[${index}/${total}] 🎮 测试: ${version.name}`);
console.log('='.repeat(70));
const page = await browser.newPage();
const logs = [];
const errors = [];
const warnings = [];
try {
// 设置页面事件监听
page.on('console', msg => {
const text = msg.text();
const type = msg.type();
logs.push({
time: new Date().toISOString(),
type: type,
text: text
});
if (type === 'error') {
errors.push(text);
} else if (type === 'warning') {
warnings.push(text);
}
});
page.on('pageerror', error => {
errors.push(`页面错误: ${error.message}`);
});
// 导航到页面
const url = BASE_URL + version.file;
console.log(`📡 URL: ${url}`);
const startTime = Date.now();
await page.goto(url, {
waitUntil: 'networkidle',
timeout: 15000
});
const loadTime = Date.now() - startTime;
console.log(`✅ 页面加载成功 (${loadTime}ms)`);
// 等待页面完全初始化
await page.waitForTimeout(3000);
// 检查Canvas元素
const canvasCount = await page.locator('canvas').count();
console.log(`🎨 Canvas数量: ${canvasCount}`);
let canvasInfo = '未找到';
if (canvasCount > 0) {
const canvas = page.locator('canvas').first();
const box = await canvas.boundingBox();
if (box) {
canvasInfo = `${Math.round(box.width)}x${Math.round(box.height)}`;
console.log(`📐 Canvas尺寸: ${canvasInfo}`);
}
}
// 查找开始按钮
const buttonSelectors = [
'#start-btn',
'#start-button',
'button:has-text("开始")',
'button:has-text("Start")',
'button:has-text("▶️")',
'button:has-text("Play")',
'button'
];
let startButton = null;
let buttonSelector = '';
for (const selector of buttonSelectors) {
const count = await page.locator(selector).count();
if (count > 0) {
startButton = page.locator(selector).first();
buttonSelector = selector;
console.log(`🔘 找到按钮: ${selector} (${count}个)`);
break;
}
}
// 点击开始按钮或按空格键
let buttonClicked = false;
if (startButton) {
try {
await startButton.click({ timeout: 3000 });
buttonClicked = true;
console.log('✅ 点击开始按钮');
} catch (clickError) {
console.log(`⚠️ 按钮点击失败: ${clickError.message}`);
}
} else {
console.log('⚠️ 未找到开始按钮,尝试按空格键');
await page.keyboard.press(' ');
}
// 等待游戏开始
await page.waitForTimeout(2000);
// 再次尝试点击(有些版本需要双击)
if (!buttonClicked && startButton) {
try {
await startButton.click({ timeout: 2000 });
console.log('✅ 第二次点击开始按钮');
buttonClicked = true;
} catch (e) {
// 忽略第二次点击失败
}
}
// 等待游戏运行
await page.waitForTimeout(5000); // 等待5秒让游戏运行
// 分析日志
const spawnKeywords = ['生成', 'spawn', 'new piece', '方块', 'piece'];
const moveKeywords = ['移动', 'move', '下落', 'drop', '位置', '位置', 'x=', 'y='];
const errorKeywords = ['error', '❌', '失败', '未找到', 'undefined', 'NaN', 'TypeError'];
const spawnLogs = logs.filter(log =>
spawnKeywords.some(keyword => log.text.toLowerCase().includes(keyword.toLowerCase()))
);
const moveLogs = logs.filter(log =>
moveKeywords.some(keyword => log.text.toLowerCase().includes(keyword.toLowerCase()))
);
const errorLogs = logs.filter(log =>
errorKeywords.some(keyword => log.text.toLowerCase().includes(keyword.toLowerCase()))
);
console.log(`📊 日志统计:`);
console.log(` 总日志: ${logs.length}条`);
console.log(` 生成日志: ${spawnLogs.length}条`);
console.log(` 移动日志: ${moveLogs.length}条`);
console.log(` 错误日志: ${errorLogs.length}条`);
// 显示关键日志
if (spawnLogs.length > 0) {
const lastSpawn = spawnLogs[spawnLogs.length - 1];
console.log(`📝 最后生成: ${lastSpawn.text.substring(0, 100)}`);
}
if (moveLogs.length > 0) {
const recentMoves = moveLogs.slice(-3);
console.log(`📝 最近移动:`);
recentMoves.forEach((log, i) => {
console.log(` ${i+1}. ${log.text.substring(0, 80)}`);
});
}
if (errorLogs.length > 0) {
console.log(`⚠️ 关键错误:`);
errorLogs.slice(0, 3).forEach((log, i) => {
console.log(` ${i+1}. ${log.text.substring(0, 100)}`);
});
}
// 截图
const screenshotName = `${version.type}_${version.name.replace(/\s+/g, '_')}.png`;
const screenshotPath = path.join(OUTPUT_DIR, screenshotName);
try {
await page.screenshot({
path: screenshotPath,
fullPage: true,
timeout: 5000
});
console.log(`📸 截图保存: ${screenshotPath}`);
} catch (screenshotError) {
console.log(`⚠️ 截图失败: ${screenshotError.message}`);
}
// 判断测试结果
let status = 'unknown';
let score = 0;
let details = [];
// 评分系统
if (canvasCount > 0) score += 20;
if (buttonClicked) score += 20;
if (spawnLogs.length > 0) score += 30;
if (moveLogs.length > 0) score += 30;
if (errorLogs.length === 0) score += 10;
// 状态判断
if (score >= 90) {
status = 'excellent';
details.push('🎉 完美工作');
} else if (score >= 70) {
status = 'good';
details.push('✅ 工作良好');
} else if (score >= 50) {
status = 'partial';
details.push('⚠️ 部分工作');
} else if (score >= 30) {
status = 'poor';
details.push('❌ 工作不佳');
} else {
status = 'failed';
details.push('💥 完全失败');
}
if (spawnLogs.length === 0) details.push('方块未生成');
if (moveLogs.length === 0) details.push('方块未移动');
if (errorLogs.length > 0) details.push(`${errorLogs.length}个错误`);
console.log(`📈 测试评分: ${score}/100 (${status})`);
console.log(`📋 详情: ${details.join(', ')}`);
// 收集测试结果
return {
...version,
status,
score,
details: details.join('; '),
canvasCount,
canvasSize: canvasInfo,
buttonClicked,
spawnLogs: spawnLogs.length,
moveLogs: moveLogs.length,
errorLogs: errorLogs.length,
totalLogs: logs.length,
loadTime,
screenshot: screenshotPath,
url,
logs: logs.slice(-20).map(log => `${log.type}: ${log.text}`), // 最后20条日志
errors: errors.slice(0, 5) // 前5个错误
};
} catch (error) {
console.log(`💥 测试异常: ${error.message}`);
return {
...version,
status: 'error',
score: 0,
details: `测试异常: ${error.message}`,
canvasCount: 0,
canvasSize: '未知',
buttonClicked: false,
spawnLogs: 0,
moveLogs: 0,
errorLogs: 1,
totalLogs: 0,
loadTime: 0,
screenshot: '',
url: BASE_URL + version.file,
logs: [],
errors: [error.message]
};
} finally {
await page.close();
}
}
async function main() {
console.log('🚀 开始全面自动化测试俄罗斯方块游戏');
console.log(`测试时间: ${new Date().toLocaleString()}`);
console.log(`测试版本数: ${ALL_VERSIONS.length}`);
console.log(`输出目录: ${OUTPUT_DIR}`);
console.log('='.repeat(80));
const browser = await chromium.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-accelerated-2d-canvas',
'--disable-gpu'
]
});
const results = [];
const startTime = Date.now();
try {
// 按优先级排序测试
const sortedVersions = [...ALL_VERSIONS].sort((a, b) => a.priority - b.priority);
for (let i = 0; i < sortedVersions.length; i++) {
const version = sortedVersions[i];
const result = await testVersion(browser, version, i + 1, sortedVersions.length);
results.push(result);
// 版本间间隔
await new Promise(resolve => setTimeout(resolve, 1000));
}
const totalTime = Date.now() - startTime;
console.log(`\n✅ 所有测试完成! 总耗时: ${(totalTime / 1000).toFixed(1)}秒`);
// 生成详细报告
generateReport(results, totalTime);
} catch (error) {
console.error(`💥 主测试过程异常: ${error.message}`);
} finally {
await browser.close();
}
}
function generateReport(results, totalTime) {
console.log('\n' + '='.repeat(80));
console.log('📋 俄罗斯方块游戏自动化测试报告');
console.log('='.repeat(80));
// 按类型和状态分组
const byType = {
canvas2d: results.filter(r => r.type === 'canvas2d'),
playcanvas: results.filter(r => r.type === 'playcanvas'),
mixed: results.filter(r => r.type === 'mixed'),
default: results.filter(r => r.type === 'default')
};
const byStatus = {
excellent: results.filter(r => r.status === 'excellent'),
good: results.filter(r => r.status === 'good'),
partial: results.filter(r => r.status === 'partial'),
poor: results.filter(r => r.status === 'poor'),
failed: results.filter(r => r.status === 'failed'),
error: results.filter(r => r.status === 'error')
};
// 统计信息
console.log(`\n📊 总体统计:`);
console.log(` 测试版本: ${results.length}个`);
console.log(` 测试耗时: ${(totalTime / 1000).toFixed(1)}秒`);
console.log(` 成功版本: ${byStatus.excellent.length + byStatus.good.length}`);
console.log(` 部分成功: ${byStatus.partial.length}`);
console.log(` 失败版本: ${byStatus.poor.length + byStatus.failed.length + byStatus.error.length}`);
console.log(`\n📊 按类型统计:`);
Object.entries(byType).forEach(([type, versions]) => {
if (versions.length > 0) {
const excellent = versions.filter(v => v.status === 'excellent').length;
const good = versions.filter(v => v.status === 'good').length;
console.log(` ${type}: ${versions.length}个 (优秀:${excellent}, 良好:${good})`);
}
});
// 最佳版本推荐
const excellentVersions = [...byStatus.excellent].sort((a, b) => b.score - a.score);
const goodVersions = [...byStatus.good].sort((a, b) => b.score - a.score);
console.log('\n' + '='.repeat(80));
console.log('🏆 最佳版本推荐');
console.log('='.repeat(80));
if (excellentVersions.length > 0) {
console.log('\n🎉 优秀版本 (评分≥90):');
excellentVersions.slice(0, 3).forEach((version, i) => {
console.log(` ${i+1}. ${version.name}`);
console.log(` 文件: ${version.file}`);
console.log(` 评分: ${version.score}/100`);
console.log(` Canvas: ${version.canvasSize}`);
console.log(` 生成: ${version.spawnLogs}次, 移动: ${version.moveLogs}次`);
console.log(` 错误: ${version.errorLogs}个`);
console.log(` 访问: ${version.url}`);
console.log('');
});
}
if (goodVersions.length > 0 && excellentVersions.length < 3) {
console.log('\n✅ 良好版本 (评分≥70):');
goodVersions.slice(0, 3).forEach((version, i) => {
console.log(` ${i+1}. ${version.name}`);
console.log(` 评分: ${version.score}/100`);
console.log(` 详情: ${version.details}`);
console.log(` 访问: ${version.url}`);
console.log('');
});
}
// 问题版本
const problemVersions = results.filter(r =>
r.status === 'poor' || r.status === 'failed' || r.status === 'error'
);
if (problemVersions.length > 0) {
console.log('\n' + '='.repeat(80));
console.log('⚠️ 问题版本需要修复');
console.log('='.repeat(80));
problemVersions.slice(0, 5).forEach((version, i) => {
console.log(` ${i+1}. ${version.name} (${version.file})`);
console.log(` 状态: ${version.status}, 评分: ${version.score}/100`);
console.log(` 问题: ${version.details}`);
if (version.errors && version.errors.length > 0) {
console.log(` 错误: ${version.errors[0]}`);
}
console.log('');
});
}
// 生成CSV报告
generateCsvReport(results);
// 生成HTML报告
generateHtmlReport(results, excellentVersions);
console.log(`\n📁 所有截图保存在: ${OUTPUT_DIR}`);
console.log(`📄 CSV报告: ${path.join(OUTPUT_DIR, 'tetris_test_results.csv')}`);
console.log(`📄 HTML报告: ${path.join(OUTPUT_DIR, 'tetris_test_report.html')}`);
}
function generateCsvReport(results) {
const csvPath = path.join(OUTPUT_DIR, 'tetris_test_results.csv');
const headers = [
'序号', '名称', '文件', '类型', '状态', '评分',
'Canvas数量', 'Canvas尺寸', '按钮点击',
'生成次数', '移动次数', '错误数', '总日志数',
'加载时间(ms)', '详情', 'URL'
];
const csvRows = [
headers.join(','),
...results.map((result, index) => [
index + 1,
`"${result.name}"`,
`"${result.file}"`,
`"${result.type}"`,
`"${result.status}"`,
result.score,
result.canvasCount,
`"${result.canvasSize}"`,
result.buttonClicked ? '是' : '否',
result.spawnLogs,
result.moveLogs,
result.errorLogs,
result.totalLogs,
result.loadTime,
`"${result.details}"`,
`"${result.url}"`
].join(','))
];
fs.writeFileSync(csvPath, csvRows.join('\n'));
console.log(`✅ CSV报告已生成: ${csvPath}`);
}
function generateHtmlReport(results, bestVersions) {
const htmlPath = path.join(OUTPUT_DIR, 'tetris_test_report.html');
const statusColors = {
excellent: '#4CAF50',
good: '#8BC34A',
partial: '#FFC107',
poor: '#FF9800',
failed: '#F44336',
error: '#9C27B0'
};
const statusTexts = {
excellent: '优秀',
good: '良好',
partial: '部分工作',
poor: '工作不佳',
failed: '失败',
error: '异常'
};
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>俄罗斯方块游戏自动化测试报告</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; background: #f5f7fa; padding: 20px; }
.container { max-width: 1400px; margin: 0 auto; }
header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; border-radius: 10px; margin-bottom: 30px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); }
h1 { font-size: 2.5em; margin-bottom: 10px; }
.subtitle { font-size: 1.2em; opacity: 0.9; }
.summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 30px; }
.card { background: white; padding: 25px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }
.card h3 { color: #555; margin-bottom: 15px; font-size: 1.1em; }
.card .number { font-size: 2.5em; font-weight: bold; margin-bottom: 10px; }
.card .label { color: #777; font-size: 0.9em; }
.best-versions { background: #e8f5e9; border-left: 5px solid #4CAF50; padding: 20px; margin-bottom: 30px; border-radius: 5px; }
.best-versions h2 { color: #2e7d32; margin-bottom: 15px; }
.results-table { background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 2px 10px rgba(0,0,0,0.08); margin-bottom: 30px; }
table { width: 100%; border-collapse: collapse; }
th { background: #f8f9fa; padding: 15px; text-align: left; font-weight: 600; color: #555; border-bottom: 2px solid #e9ecef; }
td { padding: 12px 15px; border-bottom: 1px solid #e9ecef; }
tr:hover { background: #f8f9fa; }
.status-badge { display: inline-block; padding: 4px 12px; border-radius: 20px; font-size: 0.85em; font-weight: 600; color: white; }
.score { font-weight: bold; }
.score-high { color: #4CAF50; }
.score-medium { color: #FF9800; }
.score-low { color: #F44336; }
.version-details { margin-top: 30px; }
.detail-card { background: white; padding: 20px; border-radius: 10px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }
.detail-card h3 { color: #333; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 2px solid #f0f0f0; }
footer { text-align: center; margin-top: 40px; padding: 20px; color: #777; font-size: 0.9em; border-top: 1px solid #e9ecef; }
@media (max-width: 768px) {
.summary-cards { grid-template-columns: 1fr; }
table { display: block; overflow-x: auto; }
}
</style>
</head>
<body>
<div class="container">
<header>
<h1>🎮 俄罗斯方块游戏自动化测试报告</h1>
<div class="subtitle">测试时间: ${new Date().toLocaleString()} | 测试版本: ${results.length}个</div>
</header>
<div class="summary-cards">
<div class="card">
<h3>📊 总体统计</h3>
<div class="number">${results.length}</div>
<div class="label">测试版本总数</div>
</div>
<div class="card">
<h3>✅ 成功版本</h3>
<div class="number">${results.filter(r => r.status === 'excellent' || r.status === 'good').length}</div>
<div class="label">优秀 + 良好</div>
</div>
<div class="card">
<h3>⚠️ 问题版本</h3>
<div class="number">${results.filter(r => r.status === 'poor' || r.status === 'failed' || r.status === 'error').length}</div>
<div class="label">需要修复</div>
</div>
<div class="card">
<h3>🎯 最佳评分</h3>
<div class="number">${results.length > 0 ? Math.max(...results.map(r => r.score)) : 0}/100</div>
<div class="label">最高分版本</div>
</div>
</div>
${bestVersions.length > 0 ? `
<div class="best-versions">
<h2>🏆 推荐使用版本</h2>
${bestVersions.slice(0, 3).map(version => `
<div class="detail-card">
<h3>${version.name} (${version.file})</h3>
<p><strong>评分:</strong> <span class="score score-high">${version.score}/100</span></p>
<p><strong>Canvas尺寸:</strong> ${version.canvasSize}</p>
<p><strong>游戏表现:</strong> 方块生成${version.spawnLogs}次,移动${version.moveLogs}次</p>
<p><strong>错误数量:</strong> ${version.errorLogs}个</p>
<p><strong>访问地址:</strong> <a href="${version.url}" target="_blank">${version.url}</a></p>
${version.screenshot ? `<p><strong>测试截图:</strong> <br><img src="file://${version.screenshot}" alt="${version.name}" style="max-width: 300px; margin-top: 10px; border: 1px solid #ddd;"></p>` : ''}
</div>
`).join('')}
</div>
` : ''}
<div class="results-table">
<h2 style="padding: 20px 20px 0;">📋 详细测试结果</h2>
<table>
<thead>
<tr>
<th>#</th>
<th>版本名称</th>
<th>文件</th>
<th>类型</th>
<th>状态</th>
<th>评分</th>
<th>Canvas</th>
<th>生成/移动</th>
<th>错误</th>
<th>详情</th>
</tr>
</thead>
<tbody>
${results.map((result, index) => `
<tr>
<td>${index + 1}</td>
<td><strong>${result.name}</strong></td>
<td><code>${result.file}</code></td>
<td>${result.type}</td>
<td>
<span class="status-badge" style="background: ${statusColors[result.status] || '#999'}">
${statusTexts[result.status] || result.status}
</span>
</td>
<td>
<span class="score ${result.score >= 90 ? 'score-high' : result.score >= 70 ? 'score-medium' : 'score-low'}">
${result.score}/100
</span>
</td>
<td>${result.canvasSize}</td>
<td>${result.spawnLogs}/${result.moveLogs}</td>
<td>${result.errorLogs}</td>
<td>${result.details}</td>
</tr>
`).join('')}
</tbody>
</table>
</div>
<footer>
<p>测试工具: Playwright 1.58.0 | 浏览器: Chromium (Headless) | 生成时间: ${new Date().toLocaleString()}</p>
<p>所有截图和详细数据保存在: ${OUTPUT_DIR}</p>
</footer>
</div>
</body>
</html>`;
fs.writeFileSync(htmlPath, html);
console.log(`✅ HTML报告已生成: ${htmlPath}`);
}
// 运行测试
main().catch(error => {
console.error('💥 测试程序异常:', error);
process.exit(1);
});