diff --git a/.gitignore b/.gitignore index 4151a1b..857de1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,75 +1,140 @@ -# 浏览器二进制文件 -playwright/ -ms-playwright/ - -# 数据库文件(敏感数据) -data/*.db -data/*.db-shm -data/*.db-wal -data/*.backup* -data/secret_key.txt -data/update/ - -# Cookies(敏感用户凭据) -data/cookies/ - -# 日志文件 -logs/ -*.log - -# 截图文件 -截图/ - -# Python缓存 +# Python __pycache__/ *.py[cod] -*.class +*$py.class *.so .Python -.pytest_cache/ -.ruff_cache/ -.mypy_cache/ -.coverage -coverage.xml +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv env/ venv/ ENV/ +env.bak/ +venv.bak/ -# 环境变量文件(包含敏感信息) -.env +# Spyder project settings +.spyderproject +.spyproject -# Docker volumes -volumes/ +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Project specific +data/*.db +data/*.db-shm +data/*.db-wal +logs/ +screenshots/ +*.png +*.jpg +*.jpeg +*.gif +*.bmp +*.ico +*.pdf +qr_code_*.png +test_*.py +start_*.bat +temp_*.py +kdocs_*test*.py +simple_test.py # IDE .vscode/ .idea/ *.swp *.swo +*~ -# 系统文件 +# OS .DS_Store Thumbs.db -# 临时文件 +# Temporary files *.tmp -*.bak -*.backup - -# 部署脚本(含服务器信息) -deploy_*.sh -verify_*.sh -deploy.sh - -# 内部文档 -docs/ - -# 前端依赖(体积大,不应入库) -node_modules/ -app-frontend/node_modules/ -admin-frontend/node_modules/ - -# Local data -data/ -docker-compose.yml.bak.* +*.temp diff --git a/kdocs_async_test.py b/kdocs_async_test.py deleted file mode 100644 index 83ea811..0000000 --- a/kdocs_async_test.py +++ /dev/null @@ -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() diff --git a/kdocs_safety_test.py b/kdocs_safety_test.py deleted file mode 100644 index 9cd3c8f..0000000 --- a/kdocs_safety_test.py +++ /dev/null @@ -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() diff --git a/kdocs_safety_test_fixed.py b/kdocs_safety_test_fixed.py deleted file mode 100644 index 8f4f6e1..0000000 --- a/kdocs_safety_test_fixed.py +++ /dev/null @@ -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() diff --git a/kdocs_sync_test.py b/kdocs_sync_test.py deleted file mode 100644 index 3e9bbcd..0000000 --- a/kdocs_sync_test.py +++ /dev/null @@ -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() diff --git a/qr_code_0.png b/qr_code_0.png deleted file mode 100644 index dcae9fe..0000000 Binary files a/qr_code_0.png and /dev/null differ diff --git a/qr_code_canvas_2.png b/qr_code_canvas_2.png deleted file mode 100644 index 7ca0921..0000000 Binary files a/qr_code_canvas_2.png and /dev/null differ diff --git a/screenshots/test_simple.png b/screenshots/test_simple.png deleted file mode 100644 index 35ccad3..0000000 Binary files a/screenshots/test_simple.png and /dev/null differ diff --git a/simple_test.py b/simple_test.py deleted file mode 100644 index 1768139..0000000 --- a/simple_test.py +++ /dev/null @@ -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() diff --git a/start_async_test.bat b/start_async_test.bat deleted file mode 100644 index 168ecce..0000000 --- a/start_async_test.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo 金山文档测试工具 (异步版本) -echo ======================================== -echo. -echo 正在启动异步版本... -echo. -python kdocs_async_test.py -pause diff --git a/start_auto_login.bat b/start_auto_login.bat deleted file mode 100644 index c2571e0..0000000 --- a/start_auto_login.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo 金山文档测试工具 (完整自动登录版) -echo ======================================== -echo. -echo 正在启动完整自动登录版本... -echo. -python test_auto_login.py -pause diff --git a/start_fixed_auto_login.bat b/start_fixed_auto_login.bat deleted file mode 100644 index a0798da..0000000 --- a/start_fixed_auto_login.bat +++ /dev/null @@ -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 diff --git a/start_safety_test.bat b/start_safety_test.bat deleted file mode 100644 index 150d960..0000000 --- a/start_safety_test.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo 金山文档安全测试工具 -echo ======================================== -echo. -echo 正在启动UI安全测试工具... -echo. -python kdocs_safety_test.py -pause diff --git a/start_safety_test_fixed.bat b/start_safety_test_fixed.bat deleted file mode 100644 index d2dc8a8..0000000 --- a/start_safety_test_fixed.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo 金山文档安全测试工具 (修复版) -echo ======================================== -echo. -echo 正在启动线程安全版本... -echo. -python kdocs_safety_test_fixed.py -pause diff --git a/start_simple_test.bat b/start_simple_test.bat deleted file mode 100644 index 3d04f62..0000000 --- a/start_simple_test.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo 金山文档测试工具 (最简版) -echo ======================================== -echo. -echo 正在启动最简版本... -echo. -python simple_test.py -pause diff --git a/start_sync_test.bat b/start_sync_test.bat deleted file mode 100644 index f53fd76..0000000 --- a/start_sync_test.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo 金山文档测试工具 (同步线程版) -echo ======================================== -echo. -echo 正在启动同步线程版本... -echo. -python kdocs_sync_test.py -pause diff --git a/start_test.bat b/start_test.bat deleted file mode 100644 index a0b5c6a..0000000 --- a/start_test.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo 金山文档上传优化测试工具 -echo ======================================== -echo. -echo 正在启动测试工具... -echo. -python test_runner.py -pause diff --git a/start_test_with_login.bat b/start_test_with_login.bat deleted file mode 100644 index 9aeaf85..0000000 --- a/start_test_with_login.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -chcp 65001 >nul -echo ======================================== -echo 金山文档测试工具 (支持登录版) -echo ======================================== -echo. -echo 正在启动支持登录的测试工具... -echo. -python test_with_login.py -pause diff --git a/temp_fix_screenshot.py b/temp_fix_screenshot.py deleted file mode 100644 index a26a0f9..0000000 --- a/temp_fix_screenshot.py +++ /dev/null @@ -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() diff --git a/test_auto_login.py b/test_auto_login.py deleted file mode 100644 index 19fc300..0000000 --- a/test_auto_login.py +++ /dev/null @@ -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() diff --git a/test_no_ui.py b/test_no_ui.py deleted file mode 100644 index 8ad7146..0000000 --- a/test_no_ui.py +++ /dev/null @@ -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() diff --git a/test_runner.py b/test_runner.py deleted file mode 100644 index 8f8aea9..0000000 --- a/test_runner.py +++ /dev/null @@ -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() diff --git a/test_screenshot_functionality.py b/test_screenshot_functionality.py deleted file mode 100644 index e00418b..0000000 --- a/test_screenshot_functionality.py +++ /dev/null @@ -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() diff --git a/test_sequential.py b/test_sequential.py deleted file mode 100644 index bbbc8cc..0000000 --- a/test_sequential.py +++ /dev/null @@ -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() diff --git a/test_with_login.py b/test_with_login.py deleted file mode 100644 index 4540f84..0000000 --- a/test_with_login.py +++ /dev/null @@ -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()