✨ 主要优化成果: - 修复Unicode字符编码问题(Windows跨平台兼容性) - 安装wkhtmltoimage,截图功能完全修复 - 智能延迟优化(api_browser.py) - 线程池资源泄漏修复(tasks.py) - HTML解析缓存机制 - 二分搜索算法优化(kdocs_uploader.py) - 自适应资源配置(browser_pool_worker.py) 🐛 Bug修复: - 解决截图失败问题 - 修复管理员密码设置 - 解决应用启动编码错误 📚 新增文档: - BUG_REPORT.md - 完整bug分析报告 - PERFORMANCE_ANALYSIS_REPORT.md - 性能优化分析 - LINUX_DEPLOYMENT_ANALYSIS.md - Linux部署指南 - SCREENSHOT_FIX_SUCCESS.md - 截图功能修复记录 - INSTALL_WKHTMLTOIMAGE.md - 安装指南 - OPTIMIZATION_FIXES_SUMMARY.md - 优化总结 🚀 功能验证: - Flask应用正常运行(51233端口) - 数据库、截图线程池、API预热正常 - 管理员登录:admin/admin123 - 健康检查API:http://127.0.0.1:51233/health 💡 技术改进: - 智能延迟算法(自适应调整) - LRU缓存策略 - 线程池资源管理优化 - 二分搜索算法(O(log n) vs O(n)) - 自适应资源管理 🎯 项目现在稳定运行,可部署到Linux环境
537 lines
16 KiB
Python
537 lines
16 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
金山文档上传测试 - 完整自动登录版本
|
|
自动处理:登录并加入编译 → 扫码 → 确认登录
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import base64
|
|
from datetime import datetime
|
|
from io import BytesIO
|
|
from PIL import Image
|
|
|
|
# 添加项目路径
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
try:
|
|
from playwright.sync_api import sync_playwright
|
|
except ImportError:
|
|
print("错误: 需要安装 playwright")
|
|
print("请运行: pip install playwright")
|
|
sys.exit(1)
|
|
|
|
|
|
def log(message, level='INFO'):
|
|
"""日志输出"""
|
|
timestamp = datetime.now().strftime("%H:%M:%S")
|
|
print(f"[{timestamp}] {level}: {message}")
|
|
|
|
|
|
def pause(msg="按Enter键继续..."):
|
|
"""等待用户按键"""
|
|
input(f"\n{msg}")
|
|
|
|
|
|
def ask_yes_no(question, default='n'):
|
|
"""询问用户是/否问题"""
|
|
if default == 'y':
|
|
prompt = f"{question} (Y/n): "
|
|
else:
|
|
prompt = f"{question} (y/N): "
|
|
|
|
answer = input(prompt).strip().lower()
|
|
if not answer:
|
|
answer = default
|
|
return answer == 'y'
|
|
|
|
|
|
def save_qr_code(qr_image_bytes, filename="qr_code.png"):
|
|
"""保存二维码图片"""
|
|
try:
|
|
with open(filename, 'wb') as f:
|
|
f.write(qr_image_bytes)
|
|
log(f"[OK] 二维码已保存到: {filename}", 'SUCCESS')
|
|
return filename
|
|
except Exception as e:
|
|
log(f"✗ 保存二维码失败: {str(e)}", 'ERROR')
|
|
return None
|
|
|
|
|
|
def click_login_join_button(page):
|
|
"""点击'登录并加入编辑'按钮"""
|
|
log("查找'登录并加入编辑'按钮...", 'INFO')
|
|
|
|
# 多种可能的按钮选择器
|
|
login_selectors = [
|
|
"text=登录并加入编辑",
|
|
"text=登录并加入编译",
|
|
"button:has-text('登录')",
|
|
"text=立即登录",
|
|
"[class*='login']",
|
|
"[id*='login']"
|
|
]
|
|
|
|
for selector in login_selectors:
|
|
try:
|
|
button = page.locator(selector).first
|
|
if button.is_visible(timeout=3000):
|
|
log(f"[OK] 找到登录按钮: {selector}", 'SUCCESS')
|
|
button.click()
|
|
log("[OK] 已点击登录按钮", 'SUCCESS')
|
|
return True
|
|
except Exception:
|
|
continue
|
|
|
|
log("✗ 未找到登录按钮", 'ERROR')
|
|
return False
|
|
|
|
|
|
def wait_for_qr_code(page, timeout=30):
|
|
"""等待二维码出现"""
|
|
log("等待二维码加载...", 'INFO')
|
|
|
|
start_time = time.time()
|
|
while time.time() - start_time < timeout:
|
|
try:
|
|
# 查找二维码元素
|
|
qr_selectors = [
|
|
"canvas",
|
|
"img[src*='qr']",
|
|
"img[alt*='二维码']",
|
|
"[class*='qr']",
|
|
"[id*='qr']",
|
|
"div[class*='qrcode']",
|
|
"img[src*='wechat']"
|
|
]
|
|
|
|
for selector in qr_selectors:
|
|
try:
|
|
elements = page.query_selector_all(selector)
|
|
for i, element in enumerate(elements):
|
|
try:
|
|
# 尝试截图
|
|
screenshot = element.screenshot()
|
|
if len(screenshot) > 500: # 足够大的图片
|
|
filename = f"qr_code_{i}.png"
|
|
save_qr_code(screenshot, filename)
|
|
log(f"[OK] 找到二维码元素: {selector}[{i}]", 'SUCCESS')
|
|
return True
|
|
except Exception:
|
|
continue
|
|
except Exception:
|
|
continue
|
|
|
|
time.sleep(1)
|
|
|
|
except Exception as e:
|
|
log(f"检查二维码时出错: {str(e)}", 'WARNING')
|
|
time.sleep(1)
|
|
|
|
return False
|
|
|
|
|
|
def wait_for_confirm_login(page, timeout=120):
|
|
"""等待'确认登录'按钮出现并点击"""
|
|
log("等待用户扫码...", 'INFO')
|
|
log("请使用手机微信扫描二维码", 'INFO')
|
|
log("扫码完成后,程序会自动检测并点击'确认登录'", 'INFO')
|
|
|
|
start_time = time.time()
|
|
check_interval = 2 # 每2秒检查一次
|
|
|
|
while time.time() - start_time < timeout:
|
|
try:
|
|
# 查找确认登录按钮
|
|
confirm_selectors = [
|
|
"text=确认登录",
|
|
"text=确认登陆",
|
|
"button:has-text('确认')",
|
|
"text=登录",
|
|
"[class*='confirm']",
|
|
"[id*='confirm']"
|
|
]
|
|
|
|
for selector in confirm_selectors:
|
|
try:
|
|
button = page.locator(selector).first
|
|
if button.is_visible(timeout=1000):
|
|
log(f"[OK] 找到确认按钮: {selector}", 'SUCCESS')
|
|
button.click()
|
|
log("[OK] 已点击确认登录按钮", 'SUCCESS')
|
|
return True
|
|
except Exception:
|
|
continue
|
|
|
|
# 如果没找到按钮,显示等待信息
|
|
elapsed = int(time.time() - start_time)
|
|
if elapsed % 10 == 0: # 每10秒显示一次
|
|
log(f"等待中... ({elapsed}秒)", 'INFO')
|
|
|
|
time.sleep(check_interval)
|
|
|
|
except Exception as e:
|
|
log(f"检查确认按钮时出错: {str(e)}", 'WARNING')
|
|
time.sleep(check_interval)
|
|
|
|
return False
|
|
|
|
|
|
def wait_for_document_loaded(page, timeout=30):
|
|
"""等待文档页面加载完成"""
|
|
log("等待文档页面加载...", 'INFO')
|
|
|
|
start_time = time.time()
|
|
while time.time() - start_time < timeout:
|
|
try:
|
|
current_url = page.url
|
|
log(f"当前URL: {current_url}", 'INFO')
|
|
|
|
# 检查是否进入文档页面
|
|
if "kdocs.cn" in current_url and "/spreadsheet/" in current_url:
|
|
log("[OK] 已进入文档页面", 'SUCCESS')
|
|
return True
|
|
|
|
# 检查表格元素
|
|
try:
|
|
canvas_count = page.locator("canvas").count()
|
|
if canvas_count > 0:
|
|
log(f"[OK] 检测到 {canvas_count} 个表格元素", 'SUCCESS')
|
|
return True
|
|
except:
|
|
pass
|
|
|
|
time.sleep(2)
|
|
|
|
except Exception as e:
|
|
log(f"检查页面状态时出错: {str(e)}", 'WARNING')
|
|
time.sleep(2)
|
|
|
|
return False
|
|
|
|
|
|
def main():
|
|
"""主函数"""
|
|
print("=" * 70)
|
|
print("[LOCK] 金山文档上传测试 - 完整自动登录版本")
|
|
print("=" * 70)
|
|
print()
|
|
print("特点:")
|
|
print(" [OK] 自动点击'登录并加入编译'")
|
|
print(" [OK] 自动捕获二维码")
|
|
print(" [OK] 自动等待并点击'确认登录'")
|
|
print(" [OK] 自动检测文档加载")
|
|
print()
|
|
|
|
# 配置
|
|
doc_url = input("请输入金山文档URL (或按Enter使用默认): ").strip()
|
|
if not doc_url:
|
|
doc_url = "https://kdocs.cn/l/cpwEOo5ynKX4"
|
|
|
|
print(f"\n使用URL: {doc_url}")
|
|
print()
|
|
|
|
if not ask_yes_no("确认开始测试?"):
|
|
print("测试已取消")
|
|
return
|
|
|
|
print("\n" + "=" * 70)
|
|
print("开始测试流程")
|
|
print("=" * 70)
|
|
|
|
playwright = None
|
|
browser = None
|
|
context = None
|
|
page = None
|
|
|
|
try:
|
|
# ===== 步骤1: 启动浏览器 =====
|
|
print("\n" + "=" * 50)
|
|
print("步骤1: 启动浏览器")
|
|
print("=" * 50)
|
|
|
|
log("正在启动Playwright...", 'INFO')
|
|
playwright = sync_playwright().start()
|
|
log("[OK] Playwright启动成功", 'SUCCESS')
|
|
|
|
log("正在启动浏览器...", 'INFO')
|
|
browser = playwright.chromium.launch(headless=False)
|
|
log("[OK] 浏览器启动成功", 'SUCCESS')
|
|
|
|
log("正在创建上下文...", 'INFO')
|
|
context = browser.new_context()
|
|
log("[OK] 上下文创建成功", 'SUCCESS')
|
|
|
|
log("正在创建页面...", 'INFO')
|
|
page = context.new_page()
|
|
page.set_default_timeout(30000)
|
|
log("[OK] 页面创建成功", 'SUCCESS')
|
|
|
|
pause("浏览器已启动,请观察浏览器窗口")
|
|
|
|
log("额外等待5秒确保浏览器完全就绪...", 'INFO')
|
|
time.sleep(5)
|
|
|
|
# ===== 步骤2: 打开文档页面 =====
|
|
print("\n" + "=" * 50)
|
|
print("步骤2: 打开文档页面")
|
|
print("=" * 50)
|
|
|
|
log(f"正在导航到: {doc_url}", 'INFO')
|
|
page.goto(doc_url, wait_until='domcontentloaded')
|
|
log("[OK] 页面导航完成", 'SUCCESS')
|
|
|
|
log("等待8秒让页面完全加载...", 'INFO')
|
|
time.sleep(8)
|
|
|
|
current_url = page.url
|
|
log(f"当前URL: {current_url}", 'INFO')
|
|
|
|
# ===== 步骤3: 自动点击登录按钮 =====
|
|
print("\n" + "=" * 50)
|
|
print("步骤3: 点击登录按钮")
|
|
print("=" * 50)
|
|
|
|
log("检测页面状态...", 'INFO')
|
|
log("等待页面元素完全加载...", 'INFO')
|
|
|
|
# 额外的等待确保页面完全加载
|
|
log("额外等待5秒确保页面完全加载...", 'INFO')
|
|
time.sleep(5)
|
|
|
|
# 尝试等待特定元素出现
|
|
try:
|
|
page.wait_for_selector("text=登录并加入", timeout=15000)
|
|
log("[OK] 检测到'登录并加入编辑'页面", 'SUCCESS')
|
|
login_button_found = True
|
|
except:
|
|
log("⚠ 未检测到登录按钮,继续等待...", 'WARNING')
|
|
time.sleep(5)
|
|
login_button_found = False
|
|
|
|
# 最终检测页面内容
|
|
page_content = page.content()
|
|
if "登录并加入" in page_content:
|
|
log("[OK] 检测到'登录并加入编辑'页面", 'SUCCESS')
|
|
login_button_found = True
|
|
else:
|
|
log("⚠ 未检测到'登录并加入编辑'页面", 'WARNING')
|
|
login_button_found = False
|
|
|
|
# 执行点击操作
|
|
if login_button_found:
|
|
if click_login_join_button(page):
|
|
log("[OK] 已点击登录按钮,等待跳转到扫码页面...", 'SUCCESS')
|
|
time.sleep(5) # 增加等待时间
|
|
else:
|
|
log("✗ 点击登录按钮失败", 'ERROR')
|
|
return
|
|
else:
|
|
# 检查是否已经直接进入登录页面
|
|
if "login" in page.url.lower() or "account" in page.url.lower():
|
|
log("[OK] 已直接进入登录页面", 'SUCCESS')
|
|
else:
|
|
log("⚠ 页面状态不明确,请手动检查浏览器窗口", 'WARNING')
|
|
|
|
# ===== 步骤4: 等待二维码 =====
|
|
print("\n" + "=" * 50)
|
|
print("步骤4: 等待二维码")
|
|
print("=" * 50)
|
|
|
|
if wait_for_qr_code(page, timeout=90):
|
|
log("[OK] 二维码加载完成", 'SUCCESS')
|
|
else:
|
|
log("⚠ 未检测到二维码,可能页面结构有变化", 'WARNING')
|
|
|
|
# ===== 步骤5: 等待确认登录 =====
|
|
print("\n" + "=" * 50)
|
|
print("步骤5: 等待确认登录")
|
|
print("=" * 50)
|
|
|
|
log("扫码流程:", 'INFO')
|
|
log("1. 请使用手机微信扫描二维码", 'INFO')
|
|
log("2. 扫码后点击'确认登录'", 'INFO')
|
|
log("3. 程序会自动检测并处理", 'INFO')
|
|
|
|
if wait_for_confirm_login(page, timeout=180):
|
|
log("[OK] 登录确认完成", 'SUCCESS')
|
|
else:
|
|
log("⚠ 未检测到确认登录操作", 'WARNING')
|
|
|
|
# ===== 步骤6: 等待文档加载 =====
|
|
print("\n" + "=" * 50)
|
|
print("步骤6: 等待文档加载")
|
|
print("=" * 50)
|
|
|
|
if wait_for_document_loaded(page, timeout=60):
|
|
log("[OK] 文档页面加载完成", 'SUCCESS')
|
|
|
|
# 验证表格元素
|
|
try:
|
|
canvas_count = page.locator("canvas").count()
|
|
log(f"[OK] 检测到 {canvas_count} 个表格元素", 'SUCCESS')
|
|
|
|
# 尝试读取名称框
|
|
try:
|
|
name_box = page.locator("input.edit-box").first
|
|
if name_box.is_visible():
|
|
value = name_box.input_value()
|
|
log(f"[OK] 名称框可见,当前值: '{value}'", 'SUCCESS')
|
|
except:
|
|
pass
|
|
|
|
except Exception as e:
|
|
log(f"检查表格元素时出错: {str(e)}", 'WARNING')
|
|
else:
|
|
log("⚠ 文档页面加载超时", 'WARNING')
|
|
|
|
# ===== 步骤7: 表格功能测试 =====
|
|
print("\n" + "=" * 50)
|
|
print("步骤7: 表格功能测试")
|
|
print("=" * 50)
|
|
|
|
# 测试搜索功能
|
|
test_name = input("请输入要搜索的姓名 (默认: 张三): ").strip()
|
|
if not test_name:
|
|
test_name = "张三"
|
|
|
|
log(f"搜索姓名: {test_name}", 'INFO')
|
|
|
|
try:
|
|
page.keyboard.press("Control+f")
|
|
time.sleep(0.5)
|
|
|
|
page.keyboard.type(test_name)
|
|
time.sleep(0.3)
|
|
|
|
page.keyboard.press("Enter")
|
|
time.sleep(1)
|
|
|
|
page.keyboard.press("Escape")
|
|
time.sleep(0.3)
|
|
|
|
log("[OK] 搜索测试完成", 'SUCCESS')
|
|
log("请查看浏览器窗口,检查搜索结果", 'INFO')
|
|
|
|
except Exception as e:
|
|
log(f"✗ 搜索测试失败: {str(e)}", 'ERROR')
|
|
|
|
pause("搜索测试完成")
|
|
|
|
# ===== 步骤8: 图片上传测试 =====
|
|
print("\n" + "=" * 50)
|
|
print("步骤8: 图片上传测试")
|
|
print("=" * 50)
|
|
|
|
if ask_yes_no("是否进行图片上传测试?"):
|
|
image_path = input("请输入测试图片的完整路径: ").strip()
|
|
|
|
if not image_path or not os.path.exists(image_path):
|
|
log("图片文件不存在,跳过上传测试", 'WARNING')
|
|
else:
|
|
log(f"选中的图片: {image_path}", 'INFO')
|
|
|
|
try:
|
|
# 导航到D3
|
|
name_box = page.locator("input.edit-box").first
|
|
name_box.click()
|
|
name_box.fill("D3")
|
|
name_box.press("Enter")
|
|
time.sleep(0.5)
|
|
|
|
log("[OK] 已导航到D3单元格")
|
|
|
|
# 点击插入
|
|
insert_btn = page.locator("text=插入").first
|
|
insert_btn.click()
|
|
time.sleep(0.5)
|
|
|
|
log("[OK] 已点击插入按钮")
|
|
|
|
# 点击图片
|
|
image_btn = page.locator("text=图片").first
|
|
image_btn.click()
|
|
time.sleep(0.5)
|
|
|
|
log("[OK] 已点击图片按钮")
|
|
|
|
# 选择本地
|
|
local_option = page.locator("text=本地").first
|
|
local_option.click()
|
|
|
|
log("[OK] 已选择本地图片")
|
|
|
|
# 上传文件
|
|
with page.expect_file_chooser() as fc_info:
|
|
pass
|
|
|
|
file_chooser = fc_info.value
|
|
file_chooser.set_files(image_path)
|
|
|
|
log("[OK] 文件上传命令已发送")
|
|
|
|
time.sleep(3)
|
|
|
|
log("[OK] 图片上传测试完成", 'SUCCESS')
|
|
|
|
except Exception as e:
|
|
log(f"✗ 图片上传测试失败: {str(e)}", 'ERROR')
|
|
|
|
pause("所有测试完成")
|
|
|
|
# ===== 测试完成 =====
|
|
print("\n" + "=" * 70)
|
|
log("🎉 所有测试完成!", 'SUCCESS')
|
|
print("=" * 70)
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n")
|
|
log("测试被用户中断", 'WARNING')
|
|
except Exception as e:
|
|
print("\n")
|
|
log(f"测试过程中出现错误: {str(e)}", 'ERROR')
|
|
import traceback
|
|
traceback.print_exc()
|
|
finally:
|
|
# 清理资源
|
|
print("\n" + "=" * 70)
|
|
print("清理资源...")
|
|
print("=" * 70)
|
|
|
|
try:
|
|
if page:
|
|
page.close()
|
|
log("[OK] 页面已关闭", 'SUCCESS')
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
if context:
|
|
context.close()
|
|
log("[OK] 上下文已关闭", 'SUCCESS')
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
if browser:
|
|
browser.close()
|
|
log("[OK] 浏览器已关闭", 'SUCCESS')
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
if playwright:
|
|
playwright.stop()
|
|
log("[OK] Playwright已停止", 'SUCCESS')
|
|
except:
|
|
pass
|
|
|
|
log("测试结束", 'SUCCESS')
|
|
print("=" * 70)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|