🧹 清理不必要的文件,保持仓库整洁
❌ 删除的文件: - 测试文件 (test_*.py, kdocs_*test*.py, simple_test.py) - 启动脚本 (start_*.bat) - 临时修复文件 (temp_*.py) - 图片文件 (qr_code_*.png, screenshots/*) - 运行时生成文件 ✅ 添加的内容: - .gitignore 文件,防止推送临时文件和开发文件 📋 保留的内容: - 核心应用代码 - 数据库迁移文件 - Docker配置文件 - 必要的文档 (BUG_REPORT.md, PERFORMANCE_ANALYSIS_REPORT.md, 等) 🎯 目的: - 保持仓库整洁专业 - 只包含生产环境需要的文件 - 避免推送临时开发文件 - 提高仓库维护性
This commit is contained in:
173
.gitignore
vendored
173
.gitignore
vendored
@@ -1,75 +1,140 @@
|
||||
# 浏览器二进制文件
|
||||
playwright/
|
||||
ms-playwright/
|
||||
|
||||
# 数据库文件(敏感数据)
|
||||
data/*.db
|
||||
data/*.db-shm
|
||||
data/*.db-wal
|
||||
data/*.backup*
|
||||
data/secret_key.txt
|
||||
data/update/
|
||||
|
||||
# Cookies(敏感用户凭据)
|
||||
data/cookies/
|
||||
|
||||
# 日志文件
|
||||
logs/
|
||||
*.log
|
||||
|
||||
# 截图文件
|
||||
截图/
|
||||
|
||||
# Python缓存
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.class
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
.mypy_cache/
|
||||
.coverage
|
||||
coverage.xml
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# 环境变量文件(包含敏感信息)
|
||||
.env
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Docker volumes
|
||||
volumes/
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Project specific
|
||||
data/*.db
|
||||
data/*.db-shm
|
||||
data/*.db-wal
|
||||
logs/
|
||||
screenshots/
|
||||
*.png
|
||||
*.jpg
|
||||
*.jpeg
|
||||
*.gif
|
||||
*.bmp
|
||||
*.ico
|
||||
*.pdf
|
||||
qr_code_*.png
|
||||
test_*.py
|
||||
start_*.bat
|
||||
temp_*.py
|
||||
kdocs_*test*.py
|
||||
simple_test.py
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# 系统文件
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# 临时文件
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.bak
|
||||
*.backup
|
||||
|
||||
# 部署脚本(含服务器信息)
|
||||
deploy_*.sh
|
||||
verify_*.sh
|
||||
deploy.sh
|
||||
|
||||
# 内部文档
|
||||
docs/
|
||||
|
||||
# 前端依赖(体积大,不应入库)
|
||||
node_modules/
|
||||
app-frontend/node_modules/
|
||||
admin-frontend/node_modules/
|
||||
|
||||
# Local data
|
||||
data/
|
||||
docker-compose.yml.bak.*
|
||||
*.temp
|
||||
|
||||
@@ -1,631 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
金山文档上传安全测试工具 - 异步版本
|
||||
使用asyncio避免线程问题
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
import asyncio
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Optional, Callable
|
||||
|
||||
# 添加项目路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
try:
|
||||
from playwright.async_api import async_playwright
|
||||
except ImportError:
|
||||
print("错误: 需要安装 playwright")
|
||||
print("请运行: pip install playwright")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class AsyncBrowserManager:
|
||||
"""异步浏览器管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.playwright = None
|
||||
self.browser = None
|
||||
self.context = None
|
||||
self.page = None
|
||||
self._loop = None
|
||||
self._running = False
|
||||
|
||||
async def initialize(self, headless=False):
|
||||
"""初始化浏览器(异步)"""
|
||||
if self.playwright:
|
||||
return True
|
||||
|
||||
try:
|
||||
self.playwright = await async_playwright().start()
|
||||
self.browser = await self.playwright.chromium.launch(headless=headless)
|
||||
self.context = await self.browser.new_context()
|
||||
self.page = await self.context.new_page()
|
||||
await self.page.set_default_timeout(30000)
|
||||
self._running = True
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"初始化浏览器失败: {e}")
|
||||
await self.cleanup()
|
||||
return False
|
||||
|
||||
async def goto(self, url: str):
|
||||
"""导航到URL"""
|
||||
if not self.page:
|
||||
raise Exception("浏览器未初始化")
|
||||
await self.page.goto(url, wait_until='domcontentloaded')
|
||||
await self.page.wait_for_timeout(3000)
|
||||
|
||||
async def close(self):
|
||||
"""关闭浏览器"""
|
||||
await self.cleanup()
|
||||
|
||||
async def cleanup(self):
|
||||
"""清理资源"""
|
||||
try:
|
||||
if self.page:
|
||||
await self.page.close()
|
||||
except:
|
||||
pass
|
||||
self.page = None
|
||||
|
||||
try:
|
||||
if self.context:
|
||||
await self.context.close()
|
||||
except:
|
||||
pass
|
||||
self.context = None
|
||||
|
||||
try:
|
||||
if self.browser:
|
||||
await self.browser.close()
|
||||
except:
|
||||
pass
|
||||
self.browser = None
|
||||
|
||||
try:
|
||||
if self.playwright:
|
||||
await self.playwright.stop()
|
||||
except:
|
||||
pass
|
||||
self.playwright = None
|
||||
self._running = False
|
||||
|
||||
|
||||
class AsyncTestTool:
|
||||
def __init__(self):
|
||||
self.root = tk.Tk()
|
||||
self.root.title("金山文档上传安全测试工具 - 异步版")
|
||||
self.root.geometry("1000x700")
|
||||
self.root.configure(bg='#f0f0f0')
|
||||
|
||||
# 异步浏览器管理器
|
||||
self.browser_manager = AsyncBrowserManager()
|
||||
|
||||
# 状态变量
|
||||
self.doc_url = tk.StringVar(value="https://kdocs.cn/l/cpwEOo5ynKX4")
|
||||
self.is_running = False
|
||||
self.test_results = []
|
||||
self.async_loop = None
|
||||
self.thread_pool_executor = None
|
||||
|
||||
# 创建界面
|
||||
self.create_widgets()
|
||||
|
||||
def create_widgets(self):
|
||||
"""创建UI组件"""
|
||||
|
||||
# 顶部配置区域
|
||||
config_frame = ttk.LabelFrame(self.root, text="连接配置", padding=10)
|
||||
config_frame.pack(fill='x', padx=10, pady=5)
|
||||
|
||||
ttk.Label(config_frame, text="金山文档URL:").grid(row=0, column=0, sticky='w', padx=5, pady=2)
|
||||
ttk.Entry(config_frame, textvariable=self.doc_url, width=80).grid(row=0, column=1, padx=5, pady=2)
|
||||
|
||||
# 浏览器控制按钮
|
||||
browser_frame = ttk.Frame(config_frame)
|
||||
browser_frame.grid(row=0, column=2, padx=10)
|
||||
|
||||
ttk.Button(browser_frame, text="启动浏览器", command=self.start_browser).pack(side='left', padx=5)
|
||||
ttk.Button(browser_frame, text="打开文档", command=self.open_document).pack(side='left', padx=5)
|
||||
ttk.Button(browser_frame, text="关闭浏览器", command=self.close_browser).pack(side='left', padx=5)
|
||||
|
||||
# 状态显示
|
||||
status_frame = ttk.Frame(config_frame)
|
||||
status_frame.grid(row=1, column=0, columnspan=3, sticky='ew', padx=5, pady=5)
|
||||
|
||||
self.status_label = tk.Label(status_frame, text="浏览器状态: 未启动", bg='lightgray', relief='sunken', anchor='w')
|
||||
self.status_label.pack(fill='x')
|
||||
|
||||
# 测试步骤区域
|
||||
test_frame = ttk.LabelFrame(self.root, text="测试步骤", padding=10)
|
||||
test_frame.pack(fill='both', expand=True, padx=10, pady=5)
|
||||
|
||||
# 左侧:操作按钮
|
||||
left_frame = ttk.Frame(test_frame)
|
||||
left_frame.pack(side='left', fill='y', padx=10)
|
||||
|
||||
test_steps = [
|
||||
("1. 测试浏览器连接", self.test_browser_connection),
|
||||
("2. 测试文档打开", self.test_document_open),
|
||||
("3. 测试表格读取", self.test_table_reading),
|
||||
("4. 测试人员搜索", self.test_person_search),
|
||||
("5. 测试图片上传(单步)", self.test_image_upload_single),
|
||||
("6. 完整流程测试", self.test_complete_flow),
|
||||
]
|
||||
|
||||
for text, command in test_steps:
|
||||
btn = ttk.Button(left_frame, text=text, command=command, width=25)
|
||||
btn.pack(pady=5)
|
||||
|
||||
# 右侧:操作详情和确认
|
||||
right_frame = ttk.Frame(test_frame)
|
||||
right_frame.pack(side='left', fill='both', expand=True, padx=10)
|
||||
|
||||
ttk.Label(right_frame, text="当前操作:", font=('Arial', 10, 'bold')).pack(anchor='w')
|
||||
self.operation_label = tk.Label(right_frame, text="等待操作...", bg='white', height=3, relief='sunken', anchor='w')
|
||||
self.operation_label.pack(fill='x', pady=5)
|
||||
|
||||
# 确认按钮区域
|
||||
confirm_frame = ttk.Frame(right_frame)
|
||||
confirm_frame.pack(fill='x', pady=10)
|
||||
|
||||
self.confirm_button = ttk.Button(confirm_frame, text="确认执行", command=self.execute_operation, state='disabled')
|
||||
self.confirm_button.pack(side='left', padx=5)
|
||||
|
||||
ttk.Button(confirm_frame, text="取消", command=self.cancel_operation).pack(side='left', padx=5)
|
||||
|
||||
# 日志区域
|
||||
log_frame = ttk.LabelFrame(self.root, text="操作日志", padding=10)
|
||||
log_frame.pack(fill='both', expand=False, padx=10, pady=5)
|
||||
|
||||
# 创建文本框和滚动条
|
||||
text_frame = ttk.Frame(log_frame)
|
||||
text_frame.pack(fill='both', expand=True)
|
||||
|
||||
self.log_text = tk.Text(text_frame, height=10, wrap='word')
|
||||
scrollbar = ttk.Scrollbar(text_frame, orient='vertical', command=self.log_text.yview)
|
||||
self.log_text.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
self.log_text.pack(side='left', fill='both', expand=True)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
def log(self, message, level='INFO'):
|
||||
"""添加日志"""
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
log_entry = f"[{timestamp}] {level}: {message}\n"
|
||||
|
||||
# 颜色标记
|
||||
if level == 'ERROR':
|
||||
tag = 'error'
|
||||
color = 'red'
|
||||
elif level == 'WARNING':
|
||||
tag = 'warning'
|
||||
color = 'orange'
|
||||
elif level == 'SUCCESS':
|
||||
tag = 'success'
|
||||
color = 'green'
|
||||
else:
|
||||
tag = 'normal'
|
||||
color = 'black'
|
||||
|
||||
self.log_text.insert('end', log_entry, tag)
|
||||
self.log_text.see('end')
|
||||
|
||||
# 配置标签颜色
|
||||
self.log_text.tag_config(tag, foreground=color)
|
||||
|
||||
# 打印到控制台
|
||||
print(log_entry.strip())
|
||||
|
||||
def update_status(self, status_text):
|
||||
"""更新状态显示"""
|
||||
self.status_label.config(text=f"浏览器状态: {status_text}")
|
||||
# 颜色编码
|
||||
if "运行" in status_text or "就绪" in status_text or "成功" in status_text:
|
||||
self.status_label.config(bg='lightgreen')
|
||||
elif "错误" in status_text or "失败" in status_text:
|
||||
self.status_label.config(bg='lightcoral')
|
||||
else:
|
||||
self.status_label.config(bg='lightgray')
|
||||
|
||||
def show_operation(self, operation_text: str, async_func: Callable):
|
||||
"""显示操作详情,等待用户确认"""
|
||||
self.operation_label.config(text=operation_text)
|
||||
self.pending_async_func = async_func
|
||||
self.confirm_button.config(state='normal')
|
||||
|
||||
def execute_operation(self):
|
||||
"""执行待处理的操作"""
|
||||
if hasattr(self, 'pending_async_func'):
|
||||
self.confirm_button.config(state='disabled')
|
||||
self.is_running = True
|
||||
|
||||
# 在新的线程中运行异步函数
|
||||
def run_async():
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
try:
|
||||
loop.run_until_complete(self.pending_async_func())
|
||||
except Exception as e:
|
||||
self.log(f"操作失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
loop.close()
|
||||
self.is_running = False
|
||||
self.operation_label.config(text="等待操作...")
|
||||
self.pending_async_func = None
|
||||
|
||||
threading.Thread(target=run_async, daemon=True).start()
|
||||
|
||||
def cancel_operation(self):
|
||||
"""取消待处理的操作"""
|
||||
self.confirm_button.config(state='disabled')
|
||||
self.operation_label.config(text="操作已取消")
|
||||
self.pending_async_func = None
|
||||
self.log("操作已取消", 'WARNING')
|
||||
|
||||
# ==================== 异步操作函数 ====================
|
||||
|
||||
async def async_start_browser(self):
|
||||
"""异步启动浏览器"""
|
||||
self.log("正在启动浏览器...", 'INFO')
|
||||
self.update_status("启动中...")
|
||||
|
||||
try:
|
||||
success = await self.browser_manager.initialize(headless=False)
|
||||
if success:
|
||||
self.log("[OK] 浏览器启动成功", 'SUCCESS')
|
||||
self.update_status("运行中 (就绪)")
|
||||
else:
|
||||
self.log("✗ 浏览器启动失败", 'ERROR')
|
||||
self.update_status("启动失败")
|
||||
except Exception as e:
|
||||
self.log(f"✗ 浏览器启动失败: {str(e)}", 'ERROR')
|
||||
self.update_status("启动失败")
|
||||
|
||||
async def async_open_document(self):
|
||||
"""异步打开文档"""
|
||||
doc_url = self.doc_url.get()
|
||||
if not doc_url or "your-doc-id" in doc_url:
|
||||
self.log("请先配置正确的金山文档URL", 'ERROR')
|
||||
self.update_status("错误: URL未配置")
|
||||
return
|
||||
|
||||
self.log(f"正在打开文档: {doc_url}", 'INFO')
|
||||
self.update_status(f"打开文档中...")
|
||||
|
||||
try:
|
||||
await self.browser_manager.goto(doc_url)
|
||||
self.log("[OK] 文档打开成功", 'SUCCESS')
|
||||
self.update_status("运行中 (文档已打开)")
|
||||
except Exception as e:
|
||||
self.log(f"✗ 文档打开失败: {str(e)}", 'ERROR')
|
||||
self.update_status("打开文档失败")
|
||||
|
||||
async def async_close_browser(self):
|
||||
"""异步关闭浏览器"""
|
||||
self.log("正在关闭浏览器...", 'INFO')
|
||||
self.update_status("关闭中...")
|
||||
|
||||
try:
|
||||
await self.browser_manager.close()
|
||||
self.log("[OK] 浏览器已关闭", 'SUCCESS')
|
||||
self.update_status("已关闭")
|
||||
except Exception as e:
|
||||
self.log(f"✗ 关闭浏览器失败: {str(e)}", 'ERROR')
|
||||
self.update_status("关闭失败")
|
||||
|
||||
async def async_test_browser_connection(self):
|
||||
"""异步测试浏览器连接"""
|
||||
self.log("开始测试浏览器连接...", 'INFO')
|
||||
|
||||
if not self.browser_manager.page:
|
||||
self.log("浏览器未启动,请先点击'启动浏览器'", 'ERROR')
|
||||
self.update_status("错误: 未启动")
|
||||
return
|
||||
|
||||
self.log("[OK] 浏览器连接正常", 'SUCCESS')
|
||||
self.log("[OK] 页面对象可用", 'SUCCESS')
|
||||
self.log("浏览器连接测试通过", 'SUCCESS')
|
||||
self.update_status("运行中 (连接正常)")
|
||||
|
||||
async def async_test_document_open(self):
|
||||
"""异步测试文档打开"""
|
||||
self.log("开始测试文档打开...", 'INFO')
|
||||
|
||||
if not self.browser_manager.page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
try:
|
||||
current_url = self.browser_manager.page.url
|
||||
self.log(f"当前页面URL: {current_url}", 'INFO')
|
||||
|
||||
# 检查是否在金山文档域名
|
||||
if "kdocs.cn" in current_url:
|
||||
self.log("[OK] 已在金山文档域名", 'SUCCESS')
|
||||
else:
|
||||
self.log("当前不在金山文档域名", 'WARNING')
|
||||
|
||||
# 检查是否有登录提示
|
||||
try:
|
||||
login_text = await self.browser_manager.page.locator("text=登录").first.is_visible()
|
||||
if login_text:
|
||||
self.log("检测到登录页面", 'WARNING')
|
||||
self.update_status("需要登录")
|
||||
else:
|
||||
self.log("未检测到登录页面", 'INFO')
|
||||
self.update_status("运行中 (文档已打开)")
|
||||
except:
|
||||
pass
|
||||
|
||||
self.log("文档打开测试完成", 'SUCCESS')
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
async def async_test_table_reading(self):
|
||||
"""异步测试表格读取"""
|
||||
self.log("开始测试表格读取...", 'INFO')
|
||||
|
||||
if not self.browser_manager.page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
try:
|
||||
self.log("尝试导航到A1单元格...", 'INFO')
|
||||
|
||||
# 查找表格元素
|
||||
canvas_count = await self.browser_manager.page.locator("canvas").count()
|
||||
self.log(f"检测到 {canvas_count} 个canvas元素(可能是表格)", 'INFO')
|
||||
|
||||
# 尝试读取名称框
|
||||
try:
|
||||
name_box = self.browser_manager.page.locator("input.edit-box").first
|
||||
is_visible = await name_box.is_visible()
|
||||
if is_visible:
|
||||
value = await name_box.input_value()
|
||||
self.log(f"名称框当前值: {value}", 'INFO')
|
||||
else:
|
||||
self.log("名称框不可见", 'INFO')
|
||||
except Exception as e:
|
||||
self.log(f"读取名称框失败: {str(e)}", 'WARNING')
|
||||
|
||||
self.log("[OK] 表格读取测试完成", 'SUCCESS')
|
||||
self.update_status("运行中 (表格可读取)")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
async def async_test_person_search(self):
|
||||
"""异步测试人员搜索"""
|
||||
self.log("开始测试人员搜索...", 'INFO')
|
||||
|
||||
if not self.browser_manager.page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
test_name = "张三" # 默认测试名称
|
||||
|
||||
self.log(f"搜索测试姓名: {test_name}", 'INFO')
|
||||
|
||||
try:
|
||||
self.log("聚焦到网格...", 'INFO')
|
||||
|
||||
# 打开搜索框
|
||||
self.log("打开搜索框 (Ctrl+F)...", 'INFO')
|
||||
await self.browser_manager.page.keyboard.press("Control+f")
|
||||
await self.browser_manager.page.wait_for_timeout(500)
|
||||
|
||||
# 输入搜索内容
|
||||
self.log(f"输入搜索内容: {test_name}", 'INFO')
|
||||
await self.browser_manager.page.keyboard.type(test_name)
|
||||
await self.browser_manager.page.wait_for_timeout(300)
|
||||
|
||||
# 按回车搜索
|
||||
self.log("执行搜索 (Enter)...", 'INFO')
|
||||
await self.browser_manager.page.keyboard.press("Enter")
|
||||
await self.browser_manager.page.wait_for_timeout(1000)
|
||||
|
||||
# 关闭搜索
|
||||
await self.browser_manager.page.keyboard.press("Escape")
|
||||
await self.browser_manager.page.wait_for_timeout(300)
|
||||
|
||||
self.log("[OK] 人员搜索测试完成", 'SUCCESS')
|
||||
self.log("注意:请检查浏览器窗口,看是否高亮显示了相关内容", 'INFO')
|
||||
self.update_status("运行中 (搜索功能正常)")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 搜索测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
async def async_test_image_upload_single(self):
|
||||
"""异步测试图片上传(单步)"""
|
||||
self.log("开始测试图片上传(单步)...", 'INFO')
|
||||
|
||||
if not self.browser_manager.page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 让用户选择图片文件
|
||||
image_path = filedialog.askopenfilename(
|
||||
title="选择测试图片",
|
||||
filetypes=[("图片文件", "*.jpg *.jpeg *.png *.gif")]
|
||||
)
|
||||
|
||||
if not image_path:
|
||||
self.log("未选择图片文件,操作取消", 'WARNING')
|
||||
return
|
||||
|
||||
self.log(f"选择的图片: {image_path}", 'INFO')
|
||||
|
||||
try:
|
||||
# 1. 导航到测试单元格
|
||||
self.log("导航到 D3 单元格...", 'INFO')
|
||||
name_box = self.browser_manager.page.locator("input.edit-box").first
|
||||
await name_box.click()
|
||||
await name_box.fill("D3")
|
||||
await name_box.press("Enter")
|
||||
await self.browser_manager.page.wait_for_timeout(500)
|
||||
|
||||
# 2. 点击插入菜单
|
||||
self.log("点击插入按钮...", 'INFO')
|
||||
insert_btn = self.browser_manager.page.locator("text=插入").first
|
||||
await insert_btn.click()
|
||||
await self.browser_manager.page.wait_for_timeout(500)
|
||||
|
||||
# 3. 点击图片选项
|
||||
self.log("点击图片选项...", 'INFO')
|
||||
image_btn = self.browser_manager.page.locator("text=图片").first
|
||||
await image_btn.click()
|
||||
await self.browser_manager.page.wait_for_timeout(500)
|
||||
|
||||
# 4. 选择本地图片
|
||||
self.log("选择本地图片...", 'INFO')
|
||||
local_option = self.browser_manager.page.locator("text=本地").first
|
||||
await local_option.click()
|
||||
|
||||
# 5. 上传文件
|
||||
self.log("上传文件...", 'INFO')
|
||||
async with self.browser_manager.page.expect_file_chooser() as fc_info:
|
||||
pass
|
||||
|
||||
file_chooser = fc_info.value
|
||||
await file_chooser.set_files(image_path)
|
||||
|
||||
self.log("[OK] 图片上传测试完成", 'SUCCESS')
|
||||
self.log("请检查浏览器窗口,看图片是否上传成功", 'INFO')
|
||||
self.update_status("运行中 (上传测试完成)")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 图片上传测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
async def async_test_complete_flow(self):
|
||||
"""异步完整流程测试"""
|
||||
self.log("=" * 50)
|
||||
self.log("开始完整流程测试", 'INFO')
|
||||
self.log("=" * 50)
|
||||
|
||||
if not self.browser_manager.page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
self.log("完整流程测试完成", 'SUCCESS')
|
||||
self.log("=" * 50)
|
||||
self.update_status("运行中 (完整测试完成)")
|
||||
|
||||
# ==================== 包装函数 ====================
|
||||
|
||||
def start_browser(self):
|
||||
"""启动浏览器"""
|
||||
self.show_operation(
|
||||
"即将执行:启动浏览器\n"
|
||||
"说明:使用Playwright启动Chromium浏览器\n"
|
||||
"安全:这是安全的操作,不会影响任何数据",
|
||||
self.async_start_browser
|
||||
)
|
||||
|
||||
def open_document(self):
|
||||
"""打开文档"""
|
||||
self.show_operation(
|
||||
"即将执行:打开金山文档\n"
|
||||
"说明:导航到配置的金山文档URL\n"
|
||||
"安全:这是安全的操作,仅读取文档",
|
||||
self.async_open_document
|
||||
)
|
||||
|
||||
def close_browser(self):
|
||||
"""关闭浏览器"""
|
||||
self.show_operation(
|
||||
"即将执行:关闭浏览器\n"
|
||||
"说明:关闭所有浏览器实例和上下文\n"
|
||||
"安全:这是安全的操作",
|
||||
self.async_close_browser
|
||||
)
|
||||
|
||||
def test_browser_connection(self):
|
||||
"""测试浏览器连接"""
|
||||
self.show_operation(
|
||||
"即将执行:测试浏览器连接\n"
|
||||
"说明:检查浏览器和页面对象是否正常\n"
|
||||
"安全:这是安全的检查操作",
|
||||
self.async_test_browser_connection
|
||||
)
|
||||
|
||||
def test_document_open(self):
|
||||
"""测试文档打开"""
|
||||
self.show_operation(
|
||||
"即将执行:测试文档打开\n"
|
||||
"说明:检查当前页面状态和URL\n"
|
||||
"安全:这是安全的检查操作",
|
||||
self.async_test_document_open
|
||||
)
|
||||
|
||||
def test_table_reading(self):
|
||||
"""测试表格读取"""
|
||||
self.show_operation(
|
||||
"即将执行:测试表格读取\n"
|
||||
"说明:尝试读取表格元素和单元格\n"
|
||||
"安全:这是安全的只读操作,不会修改任何数据",
|
||||
self.async_test_table_reading
|
||||
)
|
||||
|
||||
def test_person_search(self):
|
||||
"""测试人员搜索"""
|
||||
self.show_operation(
|
||||
"即将执行:测试人员搜索\n"
|
||||
"说明:执行 Ctrl+F 搜索操作\n"
|
||||
"⚠️ 安全:这是安全的搜索操作,不会修改数据\n"
|
||||
"测试内容:搜索默认姓名'张三'",
|
||||
self.async_test_person_search
|
||||
)
|
||||
|
||||
def test_image_upload_single(self):
|
||||
"""测试图片上传(单步)"""
|
||||
self.show_operation(
|
||||
"即将执行:测试图片上传(单步)\n"
|
||||
"⚠️ 警告:此操作会上传图片到D3单元格\n"
|
||||
"⚠️ 安全:仅影响单个单元格,不会有批量操作\n"
|
||||
"操作流程:\n"
|
||||
"1. 导航到D3单元格\n"
|
||||
"2. 点击插入 → 图片 → 本地\n"
|
||||
"3. 上传用户选择的图片文件\n"
|
||||
"请选择一个小图片文件进行测试",
|
||||
self.async_test_image_upload_single
|
||||
)
|
||||
|
||||
def test_complete_flow(self):
|
||||
"""完整流程测试"""
|
||||
self.show_operation(
|
||||
"即将执行:完整流程测试\n"
|
||||
"⚠️ 警告:这是完整的上传流程测试\n"
|
||||
"说明:执行完整的图片上传操作\n"
|
||||
"⚠️ 安全:会实际执行上传,请确保选择了正确的测试图片\n"
|
||||
"操作包括:\n"
|
||||
"1. 定位人员位置\n"
|
||||
"2. 上传截图\n"
|
||||
"3. 验证结果",
|
||||
self.async_test_complete_flow
|
||||
)
|
||||
|
||||
def run(self):
|
||||
"""启动GUI"""
|
||||
self.log("异步安全测试工具已启动", 'INFO')
|
||||
self.log("请按照以下步骤操作:", 'INFO')
|
||||
self.log("1. 点击'启动浏览器' → 2. 点击'打开文档' → 3. 执行各项测试", 'INFO')
|
||||
self.log("每一步操作都需要您手动确认", 'WARNING')
|
||||
self.log("已自动填入您的金山文档URL", 'INFO')
|
||||
self.update_status("就绪")
|
||||
self.root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tool = AsyncTestTool()
|
||||
tool.run()
|
||||
@@ -1,526 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
金山文档上传安全测试工具
|
||||
每一步操作都需要手动确认,确保安全
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Optional, Callable
|
||||
|
||||
# 添加项目路径
|
||||
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)
|
||||
|
||||
|
||||
class SafetyTestTool:
|
||||
def __init__(self):
|
||||
self.root = tk.Tk()
|
||||
self.root.title("金山文档上传安全测试工具 v1.0")
|
||||
self.root.geometry("1000x700")
|
||||
self.root.configure(bg='#f0f0f0')
|
||||
|
||||
# 状态变量
|
||||
self.playwright = None
|
||||
self.browser = None
|
||||
self.context = None
|
||||
self.page = None
|
||||
self.doc_url = tk.StringVar(value="https://www.kdocs.cn/spreadsheet/your-doc-id")
|
||||
self.is_running = False
|
||||
self.test_results = []
|
||||
|
||||
# 创建界面
|
||||
self.create_widgets()
|
||||
|
||||
def create_widgets(self):
|
||||
"""创建UI组件"""
|
||||
|
||||
# 顶部配置区域
|
||||
config_frame = ttk.LabelFrame(self.root, text="连接配置", padding=10)
|
||||
config_frame.pack(fill='x', padx=10, pady=5)
|
||||
|
||||
ttk.Label(config_frame, text="金山文档URL:").grid(row=0, column=0, sticky='w', padx=5, pady=2)
|
||||
ttk.Entry(config_frame, textvariable=self.doc_url, width=80).grid(row=0, column=1, padx=5, pady=2)
|
||||
|
||||
# 浏览器控制按钮
|
||||
browser_frame = ttk.Frame(config_frame)
|
||||
browser_frame.grid(row=0, column=2, padx=10)
|
||||
|
||||
ttk.Button(browser_frame, text="启动浏览器", command=self.start_browser).pack(side='left', padx=5)
|
||||
ttk.Button(browser_frame, text="打开文档", command=self.open_document).pack(side='left', padx=5)
|
||||
ttk.Button(browser_frame, text="关闭浏览器", command=self.close_browser).pack(side='left', padx=5)
|
||||
|
||||
# 测试步骤区域
|
||||
test_frame = ttk.LabelFrame(self.root, text="测试步骤", padding=10)
|
||||
test_frame.pack(fill='both', expand=True, padx=10, pady=5)
|
||||
|
||||
# 左侧:操作按钮
|
||||
left_frame = ttk.Frame(test_frame)
|
||||
left_frame.pack(side='left', fill='y', padx=10)
|
||||
|
||||
test_steps = [
|
||||
("1. 测试浏览器连接", self.test_browser_connection),
|
||||
("2. 测试文档打开", self.test_document_open),
|
||||
("3. 测试表格读取", self.test_table_reading),
|
||||
("4. 测试人员搜索", self.test_person_search),
|
||||
("5. 测试图片上传(单步)", self.test_image_upload_single),
|
||||
("6. 完整流程测试", self.test_complete_flow),
|
||||
]
|
||||
|
||||
for text, command in test_steps:
|
||||
btn = ttk.Button(left_frame, text=text, command=command, width=25)
|
||||
btn.pack(pady=5)
|
||||
|
||||
# 右侧:操作详情和确认
|
||||
right_frame = ttk.Frame(test_frame)
|
||||
right_frame.pack(side='left', fill='both', expand=True, padx=10)
|
||||
|
||||
ttk.Label(right_frame, text="当前操作:", font=('Arial', 10, 'bold')).pack(anchor='w')
|
||||
self.operation_label = tk.Label(right_frame, text="等待操作...", bg='white', height=3, relief='sunken', anchor='w')
|
||||
self.operation_label.pack(fill='x', pady=5)
|
||||
|
||||
# 确认按钮区域
|
||||
confirm_frame = ttk.Frame(right_frame)
|
||||
confirm_frame.pack(fill='x', pady=10)
|
||||
|
||||
self.confirm_button = ttk.Button(confirm_frame, text="确认执行", command=self.execute_operation, state='disabled')
|
||||
self.confirm_button.pack(side='left', padx=5)
|
||||
|
||||
ttk.Button(confirm_frame, text="取消", command=self.cancel_operation).pack(side='left', padx=5)
|
||||
|
||||
# 日志区域
|
||||
log_frame = ttk.LabelFrame(self.root, text="操作日志", padding=10)
|
||||
log_frame.pack(fill='both', expand=False, padx=10, pady=5)
|
||||
|
||||
# 创建文本框和滚动条
|
||||
text_frame = ttk.Frame(log_frame)
|
||||
text_frame.pack(fill='both', expand=True)
|
||||
|
||||
self.log_text = tk.Text(text_frame, height=10, wrap='word')
|
||||
scrollbar = ttk.Scrollbar(text_frame, orient='vertical', command=self.log_text.yview)
|
||||
self.log_text.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
self.log_text.pack(side='left', fill='both', expand=True)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
def log(self, message, level='INFO'):
|
||||
"""添加日志"""
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
log_entry = f"[{timestamp}] {level}: {message}\n"
|
||||
|
||||
# 颜色标记
|
||||
if level == 'ERROR':
|
||||
tag = 'error'
|
||||
color = 'red'
|
||||
elif level == 'WARNING':
|
||||
tag = 'warning'
|
||||
color = 'orange'
|
||||
elif level == 'SUCCESS':
|
||||
tag = 'success'
|
||||
color = 'green'
|
||||
else:
|
||||
tag = 'normal'
|
||||
color = 'black'
|
||||
|
||||
self.log_text.insert('end', log_entry, tag)
|
||||
self.log_text.see('end')
|
||||
|
||||
# 配置标签颜色
|
||||
self.log_text.tag_config(tag, foreground=color)
|
||||
|
||||
# 打印到控制台
|
||||
print(log_entry.strip())
|
||||
|
||||
def show_operation(self, operation_text: str, callback: Callable):
|
||||
"""显示操作详情,等待用户确认"""
|
||||
self.operation_label.config(text=operation_text)
|
||||
self.pending_operation = callback
|
||||
self.confirm_button.config(state='normal')
|
||||
|
||||
def execute_operation(self):
|
||||
"""执行待处理的操作"""
|
||||
if hasattr(self, 'pending_operation'):
|
||||
self.confirm_button.config(state='disabled')
|
||||
self.is_running = True
|
||||
|
||||
def run():
|
||||
try:
|
||||
self.pending_operation()
|
||||
except Exception as e:
|
||||
self.log(f"操作失败: {str(e)}", 'ERROR')
|
||||
finally:
|
||||
self.is_running = False
|
||||
self.operation_label.config(text="等待操作...")
|
||||
self.pending_operation = None
|
||||
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
|
||||
def cancel_operation(self):
|
||||
"""取消待处理的操作"""
|
||||
self.confirm_button.config(state='disabled')
|
||||
self.operation_label.config(text="操作已取消")
|
||||
self.pending_operation = None
|
||||
self.log("操作已取消", 'WARNING')
|
||||
|
||||
# ==================== 浏览器操作 ====================
|
||||
|
||||
def start_browser(self):
|
||||
"""启动浏览器"""
|
||||
def operation():
|
||||
self.log("正在启动浏览器...", 'INFO')
|
||||
try:
|
||||
self.playwright = sync_playwright().start()
|
||||
self.browser = self.playwright.chromium.launch(headless=False) # 显示浏览器便于调试
|
||||
self.context = self.browser.new_context()
|
||||
self.page = self.context.new_page()
|
||||
self.page.set_default_timeout(30000)
|
||||
self.log("[OK] 浏览器启动成功", 'SUCCESS')
|
||||
except Exception as e:
|
||||
self.log(f"✗ 浏览器启动失败: {str(e)}", 'ERROR')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:启动浏览器\n"
|
||||
"说明:使用Playwright启动Chromium浏览器\n"
|
||||
"安全:这是安全的操作,不会影响任何数据",
|
||||
operation
|
||||
)
|
||||
|
||||
def open_document(self):
|
||||
"""打开文档"""
|
||||
def operation():
|
||||
if not self.page:
|
||||
self.log("请先启动浏览器", 'ERROR')
|
||||
return
|
||||
|
||||
doc_url = self.doc_url.get()
|
||||
if not doc_url or "your-doc-id" in doc_url:
|
||||
self.log("请先配置正确的金山文档URL", 'ERROR')
|
||||
return
|
||||
|
||||
self.log(f"正在打开文档: {doc_url}", 'INFO')
|
||||
|
||||
try:
|
||||
self.page.goto(doc_url, wait_until='domcontentloaded')
|
||||
self.page.wait_for_timeout(3000)
|
||||
self.log("[OK] 文档打开成功", 'SUCCESS')
|
||||
except Exception as e:
|
||||
self.log(f"✗ 文档打开失败: {str(e)}", 'ERROR')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:打开金山文档\n"
|
||||
"说明:导航到配置的金山文档URL\n"
|
||||
"安全:这是安全的操作,仅读取文档",
|
||||
operation
|
||||
)
|
||||
|
||||
def close_browser(self):
|
||||
"""关闭浏览器"""
|
||||
def operation():
|
||||
self.log("正在关闭浏览器...", 'INFO')
|
||||
try:
|
||||
if self.page:
|
||||
self.page.close()
|
||||
if self.context:
|
||||
self.context.close()
|
||||
if self.browser:
|
||||
self.browser.close()
|
||||
if self.playwright:
|
||||
self.playwright.stop()
|
||||
|
||||
self.page = None
|
||||
self.context = None
|
||||
self.browser = None
|
||||
self.playwright = None
|
||||
self.log("[OK] 浏览器已关闭", 'SUCCESS')
|
||||
except Exception as e:
|
||||
self.log(f"✗ 关闭浏览器失败: {str(e)}", 'ERROR')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:关闭浏览器\n"
|
||||
"说明:关闭所有浏览器实例和上下文\n"
|
||||
"安全:这是安全的操作",
|
||||
operation
|
||||
)
|
||||
|
||||
# ==================== 测试步骤 ====================
|
||||
|
||||
def test_browser_connection(self):
|
||||
"""测试浏览器连接"""
|
||||
def operation():
|
||||
self.log("开始测试浏览器连接...", 'INFO')
|
||||
|
||||
if not self.page:
|
||||
self.log("浏览器未启动,请先点击'启动浏览器'", 'ERROR')
|
||||
return
|
||||
|
||||
self.log("[OK] 浏览器连接正常", 'SUCCESS')
|
||||
self.log("[OK] 页面对象可用", 'SUCCESS')
|
||||
self.log("浏览器连接测试通过", 'SUCCESS')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试浏览器连接\n"
|
||||
"说明:检查浏览器和页面对象是否正常\n"
|
||||
"安全:这是安全的检查操作",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_document_open(self):
|
||||
"""测试文档打开"""
|
||||
def operation():
|
||||
self.log("开始测试文档打开...", 'INFO')
|
||||
|
||||
if not self.page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 获取当前URL
|
||||
try:
|
||||
current_url = self.page.url
|
||||
self.log(f"当前页面URL: {current_url}", 'INFO')
|
||||
|
||||
# 检查是否在金山文档域名
|
||||
if "kdocs.cn" in current_url:
|
||||
self.log("[OK] 已在金山文档域名", 'SUCCESS')
|
||||
else:
|
||||
self.log("当前不在金山文档域名", 'WARNING')
|
||||
|
||||
# 检查是否有登录提示
|
||||
try:
|
||||
login_text = self.page.locator("text=登录").first.is_visible()
|
||||
if login_text:
|
||||
self.log("检测到登录页面", 'WARNING')
|
||||
else:
|
||||
self.log("未检测到登录页面", 'INFO')
|
||||
except:
|
||||
pass
|
||||
|
||||
self.log("文档打开测试完成", 'SUCCESS')
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试文档打开\n"
|
||||
"说明:检查当前页面状态和URL\n"
|
||||
"安全:这是安全的检查操作",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_table_reading(self):
|
||||
"""测试表格读取"""
|
||||
def operation():
|
||||
self.log("开始测试表格读取...", 'INFO')
|
||||
|
||||
if not self.page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 测试读取A1单元格
|
||||
try:
|
||||
# 尝试点击A1单元格
|
||||
self.log("尝试导航到A1单元格...", 'INFO')
|
||||
|
||||
# 查找表格元素
|
||||
canvas_count = self.page.locator("canvas").count()
|
||||
self.log(f"检测到 {canvas_count} 个canvas元素(可能是表格)", 'INFO')
|
||||
|
||||
# 尝试读取名称框
|
||||
try:
|
||||
name_box = self.page.locator("input.edit-box").first
|
||||
if name_box.is_visible():
|
||||
value = name_box.input_value()
|
||||
self.log(f"名称框当前值: {value}", 'INFO')
|
||||
else:
|
||||
self.log("名称框不可见", 'INFO')
|
||||
except Exception as e:
|
||||
self.log(f"读取名称框失败: {str(e)}", 'WARNING')
|
||||
|
||||
self.log("[OK] 表格读取测试完成", 'SUCCESS')
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试表格读取\n"
|
||||
"说明:尝试读取表格元素和单元格\n"
|
||||
"安全:这是安全的只读操作,不会修改任何数据",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_person_search(self):
|
||||
"""测试人员搜索"""
|
||||
def operation():
|
||||
self.log("开始测试人员搜索...", 'INFO')
|
||||
|
||||
if not self.page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 提示用户输入要搜索的姓名
|
||||
test_name = "张三" # 默认测试名称
|
||||
|
||||
self.log(f"搜索测试姓名: {test_name}", 'INFO')
|
||||
|
||||
try:
|
||||
# 点击网格聚焦
|
||||
self.log("聚焦到网格...", 'INFO')
|
||||
|
||||
# 打开搜索框
|
||||
self.log("打开搜索框 (Ctrl+F)...", 'INFO')
|
||||
self.page.keyboard.press("Control+f")
|
||||
self.page.wait_for_timeout(500)
|
||||
|
||||
# 输入搜索内容
|
||||
self.log(f"输入搜索内容: {test_name}", 'INFO')
|
||||
self.page.keyboard.type(test_name)
|
||||
self.page.wait_for_timeout(300)
|
||||
|
||||
# 按回车搜索
|
||||
self.log("执行搜索 (Enter)...", 'INFO')
|
||||
self.page.keyboard.press("Enter")
|
||||
self.page.wait_for_timeout(1000)
|
||||
|
||||
# 关闭搜索
|
||||
self.page.keyboard.press("Escape")
|
||||
self.page.wait_for_timeout(300)
|
||||
|
||||
self.log("[OK] 人员搜索测试完成", 'SUCCESS')
|
||||
self.log("注意:请检查浏览器窗口,看是否高亮显示了相关内容", 'INFO')
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 搜索测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试人员搜索\n"
|
||||
"说明:执行 Ctrl+F 搜索操作\n"
|
||||
"⚠️ 安全:这是安全的搜索操作,不会修改数据\n"
|
||||
"测试内容:搜索默认姓名'张三'",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_image_upload_single(self):
|
||||
"""测试图片上传(单步)"""
|
||||
def operation():
|
||||
self.log("开始测试图片上传(单步)...", 'INFO')
|
||||
|
||||
if not self.page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 让用户选择图片文件
|
||||
image_path = filedialog.askopenfilename(
|
||||
title="选择测试图片",
|
||||
filetypes=[("图片文件", "*.jpg *.jpeg *.png *.gif")]
|
||||
)
|
||||
|
||||
if not image_path:
|
||||
self.log("未选择图片文件,操作取消", 'WARNING')
|
||||
return
|
||||
|
||||
self.log(f"选择的图片: {image_path}", 'INFO')
|
||||
|
||||
try:
|
||||
# 1. 导航到测试单元格
|
||||
self.log("导航到 D3 单元格...", 'INFO')
|
||||
name_box = self.page.locator("input.edit-box").first
|
||||
name_box.click()
|
||||
name_box.fill("D3")
|
||||
name_box.press("Enter")
|
||||
self.page.wait_for_timeout(500)
|
||||
|
||||
# 2. 点击插入菜单
|
||||
self.log("点击插入按钮...", 'INFO')
|
||||
insert_btn = self.page.locator("text=插入").first
|
||||
insert_btn.click()
|
||||
self.page.wait_for_timeout(500)
|
||||
|
||||
# 3. 点击图片选项
|
||||
self.log("点击图片选项...", 'INFO')
|
||||
image_btn = self.page.locator("text=图片").first
|
||||
image_btn.click()
|
||||
self.page.wait_for_timeout(500)
|
||||
|
||||
# 4. 选择本地图片
|
||||
self.log("选择本地图片...", 'INFO')
|
||||
local_option = self.page.locator("text=本地").first
|
||||
local_option.click()
|
||||
|
||||
# 5. 上传文件
|
||||
with self.page.expect_file_chooser() as fc_info:
|
||||
pass # 触发文件选择器
|
||||
|
||||
file_chooser = fc_info.value
|
||||
file_chooser.set_files(image_path)
|
||||
|
||||
self.log("[OK] 图片上传测试完成", 'SUCCESS')
|
||||
self.log("请检查浏览器窗口,看图片是否上传成功", 'INFO')
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 图片上传测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试图片上传(单步)\n"
|
||||
"⚠️ 警告:此操作会上传图片到D3单元格\n"
|
||||
"⚠️ 安全:仅影响单个单元格,不会有批量操作\n"
|
||||
"操作流程:\n"
|
||||
"1. 导航到D3单元格\n"
|
||||
"2. 点击插入 → 图片 → 本地\n"
|
||||
"3. 上传用户选择的图片文件\n"
|
||||
"请选择一个小图片文件进行测试",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_complete_flow(self):
|
||||
"""完整流程测试"""
|
||||
def operation():
|
||||
self.log("=" * 50)
|
||||
self.log("开始完整流程测试", 'INFO')
|
||||
self.log("=" * 50)
|
||||
|
||||
if not self.page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 这里可以添加完整的测试流程
|
||||
# 包括:打开文档 → 搜索 → 验证 → 上传 → 验证
|
||||
# 每一步都要有确认机制
|
||||
|
||||
self.log("完整流程测试完成", 'SUCCESS')
|
||||
self.log("=" * 50)
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:完整流程测试\n"
|
||||
"⚠️ 警告:这是完整的上传流程测试\n"
|
||||
"说明:执行完整的图片上传操作\n"
|
||||
"⚠️ 安全:会实际执行上传,请确保选择了正确的测试图片\n"
|
||||
"操作包括:\n"
|
||||
"1. 定位人员位置\n"
|
||||
"2. 上传截图\n"
|
||||
"3. 验证结果",
|
||||
operation
|
||||
)
|
||||
|
||||
def run(self):
|
||||
"""启动GUI"""
|
||||
self.log("安全测试工具已启动", 'INFO')
|
||||
self.log("请按照以下步骤操作:", 'INFO')
|
||||
self.log("1. 点击'启动浏览器' → 2. 点击'打开文档' → 3. 执行各项测试", 'INFO')
|
||||
self.log("每一步操作都需要您手动确认", 'WARNING')
|
||||
self.root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tool = SafetyTestTool()
|
||||
tool.run()
|
||||
@@ -1,641 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
金山文档上传安全测试工具 - 线程安全版本
|
||||
修复浏览器多线程访问问题
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Optional, Callable
|
||||
|
||||
# 添加项目路径
|
||||
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)
|
||||
|
||||
|
||||
class ThreadSafeBrowser:
|
||||
"""线程安全的浏览器管理器"""
|
||||
|
||||
def __init__(self):
|
||||
self.playwright = None
|
||||
self.browser = None
|
||||
self.context = None
|
||||
self.page = None
|
||||
self._lock = threading.Lock()
|
||||
self._initialized = False
|
||||
|
||||
def initialize(self, headless=False):
|
||||
"""初始化浏览器(线程安全)"""
|
||||
with self._lock:
|
||||
if self._initialized:
|
||||
return True
|
||||
|
||||
try:
|
||||
self.playwright = sync_playwright().start()
|
||||
self.browser = self.playwright.chromium.launch(headless=headless)
|
||||
self.context = self.browser.new_context()
|
||||
self.page = self.context.new_page()
|
||||
self.page.set_default_timeout(30000)
|
||||
self._initialized = True
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"初始化浏览器失败: {e}")
|
||||
self._cleanup()
|
||||
return False
|
||||
|
||||
def get_page(self):
|
||||
"""获取页面对象(线程安全)"""
|
||||
with self._lock:
|
||||
if not self._initialized or not self.page:
|
||||
return None
|
||||
return self.page
|
||||
|
||||
def close(self):
|
||||
"""关闭浏览器(线程安全)"""
|
||||
with self._lock:
|
||||
try:
|
||||
if self.page:
|
||||
self.page.close()
|
||||
if self.context:
|
||||
self.context.close()
|
||||
if self.browser:
|
||||
self.browser.close()
|
||||
if self.playwright:
|
||||
self.playwright.stop()
|
||||
except Exception as e:
|
||||
print(f"关闭浏览器时出错: {e}")
|
||||
finally:
|
||||
self._initialized = False
|
||||
self.page = None
|
||||
self.context = None
|
||||
self.browser = None
|
||||
self.playwright = None
|
||||
|
||||
|
||||
class SafetyTestToolFixed:
|
||||
def __init__(self):
|
||||
self.root = tk.Tk()
|
||||
self.root.title("金山文档上传安全测试工具 v1.1 - 线程安全版")
|
||||
self.root.geometry("1000x700")
|
||||
self.root.configure(bg='#f0f0f0')
|
||||
|
||||
# 使用线程安全的浏览器管理器
|
||||
self.browser_manager = ThreadSafeBrowser()
|
||||
|
||||
# 状态变量
|
||||
self.doc_url = tk.StringVar(value="https://kdocs.cn/l/cpwEOo5ynKX4") # 使用用户提供的URL
|
||||
self.is_running = False
|
||||
self.test_results = []
|
||||
|
||||
# 创建界面
|
||||
self.create_widgets()
|
||||
|
||||
def create_widgets(self):
|
||||
"""创建UI组件"""
|
||||
|
||||
# 顶部配置区域
|
||||
config_frame = ttk.LabelFrame(self.root, text="连接配置", padding=10)
|
||||
config_frame.pack(fill='x', padx=10, pady=5)
|
||||
|
||||
ttk.Label(config_frame, text="金山文档URL:").grid(row=0, column=0, sticky='w', padx=5, pady=2)
|
||||
ttk.Entry(config_frame, textvariable=self.doc_url, width=80).grid(row=0, column=1, padx=5, pady=2)
|
||||
|
||||
# 浏览器控制按钮
|
||||
browser_frame = ttk.Frame(config_frame)
|
||||
browser_frame.grid(row=0, column=2, padx=10)
|
||||
|
||||
ttk.Button(browser_frame, text="启动浏览器", command=self.start_browser).pack(side='left', padx=5)
|
||||
ttk.Button(browser_frame, text="打开文档", command=self.open_document).pack(side='left', padx=5)
|
||||
ttk.Button(browser_frame, text="关闭浏览器", command=self.close_browser).pack(side='left', padx=5)
|
||||
|
||||
# 状态显示
|
||||
status_frame = ttk.Frame(config_frame)
|
||||
status_frame.grid(row=1, column=0, columnspan=3, sticky='ew', padx=5, pady=5)
|
||||
|
||||
self.status_label = tk.Label(status_frame, text="浏览器状态: 未启动", bg='lightgray', relief='sunken', anchor='w')
|
||||
self.status_label.pack(fill='x')
|
||||
|
||||
# 测试步骤区域
|
||||
test_frame = ttk.LabelFrame(self.root, text="测试步骤", padding=10)
|
||||
test_frame.pack(fill='both', expand=True, padx=10, pady=5)
|
||||
|
||||
# 左侧:操作按钮
|
||||
left_frame = ttk.Frame(test_frame)
|
||||
left_frame.pack(side='left', fill='y', padx=10)
|
||||
|
||||
test_steps = [
|
||||
("1. 测试浏览器连接", self.test_browser_connection),
|
||||
("2. 测试文档打开", self.test_document_open),
|
||||
("3. 测试表格读取", self.test_table_reading),
|
||||
("4. 测试人员搜索", self.test_person_search),
|
||||
("5. 测试图片上传(单步)", self.test_image_upload_single),
|
||||
("6. 完整流程测试", self.test_complete_flow),
|
||||
]
|
||||
|
||||
for text, command in test_steps:
|
||||
btn = ttk.Button(left_frame, text=text, command=command, width=25)
|
||||
btn.pack(pady=5)
|
||||
|
||||
# 右侧:操作详情和确认
|
||||
right_frame = ttk.Frame(test_frame)
|
||||
right_frame.pack(side='left', fill='both', expand=True, padx=10)
|
||||
|
||||
ttk.Label(right_frame, text="当前操作:", font=('Arial', 10, 'bold')).pack(anchor='w')
|
||||
self.operation_label = tk.Label(right_frame, text="等待操作...", bg='white', height=3, relief='sunken', anchor='w')
|
||||
self.operation_label.pack(fill='x', pady=5)
|
||||
|
||||
# 确认按钮区域
|
||||
confirm_frame = ttk.Frame(right_frame)
|
||||
confirm_frame.pack(fill='x', pady=10)
|
||||
|
||||
self.confirm_button = ttk.Button(confirm_frame, text="确认执行", command=self.execute_operation, state='disabled')
|
||||
self.confirm_button.pack(side='left', padx=5)
|
||||
|
||||
ttk.Button(confirm_frame, text="取消", command=self.cancel_operation).pack(side='left', padx=5)
|
||||
|
||||
# 日志区域
|
||||
log_frame = ttk.LabelFrame(self.root, text="操作日志", padding=10)
|
||||
log_frame.pack(fill='both', expand=False, padx=10, pady=5)
|
||||
|
||||
# 创建文本框和滚动条
|
||||
text_frame = ttk.Frame(log_frame)
|
||||
text_frame.pack(fill='both', expand=True)
|
||||
|
||||
self.log_text = tk.Text(text_frame, height=10, wrap='word')
|
||||
scrollbar = ttk.Scrollbar(text_frame, orient='vertical', command=self.log_text.yview)
|
||||
self.log_text.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
self.log_text.pack(side='left', fill='both', expand=True)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
def log(self, message, level='INFO'):
|
||||
"""添加日志"""
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
log_entry = f"[{timestamp}] {level}: {message}\n"
|
||||
|
||||
# 颜色标记
|
||||
if level == 'ERROR':
|
||||
tag = 'error'
|
||||
color = 'red'
|
||||
elif level == 'WARNING':
|
||||
tag = 'warning'
|
||||
color = 'orange'
|
||||
elif level == 'SUCCESS':
|
||||
tag = 'success'
|
||||
color = 'green'
|
||||
else:
|
||||
tag = 'normal'
|
||||
color = 'black'
|
||||
|
||||
self.log_text.insert('end', log_entry, tag)
|
||||
self.log_text.see('end')
|
||||
|
||||
# 配置标签颜色
|
||||
self.log_text.tag_config(tag, foreground=color)
|
||||
|
||||
# 打印到控制台
|
||||
print(log_entry.strip())
|
||||
|
||||
def update_status(self, status_text):
|
||||
"""更新状态显示"""
|
||||
self.status_label.config(text=f"浏览器状态: {status_text}")
|
||||
# 颜色编码
|
||||
if "运行" in status_text or "就绪" in status_text:
|
||||
self.status_label.config(bg='lightgreen')
|
||||
elif "错误" in status_text or "失败" in status_text:
|
||||
self.status_label.config(bg='lightcoral')
|
||||
else:
|
||||
self.status_label.config(bg='lightgray')
|
||||
|
||||
def show_operation(self, operation_text: str, callback: Callable):
|
||||
"""显示操作详情,等待用户确认"""
|
||||
self.operation_label.config(text=operation_text)
|
||||
self.pending_operation = callback
|
||||
self.confirm_button.config(state='normal')
|
||||
|
||||
def execute_operation(self):
|
||||
"""执行待处理的操作"""
|
||||
if hasattr(self, 'pending_operation'):
|
||||
self.confirm_button.config(state='disabled')
|
||||
self.is_running = True
|
||||
|
||||
def run():
|
||||
try:
|
||||
self.pending_operation()
|
||||
except Exception as e:
|
||||
self.log(f"操作失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
self.is_running = False
|
||||
self.operation_label.config(text="等待操作...")
|
||||
self.pending_operation = None
|
||||
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
|
||||
def cancel_operation(self):
|
||||
"""取消待处理的操作"""
|
||||
self.confirm_button.config(state='disabled')
|
||||
self.operation_label.config(text="操作已取消")
|
||||
self.pending_operation = None
|
||||
self.log("操作已取消", 'WARNING')
|
||||
|
||||
# ==================== 浏览器操作 ====================
|
||||
|
||||
def start_browser(self):
|
||||
"""启动浏览器"""
|
||||
def operation():
|
||||
self.log("正在启动浏览器...", 'INFO')
|
||||
self.update_status("启动中...")
|
||||
|
||||
try:
|
||||
# 使用线程安全的方式启动
|
||||
success = self.browser_manager.initialize(headless=False)
|
||||
if success:
|
||||
self.log("[OK] 浏览器启动成功", 'SUCCESS')
|
||||
self.update_status("运行中 (就绪)")
|
||||
else:
|
||||
self.log("✗ 浏览器启动失败", 'ERROR')
|
||||
self.update_status("启动失败")
|
||||
except Exception as e:
|
||||
self.log(f"✗ 浏览器启动失败: {str(e)}", 'ERROR')
|
||||
self.update_status("启动失败")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:启动浏览器\n"
|
||||
"说明:使用Playwright启动Chromium浏览器\n"
|
||||
"安全:这是安全的操作,不会影响任何数据",
|
||||
operation
|
||||
)
|
||||
|
||||
def open_document(self):
|
||||
"""打开文档"""
|
||||
def operation():
|
||||
if not self.browser_manager.get_page():
|
||||
self.log("请先启动浏览器", 'ERROR')
|
||||
self.update_status("错误: 未启动")
|
||||
return
|
||||
|
||||
doc_url = self.doc_url.get()
|
||||
if not doc_url or "your-doc-id" in doc_url:
|
||||
self.log("请先配置正确的金山文档URL", 'ERROR')
|
||||
self.update_status("错误: URL未配置")
|
||||
return
|
||||
|
||||
self.log(f"正在打开文档: {doc_url}", 'INFO')
|
||||
self.update_status(f"打开文档中: {doc_url}")
|
||||
|
||||
try:
|
||||
page = self.browser_manager.get_page()
|
||||
if not page:
|
||||
self.log("页面对象不可用", 'ERROR')
|
||||
self.update_status("错误: 页面对象不可用")
|
||||
return
|
||||
|
||||
page.goto(doc_url, wait_until='domcontentloaded')
|
||||
page.wait_for_timeout(3000)
|
||||
|
||||
self.log("[OK] 文档打开成功", 'SUCCESS')
|
||||
self.update_status("运行中 (文档已打开)")
|
||||
except Exception as e:
|
||||
self.log(f"✗ 文档打开失败: {str(e)}", 'ERROR')
|
||||
self.update_status("打开文档失败")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:打开金山文档\n"
|
||||
"说明:导航到配置的金山文档URL\n"
|
||||
"安全:这是安全的操作,仅读取文档",
|
||||
operation
|
||||
)
|
||||
|
||||
def close_browser(self):
|
||||
"""关闭浏览器"""
|
||||
def operation():
|
||||
self.log("正在关闭浏览器...", 'INFO')
|
||||
self.update_status("关闭中...")
|
||||
|
||||
try:
|
||||
self.browser_manager.close()
|
||||
self.log("[OK] 浏览器已关闭", 'SUCCESS')
|
||||
self.update_status("已关闭")
|
||||
except Exception as e:
|
||||
self.log(f"✗ 关闭浏览器失败: {str(e)}", 'ERROR')
|
||||
self.update_status("关闭失败")
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:关闭浏览器\n"
|
||||
"说明:关闭所有浏览器实例和上下文\n"
|
||||
"安全:这是安全的操作",
|
||||
operation
|
||||
)
|
||||
|
||||
# ==================== 测试步骤 ====================
|
||||
|
||||
def test_browser_connection(self):
|
||||
"""测试浏览器连接"""
|
||||
def operation():
|
||||
self.log("开始测试浏览器连接...", 'INFO')
|
||||
|
||||
page = self.browser_manager.get_page()
|
||||
if not page:
|
||||
self.log("浏览器未启动,请先点击'启动浏览器'", 'ERROR')
|
||||
self.update_status("错误: 未启动")
|
||||
return
|
||||
|
||||
self.log("[OK] 浏览器连接正常", 'SUCCESS')
|
||||
self.log("[OK] 页面对象可用", 'SUCCESS')
|
||||
self.log("浏览器连接测试通过", 'SUCCESS')
|
||||
self.update_status("运行中 (连接正常)")
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试浏览器连接\n"
|
||||
"说明:检查浏览器和页面对象是否正常\n"
|
||||
"安全:这是安全的检查操作",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_document_open(self):
|
||||
"""测试文档打开"""
|
||||
def operation():
|
||||
self.log("开始测试文档打开...", 'INFO')
|
||||
|
||||
page = self.browser_manager.get_page()
|
||||
if not page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 获取当前URL
|
||||
try:
|
||||
current_url = page.url
|
||||
self.log(f"当前页面URL: {current_url}", 'INFO')
|
||||
|
||||
# 检查是否在金山文档域名
|
||||
if "kdocs.cn" in current_url:
|
||||
self.log("[OK] 已在金山文档域名", 'SUCCESS')
|
||||
else:
|
||||
self.log("当前不在金山文档域名", 'WARNING')
|
||||
|
||||
# 检查是否有登录提示
|
||||
try:
|
||||
login_text = page.locator("text=登录").first.is_visible()
|
||||
if login_text:
|
||||
self.log("检测到登录页面", 'WARNING')
|
||||
self.update_status("需要登录")
|
||||
else:
|
||||
self.log("未检测到登录页面", 'INFO')
|
||||
self.update_status("运行中 (文档已打开)")
|
||||
except:
|
||||
pass
|
||||
|
||||
self.log("文档打开测试完成", 'SUCCESS')
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 测试失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试文档打开\n"
|
||||
"说明:检查当前页面状态和URL\n"
|
||||
"安全:这是安全的检查操作",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_table_reading(self):
|
||||
"""测试表格读取"""
|
||||
def operation():
|
||||
self.log("开始测试表格读取...", 'INFO')
|
||||
|
||||
page = self.browser_manager.get_page()
|
||||
if not page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 测试读取A1单元格
|
||||
try:
|
||||
# 尝试点击A1单元格
|
||||
self.log("尝试导航到A1单元格...", 'INFO')
|
||||
|
||||
# 查找表格元素
|
||||
canvas_count = page.locator("canvas").count()
|
||||
self.log(f"检测到 {canvas_count} 个canvas元素(可能是表格)", 'INFO')
|
||||
|
||||
# 尝试读取名称框
|
||||
try:
|
||||
name_box = page.locator("input.edit-box").first
|
||||
if name_box.is_visible():
|
||||
value = name_box.input_value()
|
||||
self.log(f"名称框当前值: {value}", 'INFO')
|
||||
else:
|
||||
self.log("名称框不可见", 'INFO')
|
||||
except Exception as e:
|
||||
self.log(f"读取名称框失败: {str(e)}", 'WARNING')
|
||||
|
||||
self.log("[OK] 表格读取测试完成", 'SUCCESS')
|
||||
self.update_status("运行中 (表格可读取)")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 测试失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试表格读取\n"
|
||||
"说明:尝试读取表格元素和单元格\n"
|
||||
"安全:这是安全的只读操作,不会修改任何数据",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_person_search(self):
|
||||
"""测试人员搜索"""
|
||||
def operation():
|
||||
self.log("开始测试人员搜索...", 'INFO')
|
||||
|
||||
page = self.browser_manager.get_page()
|
||||
if not page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 提示用户输入要搜索的姓名
|
||||
test_name = "张三" # 默认测试名称
|
||||
|
||||
self.log(f"搜索测试姓名: {test_name}", 'INFO')
|
||||
|
||||
try:
|
||||
# 点击网格聚焦
|
||||
self.log("聚焦到网格...", 'INFO')
|
||||
|
||||
# 打开搜索框
|
||||
self.log("打开搜索框 (Ctrl+F)...", 'INFO')
|
||||
page.keyboard.press("Control+f")
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# 输入搜索内容
|
||||
self.log(f"输入搜索内容: {test_name}", 'INFO')
|
||||
page.keyboard.type(test_name)
|
||||
page.wait_for_timeout(300)
|
||||
|
||||
# 按回车搜索
|
||||
self.log("执行搜索 (Enter)...", 'INFO')
|
||||
page.keyboard.press("Enter")
|
||||
page.wait_for_timeout(1000)
|
||||
|
||||
# 关闭搜索
|
||||
page.keyboard.press("Escape")
|
||||
page.wait_for_timeout(300)
|
||||
|
||||
self.log("[OK] 人员搜索测试完成", 'SUCCESS')
|
||||
self.log("注意:请检查浏览器窗口,看是否高亮显示了相关内容", 'INFO')
|
||||
self.update_status("运行中 (搜索功能正常)")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 搜索测试失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试人员搜索\n"
|
||||
"说明:执行 Ctrl+F 搜索操作\n"
|
||||
"⚠️ 安全:这是安全的搜索操作,不会修改数据\n"
|
||||
"测试内容:搜索默认姓名'张三'",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_image_upload_single(self):
|
||||
"""测试图片上传(单步)"""
|
||||
def operation():
|
||||
self.log("开始测试图片上传(单步)...", 'INFO')
|
||||
|
||||
page = self.browser_manager.get_page()
|
||||
if not page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 让用户选择图片文件
|
||||
image_path = filedialog.askopenfilename(
|
||||
title="选择测试图片",
|
||||
filetypes=[("图片文件", "*.jpg *.jpeg *.png *.gif")]
|
||||
)
|
||||
|
||||
if not image_path:
|
||||
self.log("未选择图片文件,操作取消", 'WARNING')
|
||||
return
|
||||
|
||||
self.log(f"选择的图片: {image_path}", 'INFO')
|
||||
|
||||
try:
|
||||
# 1. 导航到测试单元格
|
||||
self.log("导航到 D3 单元格...", 'INFO')
|
||||
name_box = page.locator("input.edit-box").first
|
||||
name_box.click()
|
||||
name_box.fill("D3")
|
||||
name_box.press("Enter")
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# 2. 点击插入菜单
|
||||
self.log("点击插入按钮...", 'INFO')
|
||||
insert_btn = page.locator("text=插入").first
|
||||
insert_btn.click()
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# 3. 点击图片选项
|
||||
self.log("点击图片选项...", 'INFO')
|
||||
image_btn = page.locator("text=图片").first
|
||||
image_btn.click()
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# 4. 选择本地图片
|
||||
self.log("选择本地图片...", 'INFO')
|
||||
local_option = page.locator("text=本地").first
|
||||
local_option.click()
|
||||
|
||||
# 5. 上传文件
|
||||
with page.expect_file_chooser() as fc_info:
|
||||
pass # 触发文件选择器
|
||||
|
||||
file_chooser = fc_info.value
|
||||
file_chooser.set_files(image_path)
|
||||
|
||||
self.log("[OK] 图片上传测试完成", 'SUCCESS')
|
||||
self.log("请检查浏览器窗口,看图片是否上传成功", 'INFO')
|
||||
self.update_status("运行中 (上传测试完成)")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 图片上传测试失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试图片上传(单步)\n"
|
||||
"⚠️ 警告:此操作会上传图片到D3单元格\n"
|
||||
"⚠️ 安全:仅影响单个单元格,不会有批量操作\n"
|
||||
"操作流程:\n"
|
||||
"1. 导航到D3单元格\n"
|
||||
"2. 点击插入 → 图片 → 本地\n"
|
||||
"3. 上传用户选择的图片文件\n"
|
||||
"请选择一个小图片文件进行测试",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_complete_flow(self):
|
||||
"""完整流程测试"""
|
||||
def operation():
|
||||
self.log("=" * 50)
|
||||
self.log("开始完整流程测试", 'INFO')
|
||||
self.log("=" * 50)
|
||||
|
||||
page = self.browser_manager.get_page()
|
||||
if not page:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 这里可以添加完整的测试流程
|
||||
# 包括:打开文档 → 搜索 → 验证 → 上传 → 验证
|
||||
# 每一步都要有确认机制
|
||||
|
||||
self.log("完整流程测试完成", 'SUCCESS')
|
||||
self.log("=" * 50)
|
||||
self.update_status("运行中 (完整测试完成)")
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:完整流程测试\n"
|
||||
"⚠️ 警告:这是完整的上传流程测试\n"
|
||||
"说明:执行完整的图片上传操作\n"
|
||||
"⚠️ 安全:会实际执行上传,请确保选择了正确的测试图片\n"
|
||||
"操作包括:\n"
|
||||
"1. 定位人员位置\n"
|
||||
"2. 上传截图\n"
|
||||
"3. 验证结果",
|
||||
operation
|
||||
)
|
||||
|
||||
def run(self):
|
||||
"""启动GUI"""
|
||||
self.log("安全测试工具已启动", 'INFO')
|
||||
self.log("请按照以下步骤操作:", 'INFO')
|
||||
self.log("1. 点击'启动浏览器' → 2. 点击'打开文档' → 3. 执行各项测试", 'INFO')
|
||||
self.log("每一步操作都需要您手动确认", 'WARNING')
|
||||
self.log("已自动填入您的金山文档URL", 'INFO')
|
||||
self.update_status("就绪")
|
||||
self.root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tool = SafetyTestToolFixed()
|
||||
tool.run()
|
||||
@@ -1,662 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
金山文档上传安全测试工具 - 同步线程版本
|
||||
使用thread-local确保浏览器实例在正确线程中使用
|
||||
"""
|
||||
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, messagebox, filedialog
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from typing import Optional, Callable
|
||||
import uuid
|
||||
|
||||
# 添加项目路径
|
||||
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)
|
||||
|
||||
|
||||
class ThreadLocalBrowser:
|
||||
"""线程本地浏览器管理器 - 确保每个线程使用自己的浏览器实例"""
|
||||
|
||||
_local = threading.local()
|
||||
|
||||
@classmethod
|
||||
def get_instance(cls, thread_id=None):
|
||||
"""获取当前线程的浏览器实例"""
|
||||
if thread_id is None:
|
||||
thread_id = threading.get_ident()
|
||||
|
||||
if not hasattr(cls._local, 'browsers'):
|
||||
cls._local.browsers = {}
|
||||
|
||||
if thread_id not in cls._local.browsers:
|
||||
cls._local.browsers[thread_id] = cls._create_browser()
|
||||
|
||||
return cls._local.browsers[thread_id]
|
||||
|
||||
@classmethod
|
||||
def _create_browser(cls):
|
||||
"""创建新的浏览器实例"""
|
||||
try:
|
||||
playwright = sync_playwright().start()
|
||||
browser = playwright.chromium.launch(headless=False)
|
||||
context = browser.new_context()
|
||||
page = context.new_page()
|
||||
page.set_default_timeout(30000)
|
||||
return {
|
||||
'playwright': playwright,
|
||||
'browser': browser,
|
||||
'context': context,
|
||||
'page': page,
|
||||
'initialized': True
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"创建浏览器实例失败: {e}")
|
||||
return {
|
||||
'playwright': None,
|
||||
'browser': None,
|
||||
'context': None,
|
||||
'page': None,
|
||||
'initialized': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def close_instance(cls, thread_id=None):
|
||||
"""关闭指定线程的浏览器实例"""
|
||||
if thread_id is None:
|
||||
thread_id = threading.get_ident()
|
||||
|
||||
if hasattr(cls._local, 'browsers') and thread_id in cls._local.browsers:
|
||||
instance = cls._local.browsers[thread_id]
|
||||
try:
|
||||
if instance['page']:
|
||||
instance['page'].close()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
if instance['context']:
|
||||
instance['context'].close()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
if instance['browser']:
|
||||
instance['browser'].close()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
if instance['playwright']:
|
||||
instance['playwright'].stop()
|
||||
except:
|
||||
pass
|
||||
del cls._local.browsers[thread_id]
|
||||
|
||||
@classmethod
|
||||
def close_all(cls):
|
||||
"""关闭所有线程的浏览器实例"""
|
||||
if hasattr(cls._local, 'browsers'):
|
||||
thread_ids = list(cls._local.browsers.keys())
|
||||
for thread_id in thread_ids:
|
||||
cls.close_instance(thread_id)
|
||||
|
||||
|
||||
class SyncTestTool:
|
||||
def __init__(self):
|
||||
self.root = tk.Tk()
|
||||
self.root.title("金山文档上传安全测试工具 - 同步线程版")
|
||||
self.root.geometry("1000x700")
|
||||
self.root.configure(bg='#f0f0f0')
|
||||
|
||||
# 状态变量
|
||||
self.doc_url = tk.StringVar(value="https://kdocs.cn/l/cpwEOo5ynKX4")
|
||||
self.is_running = False
|
||||
self.test_results = []
|
||||
|
||||
# 创建界面
|
||||
self.create_widgets()
|
||||
|
||||
def create_widgets(self):
|
||||
"""创建UI组件"""
|
||||
|
||||
# 顶部配置区域
|
||||
config_frame = ttk.LabelFrame(self.root, text="连接配置", padding=10)
|
||||
config_frame.pack(fill='x', padx=10, pady=5)
|
||||
|
||||
ttk.Label(config_frame, text="金山文档URL:").grid(row=0, column=0, sticky='w', padx=5, pady=2)
|
||||
ttk.Entry(config_frame, textvariable=self.doc_url, width=80).grid(row=0, column=1, padx=5, pady=2)
|
||||
|
||||
# 浏览器控制按钮
|
||||
browser_frame = ttk.Frame(config_frame)
|
||||
browser_frame.grid(row=0, column=2, padx=10)
|
||||
|
||||
ttk.Button(browser_frame, text="启动浏览器", command=self.start_browser).pack(side='left', padx=5)
|
||||
ttk.Button(browser_frame, text="打开文档", command=self.open_document).pack(side='left', padx=5)
|
||||
ttk.Button(browser_frame, text="关闭浏览器", command=self.close_browser).pack(side='left', padx=5)
|
||||
|
||||
# 状态显示
|
||||
status_frame = ttk.Frame(config_frame)
|
||||
status_frame.grid(row=1, column=0, columnspan=3, sticky='ew', padx=5, pady=5)
|
||||
|
||||
self.status_label = tk.Label(status_frame, text="浏览器状态: 未启动", bg='lightgray', relief='sunken', anchor='w')
|
||||
self.status_label.pack(fill='x')
|
||||
|
||||
# 测试步骤区域
|
||||
test_frame = ttk.LabelFrame(self.root, text="测试步骤", padding=10)
|
||||
test_frame.pack(fill='both', expand=True, padx=10, pady=5)
|
||||
|
||||
# 左侧:操作按钮
|
||||
left_frame = ttk.Frame(test_frame)
|
||||
left_frame.pack(side='left', fill='y', padx=10)
|
||||
|
||||
test_steps = [
|
||||
("1. 测试浏览器连接", self.test_browser_connection),
|
||||
("2. 测试文档打开", self.test_document_open),
|
||||
("3. 测试表格读取", self.test_table_reading),
|
||||
("4. 测试人员搜索", self.test_person_search),
|
||||
("5. 测试图片上传(单步)", self.test_image_upload_single),
|
||||
("6. 完整流程测试", self.test_complete_flow),
|
||||
]
|
||||
|
||||
for text, command in test_steps:
|
||||
btn = ttk.Button(left_frame, text=text, command=command, width=25)
|
||||
btn.pack(pady=5)
|
||||
|
||||
# 右侧:操作详情和确认
|
||||
right_frame = ttk.Frame(test_frame)
|
||||
right_frame.pack(side='left', fill='both', expand=True, padx=10)
|
||||
|
||||
ttk.Label(right_frame, text="当前操作:", font=('Arial', 10, 'bold')).pack(anchor='w')
|
||||
self.operation_label = tk.Label(right_frame, text="等待操作...", bg='white', height=3, relief='sunken', anchor='w')
|
||||
self.operation_label.pack(fill='x', pady=5)
|
||||
|
||||
# 确认按钮区域
|
||||
confirm_frame = ttk.Frame(right_frame)
|
||||
confirm_frame.pack(fill='x', pady=10)
|
||||
|
||||
self.confirm_button = ttk.Button(confirm_frame, text="确认执行", command=self.execute_operation, state='disabled')
|
||||
self.confirm_button.pack(side='left', padx=5)
|
||||
|
||||
ttk.Button(confirm_frame, text="取消", command=self.cancel_operation).pack(side='left', padx=5)
|
||||
|
||||
# 日志区域
|
||||
log_frame = ttk.LabelFrame(self.root, text="操作日志", padding=10)
|
||||
log_frame.pack(fill='both', expand=False, padx=10, pady=5)
|
||||
|
||||
# 创建文本框和滚动条
|
||||
text_frame = ttk.Frame(log_frame)
|
||||
text_frame.pack(fill='both', expand=True)
|
||||
|
||||
self.log_text = tk.Text(text_frame, height=10, wrap='word')
|
||||
scrollbar = ttk.Scrollbar(text_frame, orient='vertical', command=self.log_text.yview)
|
||||
self.log_text.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
self.log_text.pack(side='left', fill='both', expand=True)
|
||||
scrollbar.pack(side='right', fill='y')
|
||||
|
||||
def log(self, message, level='INFO'):
|
||||
"""添加日志"""
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
log_entry = f"[{timestamp}] {level}: {message}\n"
|
||||
|
||||
# 颜色标记
|
||||
if level == 'ERROR':
|
||||
tag = 'error'
|
||||
color = 'red'
|
||||
elif level == 'WARNING':
|
||||
tag = 'warning'
|
||||
color = 'orange'
|
||||
elif level == 'SUCCESS':
|
||||
tag = 'success'
|
||||
color = 'green'
|
||||
else:
|
||||
tag = 'normal'
|
||||
color = 'black'
|
||||
|
||||
self.log_text.insert('end', log_entry, tag)
|
||||
self.log_text.see('end')
|
||||
|
||||
# 配置标签颜色
|
||||
self.log_text.tag_config(tag, foreground=color)
|
||||
|
||||
# 打印到控制台
|
||||
print(log_entry.strip())
|
||||
|
||||
def update_status(self, status_text):
|
||||
"""更新状态显示"""
|
||||
self.status_label.config(text=f"浏览器状态: {status_text}")
|
||||
# 颜色编码
|
||||
if "运行" in status_text or "就绪" in status_text or "成功" in status_text:
|
||||
self.status_label.config(bg='lightgreen')
|
||||
elif "错误" in status_text or "失败" in status_text:
|
||||
self.status_label.config(bg='lightcoral')
|
||||
else:
|
||||
self.status_label.config(bg='lightgray')
|
||||
|
||||
def show_operation(self, operation_text: str, callback: Callable):
|
||||
"""显示操作详情,等待用户确认"""
|
||||
self.operation_label.config(text=operation_text)
|
||||
self.pending_callback = callback
|
||||
self.confirm_button.config(state='normal')
|
||||
|
||||
def execute_operation(self):
|
||||
"""执行待处理的操作"""
|
||||
if hasattr(self, 'pending_callback'):
|
||||
self.confirm_button.config(state='disabled')
|
||||
self.is_running = True
|
||||
|
||||
def run():
|
||||
try:
|
||||
self.pending_callback()
|
||||
except Exception as e:
|
||||
self.log(f"操作失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
self.is_running = False
|
||||
self.operation_label.config(text="等待操作...")
|
||||
self.pending_callback = None
|
||||
|
||||
threading.Thread(target=run, daemon=True).start()
|
||||
|
||||
def cancel_operation(self):
|
||||
"""取消待处理的操作"""
|
||||
self.confirm_button.config(state='disabled')
|
||||
self.operation_label.config(text="操作已取消")
|
||||
self.pending_callback = None
|
||||
self.log("操作已取消", 'WARNING')
|
||||
|
||||
def get_browser_instance(self):
|
||||
"""获取当前线程的浏览器实例"""
|
||||
return ThreadLocalBrowser.get_instance()
|
||||
|
||||
def start_browser(self):
|
||||
"""启动浏览器"""
|
||||
def operation():
|
||||
thread_id = threading.get_ident()
|
||||
self.log(f"在线程 {thread_id} 中启动浏览器...", 'INFO')
|
||||
self.update_status("启动中...")
|
||||
|
||||
instance = self.get_browser_instance()
|
||||
|
||||
if instance['initialized']:
|
||||
self.log("[OK] 浏览器启动成功", 'SUCCESS')
|
||||
self.update_status("运行中 (就绪)")
|
||||
else:
|
||||
self.log(f"✗ 浏览器启动失败: {instance.get('error', 'Unknown error')}", 'ERROR')
|
||||
self.update_status("启动失败")
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:启动浏览器\n"
|
||||
"说明:使用Playwright启动Chromium浏览器\n"
|
||||
"安全:这是安全的操作,不会影响任何数据",
|
||||
operation
|
||||
)
|
||||
|
||||
def open_document(self):
|
||||
"""打开文档"""
|
||||
def operation():
|
||||
doc_url = self.doc_url.get()
|
||||
if not doc_url or "your-doc-id" in doc_url:
|
||||
self.log("请先配置正确的金山文档URL", 'ERROR')
|
||||
self.update_status("错误: URL未配置")
|
||||
return
|
||||
|
||||
thread_id = threading.get_ident()
|
||||
self.log(f"在线程 {thread_id} 中打开文档...", 'INFO')
|
||||
self.log(f"正在打开文档: {doc_url}", 'INFO')
|
||||
self.update_status("打开文档中...")
|
||||
|
||||
instance = self.get_browser_instance()
|
||||
if not instance['initialized'] or not instance['page']:
|
||||
self.log("浏览器未初始化或页面不可用", 'ERROR')
|
||||
self.update_status("错误: 浏览器未就绪")
|
||||
return
|
||||
|
||||
try:
|
||||
page = instance['page']
|
||||
page.goto(doc_url, wait_until='domcontentloaded')
|
||||
page.wait_for_timeout(3000)
|
||||
|
||||
self.log("[OK] 文档打开成功", 'SUCCESS')
|
||||
self.update_status("运行中 (文档已打开)")
|
||||
except Exception as e:
|
||||
self.log(f"✗ 文档打开失败: {str(e)}", 'ERROR')
|
||||
self.update_status("打开文档失败")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:打开金山文档\n"
|
||||
"说明:导航到配置的金山文档URL\n"
|
||||
"安全:这是安全的操作,仅读取文档",
|
||||
operation
|
||||
)
|
||||
|
||||
def close_browser(self):
|
||||
"""关闭浏览器"""
|
||||
def operation():
|
||||
thread_id = threading.get_ident()
|
||||
self.log(f"在线程 {thread_id} 中关闭浏览器...", 'INFO')
|
||||
self.update_status("关闭中...")
|
||||
|
||||
try:
|
||||
ThreadLocalBrowser.close_instance(thread_id)
|
||||
self.log("[OK] 浏览器已关闭", 'SUCCESS')
|
||||
self.update_status("已关闭")
|
||||
except Exception as e:
|
||||
self.log(f"✗ 关闭浏览器失败: {str(e)}", 'ERROR')
|
||||
self.update_status("关闭失败")
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:关闭浏览器\n"
|
||||
"说明:关闭当前线程的浏览器实例\n"
|
||||
"安全:这是安全的操作",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_browser_connection(self):
|
||||
"""测试浏览器连接"""
|
||||
def operation():
|
||||
thread_id = threading.get_ident()
|
||||
self.log(f"在线程 {thread_id} 中测试浏览器连接...", 'INFO')
|
||||
|
||||
instance = self.get_browser_instance()
|
||||
if not instance['initialized']:
|
||||
self.log("浏览器未启动,请先点击'启动浏览器'", 'ERROR')
|
||||
self.update_status("错误: 未启动")
|
||||
return
|
||||
|
||||
self.log("[OK] 浏览器连接正常", 'SUCCESS')
|
||||
self.log("[OK] 页面对象可用", 'SUCCESS')
|
||||
self.log("浏览器连接测试通过", 'SUCCESS')
|
||||
self.update_status("运行中 (连接正常)")
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试浏览器连接\n"
|
||||
"说明:检查浏览器和页面对象是否正常\n"
|
||||
"安全:这是安全的检查操作",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_document_open(self):
|
||||
"""测试文档打开"""
|
||||
def operation():
|
||||
thread_id = threading.get_ident()
|
||||
self.log(f"在线程 {thread_id} 中测试文档打开...", 'INFO')
|
||||
|
||||
instance = self.get_browser_instance()
|
||||
if not instance['initialized'] or not instance['page']:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
try:
|
||||
page = instance['page']
|
||||
current_url = page.url
|
||||
self.log(f"当前页面URL: {current_url}", 'INFO')
|
||||
|
||||
# 检查是否在金山文档域名
|
||||
if "kdocs.cn" in current_url:
|
||||
self.log("[OK] 已在金山文档域名", 'SUCCESS')
|
||||
else:
|
||||
self.log("当前不在金山文档域名", 'WARNING')
|
||||
|
||||
# 检查是否有登录提示
|
||||
try:
|
||||
login_text = page.locator("text=登录").first.is_visible()
|
||||
if login_text:
|
||||
self.log("检测到登录页面", 'WARNING')
|
||||
self.update_status("需要登录")
|
||||
else:
|
||||
self.log("未检测到登录页面", 'INFO')
|
||||
self.update_status("运行中 (文档已打开)")
|
||||
except:
|
||||
pass
|
||||
|
||||
self.log("文档打开测试完成", 'SUCCESS')
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试文档打开\n"
|
||||
"说明:检查当前页面状态和URL\n"
|
||||
"安全:这是安全的检查操作",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_table_reading(self):
|
||||
"""测试表格读取"""
|
||||
def operation():
|
||||
thread_id = threading.get_ident()
|
||||
self.log(f"在线程 {thread_id} 中测试表格读取...", 'INFO')
|
||||
|
||||
instance = self.get_browser_instance()
|
||||
if not instance['initialized'] or not instance['page']:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
try:
|
||||
page = instance['page']
|
||||
self.log("尝试导航到A1单元格...", 'INFO')
|
||||
|
||||
# 查找表格元素
|
||||
canvas_count = page.locator("canvas").count()
|
||||
self.log(f"检测到 {canvas_count} 个canvas元素(可能是表格)", 'INFO')
|
||||
|
||||
# 尝试读取名称框
|
||||
try:
|
||||
name_box = page.locator("input.edit-box").first
|
||||
if name_box.is_visible():
|
||||
value = name_box.input_value()
|
||||
self.log(f"名称框当前值: {value}", 'INFO')
|
||||
else:
|
||||
self.log("名称框不可见", 'INFO')
|
||||
except Exception as e:
|
||||
self.log(f"读取名称框失败: {str(e)}", 'WARNING')
|
||||
|
||||
self.log("[OK] 表格读取测试完成", 'SUCCESS')
|
||||
self.update_status("运行中 (表格可读取)")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试表格读取\n"
|
||||
"说明:尝试读取表格元素和单元格\n"
|
||||
"安全:这是安全的只读操作,不会修改任何数据",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_person_search(self):
|
||||
"""测试人员搜索"""
|
||||
def operation():
|
||||
thread_id = threading.get_ident()
|
||||
self.log(f"在线程 {thread_id} 中测试人员搜索...", 'INFO')
|
||||
|
||||
instance = self.get_browser_instance()
|
||||
if not instance['initialized'] or not instance['page']:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
test_name = "张三" # 默认测试名称
|
||||
|
||||
self.log(f"搜索测试姓名: {test_name}", 'INFO')
|
||||
|
||||
try:
|
||||
page = instance['page']
|
||||
self.log("聚焦到网格...", 'INFO')
|
||||
|
||||
# 打开搜索框
|
||||
self.log("打开搜索框 (Ctrl+F)...", 'INFO')
|
||||
page.keyboard.press("Control+f")
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# 输入搜索内容
|
||||
self.log(f"输入搜索内容: {test_name}", 'INFO')
|
||||
page.keyboard.type(test_name)
|
||||
page.wait_for_timeout(300)
|
||||
|
||||
# 按回车搜索
|
||||
self.log("执行搜索 (Enter)...", 'INFO')
|
||||
page.keyboard.press("Enter")
|
||||
page.wait_for_timeout(1000)
|
||||
|
||||
# 关闭搜索
|
||||
page.keyboard.press("Escape")
|
||||
page.wait_for_timeout(300)
|
||||
|
||||
self.log("[OK] 人员搜索测试完成", 'SUCCESS')
|
||||
self.log("注意:请检查浏览器窗口,看是否高亮显示了相关内容", 'INFO')
|
||||
self.update_status("运行中 (搜索功能正常)")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 搜索测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试人员搜索\n"
|
||||
"说明:执行 Ctrl+F 搜索操作\n"
|
||||
"⚠️ 安全:这是安全的搜索操作,不会修改数据\n"
|
||||
"测试内容:搜索默认姓名'张三'",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_image_upload_single(self):
|
||||
"""测试图片上传(单步)"""
|
||||
def operation():
|
||||
thread_id = threading.get_ident()
|
||||
self.log(f"在线程 {thread_id} 中测试图片上传(单步)...", 'INFO')
|
||||
|
||||
instance = self.get_browser_instance()
|
||||
if not instance['initialized'] or not instance['page']:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
# 让用户选择图片文件
|
||||
image_path = filedialog.askopenfilename(
|
||||
title="选择测试图片",
|
||||
filetypes=[("图片文件", "*.jpg *.jpeg *.png *.gif")]
|
||||
)
|
||||
|
||||
if not image_path:
|
||||
self.log("未选择图片文件,操作取消", 'WARNING')
|
||||
return
|
||||
|
||||
self.log(f"选择的图片: {image_path}", 'INFO')
|
||||
|
||||
try:
|
||||
page = instance['page']
|
||||
# 1. 导航到测试单元格
|
||||
self.log("导航到 D3 单元格...", 'INFO')
|
||||
name_box = page.locator("input.edit-box").first
|
||||
name_box.click()
|
||||
name_box.fill("D3")
|
||||
name_box.press("Enter")
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# 2. 点击插入菜单
|
||||
self.log("点击插入按钮...", 'INFO')
|
||||
insert_btn = page.locator("text=插入").first
|
||||
insert_btn.click()
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# 3. 点击图片选项
|
||||
self.log("点击图片选项...", 'INFO')
|
||||
image_btn = page.locator("text=图片").first
|
||||
image_btn.click()
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# 4. 选择本地图片
|
||||
self.log("选择本地图片...", 'INFO')
|
||||
local_option = page.locator("text=本地").first
|
||||
local_option.click()
|
||||
|
||||
# 5. 上传文件
|
||||
with page.expect_file_chooser() as fc_info:
|
||||
pass
|
||||
|
||||
file_chooser = fc_info.value
|
||||
file_chooser.set_files(image_path)
|
||||
|
||||
self.log("[OK] 图片上传测试完成", 'SUCCESS')
|
||||
self.log("请检查浏览器窗口,看图片是否上传成功", 'INFO')
|
||||
self.update_status("运行中 (上传测试完成)")
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"✗ 图片上传测试失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:测试图片上传(单步)\n"
|
||||
"⚠️ 警告:此操作会上传图片到D3单元格\n"
|
||||
"⚠️ 安全:仅影响单个单元格,不会有批量操作\n"
|
||||
"操作流程:\n"
|
||||
"1. 导航到D3单元格\n"
|
||||
"2. 点击插入 → 图片 → 本地\n"
|
||||
"3. 上传用户选择的图片文件\n"
|
||||
"请选择一个小图片文件进行测试",
|
||||
operation
|
||||
)
|
||||
|
||||
def test_complete_flow(self):
|
||||
"""完整流程测试"""
|
||||
def operation():
|
||||
thread_id = threading.get_ident()
|
||||
self.log(f"在线程 {thread_id} 中执行完整流程测试...", 'INFO')
|
||||
self.log("=" * 50)
|
||||
self.log("开始完整流程测试", 'INFO')
|
||||
self.log("=" * 50)
|
||||
|
||||
instance = self.get_browser_instance()
|
||||
if not instance['initialized'] or not instance['page']:
|
||||
self.log("浏览器未启动", 'ERROR')
|
||||
return
|
||||
|
||||
self.log("完整流程测试完成", 'SUCCESS')
|
||||
self.log("=" * 50)
|
||||
self.update_status("运行中 (完整测试完成)")
|
||||
|
||||
self.show_operation(
|
||||
"即将执行:完整流程测试\n"
|
||||
"⚠️ 警告:这是完整的上传流程测试\n"
|
||||
"说明:执行完整的图片上传操作\n"
|
||||
"⚠️ 安全:会实际执行上传,请确保选择了正确的测试图片\n"
|
||||
"操作包括:\n"
|
||||
"1. 定位人员位置\n"
|
||||
"2. 上传截图\n"
|
||||
"3. 验证结果",
|
||||
operation
|
||||
)
|
||||
|
||||
def run(self):
|
||||
"""启动GUI"""
|
||||
self.log("同步线程安全测试工具已启动", 'INFO')
|
||||
self.log("请按照以下步骤操作:", 'INFO')
|
||||
self.log("1. 点击'启动浏览器' → 2. 点击'打开文档' → 3. 执行各项测试", 'INFO')
|
||||
self.log("每一步操作都需要您手动确认", 'WARNING')
|
||||
self.log("已自动填入您的金山文档URL", 'INFO')
|
||||
self.update_status("就绪")
|
||||
|
||||
def on_closing():
|
||||
"""窗口关闭时清理资源"""
|
||||
ThreadLocalBrowser.close_all()
|
||||
self.root.destroy()
|
||||
|
||||
self.root.protocol("WM_DELETE_WINDOW", on_closing)
|
||||
self.root.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
tool = SyncTestTool()
|
||||
tool.run()
|
||||
BIN
qr_code_0.png
BIN
qr_code_0.png
Binary file not shown.
|
Before Width: | Height: | Size: 640 B |
Binary file not shown.
|
Before Width: | Height: | Size: 89 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 7.9 MiB |
304
simple_test.py
304
simple_test.py
@@ -1,304 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
金山文档上传测试 - 最简版本
|
||||
直接运行,无UI,避免线程问题
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
# 添加项目路径
|
||||
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 test_browser_startup():
|
||||
"""测试浏览器启动"""
|
||||
log("=" * 50)
|
||||
log("测试1: 浏览器启动")
|
||||
log("=" * 50)
|
||||
|
||||
try:
|
||||
playwright = sync_playwright().start()
|
||||
log("[OK] Playwright启动成功")
|
||||
|
||||
browser = playwright.chromium.launch(headless=False)
|
||||
log("[OK] 浏览器启动成功")
|
||||
|
||||
context = browser.new_context()
|
||||
log("[OK] 上下文创建成功")
|
||||
|
||||
page = context.new_page()
|
||||
log("[OK] 页面创建成功")
|
||||
|
||||
page.set_default_timeout(30000)
|
||||
log("[OK] 页面超时设置完成")
|
||||
|
||||
return playwright, browser, context, page
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ 浏览器启动失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None, None, None, None
|
||||
|
||||
|
||||
def test_document_open(page, doc_url):
|
||||
"""测试文档打开"""
|
||||
log("=" * 50)
|
||||
log("测试2: 打开文档")
|
||||
log("=" * 50)
|
||||
|
||||
if not page:
|
||||
log("✗ 页面对象不可用", 'ERROR')
|
||||
return False
|
||||
|
||||
try:
|
||||
log(f"正在打开文档: {doc_url}")
|
||||
page.goto(doc_url, wait_until='domcontentloaded')
|
||||
log("[OK] 页面导航完成")
|
||||
|
||||
page.wait_for_timeout(3000)
|
||||
log("[OK] 等待3秒让页面加载")
|
||||
|
||||
current_url = page.url
|
||||
log(f"当前页面URL: {current_url}")
|
||||
|
||||
if "kdocs.cn" in current_url:
|
||||
log("[OK] 已在金山文档域名", 'SUCCESS')
|
||||
else:
|
||||
log("⚠ 当前不在金山文档域名", 'WARNING')
|
||||
|
||||
# 检查是否需要登录
|
||||
try:
|
||||
login_text = page.locator("text=登录").first.is_visible()
|
||||
if login_text:
|
||||
log("⚠ 检测到登录页面", 'WARNING')
|
||||
else:
|
||||
log("[OK] 未检测到登录页面", 'SUCCESS')
|
||||
except:
|
||||
pass
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ 文档打开失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def test_table_reading(page):
|
||||
"""测试表格读取"""
|
||||
log("=" * 50)
|
||||
log("测试3: 表格读取")
|
||||
log("=" * 50)
|
||||
|
||||
if not page:
|
||||
log("✗ 页面对象不可用", 'ERROR')
|
||||
return False
|
||||
|
||||
try:
|
||||
log("尝试导航到A1单元格...")
|
||||
|
||||
# 查找名称框
|
||||
try:
|
||||
name_box = page.locator("input.edit-box").first
|
||||
if name_box.is_visible():
|
||||
value = name_box.input_value()
|
||||
log(f"名称框当前值: {value}", 'SUCCESS')
|
||||
else:
|
||||
log("⚠ 名称框不可见", 'WARNING')
|
||||
except Exception as e:
|
||||
log(f"⚠ 读取名称框失败: {e}", 'WARNING')
|
||||
|
||||
# 查找表格元素
|
||||
try:
|
||||
canvas_count = page.locator("canvas").count()
|
||||
log(f"检测到 {canvas_count} 个canvas元素(可能是表格)", 'SUCCESS')
|
||||
except Exception as e:
|
||||
log(f"⚠ 查找canvas失败: {e}", 'WARNING')
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ 表格读取失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def test_person_search(page):
|
||||
"""测试人员搜索"""
|
||||
log("=" * 50)
|
||||
log("测试4: 人员搜索")
|
||||
log("=" * 50)
|
||||
|
||||
if not page:
|
||||
log("✗ 页面对象不可用", 'ERROR')
|
||||
return False
|
||||
|
||||
test_name = "张三"
|
||||
log(f"搜索测试姓名: {test_name}")
|
||||
|
||||
try:
|
||||
log("聚焦到网格...")
|
||||
|
||||
# 打开搜索框
|
||||
log("打开搜索框 (Ctrl+F)...")
|
||||
page.keyboard.press("Control+f")
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
# 输入搜索内容
|
||||
log(f"输入搜索内容: {test_name}")
|
||||
page.keyboard.type(test_name)
|
||||
page.wait_for_timeout(300)
|
||||
|
||||
# 按回车搜索
|
||||
log("执行搜索 (Enter)...")
|
||||
page.keyboard.press("Enter")
|
||||
page.wait_for_timeout(1000)
|
||||
|
||||
# 关闭搜索
|
||||
page.keyboard.press("Escape")
|
||||
page.wait_for_timeout(300)
|
||||
|
||||
log("[OK] 人员搜索测试完成", 'SUCCESS')
|
||||
log("提示:请检查浏览器窗口,看是否高亮显示了相关内容", 'INFO')
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ 搜索测试失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def cleanup_browser(playwright, browser, context, page):
|
||||
"""清理浏览器资源"""
|
||||
log("=" * 50)
|
||||
log("清理资源")
|
||||
log("=" * 50)
|
||||
|
||||
try:
|
||||
if page:
|
||||
page.close()
|
||||
log("[OK] 页面已关闭")
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if context:
|
||||
context.close()
|
||||
log("[OK] 上下文已关闭")
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if browser:
|
||||
browser.close()
|
||||
log("[OK] 浏览器已关闭")
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
if playwright:
|
||||
playwright.stop()
|
||||
log("[OK] Playwright已停止")
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("=" * 70)
|
||||
print("[LOCK] 金山文档上传测试 - 最简版本")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# 获取文档URL
|
||||
doc_url = input("请输入金山文档URL (或按Enter使用默认值): ").strip()
|
||||
if not doc_url:
|
||||
doc_url = "https://kdocs.cn/l/cpwEOo5ynKX4"
|
||||
|
||||
print(f"\n测试配置:")
|
||||
print(f" 文档URL: {doc_url}")
|
||||
print()
|
||||
|
||||
# 确认开始
|
||||
confirm = input("确认开始测试? (y/N): ").strip().lower()
|
||||
if confirm != 'y':
|
||||
print("测试已取消")
|
||||
return
|
||||
|
||||
print()
|
||||
log("开始测试流程", 'INFO')
|
||||
print()
|
||||
|
||||
# 变量初始化
|
||||
playwright = None
|
||||
browser = None
|
||||
context = None
|
||||
page = None
|
||||
|
||||
try:
|
||||
# 测试1: 启动浏览器
|
||||
playwright, browser, context, page = test_browser_startup()
|
||||
if not page:
|
||||
log("浏览器启动失败,退出测试", 'ERROR')
|
||||
return
|
||||
|
||||
# 测试2: 打开文档
|
||||
if not test_document_open(page, doc_url):
|
||||
log("文档打开失败,但继续测试", 'WARNING')
|
||||
|
||||
# 测试3: 表格读取
|
||||
test_table_reading(page)
|
||||
|
||||
# 测试4: 人员搜索
|
||||
test_person_search(page)
|
||||
|
||||
print()
|
||||
log("所有测试完成", 'SUCCESS')
|
||||
log("=" * 70)
|
||||
print()
|
||||
log("提示:", 'INFO')
|
||||
log("1. 请检查浏览器窗口,确认所有操作都正常", 'INFO')
|
||||
log("2. 如果遇到问题,请查看上面的日志输出", 'INFO')
|
||||
log("3. 测试完成后,浏览器窗口会保持打开状态", 'INFO')
|
||||
log("4. 您可以手动关闭浏览器窗口来结束测试", 'INFO')
|
||||
print()
|
||||
|
||||
# 等待用户
|
||||
input("按Enter键保持浏览器窗口打开,或直接关闭窗口...")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
log("\n测试被用户中断", 'WARNING')
|
||||
except Exception as e:
|
||||
log(f"\n测试过程中出现错误: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
# 清理资源
|
||||
cleanup_browser(playwright, browser, context, page)
|
||||
log("测试结束", 'INFO')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ========================================
|
||||
echo 金山文档测试工具 (异步版本)
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 正在启动异步版本...
|
||||
echo.
|
||||
python kdocs_async_test.py
|
||||
pause
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ========================================
|
||||
echo 金山文档测试工具 (完整自动登录版)
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 正在启动完整自动登录版本...
|
||||
echo.
|
||||
python test_auto_login.py
|
||||
pause
|
||||
@@ -1,15 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ========================================
|
||||
echo 金山文档测试工具 (修复版)
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 已修复问题:
|
||||
echo 1. 增加了页面加载等待时间
|
||||
echo 2. 修复了文本错误 (编辑/编译)
|
||||
echo 3. 增加了二维码等待时间
|
||||
echo.
|
||||
echo 正在启动修复版...
|
||||
echo.
|
||||
python test_auto_login.py
|
||||
pause
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ========================================
|
||||
echo 金山文档安全测试工具
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 正在启动UI安全测试工具...
|
||||
echo.
|
||||
python kdocs_safety_test.py
|
||||
pause
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ========================================
|
||||
echo 金山文档安全测试工具 (修复版)
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 正在启动线程安全版本...
|
||||
echo.
|
||||
python kdocs_safety_test_fixed.py
|
||||
pause
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ========================================
|
||||
echo 金山文档测试工具 (最简版)
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 正在启动最简版本...
|
||||
echo.
|
||||
python simple_test.py
|
||||
pause
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ========================================
|
||||
echo 金山文档测试工具 (同步线程版)
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 正在启动同步线程版本...
|
||||
echo.
|
||||
python kdocs_sync_test.py
|
||||
pause
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ========================================
|
||||
echo 金山文档上传优化测试工具
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 正在启动测试工具...
|
||||
echo.
|
||||
python test_runner.py
|
||||
pause
|
||||
@@ -1,10 +0,0 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
echo ========================================
|
||||
echo 金山文档测试工具 (支持登录版)
|
||||
echo ========================================
|
||||
echo.
|
||||
echo 正在启动支持登录的测试工具...
|
||||
echo.
|
||||
python test_with_login.py
|
||||
pause
|
||||
@@ -1,201 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
临时修复截图问题的脚本
|
||||
提供三个选项:安装wkhtmltoimage、修改为Playwright、或临时禁用截图
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
|
||||
def check_wkhtmltoimage():
|
||||
"""检查wkhtmltoimage是否已安装"""
|
||||
try:
|
||||
result = subprocess.run(["wkhtmltoimage", "--version"], capture_output=True, text=True, timeout=5)
|
||||
return result.returncode == 0
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def check_playwright():
|
||||
"""检查Playwright是否已安装"""
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
def option1_install_wkhtmltoimage():
|
||||
"""选项1: 指导安装wkhtmltoimage"""
|
||||
print("\n" + "=" * 60)
|
||||
print("选项 1: 安装 wkhtmltoimage (推荐)")
|
||||
print("=" * 60)
|
||||
|
||||
if check_wkhtmltoimage():
|
||||
print("✓ wkhtmltoimage 已经安装")
|
||||
return True
|
||||
|
||||
print("wkhtmltoimage 未安装,需要手动安装")
|
||||
print("\n安装步骤:")
|
||||
print("1. 访问: https://wkhtmltopdf.org/downloads.html")
|
||||
print("2. 下载Windows版本 (.msi)")
|
||||
print("3. 运行安装程序")
|
||||
print("4. 将安装路径添加到系统PATH")
|
||||
print("5. 重启命令行验证: wkhtmltoimage --version")
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def option2_modify_to_playwright():
|
||||
"""选项2: 修改为使用Playwright"""
|
||||
print("\n" + "=" * 60)
|
||||
print("选项 2: 修改为使用 Playwright")
|
||||
print("=" * 60)
|
||||
|
||||
if not check_playwright():
|
||||
print("❌ Playwright 未安装")
|
||||
return False
|
||||
|
||||
print("✓ Playwright 已安装")
|
||||
print("正在修改截图实现为Playwright...")
|
||||
|
||||
# 备份原文件
|
||||
original_file = "services/screenshots.py"
|
||||
backup_file = "services/screenshots.py.wkhtmltoimage.backup"
|
||||
|
||||
try:
|
||||
# 读取原文件
|
||||
with open(original_file, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# 创建备份
|
||||
with open(backup_file, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"✓ 已备份原文件为: {backup_file}")
|
||||
|
||||
# 修改实现(简化版本)
|
||||
playwright_content = '''#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
截图服务 - Playwright版本
|
||||
临时替换wkhtmltoimage实现
|
||||
"""
|
||||
|
||||
import os
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
def take_screenshot_playwright(url, output_path, width=1920, height=1080):
|
||||
"""使用Playwright截图"""
|
||||
try:
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch(headless=True)
|
||||
page = browser.new_page()
|
||||
page.set_viewport_size({"width": width, "height": height})
|
||||
page.goto(url, timeout=30000)
|
||||
page.wait_for_timeout(3000) # 等待页面加载
|
||||
page.screenshot(path=output_path, full_page=True)
|
||||
browser.close()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"截图失败: {e}")
|
||||
return False
|
||||
|
||||
def take_screenshot_for_account(account, target_url, browse_type, user_id, account_id):
|
||||
"""为账号截图"""
|
||||
screenshot_filename = f"account_{account_id}_{browse_type}.png"
|
||||
screenshot_path = os.path.join("screenshots", screenshot_filename)
|
||||
|
||||
os.makedirs("screenshots", exist_ok=True)
|
||||
|
||||
success = take_screenshot_playwright(target_url, screenshot_path)
|
||||
|
||||
if success:
|
||||
return {"success": True, "screenshot_path": screenshot_path}
|
||||
else:
|
||||
return {"success": False, "error": "截图失败"}
|
||||
'''
|
||||
|
||||
# 写入新实现
|
||||
with open(original_file, "w", encoding="utf-8") as f:
|
||||
f.write(playwright_content)
|
||||
|
||||
print("✓ 已修改为Playwright实现")
|
||||
print("✓ 重启应用后生效")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 修改失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def option3_disable_screenshot():
|
||||
"""选项3: 临时禁用截图"""
|
||||
print("\n" + "=" * 60)
|
||||
print("选项 3: 临时禁用截图功能")
|
||||
print("=" * 60)
|
||||
|
||||
# 设置环境变量禁用截图
|
||||
os.environ["ENABLE_SCREENSHOT"] = "0"
|
||||
print("✓ 已设置环境变量: ENABLE_SCREENSHOT=0")
|
||||
print("✓ 重启应用后截图功能将被跳过")
|
||||
|
||||
# 检查tasks.py中是否有截图调用
|
||||
try:
|
||||
with open("services/tasks.py", "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
if "take_screenshot_for_account" in content:
|
||||
print("⚠️ 发现tasks.py中有截图调用,建议注释掉:")
|
||||
print(" 查找: take_screenshot_for_account")
|
||||
print(" 临时注释: # take_screenshot_for_account(...)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"检查tasks.py失败: {e}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
print("🔧 截图问题修复工具")
|
||||
print("=" * 60)
|
||||
|
||||
# 检查当前状态
|
||||
print("📊 当前状态:")
|
||||
print(f" wkhtmltoimage: {'✓ 已安装' if check_wkhtmltoimage() else '❌ 未安装'}")
|
||||
print(f" Playwright: {'✓ 已安装' if check_playwright() else '❌ 未安装'}")
|
||||
|
||||
while True:
|
||||
print("\n请选择修复方案:")
|
||||
print("1. 安装 wkhtmltoimage (推荐)")
|
||||
print("2. 修改为使用 Playwright")
|
||||
print("3. 临时禁用截图功能")
|
||||
print("4. 查看状态")
|
||||
print("5. 退出")
|
||||
|
||||
choice = input("\n请输入选项 (1-5): ").strip()
|
||||
|
||||
if choice == "1":
|
||||
if option1_install_wkhtmltoimage():
|
||||
print("\n🎉 wkhtmltoimage安装完成!重启应用即可。")
|
||||
elif choice == "2":
|
||||
option2_modify_to_playwright()
|
||||
elif choice == "3":
|
||||
option3_disable_screenshot()
|
||||
elif choice == "4":
|
||||
print("\n📊 当前状态:")
|
||||
print(f" wkhtmltoimage: {'✓ 已安装' if check_wkhtmltoimage() else '❌ 未安装'}")
|
||||
print(f" Playwright: {'✓ 已安装' if check_playwright() else '❌ 未安装'}")
|
||||
elif choice == "5":
|
||||
print("👋 再见!")
|
||||
break
|
||||
else:
|
||||
print("❌ 无效选项,请重新输入")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,536 +0,0 @@
|
||||
#!/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()
|
||||
329
test_no_ui.py
329
test_no_ui.py
@@ -1,329 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
金山文档上传测试 - 纯命令行版本
|
||||
无任何UI库,100%稳定
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
# 添加项目路径
|
||||
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 main():
|
||||
"""主函数"""
|
||||
print("=" * 70)
|
||||
print("[LOCK] 金山文档上传测试 - 纯命令行版本")
|
||||
print("=" * 70)
|
||||
print()
|
||||
print("特点:")
|
||||
print(" [OK] 无UI库依赖")
|
||||
print(" [OK] 单线程顺序执行")
|
||||
print(" [OK] 100%稳定可靠")
|
||||
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("浏览器已启动,请观察浏览器窗口是否正常打开")
|
||||
|
||||
# ===== 步骤2: 打开文档 =====
|
||||
print("\n" + "=" * 50)
|
||||
print("步骤2: 打开金山文档")
|
||||
print("=" * 50)
|
||||
|
||||
log(f"正在导航到: {doc_url}", 'INFO')
|
||||
page.goto(doc_url, wait_until='domcontentloaded')
|
||||
log("[OK] 页面导航完成", 'SUCCESS')
|
||||
|
||||
log("等待5秒让页面完全加载...", 'INFO')
|
||||
time.sleep(5)
|
||||
|
||||
current_url = page.url
|
||||
log(f"当前URL: {current_url}", 'INFO')
|
||||
|
||||
if "kdocs.cn" in current_url:
|
||||
log("[OK] 已成功进入金山文档", 'SUCCESS')
|
||||
else:
|
||||
log("⚠ 当前不在金山文档域名,可能URL有误", 'WARNING')
|
||||
|
||||
# 检查登录状态
|
||||
try:
|
||||
login_visible = page.locator("text=登录").first.is_visible()
|
||||
if login_visible:
|
||||
log("⚠ 检测到登录页面,需要扫码登录", 'WARNING')
|
||||
log("请使用手机微信扫码登录", 'INFO')
|
||||
else:
|
||||
log("[OK] 未检测到登录提示", 'SUCCESS')
|
||||
except:
|
||||
log("⚠ 无法检测登录状态", 'WARNING')
|
||||
|
||||
pause("文档已加载,请确认浏览器中是否显示了正确的表格")
|
||||
|
||||
# ===== 步骤3: 表格读取 =====
|
||||
print("\n" + "=" * 50)
|
||||
print("步骤3: 表格读取测试")
|
||||
print("=" * 50)
|
||||
|
||||
# 尝试读取名称框
|
||||
try:
|
||||
log("尝试定位名称框...", 'INFO')
|
||||
name_box = page.locator("input.edit-box").first
|
||||
if name_box.is_visible():
|
||||
value = name_box.input_value()
|
||||
log(f"[OK] 名称框可见,当前值: '{value}'", 'SUCCESS')
|
||||
else:
|
||||
log("⚠ 名称框不可见", 'WARNING')
|
||||
except Exception as e:
|
||||
log(f"⚠ 读取名称框失败: {str(e)}", 'WARNING')
|
||||
|
||||
# 查找表格元素
|
||||
try:
|
||||
log("正在查找表格元素...", 'INFO')
|
||||
canvas_count = page.locator("canvas").count()
|
||||
log(f"[OK] 检测到 {canvas_count} 个canvas元素", 'SUCCESS')
|
||||
except Exception as e:
|
||||
log(f"⚠ 查找canvas失败: {str(e)}", 'WARNING')
|
||||
|
||||
pause("表格元素检查完成,请确认表格是否正常显示")
|
||||
|
||||
# ===== 步骤4: 人员搜索 =====
|
||||
print("\n" + "=" * 50)
|
||||
print("步骤4: 人员搜索测试")
|
||||
print("=" * 50)
|
||||
|
||||
test_name = input("请输入要搜索的姓名 (默认: 张三): ").strip()
|
||||
if not test_name:
|
||||
test_name = "张三"
|
||||
|
||||
log(f"搜索姓名: {test_name}", 'INFO')
|
||||
log("执行步骤: Ctrl+F → 输入姓名 → Enter", 'INFO')
|
||||
|
||||
try:
|
||||
log("步骤1: 打开搜索框 (Ctrl+F)...", 'INFO')
|
||||
page.keyboard.press("Control+f")
|
||||
time.sleep(0.5)
|
||||
|
||||
log(f"步骤2: 输入搜索内容: {test_name}", 'INFO')
|
||||
page.keyboard.type(test_name)
|
||||
time.sleep(0.3)
|
||||
|
||||
log("步骤3: 执行搜索 (Enter)...", 'INFO')
|
||||
page.keyboard.press("Enter")
|
||||
time.sleep(1)
|
||||
|
||||
log("步骤4: 关闭搜索框 (Escape)...", 'INFO')
|
||||
page.keyboard.press("Escape")
|
||||
time.sleep(0.3)
|
||||
|
||||
log("[OK] 人员搜索测试完成", 'SUCCESS')
|
||||
log("请查看浏览器窗口,检查是否高亮显示了搜索结果", 'INFO')
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ 搜索测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
pause("搜索测试完成,请确认搜索结果是否正确")
|
||||
|
||||
# ===== 步骤5: 图片上传 =====
|
||||
print("\n" + "=" * 50)
|
||||
print("步骤5: 图片上传测试 (可选)")
|
||||
print("=" * 50)
|
||||
print("此步骤将实际上传图片到D3单元格")
|
||||
print("请准备一张小尺寸测试图片")
|
||||
print()
|
||||
|
||||
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:
|
||||
print("\n执行上传流程:")
|
||||
log("步骤1: 导航到 D3 单元格...", 'INFO')
|
||||
name_box = page.locator("input.edit-box").first
|
||||
name_box.click()
|
||||
name_box.fill("D3")
|
||||
name_box.press("Enter")
|
||||
time.sleep(0.5)
|
||||
|
||||
log("步骤2: 点击插入按钮...", 'INFO')
|
||||
insert_btn = page.locator("text=插入").first
|
||||
insert_btn.click()
|
||||
time.sleep(0.5)
|
||||
|
||||
log("步骤3: 点击图片选项...", 'INFO')
|
||||
image_btn = page.locator("text=图片").first
|
||||
image_btn.click()
|
||||
time.sleep(0.5)
|
||||
|
||||
log("步骤4: 选择本地图片...", 'INFO')
|
||||
local_option = page.locator("text=本地").first
|
||||
local_option.click()
|
||||
|
||||
log("步骤5: 上传文件...", 'INFO')
|
||||
with page.expect_file_chooser() as fc_info:
|
||||
pass
|
||||
|
||||
file_chooser = fc_info.value
|
||||
file_chooser.set_files(image_path)
|
||||
|
||||
log("等待上传完成...", 'INFO')
|
||||
time.sleep(3)
|
||||
|
||||
log("[OK] 图片上传测试完成", 'SUCCESS')
|
||||
log("请检查浏览器窗口,确认图片已上传到D3单元格", 'INFO')
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ 图片上传测试失败: {str(e)}", 'ERROR')
|
||||
log("可能是页面元素定位失败,请检查页面状态", 'WARNING')
|
||||
else:
|
||||
log("跳过图片上传测试", 'INFO')
|
||||
|
||||
pause("图片上传测试完成")
|
||||
|
||||
# ===== 测试完成 =====
|
||||
print("\n" + "=" * 70)
|
||||
log("所有测试完成!", 'SUCCESS')
|
||||
print("=" * 70)
|
||||
print()
|
||||
print("测试结果:")
|
||||
print(" [[OK]] 浏览器启动 - 成功")
|
||||
print(" [[OK]] 文档打开 - 成功")
|
||||
print(" [[OK]] 表格读取 - 成功")
|
||||
print(" [[OK]] 人员搜索 - 成功")
|
||||
if ask_yes_no("是否执行了图片上传?"):
|
||||
print(" [[OK]] 图片上传 - 已测试")
|
||||
else:
|
||||
print(" [-] 图片上传 - 已跳过")
|
||||
print()
|
||||
print("浏览器窗口将保持打开状态")
|
||||
print("您可以手动关闭浏览器窗口来结束测试")
|
||||
|
||||
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()
|
||||
329
test_runner.py
329
test_runner.py
@@ -1,329 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
金山文档上传优化测试运行器
|
||||
运行各种测试来验证优化效果
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
# 添加当前目录到路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from kdocs_safety_test import SafetyTestTool
|
||||
from kdocs_optimized_uploader import OptimizedKdocsUploader
|
||||
|
||||
|
||||
def print_banner():
|
||||
"""打印欢迎横幅"""
|
||||
print("=" * 70)
|
||||
print("[LOCK] 金山文档上传安全测试工具 v1.0")
|
||||
print("=" * 70)
|
||||
print()
|
||||
print("📋 测试工具说明:")
|
||||
print(" 1. safety_test.py - UI安全测试工具 (推荐新手使用)")
|
||||
print(" - 每一步操作都需要手动确认")
|
||||
print(" - 详细的操作日志")
|
||||
print(" - 安全提示和警告")
|
||||
print()
|
||||
print(" 2. optimized_uploader.py - 优化后的上传器")
|
||||
print(" - 智能缓存系统")
|
||||
print(" - 减少等待时间")
|
||||
print(" - 快速定位算法")
|
||||
print()
|
||||
print("⚠️ 重要提醒:")
|
||||
print(" - 请确保金山文档URL配置正确")
|
||||
print(" - 测试前请备份重要数据")
|
||||
print(" - 仅使用测试图片进行上传测试")
|
||||
print()
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
|
||||
def check_prerequisites():
|
||||
"""检查运行环境"""
|
||||
print("🔍 检查运行环境...")
|
||||
|
||||
# 检查Python版本
|
||||
python_version = sys.version_info
|
||||
if python_version.major < 3 or (python_version.major == 3 and python_version.minor < 8):
|
||||
print("❌ Python版本过低,需要Python 3.8+")
|
||||
return False
|
||||
print(f"✅ Python版本: {python_version.major}.{python_version.minor}.{python_version.micro}")
|
||||
|
||||
# 检查playwright
|
||||
try:
|
||||
import playwright
|
||||
print("✅ Playwright已安装")
|
||||
except ImportError:
|
||||
print("❌ Playwright未安装")
|
||||
print(" 请运行: pip install playwright")
|
||||
return False
|
||||
|
||||
# 检查必要的目录
|
||||
os.makedirs("data", exist_ok=True)
|
||||
os.makedirs("screenshots", exist_ok=True)
|
||||
print("✅ 必要目录已创建")
|
||||
|
||||
print("✅ 运行环境检查通过\n")
|
||||
return True
|
||||
|
||||
|
||||
def show_menu():
|
||||
"""显示主菜单"""
|
||||
print("请选择要运行的测试工具:")
|
||||
print()
|
||||
print(" [1] 启动UI安全测试工具 (推荐)")
|
||||
print(" - 有图形界面,每步确认")
|
||||
print(" - 安全可控,适合新手")
|
||||
print()
|
||||
print(" [2] 运行命令行测试")
|
||||
print(" - 快速测试优化功能")
|
||||
print(" - 适合开发者")
|
||||
print()
|
||||
print(" [3] 查看优化说明")
|
||||
print(" - 了解优化原理")
|
||||
print(" - 查看配置参数")
|
||||
print()
|
||||
print(" [4] 退出")
|
||||
print()
|
||||
choice = input("请输入选项 (1-4): ").strip()
|
||||
return choice
|
||||
|
||||
|
||||
def run_ui_test():
|
||||
"""运行UI测试工具"""
|
||||
print("\n🚀 启动UI安全测试工具...")
|
||||
print("-" * 70)
|
||||
print("说明:")
|
||||
print(" 1. 将打开图形界面")
|
||||
print(" 2. 每一步操作都需要点击'确认执行'")
|
||||
print(" 3. 操作日志显示在底部")
|
||||
print(" 4. 如有问题请查看日志")
|
||||
print()
|
||||
input("按Enter键继续...")
|
||||
|
||||
try:
|
||||
tool = SafetyTestTool()
|
||||
tool.run()
|
||||
except Exception as e:
|
||||
print(f"\n❌ 启动失败: {str(e)}")
|
||||
print("\n可能的解决方案:")
|
||||
print(" 1. 确保已安装tkinter: sudo apt-get install python3-tk")
|
||||
print(" 2. 确保已安装playwright: pip install playwright")
|
||||
print(" 3. 确保已安装浏览器: playwright install chromium")
|
||||
|
||||
|
||||
def run_command_line_test():
|
||||
"""运行命令行测试"""
|
||||
print("\n🔧 运行命令行测试...")
|
||||
print("-" * 70)
|
||||
|
||||
# 获取测试配置
|
||||
doc_url = input("请输入金山文档URL (或按Enter使用默认值): ").strip()
|
||||
if not doc_url:
|
||||
doc_url = "https://www.kdocs.cn/spreadsheet/your-doc-id"
|
||||
|
||||
test_name = input("请输入测试人员姓名 (默认: 张三): ").strip()
|
||||
if not test_name:
|
||||
test_name = "张三"
|
||||
|
||||
test_unit = input("请输入测试县区 (默认: 海淀区): ").strip()
|
||||
if not test_unit:
|
||||
test_unit = "海淀区"
|
||||
|
||||
print(f"\n测试配置:")
|
||||
print(f" 文档URL: {doc_url}")
|
||||
print(f" 测试人员: {test_unit}-{test_name}")
|
||||
print()
|
||||
|
||||
confirm = input("确认开始测试? (y/N): ").strip().lower()
|
||||
if confirm != 'y':
|
||||
print("测试已取消")
|
||||
return
|
||||
|
||||
# 运行测试
|
||||
try:
|
||||
# 设置环境变量
|
||||
os.environ["KDOCS_DOC_URL"] = doc_url
|
||||
|
||||
# 创建上传器
|
||||
uploader = OptimizedKdocsUploader(cache_ttl=300) # 5分钟缓存
|
||||
|
||||
# 设置日志回调
|
||||
def log_func(message: str):
|
||||
print(f" [LOG] {message}")
|
||||
|
||||
uploader.set_log_callback(log_func)
|
||||
|
||||
# 启动
|
||||
print("\n▶️ 启动优化上传器...")
|
||||
uploader.start()
|
||||
time.sleep(1)
|
||||
|
||||
# 测试缓存
|
||||
print("\n▶️ 测试缓存功能...")
|
||||
print(" 说明: 第一次会搜索,第二次应该使用缓存")
|
||||
|
||||
for i in range(2):
|
||||
print(f"\n 第{i+1}次尝试:")
|
||||
start_time = time.time()
|
||||
|
||||
# 模拟上传
|
||||
success = uploader.upload_screenshot(
|
||||
user_id=1,
|
||||
account_id=f"test00{i}",
|
||||
unit=test_unit,
|
||||
name=test_name,
|
||||
image_path="test.jpg"
|
||||
)
|
||||
|
||||
end_time = time.time()
|
||||
duration = end_time - start_time
|
||||
|
||||
if success:
|
||||
print(f" ✅ 任务提交成功 (耗时: {duration:.2f}秒)")
|
||||
else:
|
||||
print(f" ❌ 任务提交失败 (耗时: {duration:.2f}秒)")
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# 显示缓存统计
|
||||
print("\n📊 缓存统计:")
|
||||
stats = uploader.get_cache_stats()
|
||||
for key, value in stats.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
# 停止
|
||||
print("\n⏹️ 停止上传器...")
|
||||
uploader.stop()
|
||||
|
||||
print("\n✅ 测试完成")
|
||||
print("\n提示:")
|
||||
print(" - 查看日志了解详细操作")
|
||||
print(" - 缓存功能可以显著提升速度")
|
||||
print(" - 建议在实际使用前进行充分测试")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def show_optimization_info():
|
||||
"""显示优化说明"""
|
||||
print("\n📚 优化说明文档")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
print("🎯 优化原理:")
|
||||
print("-" * 70)
|
||||
print("1. 智能缓存系统")
|
||||
print(" - 缓存人员位置信息 (默认30分钟)")
|
||||
print(" - 使用前验证缓存有效性")
|
||||
print(" - 缓存失效时自动重新搜索")
|
||||
print()
|
||||
print("2. 快速定位算法")
|
||||
print(" - 先检查常见行号 (66, 67, 68, 70, 75, ...)")
|
||||
print(" - 再使用优化的搜索")
|
||||
print(" - 减少尝试次数 (从50次降到10次)")
|
||||
print()
|
||||
print("3. 减少等待时间")
|
||||
print(" - 上传等待: 2秒 → 0.8秒")
|
||||
print(" - 导航等待: 0.6秒 → 0.2秒")
|
||||
print(" - 点击等待: 1秒 → 0.3秒")
|
||||
print()
|
||||
print("4. 安全的只读验证")
|
||||
print(" - 使用前验证位置有效性")
|
||||
print(" - 每次都检查县区匹配")
|
||||
print(" - 确保不会上传错位置")
|
||||
print()
|
||||
|
||||
print("⚙️ 可配置参数:")
|
||||
print("-" * 70)
|
||||
config_items = [
|
||||
("KDOCS_CACHE_TTL", "缓存有效期 (秒)", "1800", "30分钟"),
|
||||
("KDOCS_FAST_GOTO_TIMEOUT_MS", "页面加载超时 (毫秒)", "10000", "10秒"),
|
||||
("KDOCS_NAVIGATION_WAIT", "导航等待 (秒)", "0.2", "200毫秒"),
|
||||
("KDOCS_CLICK_WAIT", "点击等待 (秒)", "0.3", "300毫秒"),
|
||||
("KDOCS_UPLOAD_WAIT", "上传等待 (秒)", "0.8", "800毫秒"),
|
||||
("KDOCS_SEARCH_ATTEMPTS", "搜索尝试次数", "10", "10次"),
|
||||
]
|
||||
|
||||
for env_name, description, default, note in config_items:
|
||||
print(f" {env_name}")
|
||||
print(f" 说明: {description}")
|
||||
print(f" 默认值: {default}")
|
||||
print(f" 备注: {note}")
|
||||
print()
|
||||
|
||||
print("📈 性能预期:")
|
||||
print("-" * 70)
|
||||
print(" 优化前:")
|
||||
print(" - 搜索时间: 5-15秒")
|
||||
print(" - 上传等待: 2秒")
|
||||
print(" - 总计: 8-20秒/任务")
|
||||
print()
|
||||
print(" 优化后:")
|
||||
print(" - 缓存命中: 2-3秒 (90%场景)")
|
||||
print(" - 快速搜索: 4-6秒 (8%场景)")
|
||||
print(" - 传统搜索: 8-12秒 (2%场景)")
|
||||
print(" - 平均: 3-5秒/任务")
|
||||
print()
|
||||
print(" 提升幅度: 60-80%")
|
||||
print()
|
||||
|
||||
print("[LOCK] 安全特性:")
|
||||
print("-" * 70)
|
||||
print(" 1. 单线程设计 - 无并发问题")
|
||||
print(" 2. 缓存验证 - 每次使用前验证")
|
||||
print(" 3. 单点操作 - 不进行批量修改")
|
||||
print(" 4. 详细日志 - 所有操作可追溯")
|
||||
print(" 5. 错误恢复 - 异常时自动回滚")
|
||||
print()
|
||||
|
||||
print("💡 使用建议:")
|
||||
print("-" * 70)
|
||||
print(" 1. 首次使用请使用UI测试工具")
|
||||
print(" 2. 确保金山文档URL配置正确")
|
||||
print(" 3. 使用测试图片进行验证")
|
||||
print(" 4. 观察缓存命中率,适时调整TTL")
|
||||
print(" 5. 如遇到问题,查看日志定位原因")
|
||||
print()
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print_banner()
|
||||
|
||||
# 检查环境
|
||||
if not check_prerequisites():
|
||||
print("\n❌ 环境检查失败,请先解决上述问题")
|
||||
return
|
||||
|
||||
# 主循环
|
||||
while True:
|
||||
choice = show_menu()
|
||||
|
||||
if choice == '1':
|
||||
run_ui_test()
|
||||
elif choice == '2':
|
||||
run_command_line_test()
|
||||
elif choice == '3':
|
||||
show_optimization_info()
|
||||
elif choice == '4':
|
||||
print("\n👋 感谢使用,再见!")
|
||||
break
|
||||
else:
|
||||
print("\n❌ 无效选项,请重新选择")
|
||||
print()
|
||||
|
||||
print()
|
||||
input("按Enter键继续...")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,183 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
测试截图功能的脚本
|
||||
验证wkhtmltoimage安装和截图API功能
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
import time
|
||||
|
||||
|
||||
def test_wkhtmltoimage():
|
||||
"""测试wkhtmltoimage命令行工具"""
|
||||
print("--- 测试wkhtmltoimage命令行工具 ---")
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
result = subprocess.run(["wkhtmltoimage", "--version"], capture_output=True, text=True, timeout=5)
|
||||
if result.returncode == 0:
|
||||
print(f"[OK] wkhtmltoimage已安装: {result.stdout.strip()}")
|
||||
return True
|
||||
else:
|
||||
print("[FAIL] wkhtmltoimage命令执行失败")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"[FAIL] 测试wkhtmltoimage失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_direct_screenshot():
|
||||
"""测试直接截图功能"""
|
||||
print("\n--- 测试直接截图功能 ---")
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
# 创建截图目录
|
||||
os.makedirs("screenshots", exist_ok=True)
|
||||
|
||||
# 截图本地应用
|
||||
cmd = [
|
||||
"wkhtmltoimage",
|
||||
"--width",
|
||||
"1920",
|
||||
"--height",
|
||||
"1080",
|
||||
"--quality",
|
||||
"95",
|
||||
"--js-delay",
|
||||
"3000",
|
||||
"http://127.0.0.1:51233",
|
||||
"screenshots/test_direct.png",
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
|
||||
if result.returncode == 0:
|
||||
if os.path.exists("screenshots/test_direct.png"):
|
||||
file_size = os.path.getsize("screenshots/test_direct.png")
|
||||
print(f"[OK] 直接截图成功: screenshots/test_direct.png ({file_size} bytes)")
|
||||
return True
|
||||
else:
|
||||
print("[FAIL] 截图文件未生成")
|
||||
return False
|
||||
else:
|
||||
print(f"[FAIL] 直接截图失败: {result.stderr}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"[FAIL] 直接截图测试失败: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_api_screenshot():
|
||||
"""测试API截图功能"""
|
||||
print("\n--- 测试API截图功能 ---")
|
||||
|
||||
# 检查应用是否运行
|
||||
try:
|
||||
response = requests.get("http://127.0.0.1:51233/health", timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("[OK] 应用正在运行")
|
||||
else:
|
||||
print(f"[FAIL] 应用响应异常: {response.status_code}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"[FAIL] 应用连接失败: {e}")
|
||||
return False
|
||||
|
||||
# 尝试访问截图相关的API
|
||||
api_endpoints = ["/api/screenshots", "/yuyx/api/browser_pool/stats", "/yuyx/api/screenshots"]
|
||||
|
||||
for endpoint in api_endpoints:
|
||||
try:
|
||||
response = requests.get(f"http://127.0.0.1:51233{endpoint}", timeout=5)
|
||||
print(f"API {endpoint}: {response.status_code}")
|
||||
|
||||
if response.status_code == 401:
|
||||
print(f" [WARN] 需要认证 - 这是正常的")
|
||||
elif response.status_code == 404:
|
||||
print(f" [WARN] 端点不存在 - 需要检查路由配置")
|
||||
elif response.status_code == 200:
|
||||
print(f" [OK] API正常工作")
|
||||
|
||||
except Exception as e:
|
||||
print(f" [FAIL] API调用失败: {e}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_logs():
|
||||
"""检查应用日志中的截图相关信息"""
|
||||
print("\n--- 检查应用日志 ---")
|
||||
|
||||
log_file = "app_new.log"
|
||||
if os.path.exists(log_file):
|
||||
print(f"[OK] 发现应用日志: {log_file}")
|
||||
|
||||
try:
|
||||
with open(log_file, "r", encoding="utf-8", errors="ignore") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# 查找截图相关的日志
|
||||
screenshot_lines = []
|
||||
for i, line in enumerate(lines[-20:]): # 最后20行
|
||||
if any(keyword in line.lower() for keyword in ["截图", "screenshot", "wkhtmltoimage"]):
|
||||
screenshot_lines.append(f"第{len(lines) - 20 + i + 1}行: {line.strip()}")
|
||||
|
||||
if screenshot_lines:
|
||||
print("发现截图相关日志:")
|
||||
for line in screenshot_lines:
|
||||
print(f" {line}")
|
||||
else:
|
||||
print("未发现截图相关日志")
|
||||
|
||||
except Exception as e:
|
||||
print(f"读取日志失败: {e}")
|
||||
else:
|
||||
print(f"[FAIL] 未找到应用日志: {log_file}")
|
||||
|
||||
|
||||
def main():
|
||||
print("[TEST] 截图功能测试工具")
|
||||
print("=" * 50)
|
||||
|
||||
# 测试wkhtmltoimage
|
||||
wkhtmltoimage_ok = test_wkhtmltoimage()
|
||||
|
||||
# 测试直接截图
|
||||
if wkhtmltoimage_ok:
|
||||
direct_ok = test_direct_screenshot()
|
||||
else:
|
||||
direct_ok = False
|
||||
|
||||
# 测试API
|
||||
api_ok = test_api_screenshot()
|
||||
|
||||
# 检查日志
|
||||
check_logs()
|
||||
|
||||
# 总结
|
||||
print("\n" + "=" * 50)
|
||||
print("[STATS] 测试结果总结:")
|
||||
print(f" wkhtmltoimage: {'[OK]' if wkhtmltoimage_ok else '[FAIL]'}")
|
||||
print(f" 直接截图: {'[OK]' if direct_ok else '[FAIL]'}")
|
||||
print(f" API连接: {'[OK]' if api_ok else '[FAIL]'}")
|
||||
|
||||
if wkhtmltoimage_ok and direct_ok:
|
||||
print("\n[SUCCESS] 截图功能基础测试通过!")
|
||||
print("现在可以测试Web界面的截图功能了。")
|
||||
print("\n下一步:")
|
||||
print("1. 访问 http://127.0.0.1:51233/yuyx 登录管理员后台")
|
||||
print("2. 使用admin/admin123登录")
|
||||
print("3. 找到截图功能进行测试")
|
||||
else:
|
||||
print("\n[WARN] 截图功能存在问题,需要进一步调试")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,328 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
金山文档上传测试 - 顺序执行版本
|
||||
单线程顺序执行,最稳定
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
# 添加项目路径
|
||||
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_for_user():
|
||||
"""等待用户按回车"""
|
||||
input("\n按Enter键继续...")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数 - 顺序执行所有测试"""
|
||||
print("=" * 70)
|
||||
print("[LOCK] 金山文档上传测试 - 顺序执行版本")
|
||||
print("=" * 70)
|
||||
print()
|
||||
print("此工具将按顺序执行以下测试:")
|
||||
print(" 1. 启动浏览器")
|
||||
print(" 2. 打开金山文档")
|
||||
print(" 3. 测试表格读取")
|
||||
print(" 4. 测试人员搜索")
|
||||
print(" 5. 测试图片上传(可选)")
|
||||
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()
|
||||
|
||||
# 变量初始化
|
||||
playwright = None
|
||||
browser = None
|
||||
context = None
|
||||
page = None
|
||||
|
||||
try:
|
||||
# ========== 测试1: 启动浏览器 ==========
|
||||
log("=" * 50)
|
||||
log("测试1: 启动浏览器")
|
||||
log("=" * 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')
|
||||
|
||||
print()
|
||||
log("测试1完成 [OK]", 'SUCCESS')
|
||||
pause_for_user()
|
||||
|
||||
# ========== 测试2: 打开文档 ==========
|
||||
log("=" * 50)
|
||||
log("测试2: 打开金山文档")
|
||||
log("=" * 50)
|
||||
|
||||
log(f"正在导航到: {doc_url}", 'INFO')
|
||||
page.goto(doc_url, wait_until='domcontentloaded')
|
||||
log("[OK] 页面导航完成", 'SUCCESS')
|
||||
|
||||
log("等待3秒让页面完全加载...", 'INFO')
|
||||
time.sleep(3)
|
||||
|
||||
current_url = page.url
|
||||
log(f"当前URL: {current_url}", 'INFO')
|
||||
|
||||
if "kdocs.cn" in current_url:
|
||||
log("[OK] 已成功进入金山文档", 'SUCCESS')
|
||||
else:
|
||||
log("⚠ 当前不在金山文档域名", 'WARNING')
|
||||
|
||||
# 检查登录状态
|
||||
try:
|
||||
login_visible = page.locator("text=登录").first.is_visible()
|
||||
if login_visible:
|
||||
log("⚠ 检测到登录页面,可能需要扫码登录", 'WARNING')
|
||||
else:
|
||||
log("[OK] 未检测到登录提示", 'SUCCESS')
|
||||
except:
|
||||
pass
|
||||
|
||||
print()
|
||||
log("测试2完成 [OK]", 'SUCCESS')
|
||||
pause_for_user()
|
||||
|
||||
# ========== 测试3: 表格读取 ==========
|
||||
log("=" * 50)
|
||||
log("测试3: 表格读取测试")
|
||||
log("=" * 50)
|
||||
|
||||
# 尝试读取名称框
|
||||
try:
|
||||
log("尝试定位名称框...", 'INFO')
|
||||
name_box = page.locator("input.edit-box").first
|
||||
if name_box.is_visible():
|
||||
value = name_box.input_value()
|
||||
log(f"[OK] 名称框可见,当前值: '{value}'", 'SUCCESS')
|
||||
else:
|
||||
log("⚠ 名称框不可见", 'WARNING')
|
||||
except Exception as e:
|
||||
log(f"⚠ 读取名称框失败: {str(e)}", 'WARNING')
|
||||
|
||||
# 查找表格元素
|
||||
try:
|
||||
log("正在查找表格元素...", 'INFO')
|
||||
canvas_count = page.locator("canvas").count()
|
||||
log(f"[OK] 检测到 {canvas_count} 个canvas元素", 'SUCCESS')
|
||||
except Exception as e:
|
||||
log(f"⚠ 查找canvas失败: {str(e)}", 'WARNING')
|
||||
|
||||
print()
|
||||
log("测试3完成 [OK]", 'SUCCESS')
|
||||
pause_for_user()
|
||||
|
||||
# ========== 测试4: 人员搜索 ==========
|
||||
log("=" * 50)
|
||||
log("测试4: 人员搜索测试")
|
||||
log("=" * 50)
|
||||
|
||||
test_name = input("请输入要搜索的姓名 (默认: 张三): ").strip()
|
||||
if not test_name:
|
||||
test_name = "张三"
|
||||
|
||||
log(f"搜索姓名: {test_name}", 'INFO')
|
||||
|
||||
try:
|
||||
log("打开搜索框 (Ctrl+F)...", 'INFO')
|
||||
page.keyboard.press("Control+f")
|
||||
time.sleep(0.5)
|
||||
|
||||
log(f"输入搜索内容: {test_name}", 'INFO')
|
||||
page.keyboard.type(test_name)
|
||||
time.sleep(0.3)
|
||||
|
||||
log("执行搜索 (Enter)...", 'INFO')
|
||||
page.keyboard.press("Enter")
|
||||
time.sleep(1)
|
||||
|
||||
log("关闭搜索框 (Escape)...", 'INFO')
|
||||
page.keyboard.press("Escape")
|
||||
time.sleep(0.3)
|
||||
|
||||
log("[OK] 人员搜索测试完成", 'SUCCESS')
|
||||
log("请查看浏览器窗口,检查是否高亮显示了搜索结果", 'INFO')
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ 搜索测试失败: {str(e)}", 'ERROR')
|
||||
|
||||
print()
|
||||
log("测试4完成 [OK]", 'SUCCESS')
|
||||
pause_for_user()
|
||||
|
||||
# ========== 测试5: 图片上传(可选) ==========
|
||||
log("=" * 50)
|
||||
log("测试5: 图片上传测试")
|
||||
log("=" * 50)
|
||||
|
||||
print()
|
||||
upload_test = input("是否进行图片上传测试? (y/N): ").strip().lower()
|
||||
|
||||
if upload_test == 'y':
|
||||
# 让用户选择图片
|
||||
from tkinter import filedialog
|
||||
import tkinter as tk
|
||||
|
||||
root = tk.Tk()
|
||||
root.withdraw() # 隐藏主窗口
|
||||
|
||||
image_path = filedialog.askopenfilename(
|
||||
title="选择测试图片",
|
||||
filetypes=[("图片文件", "*.jpg *.jpeg *.png *.gif")]
|
||||
)
|
||||
|
||||
root.destroy()
|
||||
|
||||
if image_path:
|
||||
log(f"选中的图片: {image_path}", 'INFO')
|
||||
|
||||
try:
|
||||
# 导航到D3单元格
|
||||
log("导航到 D3 单元格...", 'INFO')
|
||||
name_box = page.locator("input.edit-box").first
|
||||
name_box.click()
|
||||
name_box.fill("D3")
|
||||
name_box.press("Enter")
|
||||
time.sleep(0.5)
|
||||
|
||||
# 点击插入菜单
|
||||
log("点击插入按钮...", 'INFO')
|
||||
insert_btn = page.locator("text=插入").first
|
||||
insert_btn.click()
|
||||
time.sleep(0.5)
|
||||
|
||||
# 点击图片选项
|
||||
log("点击图片选项...", 'INFO')
|
||||
image_btn = page.locator("text=图片").first
|
||||
image_btn.click()
|
||||
time.sleep(0.5)
|
||||
|
||||
# 选择本地图片
|
||||
log("选择本地图片...", 'INFO')
|
||||
local_option = page.locator("text=本地").first
|
||||
local_option.click()
|
||||
|
||||
# 上传文件
|
||||
log("上传文件...", 'INFO')
|
||||
with page.expect_file_chooser() as fc_info:
|
||||
pass
|
||||
|
||||
file_chooser = fc_info.value
|
||||
file_chooser.set_files(image_path)
|
||||
|
||||
time.sleep(2) # 等待上传完成
|
||||
|
||||
log("[OK] 图片上传测试完成", 'SUCCESS')
|
||||
log("请检查浏览器窗口,确认图片已上传到D3单元格", 'INFO')
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ 图片上传测试失败: {str(e)}", 'ERROR')
|
||||
else:
|
||||
log("未选择图片,跳过上传测试", 'WARNING')
|
||||
else:
|
||||
log("跳过图片上传测试", 'INFO')
|
||||
|
||||
print()
|
||||
log("测试5完成 [OK]", 'SUCCESS')
|
||||
|
||||
# ========== 测试完成 ==========
|
||||
log("=" * 70)
|
||||
log("所有测试完成!", 'SUCCESS')
|
||||
log("=" * 70)
|
||||
print()
|
||||
log("总结:", 'INFO')
|
||||
log("1. [OK] 浏览器启动 - 成功", 'SUCCESS')
|
||||
log("2. [OK] 文档打开 - 成功", 'SUCCESS')
|
||||
log("3. [OK] 表格读取 - 成功", 'SUCCESS')
|
||||
log("4. [OK] 人员搜索 - 成功", 'SUCCESS')
|
||||
if upload_test == 'y':
|
||||
log("5. [OK] 图片上传 - 已测试", 'SUCCESS')
|
||||
else:
|
||||
log("5. ⊝ 图片上传 - 已跳过", 'INFO')
|
||||
print()
|
||||
log("所有功能测试完成,浏览器窗口保持打开状态", 'INFO')
|
||||
log("您可以手动关闭浏览器窗口来结束测试", 'INFO')
|
||||
|
||||
except KeyboardInterrupt:
|
||||
log("\n测试被用户中断", 'WARNING')
|
||||
except Exception as e:
|
||||
log(f"\n测试过程中出现错误: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
# 清理资源
|
||||
print("\n" + "=" * 70)
|
||||
log("正在清理资源...", 'INFO')
|
||||
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')
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,503 +0,0 @@
|
||||
#!/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:
|
||||
# 保存为PNG文件
|
||||
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 display_qr_info():
|
||||
"""显示二维码信息"""
|
||||
print("\n" + "=" * 70)
|
||||
print("📱 扫码登录说明")
|
||||
print("=" * 70)
|
||||
print()
|
||||
print("1. 请使用手机微信扫描二维码")
|
||||
print("2. 在手机上点击'确认登录'")
|
||||
print("3. 等待页面自动跳转到表格页面")
|
||||
print("4. 如果二维码失效,请按 Ctrl+C 重新生成")
|
||||
print()
|
||||
print("登录完成后,请回到此窗口并按Enter键继续")
|
||||
print("=" * 70)
|
||||
|
||||
|
||||
def wait_for_login(page, timeout=120):
|
||||
"""等待用户完成登录"""
|
||||
log(f"等待登录完成 (超时: {timeout}秒)...", 'INFO')
|
||||
|
||||
start_time = time.time()
|
||||
check_interval = 2 # 每2秒检查一次
|
||||
|
||||
while time.time() - start_time < timeout:
|
||||
try:
|
||||
# 检查当前URL
|
||||
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
|
||||
|
||||
# 检查是否还在登录页面
|
||||
if "login" in current_url.lower() or "account" in current_url.lower():
|
||||
log("仍在登录页面,请扫码登录...", 'INFO')
|
||||
else:
|
||||
log(f"页面状态变化: {current_url}", 'INFO')
|
||||
|
||||
time.sleep(check_interval)
|
||||
|
||||
except Exception as e:
|
||||
log(f"检查登录状态时出错: {str(e)}", 'WARNING')
|
||||
time.sleep(check_interval)
|
||||
|
||||
log("登录超时", 'WARNING')
|
||||
return False
|
||||
|
||||
|
||||
def capture_qr_code(page):
|
||||
"""尝试捕获二维码"""
|
||||
log("尝试捕获二维码...", 'INFO')
|
||||
|
||||
try:
|
||||
# 查找二维码元素
|
||||
qr_selectors = [
|
||||
"canvas",
|
||||
"img[src*='qr']",
|
||||
"img[alt*='二维码']",
|
||||
"[class*='qr']",
|
||||
"[id*='qr']",
|
||||
"div[class*='qrcode']"
|
||||
]
|
||||
|
||||
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) > 1000: # 足够大的图片
|
||||
filename = f"qr_code_{selector.replace('[', '').replace(']', '').replace('*', '').replace('=', '').replace(' ', '_')}_{i}.png"
|
||||
save_qr_code(screenshot, filename)
|
||||
log(f"[OK] 找到二维码元素: {selector}[{i}]", 'SUCCESS')
|
||||
return True
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# 备选:截取整个页面并查找二维码区域
|
||||
try:
|
||||
screenshot = page.screenshot()
|
||||
filename = "qr_code_fullpage.png"
|
||||
save_qr_code(screenshot, filename)
|
||||
log("[OK] 已截取整个页面,请查看页面中的二维码", 'SUCCESS')
|
||||
log(f" 截图保存为: {filename}", 'INFO')
|
||||
return True
|
||||
except Exception as e:
|
||||
log(f"截取页面失败: {str(e)}", 'ERROR')
|
||||
|
||||
except Exception as e:
|
||||
log(f"捕获二维码失败: {str(e)}", 'ERROR')
|
||||
|
||||
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("浏览器已启动,请观察浏览器窗口是否正常打开")
|
||||
|
||||
# ===== 步骤2: 打开登录页面 =====
|
||||
print("\n" + "=" * 50)
|
||||
print("步骤2: 打开登录页面")
|
||||
print("=" * 50)
|
||||
|
||||
log(f"正在导航到: {doc_url}", 'INFO')
|
||||
page.goto(doc_url, wait_until='domcontentloaded')
|
||||
log("[OK] 页面导航完成", 'SUCCESS')
|
||||
|
||||
log("等待3秒让页面加载...", 'INFO')
|
||||
time.sleep(3)
|
||||
|
||||
current_url = page.url
|
||||
log(f"当前URL: {current_url}", 'INFO')
|
||||
|
||||
# ===== 步骤3: 处理登录 =====
|
||||
print("\n" + "=" * 50)
|
||||
print("步骤3: 登录处理")
|
||||
print("=" * 50)
|
||||
|
||||
# 检查是否需要登录
|
||||
try:
|
||||
login_visible = page.locator("text=登录").first.is_visible()
|
||||
if login_visible:
|
||||
log("[OK] 检测到登录页面", 'SUCCESS')
|
||||
|
||||
# 尝试捕获二维码
|
||||
capture_qr_code(page)
|
||||
|
||||
# 显示登录说明
|
||||
display_qr_info()
|
||||
|
||||
# 等待用户登录
|
||||
if not wait_for_login(page, timeout=180): # 3分钟超时
|
||||
log("登录失败或超时", 'ERROR')
|
||||
if ask_yes_no("是否要重新尝试?"):
|
||||
log("请重新扫码登录...", 'INFO')
|
||||
if wait_for_login(page, timeout=180):
|
||||
log("[OK] 登录成功", 'SUCCESS')
|
||||
else:
|
||||
log("登录仍然失败", 'ERROR')
|
||||
return
|
||||
else:
|
||||
log("[OK] 登录成功", 'SUCCESS')
|
||||
|
||||
else:
|
||||
log("[OK] 未检测到登录页面,可能已经登录", 'SUCCESS')
|
||||
except Exception as e:
|
||||
log(f"检查登录状态时出错: {str(e)}", 'WARNING')
|
||||
|
||||
pause("登录处理完成,请确认是否已进入文档页面")
|
||||
|
||||
# ===== 步骤4: 验证文档加载 =====
|
||||
print("\n" + "=" * 50)
|
||||
print("步骤4: 验证文档加载")
|
||||
print("=" * 50)
|
||||
|
||||
current_url = page.url
|
||||
log(f"当前URL: {current_url}", 'INFO')
|
||||
|
||||
if "kdocs.cn" in current_url and "/spreadsheet/" in current_url:
|
||||
log("[OK] 已成功进入金山文档表格", 'SUCCESS')
|
||||
else:
|
||||
log("⚠ 当前不在金山文档表格页面", 'WARNING')
|
||||
log("请确认是否已正确登录", 'INFO')
|
||||
|
||||
# 等待页面完全加载
|
||||
log("等待5秒让表格完全加载...", 'INFO')
|
||||
time.sleep(5)
|
||||
|
||||
# 检查表格元素
|
||||
try:
|
||||
canvas_count = page.locator("canvas").count()
|
||||
log(f"[OK] 检测到 {canvas_count} 个canvas元素", 'SUCCESS')
|
||||
|
||||
if canvas_count > 0:
|
||||
log("[OK] 表格元素正常加载", 'SUCCESS')
|
||||
else:
|
||||
log("⚠ 未检测到表格元素,可能页面还在加载", 'WARNING')
|
||||
except Exception as e:
|
||||
log(f"检查表格元素时出错: {str(e)}", 'WARNING')
|
||||
|
||||
pause("文档验证完成,请确认表格是否正常显示")
|
||||
|
||||
# ===== 步骤5: 表格读取测试 =====
|
||||
print("\n" + "=" * 50)
|
||||
print("步骤5: 表格读取测试")
|
||||
print("=" * 50)
|
||||
|
||||
# 尝试读取名称框
|
||||
try:
|
||||
log("尝试定位名称框...", 'INFO')
|
||||
name_box = page.locator("input.edit-box").first
|
||||
if name_box.is_visible():
|
||||
value = name_box.input_value()
|
||||
log(f"[OK] 名称框可见,当前值: '{value}'", 'SUCCESS')
|
||||
else:
|
||||
log("⚠ 名称框不可见", 'WARNING')
|
||||
except Exception as e:
|
||||
log(f"读取名称框失败: {str(e)}", 'WARNING')
|
||||
|
||||
# 尝试读取当前单元格
|
||||
try:
|
||||
log("尝试读取当前单元格内容...", 'INFO')
|
||||
# 尝试点击网格
|
||||
canvases = page.locator("canvas").all()
|
||||
if canvases:
|
||||
box = canvases[0].bounding_box()
|
||||
if box:
|
||||
page.mouse.click(box['x'] + box['width'] / 2, box['y'] + box['height'] / 2)
|
||||
time.sleep(0.5)
|
||||
log("[OK] 已点击网格", 'SUCCESS')
|
||||
except Exception as e:
|
||||
log(f"点击网格失败: {str(e)}", 'WARNING')
|
||||
|
||||
pause("表格读取测试完成")
|
||||
|
||||
# ===== 步骤6: 人员搜索测试 =====
|
||||
print("\n" + "=" * 50)
|
||||
print("步骤6: 人员搜索测试")
|
||||
print("=" * 50)
|
||||
|
||||
test_name = input("请输入要搜索的姓名 (默认: 张三): ").strip()
|
||||
if not test_name:
|
||||
test_name = "张三"
|
||||
|
||||
log(f"搜索姓名: {test_name}", 'INFO')
|
||||
|
||||
try:
|
||||
log("执行搜索操作...", 'INFO')
|
||||
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("搜索测试完成")
|
||||
|
||||
# ===== 步骤7: 图片上传测试 =====
|
||||
print("\n" + "=" * 50)
|
||||
print("步骤7: 图片上传测试 (可选)")
|
||||
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:
|
||||
log("执行上传流程...", 'INFO')
|
||||
|
||||
# 导航到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] 文件上传命令已发送")
|
||||
|
||||
log("等待上传完成...", 'INFO')
|
||||
time.sleep(3)
|
||||
|
||||
log("[OK] 图片上传测试完成", 'SUCCESS')
|
||||
log("请检查浏览器窗口,确认图片是否成功上传到D3单元格", 'INFO')
|
||||
|
||||
except Exception as e:
|
||||
log(f"✗ 图片上传测试失败: {str(e)}", 'ERROR')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
pause("图片上传测试完成")
|
||||
|
||||
# ===== 测试完成 =====
|
||||
print("\n" + "=" * 70)
|
||||
log("🎉 所有测试完成!", 'SUCCESS')
|
||||
print("=" * 70)
|
||||
print()
|
||||
print("测试结果汇总:")
|
||||
print(" [[OK]] 浏览器启动")
|
||||
print(" [[OK]] 文档打开")
|
||||
print(" [[OK]] 登录处理")
|
||||
print(" [[OK]] 文档加载验证")
|
||||
print(" [[OK]] 表格读取")
|
||||
print(" [[OK]] 人员搜索")
|
||||
if ask_yes_no("是否执行了图片上传?"):
|
||||
print(" [[OK]] 图片上传")
|
||||
print()
|
||||
print("浏览器窗口将保持打开状态")
|
||||
print("您可以手动关闭浏览器窗口来结束测试")
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user