🧹 清理不必要的文件,保持仓库整洁

 删除的文件:
- 测试文件 (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:
zsglpt Optimizer
2026-01-16 17:44:04 +08:00
parent 7e9a772104
commit 803fe436d3
25 changed files with 119 additions and 5322 deletions

173
.gitignore vendored
View File

@@ -1,75 +1,140 @@
# 浏览器二进制文件 # Python
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缓存
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
*.class *$py.class
*.so *.so
.Python .Python
.pytest_cache/ build/
.ruff_cache/ develop-eggs/
.mypy_cache/ dist/
.coverage downloads/
coverage.xml 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/ 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/ env/
venv/ venv/
ENV/ ENV/
env.bak/
venv.bak/
# 环境变量文件(包含敏感信息) # Spyder project settings
.env .spyderproject
.spyproject
# Docker volumes # Rope project settings
volumes/ .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 # IDE
.vscode/ .vscode/
.idea/ .idea/
*.swp *.swp
*.swo *.swo
*~
# 系统文件 # OS
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# 临时文件 # Temporary files
*.tmp *.tmp
*.bak *.temp
*.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.*

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

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

View File

@@ -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()

View File

@@ -1,10 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 金山文档测试工具 (异步版本)
echo ========================================
echo.
echo 正在启动异步版本...
echo.
python kdocs_async_test.py
pause

View File

@@ -1,10 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 金山文档测试工具 (完整自动登录版)
echo ========================================
echo.
echo 正在启动完整自动登录版本...
echo.
python test_auto_login.py
pause

View File

@@ -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

View File

@@ -1,10 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 金山文档安全测试工具
echo ========================================
echo.
echo 正在启动UI安全测试工具...
echo.
python kdocs_safety_test.py
pause

View File

@@ -1,10 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 金山文档安全测试工具 (修复版)
echo ========================================
echo.
echo 正在启动线程安全版本...
echo.
python kdocs_safety_test_fixed.py
pause

View File

@@ -1,10 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 金山文档测试工具 (最简版)
echo ========================================
echo.
echo 正在启动最简版本...
echo.
python simple_test.py
pause

View File

@@ -1,10 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 金山文档测试工具 (同步线程版)
echo ========================================
echo.
echo 正在启动同步线程版本...
echo.
python kdocs_sync_test.py
pause

View File

@@ -1,10 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 金山文档上传优化测试工具
echo ========================================
echo.
echo 正在启动测试工具...
echo.
python test_runner.py
pause

View File

@@ -1,10 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo 金山文档测试工具 (支持登录版)
echo ========================================
echo.
echo 正在启动支持登录的测试工具...
echo.
python test_with_login.py
pause

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()