447 lines
16 KiB
Python
447 lines
16 KiB
Python
#!/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() |