#!/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()