Files
nanocode/wanandroid/uniapp_android/headless_test_simple.py
T
Ubuntu 32b43f85bd a
2026-07-03 16:58:28 +08:00

736 lines
28 KiB
Python

#!/usr/bin/env python3
"""
简化版无头浏览器测试
使用requests和备用方法进行模块测试
"""
import os
import sys
import time
import json
import requests
import logging
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Tuple, Optional
from urllib.parse import urljoin
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('simple_headless_test.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
class SimpleHeadlessTester:
"""简化版无头测试器"""
def __init__(self, base_url: str = "http://localhost:8000"):
self.base_url = base_url
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
self.test_results = []
self.test_data_dir = Path("test_data")
self.test_data_dir.mkdir(exist_ok=True)
def test_url(self, url: str, module: str, test_name: str,
expected_status: int = 200, check_content: List[str] = None) -> Dict:
"""测试URL访问"""
try:
start_time = time.time()
response = self.session.get(url, timeout=10)
elapsed = (time.time() - start_time) * 1000
# 检查状态码
if response.status_code == expected_status:
passed = True
details = f"状态码: {response.status_code}, 响应时间: {elapsed:.1f}ms"
# 检查内容
if check_content:
content = response.text
missing = []
for keyword in check_content:
if keyword not in content:
missing.append(keyword)
passed = False
if missing:
details = f"缺少关键词: {', '.join(missing)}"
else:
details = f"包含所有关键词, 响应时间: {elapsed:.1f}ms"
else:
passed = False
details = f"期望状态码 {expected_status}, 实际 {response.status_code}"
# 记录结果
result = self.record_test_result(module, test_name, passed, details)
# 保存响应内容
if not passed:
self.save_test_data(f"{module}_{test_name}_failed.html", response.text)
return result
except Exception as e:
details = f"请求失败: {e}"
return self.record_test_result(module, test_name, False, details)
def test_api_json(self, url: str, module: str, test_name: str,
check_fields: List[str] = None, api_type: str = "wanandroid") -> Dict:
"""测试API JSON响应
Args:
url: API地址
module: 模块名称
test_name: 测试名称
check_fields: 需要检查的字段
api_type: API类型,'wanandroid'或'health'
"""
try:
response = self.session.get(url, timeout=10)
if response.status_code == 200:
try:
data = response.json()
# 检查必需字段
if check_fields:
missing = []
for field in check_fields:
if field not in data:
missing.append(field)
if missing:
details = f"缺少字段: {', '.join(missing)}"
return self.record_test_result(module, test_name, False, details)
# 根据API类型检查格式
if api_type == "wanandroid":
# 玩Android API格式
if 'errorCode' in data and data['errorCode'] == 0:
details = f"API格式正确, errorCode: {data['errorCode']}"
return self.record_test_result(module, test_name, True, details)
else:
details = f"API错误码: {data.get('errorCode', '未知')}"
return self.record_test_result(module, test_name, False, details)
elif api_type == "health":
# 健康检查API格式
if 'status' in data and data['status'] == 'healthy':
details = f"健康检查正常, 状态: {data['status']}"
return self.record_test_result(module, test_name, True, details)
else:
details = f"健康状态异常: {data.get('status', '未知')}"
return self.record_test_result(module, test_name, False, details)
else:
# 通用API检查
details = f"API响应正常, 状态码: {response.status_code}"
return self.record_test_result(module, test_name, True, details)
except json.JSONDecodeError as e:
details = f"JSON解析失败: {e}"
return self.record_test_result(module, test_name, False, details)
else:
details = f"HTTP状态码: {response.status_code}"
return self.record_test_result(module, test_name, False, details)
except Exception as e:
details = f"API测试失败: {e}"
return self.record_test_result(module, test_name, False, details)
def record_test_result(self, module: str, test_name: str, passed: bool,
details: str = "") -> Dict:
"""记录测试结果"""
result = {
"module": module,
"test_name": test_name,
"passed": passed,
"timestamp": datetime.now().isoformat(),
"details": details
}
self.test_results.append(result)
status = "✅" if passed else "❌"
logger.info(f"{status} {module} - {test_name}: {details}")
return result
def save_test_data(self, filename: str, content: str):
"""保存测试数据"""
filepath = self.test_data_dir / filename
with open(filepath, 'w', encoding='utf-8') as f:
f.write(content)
logger.debug(f"📄 测试数据保存: {filepath}")
def test_module_homepage(self) -> Dict:
"""测试首页模块"""
module = "首页模块"
results = []
# 测试首页访问
result = self.test_url(
urljoin(self.base_url, "/"),
module,
"首页访问",
check_content=["玩Android", "Material Design", "文章列表"]
)
results.append(result)
# 测试Material Design元素
result = self.test_url(
urljoin(self.base_url, "/"),
module,
"Material元素",
check_content=["search-container", "banner", "tab-bar"]
)
results.append(result)
# 测试CSS样式
result = self.test_url(
urljoin(self.base_url, "/"),
module,
"CSS样式",
check_content=["6200EE", "03DAC6", "Roboto"]
)
results.append(result)
return {
"module": module,
"total_tests": len(results),
"passed": sum(1 for r in results if r["passed"]),
"failed": sum(1 for r in results if not r["passed"]),
"results": results
}
def test_module_material_preview(self) -> Dict:
"""测试Material预览模块"""
module = "Material预览模块"
results = []
# 测试Material预览页面
result = self.test_url(
urljoin(self.base_url, "/material_preview.html"),
module,
"预览页面访问",
check_content=["Material Design", "颜色系统", "图标系统", "组件系统"]
)
results.append(result)
# 测试颜色系统
result = self.test_url(
urljoin(self.base_url, "/material_preview.html"),
module,
"颜色系统",
check_content=["color-swatch", "primary-color", "secondary-color", "Color System"]
)
results.append(result)
# 测试响应式设计
result = self.test_url(
urljoin(self.base_url, "/material_preview.html"),
module,
"响应式设计",
check_content=["@media", "max-width"]
)
results.append(result)
return {
"module": module,
"total_tests": len(results),
"passed": sum(1 for r in results if r["passed"]),
"failed": sum(1 for r in results if not r["passed"]),
"results": results
}
def test_module_api(self) -> Dict:
"""测试API接口模块"""
module = "API接口模块"
results = []
# 测试健康检查API
result = self.test_api_json(
urljoin(self.base_url, "/health"),
module,
"健康检查API",
check_fields=["status", "timestamp", "service"],
api_type="health"
)
results.append(result)
# 测试Banner API
result = self.test_api_json(
urljoin(self.base_url, "/api/banner/json"),
module,
"Banner API",
check_fields=["errorCode", "data"]
)
results.append(result)
# 测试文章列表API
result = self.test_api_json(
urljoin(self.base_url, "/api/article/list/0/json"),
module,
"文章列表API",
check_fields=["errorCode", "data"]
)
results.append(result)
# 测试热词API
result = self.test_api_json(
urljoin(self.base_url, "/api/hotkey/json"),
module,
"热词API",
check_fields=["errorCode", "data"]
)
results.append(result)
return {
"module": module,
"total_tests": len(results),
"passed": sum(1 for r in results if r["passed"]),
"failed": sum(1 for r in results if not r["passed"]),
"results": results
}
def test_module_quick_test(self) -> Dict:
"""测试快速测试页面模块"""
module = "快速测试页面模块"
results = []
# 测试快速测试页面
result = self.test_url(
urljoin(self.base_url, "/quick_test.html"),
module,
"快速测试页面访问",
check_content=["快速测试", "测试按钮", "服务器信息", "test-button", "按钮集合"]
)
results.append(result)
# 测试按钮功能
result = self.test_url(
urljoin(self.base_url, "/quick_test.html"),
module,
"测试按钮",
check_content=["test-button", "button-icon", "访问首页", "Material预览"]
)
results.append(result)
# 测试服务器信息显示
result = self.test_url(
urljoin(self.base_url, "/quick_test.html"),
module,
"服务器信息",
check_content=["服务器地址", "129.211.173.87", "运行时长", "测试状态"]
)
results.append(result)
return {
"module": module,
"total_tests": len(results),
"passed": sum(1 for r in results if r["passed"]),
"failed": sum(1 for r in results if not r["passed"]),
"results": results
}
def test_module_uniapp_structure(self) -> Dict:
"""测试Uniapp项目结构模块"""
module = "Uniapp结构模块"
results = []
# 检查项目文件结构
project_files = [
("pages/", "目录存在", True),
("pages/index/index.vue", "首页文件", True),
("pages/project/project.vue", "项目页面", True),
("pages/official/official.vue", "公众号页面", True),
("pages/system/system.vue", "体系页面", True),
("pages/mine/mine.vue", "我的页面", True),
("static/images/icons/", "图标目录", True),
("manifest.json", "配置文件", True),
("pages.json", "路由配置", True),
("App.vue", "应用配置", True),
]
for filepath, description, required in project_files:
exists = Path(filepath).exists()
passed = exists if required else True
result = self.record_test_result(
module,
description,
passed,
f"{filepath} {'存在' if exists else '不存在'}"
)
results.append(result)
# 检查Material图标
icons_dir = Path("static/images/icons")
if icons_dir.exists():
icon_count = len(list(icons_dir.glob("*")))
result = self.record_test_result(
module,
"Material图标",
icon_count >= 5,
f"找到{icon_count}个图标文件"
)
results.append(result)
return {
"module": module,
"total_tests": len(results),
"passed": sum(1 for r in results if r["passed"]),
"failed": sum(1 for r in results if not r["passed"]),
"results": results
}
def test_module_performance(self) -> Dict:
"""测试性能模块"""
module = "性能测试模块"
results = []
# 测试首页性能
start_time = time.time()
try:
response = self.session.get(urljoin(self.base_url, "/"), timeout=10)
elapsed = (time.time() - start_time) * 1000
if response.status_code == 200:
passed = elapsed < 1000 # 1秒内响应
result = self.record_test_result(
module,
"首页响应时间",
passed,
f"响应时间: {elapsed:.1f}ms"
)
results.append(result)
else:
result = self.record_test_result(
module,
"首页响应时间",
False,
f"HTTP状态码: {response.status_code}"
)
results.append(result)
except Exception as e:
result = self.record_test_result(
module,
"首页响应时间",
False,
f"请求失败: {e}"
)
results.append(result)
# 测试API性能
apis_to_test = [
("/health", "健康检查API"),
("/api/banner/json", "Banner API"),
("/api/hotkey/json", "热词API"),
]
for api_path, api_name in apis_to_test:
start_time = time.time()
try:
response = self.session.get(urljoin(self.base_url, api_path), timeout=10)
elapsed = (time.time() - start_time) * 1000
if response.status_code == 200:
passed = elapsed < 500 # 500ms内响应
result = self.record_test_result(
module,
f"{api_name}响应时间",
passed,
f"响应时间: {elapsed:.1f}ms"
)
results.append(result)
else:
result = self.record_test_result(
module,
f"{api_name}响应时间",
False,
f"HTTP状态码: {response.status_code}"
)
results.append(result)
except Exception as e:
result = self.record_test_result(
module,
f"{api_name}响应时间",
False,
f"请求失败: {e}"
)
results.append(result)
return {
"module": module,
"total_tests": len(results),
"passed": sum(1 for r in results if r["passed"]),
"failed": sum(1 for r in results if not r["passed"]),
"results": results
}
def run_all_tests(self) -> Dict:
"""运行所有测试"""
logger.info("🚀 开始简化版无头浏览器测试")
logger.info(f"📡 测试地址: {self.base_url}")
logger.info(f"⏰ 开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
all_results = []
# 运行各个模块测试
test_modules = [
("首页模块", self.test_module_homepage),
("Material预览模块", self.test_module_material_preview),
("API接口模块", self.test_module_api),
("快速测试页面模块", self.test_module_quick_test),
("Uniapp结构模块", self.test_module_uniapp_structure),
("性能测试模块", self.test_module_performance),
]
for module_name, test_func in test_modules:
logger.info(f"\n🔍 开始测试: {module_name}")
result = test_func()
all_results.append(result)
logger.info(f"📊 {module_name}测试完成: {result['passed']}/{result['total_tests']} 通过")
# 生成测试报告
report = self.generate_test_report(all_results)
return report
def generate_test_report(self, all_results: List[Dict]) -> Dict:
"""生成测试报告"""
total_tests = sum(r.get("total_tests", 0) for r in all_results)
total_passed = sum(r.get("passed", 0) for r in all_results)
total_failed = sum(r.get("failed", 0) for r in all_results)
report = {
"timestamp": datetime.now().isoformat(),
"base_url": self.base_url,
"total_modules": len(all_results),
"total_tests": total_tests,
"total_passed": total_passed,
"total_failed": total_failed,
"success_rate": (total_passed / total_tests * 100) if total_tests > 0 else 0,
"modules": all_results,
"summary": self._generate_summary(all_results)
}
# 保存报告到文件
report_file = "simple_headless_test_report.json"
with open(report_file, "w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
# 生成HTML报告
self._generate_html_report(report)
logger.info(f"\n🎯 测试完成!")
logger.info(f"📊 总体结果: {total_passed}/{total_tests} 通过 ({report['success_rate']:.1f}%)")
logger.info(f"📄 详细报告: {report_file}")
logger.info(f"📄 HTML报告: simple_headless_test_report.html")
return report
def _generate_summary(self, all_results: List[Dict]) -> str:
"""生成测试摘要"""
summary_lines = []
for module_result in all_results:
module_name = module_result.get("module", "未知模块")
passed = module_result.get("passed", 0)
total = module_result.get("total_tests", 0)
if total > 0:
percentage = (passed / total) * 100
status = "✅" if percentage >= 80 else "⚠️" if percentage >= 50 else "❌"
summary_lines.append(f"{status} {module_name}: {passed}/{total} ({percentage:.1f}%)")
else:
summary_lines.append(f"❌ {module_name}: 0/0 (无测试)")
return "\n".join(summary_lines)
def _generate_html_report(self, report: Dict):
"""生成HTML测试报告"""
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<title>简化版无头浏览器测试报告</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #f5f5f5; color: #333; line-height: 1.6; padding: 20px; }}
.container {{ max-width: 1200px; margin: 0 auto; }}
.header {{ background: linear-gradient(135deg, #6200EE 0%, #3700B3 100%); color: white; padding: 40px; border-radius: 15px; margin-bottom: 30px; text-align: center; }}
.header h1 {{ font-size: 36px; margin-bottom: 10px; }}
.header .subtitle {{ font-size: 18px; opacity: 0.9; }}
.summary-card {{ background: white; border-radius: 15px; padding: 30px; margin-bottom: 30px; box-shadow: 0 5px 15px rgba(0,0,0,0.1); }}
.summary-grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin-top: 20px; }}
.summary-item {{ text-align: center; padding: 20px; border-radius: 10px; }}
.summary-item.total {{ background: #e3f2fd; }}
.summary-item.passed {{ background: #e8f5e9; }}
.summary-item.failed {{ background: #ffebee; }}
.summary-item.rate {{ background: #fff3e0; }}
.summary-value {{ font-size: 32px; font-weight: bold; margin-bottom: 5px; }}
.summary-label {{ font-size: 14px; color: #666; }}
.module-section {{ margin-bottom: 30px; }}
.module-header {{ background: #6200EE; color: white; padding: 15px 20px; border-radius: 10px 10px 0 0; display: flex; justify-content: space-between; align-items: center; }}
.module-title {{ font-size: 20px; font-weight: 500; }}
.module-stats {{ font-size: 16px; }}
.test-results {{ background: white; border-radius: 0 0 10px 10px; padding: 20px; box-shadow: 0 3px 10px rgba(0,0,0,0.1); }}
.test-item {{ padding: 15px; border-bottom: 1px solid #eee; display: flex; align-items: center; }}
.test-item:last-child {{ border-bottom: none; }}
.test-status {{ font-size: 24px; margin-right: 15px; width: 40px; text-align: center; }}
.test-details {{ flex: 1; }}
.test-name {{ font-weight: 500; margin-bottom: 5px; }}
.test-info {{ font-size: 14px; color: #666; }}
.footer {{ text-align: center; margin-top: 40px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 14px; }}
@media (max-width: 768px) {{
.summary-grid {{ grid-template-columns: 1fr; }}
}}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎯 简化版无头浏览器测试报告</h1>
<div class="subtitle">玩Android Material Design开发服务器</div>
<div class="subtitle">测试时间: {report['timestamp']}</div>
<div class="subtitle">测试地址: {report['base_url']}</div>
</div>
<div class="summary-card">
<h2>📊 测试概览</h2>
<div class="summary-grid">
<div class="summary-item total">
<div class="summary-value">{report['total_modules']}</div>
<div class="summary-label">测试模块</div>
</div>
<div class="summary-item total">
<div class="summary-value">{report['total_tests']}</div>
<div class="summary-label">总测试数</div>
</div>
<div class="summary-item passed">
<div class="summary-value">{report['total_passed']}</div>
<div class="summary-label">通过测试</div>
</div>
<div class="summary-item failed">
<div class="summary-value">{report['total_failed']}</div>
<div class="summary-label">失败测试</div>
</div>
<div class="summary-item rate">
<div class="summary-value">{report['success_rate']:.1f}%</div>
<div class="summary-label">通过率</div>
</div>
</div>
</div>
<div class="summary-card">
<h2>🔍 测试摘要</h2>
<pre style="background: #f5f5f5; padding: 15px; border-radius: 8px; margin-top: 10px; white-space: pre-wrap; font-family: monospace;">{report['summary']}</pre>
</div>
<h2 style="margin: 30px 0 20px 0;">📋 详细测试结果</h2>
{self._generate_module_html(report['modules'])}
<div class="footer">
<p>© 2026 玩Android Material Design测试报告 | 生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p>测试方法: Requests库 + 内容分析</p>
</div>
</div>
</body>
</html>
"""
with open("simple_headless_test_report.html", "w", encoding="utf-8") as f:
f.write(html_content)
def _generate_module_html(self, modules: List[Dict]) -> str:
"""生成模块测试结果的HTML"""
html_parts = []
for module in modules:
module_name = module.get("module", "未知模块")
passed = module.get("passed", 0)
total = module.get("total_tests", 0)
results = module.get("results", [])
# 计算通过率
percentage = (passed / total * 100) if total > 0 else 0
# 模块头部
html_parts.append(f"""
<div class="module-section">
<div class="module-header">
<div class="module-title">{module_name}</div>
<div class="module-stats">{passed}/{total} 通过 ({percentage:.1f}%)</div>
</div>
<div class="test-results">
""")
# 测试结果
for result in results:
status = "✅" if result.get("passed", False) else "❌"
test_name = result.get("test_name", "未知测试")
details = result.get("details", "")
html_parts.append(f"""
<div class="test-item">
<div class="test-status">{status}</div>
<div class="test-details">
<div class="test-name">{test_name}</div>
<div class="test-info">{details}</div>
</div>
</div>
""")
html_parts.append("""
</div>
</div>
""")
return "\n".join(html_parts)
def main():
"""主函数"""
print("🚀 玩Android Material Design简化版无头浏览器测试")
print("="*60)
# 创建测试器
tester = SimpleHeadlessTester(base_url="http://localhost:8000")
# 运行测试
report = tester.run_all_tests()
# 输出结果
print(f"\n🎯 测试完成!")
print(f"📊 总体结果: {report['total_passed']}/{report['total_tests']} 通过 ({report['success_rate']:.1f}%)")
print(f"📄 JSON报告: simple_headless_test_report.json")
print(f"📄 HTML报告: simple_headless_test_report.html")
print(f"📝 日志文件: simple_headless_test.log")
if report['success_rate'] >= 80:
print("✅ 测试通过!")
sys.exit(0)
elif report['success_rate'] >= 50:
print("⚠️ 测试部分通过,需要改进")
sys.exit(1)
else:
print("❌ 测试失败!")
sys.exit(1)
if __name__ == "__main__":
main()