提交 6c8b339c authored 作者: 陈泽健's avatar 陈泽健

feat(executor): 新增3种断言类型+协议弹窗修复

- 新增断言: element_exists、element_count_min、url_contains
- 重构协议弹窗处理: 6级选择器回退+3种点击方式
- 更新协议确认步骤使用多选择器
- 新增协议弹窗验证脚本
- 更新交接文档(56个用例)
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 b0d1f92a
@echo off @echo off
cd /d C:\PycharmData\ubains-module-test\platform-auto-test cd /d E:\GithubData\ubains-module-test\platform-auto-test
C:\nvm4w\nodejs\claude.cmd --permission-mode bypassPermissions E:\nodejs\claude.cmd --permission-mode bypassPermissions
\ No newline at end of file
此差异已折叠。
...@@ -258,16 +258,92 @@ class PlaywrightExecutor: ...@@ -258,16 +258,92 @@ class PlaywrightExecutor:
self._page.wait_for_timeout(3000) self._page.wait_for_timeout(3000)
# 处理协议弹窗 # === 等待协议弹窗出现(最多等待 10 秒) ===
dialogs = self._page.query_selector_all('.el-dialog button, [role="dialog"] button') try:
for db in dialogs: self._page.wait_for_selector('.el-dialog:visible', timeout=10000)
text = (db.inner_text() or '').strip() logger.debug("协议弹窗已出现")
if ('确定' in text or '同意' in text) and db.is_visible(): except Exception as e:
db.click() logger.warning(f"等待协议弹窗超时(可能无弹窗): {e}")
logger.debug(f"点击协议确认: {text}")
break # === 勾选协议复选框(如有) ===
checkbox_selectors = [
'.el-dialog .el-checkbox__original',
'.el-dialog input[type="checkbox"]',
'.el-checkbox__original',
]
for cb_sel in checkbox_selectors:
try:
cb = self._page.query_selector(cb_sel)
if cb and cb.is_visible():
try:
if not cb.is_checked():
cb.click()
logger.debug(f"勾选协议复选框: {cb_sel}")
self._page.wait_for_timeout(500)
except Exception as e:
logger.debug(f"复选框操作失败(可能不需要): {e}")
break
except Exception:
continue
self._page.wait_for_timeout(5000) # === 多选择器 + 多种点击方式回退 ===
confirm_selectors = [
".el-dialog .el-dialog__body button:has-text('确定')",
".el-dialog button.el-button--primary:has-text('确定')",
".el-dialog button:has-text('确定')",
".el-dialog button:has-text('同意')",
".el-dialog button:has-text('阅读并同意')",
"[role='dialog'] button:has-text('确定')",
]
clicked = False
for sel in confirm_selectors:
try:
btn = self._page.query_selector(sel)
if btn and btn.is_visible():
# 方式1:普通点击
try:
btn.click(timeout=5000)
logger.debug(f"协议确认-普通点击成功: {sel}")
clicked = True
break
except Exception as e1:
logger.debug(f"协议确认-普通点击失败: {e1}")
# 方式2:dispatch_event
try:
btn.dispatch_event('click')
logger.debug(f"协议确认-dispatch_event成功: {sel}")
clicked = True
break
except Exception as e2:
logger.debug(f"协议确认-dispatch_event失败: {e2}")
# 方式3:JS click
try:
self._page.evaluate('(el) => el.click()', btn)
logger.debug(f"协议确认-JS click成功: {sel}")
clicked = True
break
except Exception as e3:
logger.debug(f"协议确认-JS click失败: {e3}")
except Exception as e:
logger.debug(f"选择器 {sel} 查询失败: {e}")
if not clicked:
logger.warning("所有协议弹窗点击方式均失败,尝试最后手段:键盘 Enter")
try:
self._page.keyboard.press('Enter')
logger.debug("协议确认-键盘 Enter")
except Exception as e:
logger.warning(f"键盘 Enter 也失败: {e}")
# === 等待弹窗消失 ===
try:
self._page.wait_for_selector('.el-dialog', state='hidden', timeout=5000)
logger.debug("协议弹窗已关闭")
except Exception as e:
logger.debug(f"等待弹窗关闭超时(可能已关闭): {e}")
self._page.wait_for_timeout(2000)
# 改进的登录成功判断:检查页面是否有登录表单或用户名显示 # 改进的登录成功判断:检查页面是否有登录表单或用户名显示
# 登录成功标志:页面不再有登录表单,或者显示了用户名 # 登录成功标志:页面不再有登录表单,或者显示了用户名
...@@ -1218,7 +1294,7 @@ class PlaywrightExecutor: ...@@ -1218,7 +1294,7 @@ class PlaywrightExecutor:
if total != expected_count: if total != expected_count:
raise AssertionError(f"✗ 断言失败: 期望数量 {expected_count},实际 {total}") raise AssertionError(f"✗ 断言失败: 期望数量 {expected_count},实际 {total}")
elif assert_type == "url": elif assert_type == "url_contains":
current_url = self._page.url current_url = self._page.url
if expected_value not in current_url: if expected_value not in current_url:
raise AssertionError(f"✗ 断言失败: 当前URL '{current_url}' 不包含 '{expected_value}'") raise AssertionError(f"✗ 断言失败: 当前URL '{current_url}' 不包含 '{expected_value}'")
...@@ -1228,6 +1304,37 @@ class PlaywrightExecutor: ...@@ -1228,6 +1304,37 @@ class PlaywrightExecutor:
if expected_value not in current_title: if expected_value not in current_title:
raise AssertionError(f"✗ 断言失败: 当前标题 '{current_title}' 不包含 '{expected_value}'") raise AssertionError(f"✗ 断言失败: 当前标题 '{current_title}' 不包含 '{expected_value}'")
elif assert_type == "element_exists":
# 验证元素存在(不要求可见,只要 DOM 中存在即可)
if not selectors:
raise AssertionError("断言 element_exists 缺少选择器")
found = False
for selector in selectors:
try:
el = self._page.query_selector(selector)
if el is not None:
found = True
break
except Exception:
continue
if not found:
raise AssertionError(f"✗ 断言失败: 元素不存在: {selectors}")
elif assert_type == "element_count_min":
# 验证元素数量 >= expected
if not selectors:
raise AssertionError("断言 element_count_min 缺少选择器")
min_count = int(expected_value)
total = 0
for selector in selectors:
try:
elements = self._page.query_selector_all(selector)
total += len(elements)
except Exception:
continue
if total < min_count:
raise AssertionError(f"✗ 断言失败: 期望至少 {min_count} 个元素,实际 {total} 个: {selectors}")
else: else:
raise ValueError(f"不支持的断言类型: {assert_type}") raise ValueError(f"不支持的断言类型: {assert_type}")
......
...@@ -200,7 +200,15 @@ LOGIN_CONFIG = { ...@@ -200,7 +200,15 @@ LOGIN_CONFIG = {
AGREE_STEP = { AGREE_STEP = {
"name": "确认同意协议弹窗", "name": "确认同意协议弹窗",
"action": "click", "action": "click",
"params": {"selector": 'button:has-text("确定")'}, "params": {
"selectors": [
".el-dialog button.el-button--primary:has-text('确定')",
".el-dialog button:has-text('确定')",
".el-dialog button:has-text('同意')",
".el-dialog button:has-text('阅读并同意')",
"button:has-text('确定')",
]
},
} }
......
"""
测试协议弹窗点击修复
运行方式: cd backend && python test_protocol_dialog.py
验证项:
1. 协议弹窗等待
2. 复选框勾选
3. 多选择器回退
4. 多点击方式回退
"""
import sys
import os
# 添加 backend 目录到路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from app.executors.playwright_executor import PlaywrightExecutor
def test_login_with_protocol_dialog():
"""测试登录流程中的协议弹窗处理"""
executor = PlaywrightExecutor(
config={
"headless": False, # 非无头模式,方便观察
"timeout": 30000,
}
)
try:
print("=" * 60)
print("测试协议弹窗点击修复")
print("=" * 60)
# 启动执行器
print("\n1. 启动 Playwright 执行器...")
executor.start()
print(" ✓ 执行器启动成功")
# 执行自动登录
print("\n2. 执行自动登录...")
print(" - 访问登录页")
print(" - 填写账号密码验证码")
print(" - 点击登录按钮")
print(" - 等待协议弹窗")
print(" - 勾选复选框(如有)")
print(" - 尝试多种点击方式")
success = executor.do_login()
if success:
print("\n ✓ 登录成功!")
print(f" 当前 URL: {executor._page.url}")
print(f" 登录状态: _is_logged_in = {executor._is_logged_in}")
else:
print("\n ✗ 登录失败")
print(" 请检查日志输出,确认协议弹窗处理情况")
return success
except Exception as e:
print(f"\n✗ 测试异常: {e}")
import traceback
traceback.print_exc()
return False
finally:
print("\n3. 停止执行器...")
executor.stop()
print(" ✓ 执行器已停止")
if __name__ == "__main__":
result = test_login_with_protocol_dialog()
print("\n" + "=" * 60)
if result:
print("测试结果: ✓ 通过")
else:
print("测试结果: ✗ 失败")
print("=" * 60)
sys.exit(0 if result else 1)
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论