Files
nanocode/happy-games/games/tetris/windows_compatibility_test.py
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

447 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Windows兼容性测试脚本
专门测试俄罗斯方块在Windows浏览器上的兼容性
"""
from playwright.sync_api import sync_playwright
import time
from datetime import datetime
from pathlib import Path
import json
# 测试配置
BASE_URL = "http://129.211.173.87:8895/games/tetris/"
OUTPUT_DIR = Path("/tmp/windows_compatibility_test")
OUTPUT_DIR.mkdir(exist_ok=True)
# 测试版本 - 专门针对Windows兼容性
WINDOWS_VERSIONS = [
{"file": "index.html", "name": "默认版(修复后)", "description": "修复了位置计算问题的最终Canvas 2D版"},
{"file": "tetris_final_canvas2d_fixed.html", "name": "最终Canvas 2D修复版", "description": "专门修复nextPiece位置问题的版本"},
{"file": "tetris_simple_fall_test.html", "name": "简单下落测试版", "description": "最简单的Canvas 2D版本"},
{"file": "tetris_playcanvas_working.html", "name": "PlayCanvas工作版", "description": "PlayCanvas版本,可能有兼容性问题"},
]
def test_windows_compatibility(page, version):
"""测试Windows兼容性"""
print(f"\n🪟 Windows兼容性测试: {version['name']}")
print("-" * 60)
print(f"📝 描述: {version['description']}")
url = BASE_URL + version["file"]
print(f"🌐 URL: {url}")
# 使用Windows用户代理
page.set_extra_http_headers({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
})
# 导航到页面
start_time = time.time()
page.goto(url, wait_until="networkidle", timeout=15000)
load_time = time.time() - start_time
print(f"⏱️ 页面加载时间: {load_time:.2f}秒")
# 等待页面初始化
time.sleep(2)
# 收集所有日志
all_logs = []
page_errors = []
def on_console(msg):
all_logs.append({
"time": datetime.now().strftime("%H:%M:%S.%f")[:-3],
"type": msg.type,
"text": msg.text
})
def on_pageerror(error):
page_errors.append({
"time": datetime.now().strftime("%H:%M:%S.%f")[:-3],
"error": str(error)
})
page.on("console", on_console)
page.on("pageerror", on_pageerror)
# 检查关键元素
print("\n🔍 页面元素检查:")
# Canvas检查
canvas_elements = page.query_selector_all("canvas")
canvas_count = len(canvas_elements)
print(f" 🎨 Canvas数量: {canvas_count}")
canvas_info = []
for i, canvas in enumerate(canvas_elements):
box = canvas.bounding_box()
if box:
info = f"Canvas{i+1}: {int(box['width'])}x{int(box['height'])}"
canvas_info.append(info)
print(f" {info}")
# 按钮检查
print(" 🔘 按钮检查:")
button_selectors = ["#start-btn", "#pause-btn", "#restart-btn",
"#left-btn", "#right-btn", "#down-btn", "#rotate-btn"]
found_buttons = []
for selector in button_selectors:
elements = page.query_selector_all(selector)
if elements:
found_buttons.append(f"{selector}: {len(elements)}个")
print(f" ✅ {selector}: {len(elements)}个")
# 测试游戏启动
print("\n🎮 游戏启动测试:")
# 方法1: 点击开始按钮
start_btn = page.query_selector("#start-btn")
if start_btn:
try:
start_btn.click(timeout=3000)
print(" ✅ 成功点击#start-btn按钮")
except Exception as e:
print(f" ❌ #start-btn点击失败: {e}")
else:
print(" ⚠️ 未找到#start-btn按钮")
# 方法2: 按空格键
print(" ⌨️ 尝试按空格键启动...")
page.keyboard.press(" ")
# 等待游戏运行
print(" ⏳ 等待游戏运行10秒...")
time.sleep(10)
# 分析日志
print("\n📊 日志分析:")
# 分类日志
init_logs = [log for log in all_logs if any(keyword in log["text"].lower()
for keyword in ["初始化", "init", "canvas", "上下文", "context"])]
spawn_logs = [log for log in all_logs if any(keyword in log["text"].lower()
for keyword in ["生成", "spawn", "new piece", "方块", "piece"])]
move_logs = [log for log in all_logs if any(keyword in log["text"].lower()
for keyword in ["移动", "move", "下落", "drop", "位置", "x=", "y="])]
error_logs = [log for log in all_logs if log["type"] == "error"]
warning_logs = [log for log in all_logs if log["type"] == "warning"]
print(f" 总日志数: {len(all_logs)}条")
print(f" 初始化日志: {len(init_logs)}条")
print(f" 方块生成日志: {len(spawn_logs)}条")
print(f" 方块移动日志: {len(move_logs)}条")
print(f" 错误日志: {len(error_logs)}条")
print(f" 警告日志: {len(warning_logs)}条")
print(f" 页面错误: {len(page_errors)}条")
# 检查关键问题
print("\n🔧 问题诊断:")
issues = []
# 检查NaN问题
nan_logs = [log for log in all_logs if "NaN" in log["text"] or "undefined" in log["text"]]
if nan_logs:
issues.append(f"发现NaN/undefined问题: {len(nan_logs)}处")
print(f" ❌ 发现NaN/undefined问题: {len(nan_logs)}处")
for i, log in enumerate(nan_logs[:3]):
print(f" {i+1}. {log['text'][:80]}...")
else:
print(" ✅ 未发现NaN/undefined问题")
# 检查位置计算
position_logs = [log for log in all_logs if "位置" in log["text"] or "position" in log["text"].lower()]
if position_logs:
print(f" 📍 位置信息: {len(position_logs)}条")
for i, log in enumerate(position_logs[-3:]):
print(f" {i+1}. {log['text'][:60]}...")
# 检查方块生成
if spawn_logs:
print(f" 🧱 方块生成: {len(spawn_logs)}次")
for i, log in enumerate(spawn_logs[-3:]):
print(f" {i+1}. {log['text'][:60]}...")
else:
issues.append("方块未生成")
print(" ❌ 方块未生成")
# 检查方块移动
if move_logs:
print(f" 🚀 方块移动: {len(move_logs)}次")
move_positions = []
for log in move_logs:
if "x=" in log["text"] and "y=" in log["text"]:
# 提取位置信息
import re
match = re.search(r'x=([\d.-]+).*y=([\d.-]+)', log["text"])
if match:
x, y = match.groups()
if x != "NaN" and y != "NaN":
move_positions.append((float(x), float(y)))
if move_positions:
print(f" 📈 有效移动位置: {len(move_positions)}个")
print(f" 最后位置: x={move_positions[-1][0]}, y={move_positions[-1][1]}")
else:
issues.append("方块位置无效(NaN)")
print(" ❌ 方块位置无效(NaN)")
else:
issues.append("方块未移动")
print(" ❌ 方块未移动")
# 截图
screenshot_path = OUTPUT_DIR / f"windows_{version['name'].replace(' ', '_').replace('(', '').replace(')', '')}.png"
page.screenshot(path=str(screenshot_path), full_page=True)
print(f"\n📸 截图保存: {screenshot_path}")
# 保存详细日志
log_file = OUTPUT_DIR / f"windows_{version['name'].replace(' ', '_').replace('(', '').replace(')', '')}_logs.json"
with open(log_file, "w", encoding="utf-8") as f:
json.dump({
"version": version,
"url": url,
"load_time": load_time,
"canvas_info": canvas_info,
"found_buttons": found_buttons,
"all_logs": all_logs[-100:], # 最后100条日志
"page_errors": page_errors,
"issues": issues,
"summary": {
"total_logs": len(all_logs),
"init_logs": len(init_logs),
"spawn_logs": len(spawn_logs),
"move_logs": len(move_logs),
"error_logs": len(error_logs),
"warning_logs": len(warning_logs),
"page_errors": len(page_errors),
"nan_issues": len(nan_logs)
}
}, f, ensure_ascii=False, indent=2)
print(f"📄 详细日志保存: {log_file}")
# 评估兼容性
print("\n📈 Windows兼容性评估:")
score = 100
# 扣分规则
if len(page_errors) > 0:
score -= 30
print(f" ❌ 页面错误: -30分")
if len(nan_logs) > 0:
score -= 40
print(f" ❌ NaN问题: -40分")
if len(spawn_logs) == 0:
score -= 50
print(f" ❌ 方块未生成: -50分")
elif len(move_logs) == 0:
score -= 30
print(f" ❌ 方块未移动: -30分")
if len(error_logs) > 5:
score -= 20
print(f" ❌ 过多错误日志: -20分")
# 加分规则
if len(spawn_logs) > 0 and len(move_logs) > 0 and len(nan_logs) == 0:
score += 20
print(f" ✅ 游戏正常运行: +20分")
if len(page_errors) == 0:
score += 10
print(f" ✅ 无页面错误: +10分")
# 确保分数在0-100之间
score = max(0, min(100, score))
# 评级
if score >= 90:
rating = "🏆 优秀"
status = "🎉 完全兼容"
elif score >= 70:
rating = "✅ 良好"
status = "👍 基本兼容"
elif score >= 50:
rating = "⚠️ 一般"
status = "⚠️ 部分兼容"
else:
rating = "❌ 差"
status = "❌ 不兼容"
print(f" 🏆 评分: {score}/100")
print(f" 📊 评级: {rating}")
print(f" 📋 状态: {status}")
if issues:
print(f" 🔧 发现的问题: {', '.join(issues)}")
return {
"version": version,
"score": score,
"rating": rating,
"status": status,
"issues": issues,
"summary": {
"spawn_logs": len(spawn_logs),
"move_logs": len(move_logs),
"nan_issues": len(nan_logs),
"page_errors": len(page_errors),
"error_logs": len(error_logs)
},
"screenshot": str(screenshot_path),
"log_file": str(log_file),
"url": url
}
def main():
"""主函数"""
print("=" * 80)
print("🪟 Windows浏览器兼容性测试")
print("=" * 80)
print(f"测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print(f"测试版本: {len(WINDOWS_VERSIONS)}个")
print(f"输出目录: {OUTPUT_DIR}")
print("=" * 80)
results = []
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
args=[
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
"--disable-accelerated-2d-canvas",
"--disable-gpu"
]
)
try:
for i, version in enumerate(WINDOWS_VERSIONS):
print(f"\n{'#'*80}")
print(f"测试进度: {i+1}/{len(WINDOWS_VERSIONS)}")
context = browser.new_context(
viewport={"width": 1280, "height": 720}
)
page = context.new_page()
try:
result = test_windows_compatibility(page, version)
results.append(result)
finally:
page.close()
context.close()
# 版本间间隔
time.sleep(2)
finally:
browser.close()
# 生成Windows兼容性报告
generate_windows_report(results)
print(f"\n{'='*80}")
print("✅ Windows兼容性测试完成!")
print(f"📁 所有结果保存在: {OUTPUT_DIR}")
print("=" * 80)
def generate_windows_report(results):
"""生成Windows兼容性报告"""
report_file = OUTPUT_DIR / "windows_compatibility_report.md"
with open(report_file, "w", encoding="utf-8") as f:
f.write("# 🪟 Windows浏览器兼容性测试报告\n\n")
f.write(f"**测试时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"**测试环境**: Playwright + Chromium (模拟Windows Chrome 120)\n")
f.write(f"**测试版本**: {len(results)}个\n\n")
f.write("## 📊 兼容性评分\n\n")
f.write("| 排名 | 版本名称 | 评分 | 评级 | 状态 | 方块生成 | 方块移动 | NaN问题 | 页面错误 |\n")
f.write("|------|----------|------|------|------|----------|----------|---------|----------|\n")
# 按评分排序
sorted_results = sorted(results, key=lambda x: x["score"], reverse=True)
for i, result in enumerate(sorted_results):
f.write(f"| {i+1} | {result['version']['name']} | ")
f.write(f"{result['score']}/100 | {result['rating']} | {result['status']} | ")
f.write(f"{result['summary']['spawn_logs']} | {result['summary']['move_logs']} | ")
f.write(f"{result['summary']['nan_issues']} | {result['summary']['page_errors']} |\n")
f.write("\n## 🏆 推荐版本\n\n")
# 找出最佳版本
if sorted_results:
best_result = sorted_results[0]
f.write(f"### 🥇 最佳Windows兼容版本: **{best_result['version']['name']}**\n")
f.write(f"- **评分**: {best_result['score']}/100\n")
f.write(f"- **状态**: {best_result['status']}\n")
f.write(f"- **描述**: {best_result['version']['description']}\n")
f.write(f"- **访问地址**: {best_result['url']}\n")
f.write(f"- **截图**: {best_result['screenshot']}\n")
f.write(f"- **日志**: {best_result['log_file']}\n")
if best_result["issues"]:
f.write(f"- **已修复问题**: {', '.join(best_result['issues'])}\n")
f.write("\n## 🔧 问题分析\n\n")
# 分析所有问题
f.write("### 发现的Windows兼容性问题:\n\n")
problem_categories = {}
for result in results:
if result["issues"]:
for issue in result["issues"]:
if issue not in problem_categories:
problem_categories[issue] = []
problem_categories[issue].append(result["version"]["name"])
if problem_categories:
for issue, versions in problem_categories.items():
f.write(f"#### {issue}\n")
f.write(f"- **影响版本**: {', '.join(versions)}\n")
# 提供解决方案
if "NaN" in issue or "undefined" in issue:
f.write("- **原因**: 位置计算错误,nextPiece对象缺少x/y属性\n")
f.write("- **解决方案**: 在generateNextPiece()函数中添加x和y属性\n")
elif "方块未生成" in issue:
f.write("- **原因**: 游戏初始化失败或按钮事件未绑定\n")
f.write("- **解决方案**: 检查游戏初始化代码和事件监听器\n")
elif "方块未移动" in issue:
f.write("- **原因**: 游戏循环未启动或时间计算错误\n")
f.write("- **解决方案**: 检查游戏循环逻辑和dt时间计算\n")
f.write("\n")
else:
f.write("✅ 未发现严重兼容性问题\n")
f.write("\n## 🚀 Windows兼容性最佳实践\n\n")
f.write("1. **避免NaN问题**: 确保所有游戏对象都有完整的属性\n")
f.write("2. **位置计算**: 使用整数坐标,避免浮点数计算错误\n")
f.write("3. **事件处理**: 使用标准事件API,避免浏览器特定API\n")
f.write("4. **Canvas 2D**: 优先使用纯Canvas 2D API,兼容性最好\n")
f.write("5. **错误处理**: 添加完整的try-catch错误处理\n")
f.write("6. **日志调试**: 添加详细的调试日志,便于问题诊断\n")
f.write("\n## 📁 测试文件\n\n")
f.write(f"- **报告文件**: {report_file}\n")
f.write(f"- **截图目录**: {OUTPUT_DIR}/\n")
f.write(f"- **日志文件**: {OUTPUT_DIR}/*_logs.json\n")
f.write(f"- **测试脚本**: windows_compatibility_test.py\n")
print(f"📄 Windows兼容性报告生成: {report_file}")
if __name__ == "__main__":
main()