663 lines
26 KiB
JavaScript
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);
|
|
}); |