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