Skip to content
项目
群组
代码片段
帮助
正在加载...
帮助
为 GitLab 提交贡献
登录
切换导航
U
ubains-module-test
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
1
议题
1
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
计划
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
郑晓兵
ubains-module-test
Commits
588166f7
提交
588166f7
authored
3月 06, 2026
作者:
陈泽健
浏览文件
操作
浏览文件
下载
差异文件
Merge remote-tracking branch 'origin/develop' into develop
上级
7b6aaccd
ed4148a8
隐藏空白字符变更
内嵌
并排
正在显示
29 个修改的文件
包含
6617 行增加
和
1 行删除
+6617
-1
settings.local.json
.claude/settings.local.json
+10
-1
README.md
AuxiliaryTool/TestCaseGenerator/README.md
+191
-0
browser_operator.py
AuxiliaryTool/TestCaseGenerator/browser_operator.py
+580
-0
module_config.json
AuxiliaryTool/TestCaseGenerator/config/module_config.json
+7
-0
system_config.json
AuxiliaryTool/TestCaseGenerator/config/system_config.json
+13
-0
config_manager.py
AuxiliaryTool/TestCaseGenerator/config_manager.py
+299
-0
demo_testcase_generation.py
AuxiliaryTool/TestCaseGenerator/demo_testcase_generation.py
+266
-0
element_locator.py
AuxiliaryTool/TestCaseGenerator/element_locator.py
+400
-0
login_handler.py
AuxiliaryTool/TestCaseGenerator/login_handler.py
+450
-0
main.py
AuxiliaryTool/TestCaseGenerator/main.py
+295
-0
mcp_wrapper.py
AuxiliaryTool/TestCaseGenerator/mcp_wrapper.py
+288
-0
navigation_handler.py
AuxiliaryTool/TestCaseGenerator/navigation_handler.py
+275
-0
operation_executor.py
AuxiliaryTool/TestCaseGenerator/operation_executor.py
+585
-0
test_mcp.py
AuxiliaryTool/TestCaseGenerator/test_mcp.py
+248
-0
test_screenshot.png
AuxiliaryTool/TestCaseGenerator/test_screenshot.png
+0
-0
testcase_generator.py
AuxiliaryTool/TestCaseGenerator/testcase_generator.py
+261
-0
__init__.py
AuxiliaryTool/TestCaseGenerator/utils/__init__.py
+10
-0
constants.py
AuxiliaryTool/TestCaseGenerator/utils/constants.py
+94
-0
logger.py
AuxiliaryTool/TestCaseGenerator/utils/logger.py
+72
-0
_PRD_自动化测试用例生成需求文档.md
Docs/PRD/自动化测试用例生成/_PRD_自动化测试用例生成需求文档.md
+289
-0
_PRD_自动化测试用例生成需求文档_计划执行.md
Docs/PRD/自动化测试用例生成/_PRD_自动化测试用例生成需求文档_计划执行.md
+878
-0
config.json
Docs/PRD/自动化测试用例生成/config.json
+19
-0
_PRD_XPATH定位器无法匹配元素_问题记录_计划执行.md
Docs/PRD/自动化测试用例生成/问题修复/_PRD_XPATH定位器无法匹配元素_问题记录_计划执行.md
+321
-0
_PRD_定位值存在多条件_问题记录.md
Docs/PRD/自动化测试用例生成/问题修复/_PRD_定位值存在多条件_问题记录.md
+241
-0
_PRD_定位值存在多条件_问题记录_计划执行.md
Docs/PRD/自动化测试用例生成/问题修复/_PRD_定位值存在多条件_问题记录_计划执行.md
+282
-0
_PRD_运行脚本无法登录系统_问题记录_.md
Docs/PRD/自动化测试用例生成/问题修复/_PRD_运行脚本无法登录系统_问题记录_.md
+48
-0
_PRD_运行脚本无法登录系统_问题记录_计划执行.md
Docs/PRD/自动化测试用例生成/问题修复/_PRD_运行脚本无法登录系统_问题记录_计划执行.md
+195
-0
_PRD_自动化生成功能测试报告.md
Docs/PRD/自动化生成功能测试报告/_PRD_自动化生成功能测试报告.md
+0
-0
新统一平台测试用例.xlsx
新统一平台/测试数据/新统一平台测试用例.xlsx
+0
-0
没有找到文件。
.claude/settings.local.json
浏览文件 @
588166f7
...
...
@@ -110,7 +110,16 @@
"Bash(del:*)"
,
"Bash(git --no-pager show HEAD:AuxiliaryTool/ScriptTool/ServiceSelfInspection/modules/ConfigIPCheck.psm1)"
,
"Bash(python:*)"
,
"Bash(git checkout:*)"
"Bash(git checkout:*)"
,
"mcp__chrome-devtools__navigate_page"
,
"mcp__chrome-devtools__take_snapshot"
,
"mcp__chrome-devtools__evaluate_script"
,
"mcp__chrome-devtools__take_screenshot"
,
"mcp__chrome-devtools__fill"
,
"mcp__chrome-devtools__click"
,
"mcp__chrome-devtools__wait_for"
,
"mcp__chrome-devtools__press_key"
,
"mcp__chrome-devtools__new_page"
]
}
}
AuxiliaryTool/TestCaseGenerator/README.md
0 → 100644
浏览文件 @
588166f7
# 自动化测试用例生成工具
通过Claude Code + MCP技术,自动访问系统并生成JSON格式的测试用例。
## 功能特点
-
自动登录系统(支持固定验证码)
-
两级菜单导航
-
智能元素定位(优先ID/Name/Class,备选XPATH)
-
支持添加/编辑/删除操作
-
自动生成JSON测试用例
## 目录结构
```
TestCaseGenerator/
├── main.py # 主程序入口
├── config_manager.py # 配置管理模块
├── browser_operator.py # 浏览器操作模块
├── login_handler.py # 登录处理模块
├── navigation_handler.py # 导航处理模块
├── element_locator.py # 元素定位模块
├── operation_executor.py # 操作执行模块
├── testcase_generator.py # 测试用例生成模块
├── config/ # 配置文件目录
│ ├── system_config.json # 系统配置
│ └── module_config.json # 模块配置
├── testcases/ # 输出测试用例目录
├── utils/ # 工具函数
│ ├── logger.py # 日志工具
│ └── constants.py # 常量定义
└── README.md # 本文件
```
## 配置文件
### system_config.json
系统配置文件,包含登录信息:
```
json
[
{
"system_type"
:
"new_platform"
,
"system_front_url"
:
"https://192.168.5.44"
,
"system_back_url"
:
"https://192.168.5.44/#/LoginAdmin"
,
"username"
:
"admin@xty"
,
"password"
:
"Ubains@4321"
,
"code"
:
"csba"
}
]
```
### module_config.json
模块配置文件,指定要生成测试用例的模块:
```
json
[
{
"module_name"
:
"区域管理"
,
"module_name_son"
:
"增值服务"
,
"module_function"
:
[
"添加"
,
"编辑"
,
"删除"
]
}
]
```
## 使用方法
### 基本用法
```
bash
cd
AuxiliaryTool/TestCaseGenerator
python main.py
```
### 指定配置文件
```
bash
python main.py
--system-config
config/system_config.json
--module-config
config/module_config.json
```
### 选择登录类型
```
bash
# 使用前台地址登录
python main.py
--login-type
front
# 使用后台地址登录(默认)
python main.py
--login-type
back
```
## 输出示例
生成的测试用例文件格式:
```
json
{
"name"
:
"区域管理_增值服务_添加"
,
"para"
:
[
{
"page"
:
"area/valueAddedService"
,
"step"
:
"点击【添加】按钮"
,
"locator_type"
:
"ID"
,
"locator_value"
:
"addButton"
,
"element_type"
:
"click"
,
"element_value"
:
""
,
"expected_result"
:
""
},
{
"page"
:
"area/valueAddedService"
,
"step"
:
"输入服务名称"
,
"locator_type"
:
"ID"
,
"locator_value"
:
"serviceName"
,
"element_type"
:
"input"
,
"element_value"
:
"测试服务"
,
"expected_result"
:
""
},
{
"page"
:
"area/valueAddedService"
,
"step"
:
"点击【确定】按钮"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//span[contains(text(),'确定')]"
,
"element_type"
:
"click"
,
"element_value"
:
""
,
"expected_result"
:
""
},
{
"page"
:
"area/valueAddedService"
,
"step"
:
"获取提示文本"
,
"locator_type"
:
"CSS_SELECTOR"
,
"locator_value"
:
".el-message__content"
,
"element_type"
:
"getTips"
,
"element_value"
:
"添加成功"
,
"expected_result"
:
"添加成功"
}
],
"platform"
:
"web"
,
"base_url"
:
"https://192.168.5.44/"
}
```
## 支持的元素类型
| element_type | 说明 |
|-------------|------|
| click | 点击按钮、链接等可点击元素 |
| input | 文本输入框 |
| select | 下拉选择框 |
| checkbox | 复选框/单选框 |
| switch | 开关控件 |
| getTips | 获取提示信息 |
## 元素定位策略
按优先级自动选择最优定位方式:
1.
**ID属性**
- 优先使用唯一ID
2.
**Name属性**
- 其次使用name属性
3.
**Class属性**
- 使用class名称
4.
**XPATH表达式**
- 作为备选方案
5.
**CSS选择器**
- 作为备选方案
## 注意事项
1.
**MCP依赖**
:本工具需要chrome-devtools MCP服务运行
2.
**验证码**
:目前使用固定验证码"csba"
3.
**测试数据**
:编辑/删除操作会自动创建和清理测试数据
4.
**日志文件**
:运行日志保存在
`logs/`
目录下
## 开发规范
-
所有代码包含中文注释
-
符合PEP 8代码规范
-
使用类型注解
-
完善的错误处理和日志记录
## 更新日志
### V1.0 (2026-03-06)
-
初始版本
-
实现核心功能模块
-
支持添加/编辑/删除操作
-
智能元素定位
-
JSON测试用例生成
## 相关文档
-
需求文档:
`Docs/PRD/自动化测试用例生成/_PRD_自动化测试用例生成需求文档.md`
-
计划执行:
`Docs/PRD/自动化测试用例生成/_PRD_自动化测试用例生成需求文档_计划执行.md`
-
代码规范:
`Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
AuxiliaryTool/TestCaseGenerator/browser_operator.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
浏览器操作模块
使用Selenium WebDriver提供浏览器操作接口
"""
import
time
import
uuid
from
typing
import
Dict
,
List
,
Optional
,
Any
from
selenium
import
webdriver
from
selenium.webdriver.common.by
import
By
from
selenium.webdriver.chrome.service
import
Service
from
selenium.webdriver.chrome.options
import
Options
from
selenium.webdriver.support.ui
import
WebDriverWait
from
selenium.webdriver.support
import
expected_conditions
as
EC
from
selenium.webdriver.remote.webelement
import
WebElement
from
webdriver_manager.chrome
import
ChromeDriverManager
from
utils.logger
import
get_logger
from
utils.constants
import
WAIT_TIMEOUT
,
WAIT_SHORT
,
MCP_TIMEOUT
class
BrowserOperator
:
"""浏览器操作器 - 使用Selenium WebDriver"""
def
__init__
(
self
,
headless
:
bool
=
False
):
"""
初始化浏览器操作器
Args:
headless: 是否使用无头模式
"""
self
.
logger
=
get_logger
(
"BrowserOperator"
)
self
.
driver
:
Optional
[
webdriver
.
Chrome
]
=
None
self
.
current_url
:
Optional
[
str
]
=
None
self
.
page_id
:
Optional
[
int
]
=
None
self
.
is_connected
=
False
self
.
_element_counter
=
0
self
.
_uid_to_element
:
Dict
[
str
,
WebElement
]
=
{}
# 初始化浏览器
self
.
_init_browser
(
headless
)
def
_init_browser
(
self
,
headless
:
bool
)
->
None
:
"""初始化Chrome浏览器"""
try
:
chrome_options
=
Options
()
if
headless
:
chrome_options
.
add_argument
(
"--headless"
)
# 常用Chrome选项
chrome_options
.
add_argument
(
"--no-sandbox"
)
chrome_options
.
add_argument
(
"--disable-dev-shm-usage"
)
chrome_options
.
add_argument
(
"--disable-gpu"
)
chrome_options
.
add_argument
(
"--window-size=1920,1080"
)
chrome_options
.
add_argument
(
"--lang=zh-CN"
)
# 忽略SSL错误
chrome_options
.
add_argument
(
"--ignore-certificate-errors"
)
chrome_options
.
add_argument
(
"--allow-running-insecure-content"
)
# 自动安装ChromeDriver
service
=
Service
(
ChromeDriverManager
()
.
install
())
self
.
driver
=
webdriver
.
Chrome
(
service
=
service
,
options
=
chrome_options
)
self
.
driver
.
implicitly_wait
(
10
)
self
.
is_connected
=
True
self
.
logger
.
info
(
"浏览器初始化成功"
)
except
Exception
as
e
:
self
.
logger
.
error
(
f
"浏览器初始化失败: {e}"
)
raise
def
open_page
(
self
,
url
:
str
,
timeout
:
int
=
MCP_TIMEOUT
)
->
bool
:
"""
打开新页面并导航到指定URL
Args:
url: 目标URL
timeout: 超时时间(毫秒)
Returns:
是否成功打开页面
"""
self
.
logger
.
info
(
f
"正在打开页面: {url}"
)
try
:
self
.
driver
.
get
(
url
)
self
.
current_url
=
self
.
driver
.
current_url
self
.
logger
.
info
(
f
"页面打开成功: {url}"
)
return
True
except
Exception
as
e
:
self
.
logger
.
error
(
f
"打开页面失败: {e}"
)
return
False
def
navigate
(
self
,
url
:
str
,
ignore_cache
:
bool
=
False
)
->
bool
:
"""
在当前页面导航到指定URL
Args:
url: 目标URL
ignore_cache: 是否忽略缓存
Returns:
是否成功导航
"""
self
.
logger
.
info
(
f
"正在导航到: {url}"
)
try
:
if
ignore_cache
:
self
.
driver
.
delete_all_cookies
()
self
.
driver
.
get
(
url
)
self
.
current_url
=
self
.
driver
.
current_url
self
.
logger
.
info
(
f
"导航成功: {url}"
)
return
True
except
Exception
as
e
:
self
.
logger
.
error
(
f
"导航失败: {e}"
)
return
False
def
take_snapshot
(
self
,
verbose
:
bool
=
False
)
->
Dict
:
"""
获取页面快照(模拟MCP格式)
Args:
verbose: 是否包含详细信息
Returns:
页面快照字典,包含元素uid和相关信息
"""
self
.
logger
.
debug
(
"正在获取页面快照"
)
try
:
elements
=
[]
self
.
_uid_to_element
.
clear
()
# 获取所有输入框
try
:
inputs
=
self
.
driver
.
find_elements
(
By
.
XPATH
,
"//input"
)
for
idx
,
elem
in
enumerate
(
inputs
):
uid
=
f
"input-{idx}"
self
.
_uid_to_element
[
uid
]
=
elem
element_info
=
self
.
_extract_element_info
(
elem
,
uid
,
"textbox"
)
elements
.
append
(
element_info
)
except
Exception
as
e
:
self
.
logger
.
debug
(
f
"获取输入框时出错: {e}"
)
# 获取所有按钮
try
:
buttons
=
self
.
driver
.
find_elements
(
By
.
XPATH
,
"//button"
)
for
idx
,
elem
in
enumerate
(
buttons
):
uid
=
f
"button-{idx}"
self
.
_uid_to_element
[
uid
]
=
elem
element_info
=
self
.
_extract_element_info
(
elem
,
uid
,
"button"
)
elements
.
append
(
element_info
)
except
Exception
as
e
:
self
.
logger
.
debug
(
f
"获取按钮时出错: {e}"
)
# 获取所有文本区域
try
:
textareas
=
self
.
driver
.
find_elements
(
By
.
XPATH
,
"//textarea"
)
for
idx
,
elem
in
enumerate
(
textareas
):
uid
=
f
"textarea-{idx}"
self
.
_uid_to_element
[
uid
]
=
elem
element_info
=
self
.
_extract_element_info
(
elem
,
uid
,
"textbox"
)
elements
.
append
(
element_info
)
except
Exception
as
e
:
self
.
logger
.
debug
(
f
"获取文本区域时出错: {e}"
)
snapshot
=
{
"elements"
:
elements
,
"url"
:
self
.
driver
.
current_url
if
self
.
driver
else
""
,
"title"
:
self
.
driver
.
title
if
self
.
driver
else
""
}
self
.
logger
.
debug
(
f
"页面快照获取成功,共 {len(elements)} 个元素"
)
return
snapshot
except
Exception
as
e
:
self
.
logger
.
error
(
f
"获取页面快照失败: {e}"
)
return
{
"elements"
:
[],
"url"
:
""
,
"title"
:
""
}
def
_extract_element_info
(
self
,
element
:
WebElement
,
uid
:
str
,
default_role
:
str
)
->
Dict
:
"""
提取元素信息
Args:
element: Selenium WebElement
uid: 元素唯一标识
default_role: 默认角色
Returns:
元素信息字典
"""
info
=
{
"uid"
:
uid
,
"role"
:
default_role
,
"name"
:
""
,
"attributes"
:
{}
}
try
:
# 获取文本内容
try
:
text
=
element
.
text
if
text
:
info
[
"name"
]
=
text
except
:
pass
# 获取属性
attributes
=
[
"id"
,
"name"
,
"type"
,
"class"
,
"placeholder"
,
"value"
,
"role"
,
"aria-label"
]
for
attr
in
attributes
:
try
:
value
=
element
.
get_attribute
(
attr
)
if
value
:
info
[
"attributes"
][
attr
]
=
value
# 如果name为空,使用placeholder作为name
if
not
info
[
"name"
]
and
attr
==
"placeholder"
:
info
[
"name"
]
=
value
except
:
pass
# 获取标签名
try
:
info
[
"attributes"
][
"tag"
]
=
element
.
tag_name
except
:
pass
except
Exception
as
e
:
self
.
logger
.
debug
(
f
"提取元素信息时出错: {e}"
)
return
info
def
find_element_by_text
(
self
,
snapshot
:
Dict
,
text
:
str
,
exact_match
:
bool
=
False
)
->
Optional
[
str
]:
"""
在页面快照中查找包含指定文本的元素
搜索范围:
1. 元素的 name 字段(可访问性名称)
2. 元素的 placeholder 属性
Args:
snapshot: 页面快照
text: 要查找的文本
exact_match: 是否精确匹配
Returns:
元素的uid,如果未找到则返回None
"""
self
.
logger
.
debug
(
f
"正在查找元素: {text}"
)
try
:
elements
=
snapshot
.
get
(
"elements"
,
[])
for
element
in
elements
:
# 策略1: 搜索 name 字段
element_text
=
element
.
get
(
"name"
,
""
)
if
exact_match
:
if
element_text
==
text
:
uid
=
element
.
get
(
"uid"
)
self
.
logger
.
debug
(
f
"找到元素(name匹配): {text}, uid: {uid}"
)
return
uid
else
:
if
text
in
element_text
:
uid
=
element
.
get
(
"uid"
)
self
.
logger
.
debug
(
f
"找到元素(name匹配): {text}, uid: {uid}"
)
return
uid
# 策略2: 搜索 placeholder 属性
attrs
=
element
.
get
(
"attributes"
,
{})
placeholder
=
attrs
.
get
(
"placeholder"
,
""
)
if
placeholder
:
if
exact_match
:
if
placeholder
==
text
:
uid
=
element
.
get
(
"uid"
)
self
.
logger
.
debug
(
f
"找到元素(placeholder匹配): {text}, uid: {uid}"
)
return
uid
else
:
if
text
in
placeholder
:
uid
=
element
.
get
(
"uid"
)
self
.
logger
.
debug
(
f
"找到元素(placeholder匹配): {text}, uid: {uid}"
)
return
uid
self
.
logger
.
warning
(
f
"未找到元素: {text}"
)
return
None
except
Exception
as
e
:
self
.
logger
.
error
(
f
"查找元素失败: {e}"
)
return
None
def
find_element_by_attributes
(
self
,
snapshot
:
Dict
,
**
attributes
)
->
Optional
[
str
]:
"""
通过属性查找元素
Args:
snapshot: 页面快照
**attributes: 要匹配的属性键值对,如 placeholder="用户名"
Returns:
元素的uid,如果未找到则返回None
"""
try
:
elements
=
snapshot
.
get
(
"elements"
,
[])
for
element
in
elements
:
match
=
True
for
key
,
value
in
attributes
.
items
():
attrs
=
element
.
get
(
"attributes"
,
{})
element_value
=
str
(
attrs
.
get
(
key
,
""
))
if
value
not
in
element_value
:
match
=
False
break
if
match
:
uid
=
element
.
get
(
"uid"
)
self
.
logger
.
debug
(
f
"找到匹配元素(属性: {attributes}), uid: {uid}"
)
return
uid
self
.
logger
.
warning
(
f
"未找到匹配元素的属性: {attributes}"
)
return
None
except
Exception
as
e
:
self
.
logger
.
error
(
f
"通过属性查找元素失败: {e}"
)
return
None
def
find_inputs_by_role
(
self
,
snapshot
:
Dict
,
role
:
str
=
"textbox"
)
->
List
[
Dict
]:
"""
查找所有指定角色的输入元素
Args:
snapshot: 页面快照
role: 角色,默认为textbox
Returns:
匹配的元素列表,包含uid和属性信息
"""
try
:
elements
=
snapshot
.
get
(
"elements"
,
[])
matched_elements
=
[]
for
element
in
elements
:
element_role
=
element
.
get
(
"role"
,
""
)
if
element_role
==
role
:
matched_elements
.
append
({
"uid"
:
element
.
get
(
"uid"
),
"attributes"
:
element
.
get
(
"attributes"
,
{})
})
self
.
logger
.
debug
(
f
"找到 {len(matched_elements)} 个 role='{role}' 的元素"
)
return
matched_elements
except
Exception
as
e
:
self
.
logger
.
error
(
f
"查找输入元素失败: {e}"
)
return
[]
def
click_element
(
self
,
uid
:
str
,
wait_after
:
int
=
WAIT_SHORT
)
->
bool
:
"""
点击指定元素
Args:
uid: 元素的唯一标识符
wait_after: 点击后等待时间(秒)
Returns:
是否成功点击
"""
self
.
logger
.
debug
(
f
"正在点击元素: {uid}"
)
try
:
element
=
self
.
_uid_to_element
.
get
(
uid
)
if
not
element
:
self
.
logger
.
error
(
f
"未找到元素: {uid}"
)
return
False
element
.
click
()
# 等待页面响应
if
wait_after
>
0
:
time
.
sleep
(
wait_after
)
self
.
logger
.
debug
(
f
"元素点击成功: {uid}"
)
return
True
except
Exception
as
e
:
self
.
logger
.
error
(
f
"点击元素失败: {e}"
)
return
False
def
fill_input
(
self
,
uid
:
str
,
value
:
str
,
clear_first
:
bool
=
True
)
->
bool
:
"""
填写输入框
Args:
uid: 元素的唯一标识符
value: 要填写的值
clear_first: 是否先清空输入框
Returns:
是否成功填写
"""
self
.
logger
.
debug
(
f
"正在填写输入框: {uid}, 值: {value}"
)
try
:
element
=
self
.
_uid_to_element
.
get
(
uid
)
if
not
element
:
self
.
logger
.
error
(
f
"未找到元素: {uid}"
)
return
False
if
clear_first
:
element
.
clear
()
element
.
send_keys
(
value
)
self
.
logger
.
debug
(
f
"输入框填写成功: {uid}"
)
return
True
except
Exception
as
e
:
self
.
logger
.
error
(
f
"填写输入框失败: {e}"
)
return
False
def
select_option
(
self
,
uid
:
str
,
value
:
str
)
->
bool
:
"""
在下拉选择框中选择选项
Args:
uid: 元素的唯一标识符
value: 要选择的选项值
Returns:
是否成功选择
"""
self
.
logger
.
debug
(
f
"正在选择下拉选项: {uid}, 值: {value}"
)
try
:
from
selenium.webdriver.support.select
import
Select
element
=
self
.
_uid_to_element
.
get
(
uid
)
if
not
element
:
self
.
logger
.
error
(
f
"未找到元素: {uid}"
)
return
False
select
=
Select
(
element
)
select
.
select_by_visible_text
(
value
)
self
.
logger
.
debug
(
f
"下拉选项选择成功: {uid}"
)
return
True
except
Exception
as
e
:
self
.
logger
.
error
(
f
"选择下拉选项失败: {e}"
)
return
False
def
get_url
(
self
)
->
Optional
[
str
]:
"""
获取当前页面URL
Returns:
当前页面URL
"""
try
:
if
self
.
driver
:
return
self
.
driver
.
current_url
except
:
pass
return
self
.
current_url
def
wait_for_element
(
self
,
text
:
str
,
timeout
:
int
=
WAIT_TIMEOUT
)
->
bool
:
"""
等待包含指定文本的元素出现
Args:
text: 要等待的元素文本
timeout: 超时时间(秒)
Returns:
元素是否出现
"""
self
.
logger
.
debug
(
f
"正在等待元素出现: {text}, 超时: {timeout}秒"
)
start_time
=
time
.
time
()
while
time
.
time
()
-
start_time
<
timeout
:
snapshot
=
self
.
take_snapshot
()
uid
=
self
.
find_element_by_text
(
snapshot
,
text
)
if
uid
:
self
.
logger
.
debug
(
f
"元素已出现: {text}"
)
return
True
time
.
sleep
(
1
)
self
.
logger
.
warning
(
f
"等待元素超时: {text}"
)
return
False
def
wait_for_url_contains
(
self
,
url_fragment
:
str
,
timeout
:
int
=
WAIT_TIMEOUT
)
->
bool
:
"""
等待URL包含指定片段
Args:
url_fragment: URL片段
timeout: 超时时间(秒)
Returns:
URL是否包含指定片段
"""
self
.
logger
.
debug
(
f
"正在等待URL包含: {url_fragment}"
)
start_time
=
time
.
time
()
while
time
.
time
()
-
start_time
<
timeout
:
current_url
=
self
.
get_url
()
if
current_url
and
url_fragment
in
current_url
:
self
.
logger
.
debug
(
f
"URL已包含指定片段: {url_fragment}"
)
return
True
time
.
sleep
(
1
)
self
.
logger
.
warning
(
f
"等待URL超时: {url_fragment}"
)
return
False
def
execute_script
(
self
,
script
:
str
)
->
Any
:
"""
执行JavaScript脚本
Args:
script: JavaScript脚本
Returns:
脚本执行结果
"""
self
.
logger
.
debug
(
f
"正在执行脚本: {script[:50]}..."
)
try
:
if
self
.
driver
:
result
=
self
.
driver
.
execute_script
(
script
)
self
.
logger
.
debug
(
"脚本执行成功"
)
return
result
return
None
except
Exception
as
e
:
self
.
logger
.
error
(
f
"脚本执行失败: {e}"
)
return
None
def
get_element_attributes
(
self
,
uid
:
str
)
->
Dict
:
"""
获取元素属性
Args:
uid: 元素的唯一标识符
Returns:
元素属性字典
"""
self
.
logger
.
debug
(
f
"正在获取元素属性: {uid}"
)
try
:
element
=
self
.
_uid_to_element
.
get
(
uid
)
if
not
element
:
self
.
logger
.
error
(
f
"未找到元素: {uid}"
)
return
{}
# 获取所有属性
attrs
=
{}
for
attr
in
[
"id"
,
"name"
,
"class"
,
"type"
,
"placeholder"
,
"value"
,
"role"
,
"aria-label"
]:
try
:
value
=
element
.
get_attribute
(
attr
)
if
value
:
attrs
[
attr
]
=
value
except
:
pass
self
.
logger
.
debug
(
f
"元素属性获取成功: {uid}"
)
return
attrs
except
Exception
as
e
:
self
.
logger
.
error
(
f
"获取元素属性失败: {e}"
)
return
{}
def
close
(
self
)
->
None
:
"""关闭浏览器"""
self
.
logger
.
info
(
"正在关闭浏览器"
)
try
:
if
self
.
driver
:
self
.
driver
.
quit
()
self
.
driver
=
None
self
.
is_connected
=
False
self
.
current_url
=
None
self
.
_uid_to_element
.
clear
()
self
.
logger
.
info
(
"浏览器已关闭"
)
except
Exception
as
e
:
self
.
logger
.
error
(
f
"关闭浏览器失败: {e}"
)
AuxiliaryTool/TestCaseGenerator/config/module_config.json
0 → 100644
浏览文件 @
588166f7
[
{
"module_name"
:
"区域管理"
,
"module_name_son"
:
"增值服务"
,
"module_function"
:
[
"添加"
,
"编辑"
,
"删除"
]
}
]
AuxiliaryTool/TestCaseGenerator/config/system_config.json
0 → 100644
浏览文件 @
588166f7
[
{
"system_type"
:
"new_platform"
,
"system_front_url"
:
"https://192.168.5.44"
,
"system_back_url"
:
"https://192.168.5.44/#/LoginAdmin"
,
"back_username"
:
[
"XPATH"
,
"//input[@placeholder='请输入账号或手机号或邮箱号']"
,
"admin@xty"
],
"back_password"
:
[
"XPATH"
,
"//input[@placeholder='请输入密码']"
,
"Ubains@4321"
],
"back_code"
:
[
"XPATH"
,
"//input[@placeholder='请输入图形验证码']"
,
"csba"
],
"front_username"
:
[
"XPATH"
,
"//input[@placeholder='手机号/用户名/邮箱']"
,
"admin@xty"
],
"front_password"
:
[
"XPATH"
,
"//input[@placeholder='密码']"
,
"Ubains@4321"
],
"front_code"
:
[
"XPATH"
,
"//input[@placeholder='图形验证']"
,
"csba"
]
}
]
AuxiliaryTool/TestCaseGenerator/config_manager.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
配置管理模块
负责读取、验证和管理配置文件
"""
import
json
from
pathlib
import
Path
from
typing
import
List
,
Dict
,
Optional
from
utils.logger
import
get_logger
class
ConfigManager
:
"""配置管理器"""
def
__init__
(
self
,
config_dir
:
Optional
[
Path
]
=
None
):
"""
初始化配置管理器
Args:
config_dir: 配置文件目录,默认为当前目录下的config文件夹
"""
self
.
logger
=
get_logger
(
"ConfigManager"
)
if
config_dir
is
None
:
# 默认配置目录为当前脚本目录下的config文件夹
self
.
config_dir
=
Path
(
__file__
)
.
parent
/
"config"
else
:
self
.
config_dir
=
Path
(
config_dir
)
self
.
system_config
:
Optional
[
List
[
Dict
]]
=
None
self
.
module_config
:
Optional
[
List
[
Dict
]]
=
None
def
load_system_config
(
self
,
config_file
:
str
=
"system_config.json"
)
->
List
[
Dict
]:
"""
加载系统配置文件
Args:
config_file: 配置文件名或相对/绝对路径
Returns:
系统配置列表
Raises:
FileNotFoundError: 配置文件不存在
json.JSONDecodeError: JSON格式错误
ValueError: 配置格式验证失败
"""
# 如果传入的是绝对路径,直接使用
config_path
=
Path
(
config_file
)
if
not
config_path
.
is_absolute
():
# 相对路径:相对于config目录
config_path
=
self
.
config_dir
/
Path
(
config_file
)
.
name
if
not
config_path
.
exists
():
raise
FileNotFoundError
(
f
"系统配置文件不存在: {config_path}"
)
self
.
logger
.
info
(
f
"正在加载系统配置文件: {config_path}"
)
try
:
with
open
(
config_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
config
=
json
.
load
(
f
)
except
json
.
JSONDecodeError
as
e
:
self
.
logger
.
error
(
f
"系统配置文件JSON格式错误: {e}"
)
raise
# 验证配置格式
if
not
self
.
_validate_system_config
(
config
):
raise
ValueError
(
"系统配置格式验证失败"
)
self
.
system_config
=
config
self
.
logger
.
info
(
f
"系统配置加载成功,共 {len(config)} 个系统配置"
)
return
config
def
load_module_config
(
self
,
config_file
:
str
=
"module_config.json"
)
->
List
[
Dict
]:
"""
加载模块配置文件
Args:
config_file: 配置文件名或相对/绝对路径
Returns:
模块配置列表
Raises:
FileNotFoundError: 配置文件不存在
json.JSONDecodeError: JSON格式错误
ValueError: 配置格式验证失败
"""
# 如果传入的是绝对路径,直接使用
config_path
=
Path
(
config_file
)
if
not
config_path
.
is_absolute
():
# 相对路径:相对于config目录
config_path
=
self
.
config_dir
/
Path
(
config_file
)
.
name
if
not
config_path
.
exists
():
raise
FileNotFoundError
(
f
"模块配置文件不存在: {config_path}"
)
self
.
logger
.
info
(
f
"正在加载模块配置文件: {config_path}"
)
try
:
with
open
(
config_path
,
'r'
,
encoding
=
'utf-8'
)
as
f
:
config
=
json
.
load
(
f
)
except
json
.
JSONDecodeError
as
e
:
self
.
logger
.
error
(
f
"模块配置文件JSON格式错误: {e}"
)
raise
# 验证配置格式
if
not
self
.
_validate_module_config
(
config
):
raise
ValueError
(
"模块配置格式验证失败"
)
self
.
module_config
=
config
self
.
logger
.
info
(
f
"模块配置加载成功,共 {len(config)} 个模块配置"
)
return
config
def
_validate_system_config
(
self
,
config
:
List
[
Dict
])
->
bool
:
"""
验证系统配置格式(更新版)
Args:
config: 系统配置字典列表
Returns:
验证是否通过
"""
if
not
isinstance
(
config
,
list
):
self
.
logger
.
error
(
"系统配置必须是列表格式"
)
return
False
# 基础必填字段
required_fields
=
[
"system_type"
,
"system_front_url"
,
"system_back_url"
]
# 登录字段(格式:["定位类型", "定位值", "输入值"])
login_fields
=
[
"back_username"
,
"back_password"
,
"back_code"
,
"front_username"
,
"front_password"
,
"front_code"
]
for
idx
,
system
in
enumerate
(
config
):
if
not
isinstance
(
system
,
dict
):
self
.
logger
.
error
(
f
"系统配置第{idx+1}项必须是字典格式"
)
return
False
# 检查必填字段
for
field
in
required_fields
:
if
field
not
in
system
:
self
.
logger
.
error
(
f
"系统配置第{idx+1}项缺少必填字段: {field}"
)
return
False
# 验证URL格式
if
not
system
[
"system_front_url"
]
.
startswith
((
"http://"
,
"https://"
)):
self
.
logger
.
error
(
f
"系统配置第{idx+1}项的system_front_url格式错误"
)
return
False
if
not
system
[
"system_back_url"
]
.
startswith
((
"http://"
,
"https://"
)):
self
.
logger
.
error
(
f
"系统配置第{idx+1}项的system_back_url格式错误"
)
return
False
# 检查登录字段格式(如果存在)
for
field
in
login_fields
:
if
field
in
system
:
field_value
=
system
[
field
]
if
not
isinstance
(
field_value
,
list
)
or
len
(
field_value
)
!=
3
:
self
.
logger
.
error
(
f
"登录字段 {field} 格式错误,应为 [定位类型, 定位值, 输入值]"
)
return
False
# 验证定位类型
locator_type
=
field_value
[
0
]
if
locator_type
not
in
[
"ID"
,
"XPATH"
,
"CSS_SELECTOR"
,
"NAME"
]:
self
.
logger
.
error
(
f
"不支持的定位类型: {locator_type}"
)
return
False
return
True
def
get_login_config
(
self
,
login_type
:
str
=
"back"
)
->
Dict
[
str
,
tuple
]:
"""
获取登录配置
Args:
login_type: 登录类型,"back"或"front"
Returns:
登录配置字典,格式:
{
"username": ("XPATH", "//input[@placeholder='...']", "admin@xty"),
"password": ("XPATH", "//input[@placeholder='...']", "password"),
"code": ("XPATH", "//input[@placeholder='...']", "csba")
}
Raises:
ValueError: 配置未加载或不支持的登录类型
"""
if
self
.
system_config
is
None
:
raise
ValueError
(
"系统配置未加载,请先调用load_system_config()"
)
system
=
self
.
system_config
[
0
]
# 使用第一个系统配置
# 根据登录类型获取配置
prefix
=
f
"{login_type}_"
login_config
=
{}
for
field
in
[
"username"
,
"password"
,
"code"
]:
config_key
=
f
"{prefix}{field}"
if
config_key
not
in
system
:
self
.
logger
.
error
(
f
"缺少登录配置: {config_key}"
)
raise
ValueError
(
f
"缺少登录配置: {config_key}"
)
config_value
=
system
[
config_key
]
# 格式:["定位类型", "定位值", "输入值"]
login_config
[
field
]
=
(
config_value
[
0
],
config_value
[
1
],
config_value
[
2
])
return
login_config
def
_validate_module_config
(
self
,
config
:
List
[
Dict
])
->
bool
:
"""
验证模块配置格式
Args:
config: 模块配置字典
Returns:
验证是否通过
"""
if
not
isinstance
(
config
,
list
):
self
.
logger
.
error
(
"模块配置必须是列表格式"
)
return
False
required_fields
=
[
"module_name"
,
"module_name_son"
,
"module_function"
]
for
idx
,
module
in
enumerate
(
config
):
if
not
isinstance
(
module
,
dict
):
self
.
logger
.
error
(
f
"模块配置第{idx+1}项必须是字典格式"
)
return
False
# 检查必填字段
for
field
in
required_fields
:
if
field
not
in
module
:
self
.
logger
.
error
(
f
"模块配置第{idx+1}项缺少必填字段: {field}"
)
return
False
# 验证module_function是列表
if
not
isinstance
(
module
[
"module_function"
],
list
):
self
.
logger
.
error
(
f
"模块配置第{idx+1}项的module_function必须是列表格式"
)
return
False
# 验证功能类型
valid_functions
=
[
"添加"
,
"编辑"
,
"删除"
]
for
func
in
module
[
"module_function"
]:
if
func
not
in
valid_functions
:
self
.
logger
.
warning
(
f
"模块配置第{idx+1}项包含未知功能类型: {func},"
f
"支持的类型: {valid_functions}"
)
return
True
def
get_system_config
(
self
,
index
:
int
=
0
)
->
Dict
:
"""
获取指定索引的系统配置
Args:
index: 配置索引,默认为0
Returns:
系统配置字典
Raises:
IndexError: 索引超出范围
ValueError: 配置未加载
"""
if
self
.
system_config
is
None
:
raise
ValueError
(
"系统配置未加载,请先调用load_system_config()"
)
if
index
<
0
or
index
>=
len
(
self
.
system_config
):
raise
IndexError
(
f
"系统配置索引超出范围: {index}"
)
return
self
.
system_config
[
index
]
def
get_module_configs
(
self
)
->
List
[
Dict
]:
"""
获取所有模块配置
Returns:
模块配置列表
Raises:
ValueError: 配置未加载
"""
if
self
.
module_config
is
None
:
raise
ValueError
(
"模块配置未加载,请先调用load_module_config()"
)
return
self
.
module_config
def
get_output_dir
(
self
)
->
Path
:
"""
获取测试用例输出目录
Returns:
输出目录路径
"""
# 输出目录为配置文件同目录下的testcases文件夹
return
self
.
config_dir
/
"testcases"
AuxiliaryTool/TestCaseGenerator/demo_testcase_generation.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
测试用例生成演示脚本
通过chrome-devtools MCP工具演示完整的测试用例生成流程
"""
import
json
import
time
from
pathlib
import
Path
from
typing
import
Dict
,
List
,
Optional
class
TestCaseDemo
:
"""测试用例生成演示类"""
def
__init__
(
self
):
"""初始化演示类"""
self
.
steps
=
[]
self
.
current_page
=
""
self
.
current_route
=
""
def
log
(
self
,
message
:
str
):
"""打印日志"""
print
(
f
"[{time.strftime('
%
H:
%
M:
%
S')}] {message}"
)
def
navigate_to_baidu
(
self
):
"""导航到百度首页"""
self
.
log
(
"步骤1: 导航到百度首页"
)
result
=
mcp__chrome_devtools__navigate_page
(
type
=
"url"
,
url
=
"https://www.baidu.com"
)
time
.
sleep
(
2
)
self
.
current_page
=
"https://www.baidu.com"
self
.
log
(
f
"✓ 成功导航到: {self.current_page}"
)
return
True
def
take_snapshot_and_analyze
(
self
):
"""获取页面快照并分析"""
self
.
log
(
"步骤2: 获取页面快照"
)
snapshot
=
mcp__chrome_devtools__take_snapshot
()
self
.
log
(
f
"✓ 快照获取成功,元素数量: {len(str(snapshot).split('uid='))}"
)
return
snapshot
def
find_search_box
(
self
,
snapshot
:
Dict
):
"""查找搜索框元素"""
self
.
log
(
"步骤3: 查找搜索框元素"
)
# 在快照中查找搜索框
snapshot_text
=
str
(
snapshot
)
# 查找搜索框uid
if
"uid=1_23 textbox"
in
snapshot_text
:
search_box_uid
=
"1_23"
self
.
log
(
f
"✓ 找到搜索框: uid={search_box_uid}"
)
# 记录步骤
self
.
add_step
(
page
=
"baidu/home"
,
step
=
"定位搜索框"
,
locator_type
=
"UID"
,
locator_value
=
"1_23"
,
element_type
=
"input"
,
element_value
=
""
)
return
search_box_uid
self
.
log
(
"✗ 未找到搜索框"
)
return
None
def
fill_search_box
(
self
,
uid
:
str
,
text
:
str
):
"""填写搜索框"""
self
.
log
(
f
"步骤4: 填写搜索框,内容: {text}"
)
# 使用JavaScript填写
script
=
f
"(text) => {{ const input = document.getElementById('kw'); if (input) {{ input.value = text; input.dispatchEvent(new Event('input', {{ bubbles: true }})); return {{success: true, value: text}}; }} return {{success: false}}; }}"
result
=
mcp__chrome_devtools__evaluate_script
(
function
=
script
,
args
=
[])
self
.
log
(
f
"✓ 填写完成: {result}"
)
# 记录步骤
self
.
add_step
(
page
=
"baidu/home"
,
step
=
f
"输入搜索内容: {text}"
,
locator_type
=
"ID"
,
locator_value
=
"kw"
,
element_type
=
"input"
,
element_value
=
text
)
return
True
def
find_search_button
(
self
,
snapshot
:
Dict
):
"""查找搜索按钮"""
self
.
log
(
"步骤5: 查找搜索按钮"
)
snapshot_text
=
str
(
snapshot
)
if
"uid=1_24 button"
in
snapshot_text
and
"百度一下"
in
snapshot_text
:
button_uid
=
"1_24"
self
.
log
(
f
"✓ 找到搜索按钮: uid={button_uid}"
)
# 记录步骤
self
.
add_step
(
page
=
"baidu/home"
,
step
=
"定位搜索按钮"
,
locator_type
=
"UID"
,
locator_value
=
"1_24"
,
element_type
=
"click"
,
element_value
=
""
)
return
button_uid
self
.
log
(
"✗ 未找到搜索按钮"
)
return
None
def
click_search_button
(
self
,
uid
:
str
):
"""点击搜索按钮"""
self
.
log
(
"步骤6: 点击搜索按钮"
)
# 使用JavaScript点击
script
=
"() => { const btn = document.getElementById('su'); if (btn) { btn.click(); return {success: true}; } return {success: false}; }"
result
=
mcp__chrome_devtools__evaluate_script
(
function
=
script
,
args
=
[])
self
.
log
(
f
"✓ 点击完成: {result}"
)
# 记录步骤
self
.
add_step
(
page
=
"baidu/home"
,
step
=
"点击【百度一下】按钮"
,
locator_type
=
"ID"
,
locator_value
=
"su"
,
element_type
=
"click"
,
element_value
=
""
)
time
.
sleep
(
2
)
return
True
def
verify_search_results
(
self
):
"""验证搜索结果页面"""
self
.
log
(
"步骤7: 验证搜索结果页面"
)
# 获取当前URL
script
=
"() => { return window.location.href; }"
result
=
mcp__chrome_devtools__evaluate_script
(
function
=
script
,
args
=
[])
self
.
log
(
f
"✓ 当前URL: {result}"
)
if
"s?"
in
result
or
"wd="
in
result
:
self
.
log
(
"✓ 成功跳转到搜索结果页面"
)
# 记录验证步骤
self
.
add_step
(
page
=
"baidu/search"
,
step
=
"验证搜索结果页面"
,
locator_type
=
"URL"
,
locator_value
=
"contains s?"
,
element_type
=
"getTips"
,
element_value
=
"搜索成功"
,
expected_result
=
"跳转到搜索结果页面"
)
return
True
return
False
def
add_step
(
self
,
page
:
str
,
step
:
str
,
locator_type
:
str
,
locator_value
:
str
,
element_type
:
str
,
element_value
:
str
,
expected_result
:
str
=
""
):
"""添加测试步骤"""
self
.
steps
.
append
({
"page"
:
page
,
"step"
:
step
,
"locator_type"
:
locator_type
,
"locator_value"
:
locator_value
,
"element_type"
:
element_type
,
"element_value"
:
element_value
,
"expected_result"
:
expected_result
})
def
generate_testcase
(
self
)
->
Dict
:
"""生成测试用例"""
self
.
log
(
"生成测试用例JSON"
)
testcase
=
{
"name"
:
"百度_首页_搜索"
,
"para"
:
self
.
steps
,
"platform"
:
"web"
,
"base_url"
:
"https://www.baidu.com"
}
return
testcase
def
save_testcase
(
self
,
testcase
:
Dict
,
output_path
:
str
):
"""保存测试用例到文件"""
self
.
log
(
f
"保存测试用例到: {output_path}"
)
output_file
=
Path
(
output_path
)
output_file
.
parent
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
with
open
(
output_file
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
json
.
dump
(
testcase
,
f
,
ensure_ascii
=
False
,
indent
=
2
)
self
.
log
(
f
"✓ 测试用例已保存"
)
def
run
(
self
):
"""运行完整演示"""
self
.
log
(
"="
*
60
)
self
.
log
(
"测试用例生成演示开始"
)
self
.
log
(
"="
*
60
)
try
:
# 1. 导航到百度
if
not
self
.
navigate_to_baidu
():
return
False
# 2. 获取快照
snapshot
=
self
.
take_snapshot_and_analyze
()
# 3. 查找搜索框
search_box_uid
=
self
.
find_search_box
(
snapshot
)
if
not
search_box_uid
:
return
False
# 4. 填写搜索框
self
.
fill_search_box
(
search_box_uid
,
"自动化测试"
)
# 5. 查找搜索按钮
button_uid
=
self
.
find_search_button
(
snapshot
)
if
not
button_uid
:
return
False
# 6. 点击搜索按钮
self
.
click_search_button
(
button_uid
)
# 7. 验证搜索结果
self
.
verify_search_results
()
# 8. 生成测试用例
testcase
=
self
.
generate_testcase
()
# 9. 保存测试用例
output_path
=
"E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator/testcases/百度_首页_搜索.json"
self
.
save_testcase
(
testcase
,
output_path
)
# 10. 打印测试用例
self
.
log
(
"
\n
生成的测试用例:"
)
self
.
log
(
"-"
*
60
)
print
(
json
.
dumps
(
testcase
,
ensure_ascii
=
False
,
indent
=
2
))
self
.
log
(
"
\n
"
+
"="
*
60
)
self
.
log
(
"演示完成!"
)
self
.
log
(
"="
*
60
)
return
True
except
Exception
as
e
:
self
.
log
(
f
"✗ 演示过程中发生错误: {e}"
)
return
False
def
main
():
"""主函数"""
demo
=
TestCaseDemo
()
success
=
demo
.
run
()
return
0
if
success
else
1
# 此脚本需要在Claude Code环境中运行,以访问MCP工具
# 使用方法:在Claude Code中执行此脚本的内容
AuxiliaryTool/TestCaseGenerator/element_locator.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
元素定位模块
负责智能检测元素定位方式,按优先级选择最优定位策略
"""
from
typing
import
Dict
,
List
,
Optional
,
Tuple
from
browser_operator
import
BrowserOperator
from
utils.logger
import
get_logger
from
utils.constants
import
LOCATOR_PRIORITIES
class
ElementLocator
:
"""元素定位器 - 智能检测元素定位方式"""
def
__init__
(
self
,
browser
:
BrowserOperator
):
"""
初始化元素定位器
Args:
browser: 浏览器操作器实例
"""
self
.
browser
=
browser
self
.
logger
=
get_logger
(
"ElementLocator"
)
def
locate_element
(
self
,
element_info
:
Dict
)
->
Dict
:
"""
定位元素并生成最优定位策略
Args:
element_info: 元素信息字典,包含uid、text等
Returns:
定位结果字典,包含locator_type、locator_value等
"""
uid
=
element_info
.
get
(
"uid"
)
if
not
uid
:
self
.
logger
.
error
(
"元素信息中缺少uid"
)
return
{}
self
.
logger
.
debug
(
f
"正在分析元素定位方式: {uid}"
)
# 获取元素属性
attributes
=
self
.
browser
.
get_element_attributes
(
uid
)
# 按优先级分析定位策略
for
strategy
in
LOCATOR_PRIORITIES
:
locator_value
=
self
.
_generate_locator_value
(
attributes
,
strategy
)
if
locator_value
:
result
=
{
"locator_type"
:
strategy
,
"locator_value"
:
locator_value
,
"backup_locators"
:
self
.
_generate_backup_locators
(
attributes
,
strategy
),
"attributes"
:
attributes
}
self
.
logger
.
debug
(
f
"选择定位策略: {strategy}, 值: {locator_value}"
)
return
result
# 如果所有策略都失败,返回空
self
.
logger
.
warning
(
f
"未能为元素生成定位策略: {uid}"
)
return
{}
def
_generate_locator_value
(
self
,
attributes
:
Dict
,
strategy
:
str
)
->
Optional
[
str
]:
"""
根据策略生成定位值
Args:
attributes: 元素属性字典
strategy: 定位策略
Returns:
定位值,生成失败返回None
"""
if
strategy
==
"ID"
:
return
self
.
_generate_id_locator
(
attributes
)
elif
strategy
==
"NAME"
:
return
self
.
_generate_name_locator
(
attributes
)
elif
strategy
==
"CLASS"
:
return
self
.
_generate_class_locator
(
attributes
)
elif
strategy
==
"XPATH"
:
return
self
.
_generate_xpath_locator
(
attributes
)
elif
strategy
==
"CSS_SELECTOR"
:
return
self
.
_generate_css_selector
(
attributes
)
return
None
def
_generate_id_locator
(
self
,
attributes
:
Dict
)
->
Optional
[
str
]:
"""
生成ID定位器
Args:
attributes: 元素属性字典
Returns:
ID定位值,如果没有ID返回None
"""
element_id
=
attributes
.
get
(
"id"
)
if
not
element_id
:
return
None
# 验证ID是否唯一(非空且不是动态生成的)
if
self
.
_is_valid_id
(
element_id
):
self
.
logger
.
debug
(
f
"使用ID定位: {element_id}"
)
return
element_id
return
None
def
_generate_name_locator
(
self
,
attributes
:
Dict
)
->
Optional
[
str
]:
"""
生成Name定位器
Args:
attributes: 元素属性字典
Returns:
Name定位值,如果没有name返回None
"""
name
=
attributes
.
get
(
"name"
)
if
not
name
:
return
None
self
.
logger
.
debug
(
f
"使用Name定位: {name}"
)
return
name
def
_generate_class_locator
(
self
,
attributes
:
Dict
)
->
Optional
[
str
]:
"""
生成Class定位器
Args:
attributes: 元素属性字典
Returns:
Class定位值,如果没有class返回None
"""
class_list
=
attributes
.
get
(
"class"
)
if
not
class_list
:
return
None
# 如果是字符串,按空格分割
if
isinstance
(
class_list
,
str
):
classes
=
class_list
.
split
()
else
:
classes
=
class_list
# 优先使用第一个class
if
classes
:
first_class
=
classes
[
0
]
.
strip
()
# 过滤掉动态生成的class(如包含数字的)
if
not
self
.
_is_dynamic_class
(
first_class
):
self
.
logger
.
debug
(
f
"使用Class定位: {first_class}"
)
return
first_class
return
None
def
_generate_xpath_locator
(
self
,
attributes
:
Dict
)
->
Optional
[
str
]:
"""
生成XPATH定位器
Args:
attributes: 元素属性字典
Returns:
XPATH定位值
"""
# 优先使用包含文本的XPATH
text
=
attributes
.
get
(
"text"
,
""
)
tag
=
attributes
.
get
(
"tag"
,
"*"
)
if
text
:
# 使用包含文本的XPATH(使用 . 而不是 text() 以匹配Element UI按钮span子元素内的文本)
xpath
=
f
"//{tag}[contains(.,'{text}')]"
self
.
logger
.
debug
(
f
"使用XPATH定位(文本): {xpath}"
)
return
xpath
# 如果有ID,使用ID的XPATH
element_id
=
attributes
.
get
(
"id"
)
if
element_id
:
xpath
=
f
"//{tag}[@id='{element_id}']"
self
.
logger
.
debug
(
f
"使用XPATH定位(ID): {xpath}"
)
return
xpath
# 如果有name,使用name的XPATH
name
=
attributes
.
get
(
"name"
)
if
name
:
xpath
=
f
"//{tag}[@name='{name}']"
self
.
logger
.
debug
(
f
"使用XPATH定位(name): {xpath}"
)
return
xpath
# 如果有placeholder,使用placeholder的XPATH
placeholder
=
attributes
.
get
(
"placeholder"
)
if
placeholder
:
xpath
=
f
"//{tag}[@placeholder='{placeholder}']"
self
.
logger
.
debug
(
f
"使用XPATH定位(placeholder): {xpath}"
)
return
xpath
# 默认使用标签名
xpath
=
f
"//{tag}"
self
.
logger
.
debug
(
f
"使用XPATH定位(标签): {xpath}"
)
return
xpath
def
_generate_css_selector
(
self
,
attributes
:
Dict
)
->
Optional
[
str
]:
"""
生成CSS选择器定位器
Args:
attributes: 元素属性字典
Returns:
CSS选择器定位值
"""
selectors
=
[]
tag
=
attributes
.
get
(
"tag"
,
"*"
)
# 添加标签名
if
tag
!=
"*"
:
selectors
.
append
(
tag
)
# 添加ID
element_id
=
attributes
.
get
(
"id"
)
if
element_id
:
selectors
.
append
(
f
"#{element_id}"
)
# 添加class
class_list
=
attributes
.
get
(
"class"
)
if
class_list
:
if
isinstance
(
class_list
,
str
):
classes
=
class_list
.
split
()
else
:
classes
=
class_list
if
classes
:
first_class
=
classes
[
0
]
.
strip
()
if
not
self
.
_is_dynamic_class
(
first_class
):
selectors
.
append
(
f
".{first_class}"
)
# 添加属性选择器
name
=
attributes
.
get
(
"name"
)
if
name
:
selectors
.
append
(
f
"[name='{name}']"
)
# 组合选择器
if
selectors
:
css_selector
=
""
.
join
(
selectors
)
self
.
logger
.
debug
(
f
"使用CSS选择器: {css_selector}"
)
return
css_selector
return
None
def
_generate_backup_locators
(
self
,
attributes
:
Dict
,
primary_strategy
:
str
)
->
List
[
Dict
]:
"""
生成备用定位方式
Args:
attributes: 元素属性字典
primary_strategy: 主定位策略
Returns:
备用定位器列表
"""
backup_locators
=
[]
for
strategy
in
LOCATOR_PRIORITIES
:
# 跳过主策略
if
strategy
==
primary_strategy
:
continue
locator_value
=
self
.
_generate_locator_value
(
attributes
,
strategy
)
if
locator_value
:
backup_locators
.
append
({
"locator_type"
:
strategy
,
"locator_value"
:
locator_value
})
return
backup_locators
def
_is_valid_id
(
self
,
element_id
:
str
)
->
bool
:
"""
验证ID是否有效(非动态生成)
Args:
element_id: 元素ID
Returns:
ID是否有效
"""
# 空值无效
if
not
element_id
:
return
False
# 动态ID通常包含数字或特殊字符模式
# 这里简单判断:不包含连续3位以上数字
import
re
if
re
.
search
(
r'\d{3,}'
,
element_id
):
return
False
return
True
def
_is_dynamic_class
(
self
,
class_name
:
str
)
->
bool
:
"""
判断class是否是动态生成的
Args:
class_name: 类名
Returns:
是否是动态class
"""
# 动态class通常包含hash或随机字符串
# 这里简单判断:包含数字或特殊下划线
if
any
(
char
in
class_name
for
char
in
[
'_'
,
'-'
]):
# 如果同时包含数字,很可能是动态生成的
if
any
(
char
.
isdigit
()
for
char
in
class_name
):
return
True
return
False
def
analyze_element_from_snapshot
(
self
,
snapshot
:
Dict
,
text
:
str
)
->
Dict
:
"""
从页面快照中分析元素
Args:
snapshot: 页面快照
text: 元素文本
Returns:
元素分析结果
"""
self
.
logger
.
debug
(
f
"正在从快照分析元素: {text}"
)
# 查找元素
uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
text
)
if
not
uid
:
self
.
logger
.
warning
(
f
"未找到元素: {text}"
)
return
{}
# 获取元素属性
attributes
=
self
.
browser
.
get_element_attributes
(
uid
)
# 分析定位策略
element_info
=
{
"uid"
:
uid
,
"text"
:
text
,
"attributes"
:
attributes
}
return
self
.
locate_element
(
element_info
)
def
determine_element_type
(
self
,
attributes
:
Dict
)
->
Optional
[
str
]:
"""
确定元素类型
Args:
attributes: 元素属性字典
Returns:
元素类型(click/input/select/checkbox/switch/getTips/getText)
- getTips: 弹窗提示文本
- getText: 列表搜索后文本获取
"""
tag
=
attributes
.
get
(
"tag"
,
""
)
role
=
attributes
.
get
(
"role"
,
""
)
input_type
=
attributes
.
get
(
"type"
,
""
)
# 输入框
if
tag
==
"input"
or
tag
==
"textarea"
:
if
input_type
==
"checkbox"
:
return
"checkbox"
elif
input_type
==
"radio"
:
return
"checkbox"
else
:
return
"input"
# 下拉选择框
if
tag
==
"select"
:
return
"select"
# 按钮或链接
if
tag
==
"button"
or
tag
==
"a"
:
return
"click"
# 根据ARIA角色判断
if
role
==
"button"
:
return
"click"
elif
role
==
"textbox"
:
return
"input"
elif
role
==
"combobox"
:
return
"select"
elif
role
==
"switch"
:
return
"switch"
elif
role
==
"checkbox"
:
return
"checkbox"
# 默认为可点击元素
return
"click"
AuxiliaryTool/TestCaseGenerator/login_handler.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
登录处理模块
负责处理系统登录流程(使用配置文件中的定位器)
"""
from
typing
import
Dict
,
Optional
,
List
,
Tuple
from
browser_operator
import
BrowserOperator
from
utils.logger
import
get_logger
from
utils.constants
import
WAIT_TIMEOUT
,
WAIT_SHORT
from
config_manager
import
ConfigManager
class
LoginHandler
:
"""登录处理器"""
def
__init__
(
self
,
browser
:
BrowserOperator
,
config_manager
:
ConfigManager
):
"""
初始化登录处理器
Args:
browser: 浏览器操作器实例
config_manager: 配置管理器实例
"""
self
.
browser
=
browser
self
.
config_manager
=
config_manager
self
.
logger
=
get_logger
(
"LoginHandler"
)
def
login
(
self
,
url
:
str
,
login_type
:
str
=
"back"
)
->
bool
:
"""
执行登录操作(使用配置文件中的定位器)
Args:
url: 登录页面URL
login_type: 登录类型,"back"或"front"
Returns:
是否登录成功
"""
self
.
logger
.
info
(
f
"开始登录流程,登录类型: {login_type}"
)
# 1. 获取登录配置
try
:
login_config
=
self
.
config_manager
.
get_login_config
(
login_type
)
except
ValueError
as
e
:
self
.
logger
.
error
(
f
"获取登录配置失败: {e}"
)
return
False
username_config
=
login_config
[
"username"
]
password_config
=
login_config
[
"password"
]
code_config
=
login_config
[
"code"
]
# 2. 打开登录页面
self
.
logger
.
info
(
"正在打开登录页面..."
)
if
not
self
.
browser
.
open_page
(
url
):
self
.
logger
.
error
(
"打开登录页面失败"
)
return
False
# 等待页面加载
import
time
time
.
sleep
(
WAIT_SHORT
)
# 3. 获取页面快照
snapshot
=
self
.
browser
.
take_snapshot
()
# 4. 使用配置的定位器填写用户名
if
not
self
.
_fill_input_by_locator
(
snapshot
,
username_config
):
self
.
logger
.
error
(
"填写用户名失败"
)
return
False
# 5. 使用配置的定位器填写密码
if
not
self
.
_fill_input_by_locator
(
snapshot
,
password_config
):
self
.
logger
.
error
(
"填写密码失败"
)
return
False
# 6. 使用配置的定位器填写验证码(如果存在)
if
code_config
:
if
not
self
.
_fill_input_by_locator
(
snapshot
,
code_config
):
self
.
logger
.
warning
(
"填写验证码失败,继续尝试登录"
)
else
:
self
.
logger
.
info
(
"无验证码配置,跳过验证码填写"
)
# 7. 查找并点击登录按钮
login_button_uid
=
self
.
_find_login_button
(
snapshot
)
if
not
login_button_uid
:
self
.
logger
.
warning
(
"快照中未找到登录按钮,尝试使用JavaScript直接查找并点击"
)
if
not
self
.
_click_login_button_by_javascript
():
self
.
logger
.
error
(
"未找到登录按钮"
)
return
False
else
:
self
.
logger
.
info
(
"正在点击登录按钮"
)
if
not
self
.
browser
.
click_element
(
login_button_uid
):
self
.
logger
.
error
(
"点击登录按钮失败"
)
return
False
# 8. 等待登录完成
self
.
logger
.
info
(
"等待登录完成..."
)
time
.
sleep
(
WAIT_SHORT
)
# 9. 验证登录成功
if
self
.
verify_login_success
():
self
.
logger
.
info
(
"登录成功"
)
return
True
else
:
self
.
logger
.
error
(
"登录验证失败"
)
return
False
def
_fill_input_by_locator
(
self
,
snapshot
:
Dict
,
config
:
Tuple
)
->
bool
:
"""
使用配置的定位器填写输入框
Args:
snapshot: 页面快照
config: 配置元组 (locator_type, locator_value, input_value)
Returns:
是否成功填写
"""
locator_type
,
locator_value
,
input_value
=
config
self
.
logger
.
debug
(
f
"使用定位器填写输入: [{locator_type}, {locator_value}, {input_value}]"
)
# 查找元素
uid
=
self
.
_find_element_by_locator
(
snapshot
,
locator_type
,
locator_value
)
if
not
uid
:
# 对于XPATH定位器,尝试使用JavaScript直接查找并填写
if
locator_type
==
"XPATH"
:
self
.
logger
.
warning
(
f
"快照中未找到元素,尝试使用JavaScript直接查找: {locator_value}"
)
return
self
.
_fill_input_by_javascript_xpath
(
locator_value
,
input_value
)
self
.
logger
.
error
(
f
"使用定位器未找到元素: {locator_value}"
)
return
False
# 填写输入框
self
.
logger
.
info
(
f
"正在填写值: {input_value}"
)
if
not
self
.
browser
.
fill_input
(
uid
,
input_value
):
self
.
logger
.
error
(
"填写失败"
)
return
False
return
True
def
_fill_input_by_javascript_xpath
(
self
,
xpath
:
str
,
value
:
str
)
->
bool
:
"""
使用JavaScript通过XPath直接填写输入框
Args:
xpath: XPath表达式
value: 要填写的值
Returns:
是否成功填写
"""
script
=
f
'''
() => {{
const result = document.evaluate("{xpath}", document, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
const element = result.singleNodeValue;
if (element) {{
element.value = "{value}";
element.dispatchEvent(new Event('input', {{ bubbles: true }}));
element.dispatchEvent(new Event('change', {{ bubbles: true }}));
return true;
}}
return false;
}}
'''
try
:
result
=
self
.
browser
.
execute_script
(
script
)
if
result
:
self
.
logger
.
info
(
f
"使用JavaScript填写成功: {xpath}"
)
return
True
else
:
self
.
logger
.
error
(
f
"JavaScript填写失败,未找到元素: {xpath}"
)
return
False
except
Exception
as
e
:
self
.
logger
.
error
(
f
"JavaScript填写异常: {e}"
)
return
False
def
_find_element_by_locator
(
self
,
snapshot
:
Dict
,
locator_type
:
str
,
locator_value
:
str
)
->
Optional
[
str
]:
"""
根据定位器类型和值查找元素
Args:
snapshot: 页面快照
locator_type: 定位类型(XPATH/ID/CSS_SELECTOR等)
locator_value: 定位值
Returns:
元素的uid,未找到返回None
"""
elements
=
snapshot
.
get
(
"elements"
,
[])
for
element
in
elements
:
if
locator_type
==
"XPATH"
:
# XPath定位需要通过JavaScript或其他方式匹配
# 这里简化处理:如果元素的某个属性包含定位值的部分内容
if
self
.
_match_xpath_locator
(
element
,
locator_value
):
return
element
.
get
(
"uid"
)
elif
locator_type
==
"ID"
:
if
element
.
get
(
"id"
)
==
locator_value
:
return
element
.
get
(
"uid"
)
elif
locator_type
==
"NAME"
:
if
element
.
get
(
"name"
)
==
locator_value
:
return
element
.
get
(
"uid"
)
elif
locator_type
==
"CSS_SELECTOR"
:
# CSS选择器匹配
if
self
.
_match_css_selector
(
element
,
locator_value
):
return
element
.
get
(
"uid"
)
self
.
logger
.
warning
(
f
"未找到匹配元素: [{locator_type}] {locator_value}"
)
return
None
def
_match_xpath_locator
(
self
,
element
:
Dict
,
xpath
:
str
)
->
bool
:
"""
增强的XPath匹配(支持MCP快照格式)
支持的XPath格式:
- //input[@placeholder='value']
- //input[@id='value']
- //input[@name='value']
- //input[@type='value']
Args:
element: 元素字典(来自MCP快照)
xpath: XPath表达式
Returns:
是否匹配
"""
import
re
# MCP快照中的元素格式:
# {
# "uid": "...",
# "role": "textbox",
# "name": "显示文本(可能是placeholder)",
# "attributes": {"placeholder": "...", "type": "...", "id": "...", ...}
# }
# 获取元素属性(MCP快照将属性存在attributes字段中)
element_attrs
=
element
.
get
(
"attributes"
,
{})
element_name
=
element
.
get
(
"name"
,
""
)
# 匹配 placeholder 属性
if
"@placeholder="
in
xpath
:
match
=
re
.
search
(
r"@placeholder='([^']+)'"
,
xpath
)
if
match
:
placeholder_value
=
match
.
group
(
1
)
# 优先从attributes中获取placeholder
element_placeholder
=
element_attrs
.
get
(
"placeholder"
,
""
)
if
element_placeholder
:
return
placeholder_value
==
element_placeholder
or
placeholder_value
in
element_placeholder
# 备选:从name字段匹配(MCP快照中name可能显示placeholder文本)
if
placeholder_value
in
element_name
:
return
True
# 匹配 id 属性
if
"@id="
in
xpath
:
match
=
re
.
search
(
r"@id='([^']+)'"
,
xpath
)
if
match
:
id_value
=
match
.
group
(
1
)
# 优先从attributes中获取id
element_id
=
element_attrs
.
get
(
"id"
,
""
)
if
element_id
:
return
element_id
==
id_value
# 备选:从顶层id字段获取
if
element
.
get
(
"id"
)
==
id_value
:
return
True
# 匹配 name 属性
if
"@name="
in
xpath
:
match
=
re
.
search
(
r"@name='([^']+)'"
,
xpath
)
if
match
:
name_value
=
match
.
group
(
1
)
# 优先从attributes中获取name
element_attr_name
=
element_attrs
.
get
(
"name"
,
""
)
if
element_attr_name
:
return
element_attr_name
==
name_value
# 备选:从顶层name字段获取
if
element
.
get
(
"name"
)
==
name_value
:
return
True
# 匹配 type 属性
if
"@type="
in
xpath
:
match
=
re
.
search
(
r"@type='([^']+)'"
,
xpath
)
if
match
:
type_value
=
match
.
group
(
1
)
element_type
=
element_attrs
.
get
(
"type"
,
""
)
return
element_type
==
type_value
return
False
def
_match_css_selector
(
self
,
element
:
Dict
,
selector
:
str
)
->
bool
:
"""
简化的CSS选择器匹配
Args:
element: 元素字典
selector: CSS选择器
Returns:
是否匹配
"""
# 简化实现:支持class选择器
if
selector
.
startswith
(
"."
)
and
"class"
in
element
:
class_value
=
selector
[
1
:]
element_classes
=
element
.
get
(
"class"
,
""
)
if
isinstance
(
element_classes
,
str
):
return
class_value
in
element_classes
.
split
()
elif
isinstance
(
element_classes
,
list
):
return
class_value
in
element_classes
return
False
def
_find_login_button
(
self
,
snapshot
:
Dict
)
->
Optional
[
str
]:
"""
查找登录按钮(增强版)
Args:
snapshot: 页面快照
Returns:
登录按钮的uid,未找到返回None
"""
# 常见的登录按钮标识
login_keywords
=
[
"登录"
,
"login"
,
"登 录"
,
"提交"
]
for
keyword
in
login_keywords
:
uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
keyword
)
if
uid
:
return
uid
# 尝试通过XPATH查找
elements
=
snapshot
.
get
(
"elements"
,
[])
for
element
in
elements
:
role
=
element
.
get
(
"role"
,
""
)
name
=
element
.
get
(
"name"
,
""
)
if
(
role
==
"button"
or
element
.
get
(
"tag"
)
==
"button"
)
and
\
any
(
keyword
in
name
for
keyword
in
[
"登录"
,
"提交"
]):
return
element
.
get
(
"uid"
)
self
.
logger
.
warning
(
"未找到登录按钮"
)
return
None
# 旧的自动发现方法已移除,改用配置文件指定的定位器
def
_click_login_button_by_javascript
(
self
)
->
bool
:
"""
使用JavaScript查找并点击登录按钮
Returns:
是否成功点击
"""
# 常见的登录按钮XPATH
login_xpaths
=
[
"//button[contains(.,'登录')]"
,
"//button[contains(.,'登 录')]"
,
"//button[contains(.,'提交')]"
,
"//input[@type='submit']"
,
"//button[@type='submit']"
,
]
for
xpath
in
login_xpaths
:
script
=
f
'''
() => {{
const result = document.evaluate("{xpath}", document, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
const element = result.singleNodeValue;
if (element) {{
element.click();
return true;
}}
return false;
}}
'''
try
:
result
=
self
.
browser
.
execute_script
(
script
)
if
result
:
self
.
logger
.
info
(
f
"使用JavaScript点击登录按钮成功: {xpath}"
)
return
True
except
Exception
as
e
:
self
.
logger
.
debug
(
f
"JavaScript点击失败: {xpath}, 错误: {e}"
)
self
.
logger
.
error
(
"JavaScript未找到登录按钮"
)
return
False
def
verify_login_success
(
self
)
->
bool
:
"""
验证登录是否成功
Returns:
是否登录成功
"""
self
.
logger
.
debug
(
"正在验证登录状态"
)
# 方法1:检查URL是否已跳转
current_url
=
self
.
browser
.
get_url
()
# 如果URL不包含login或Login,说明已登录
if
current_url
and
"login"
not
in
current_url
.
lower
()
and
"Login"
not
in
current_url
:
self
.
logger
.
debug
(
"URL已跳转,登录验证通过"
)
return
True
# 方法2:检查页面是否包含登录后的标识元素
# 如:用户信息、退出按钮等
snapshot
=
self
.
browser
.
take_snapshot
()
success_indicators
=
[
"退出"
,
"logout"
,
"用户信息"
,
"首页"
,
"home"
]
for
indicator
in
success_indicators
:
uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
indicator
)
if
uid
:
self
.
logger
.
debug
(
f
"找到登录后标识元素: {indicator}"
)
return
True
self
.
logger
.
warning
(
"未能通过常规方式验证登录状态"
)
return
False
def
logout
(
self
)
->
bool
:
"""
退出登录
Returns:
是否退出成功
"""
self
.
logger
.
info
(
"开始退出登录"
)
try
:
snapshot
=
self
.
browser
.
take_snapshot
()
# 查找退出按钮
logout_keywords
=
[
"退出"
,
"logout"
,
"注销"
]
for
keyword
in
logout_keywords
:
uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
keyword
)
if
uid
:
self
.
logger
.
info
(
"正在点击退出按钮"
)
if
self
.
browser
.
click_element
(
uid
):
self
.
logger
.
info
(
"退出登录成功"
)
return
True
self
.
logger
.
warning
(
"未找到退出按钮"
)
return
False
except
Exception
as
e
:
self
.
logger
.
error
(
f
"退出登录失败: {e}"
)
return
False
AuxiliaryTool/TestCaseGenerator/main.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
自动化测试用例生成工具 - 主程序
通过Claude Code + MCP自动生成测试用例
"""
import
sys
import
time
from
pathlib
import
Path
from
typing
import
Dict
,
List
,
Optional
# 添加项目根目录到路径
sys
.
path
.
insert
(
0
,
str
(
Path
(
__file__
)
.
parent
))
from
config_manager
import
ConfigManager
from
browser_operator
import
BrowserOperator
from
login_handler
import
LoginHandler
from
navigation_handler
import
NavigationHandler
from
element_locator
import
ElementLocator
from
operation_executor
import
OperationExecutor
from
testcase_generator
import
TestCaseGenerator
from
utils.logger
import
get_logger
,
setup_file_logger
from
utils.constants
import
LOGS_DIR
class
TestCaseGeneratorMain
:
"""测试用例生成器主控类"""
def
__init__
(
self
,
system_config_path
:
str
=
"config/system_config.json"
,
module_config_path
:
str
=
"config/module_config.json"
,
login_type
:
str
=
"back"
):
"""
初始化主控类
Args:
system_config_path: 系统配置文件路径
module_config_path: 模块配置文件路径
login_type: 登录类型,"front"或"back",默认为"back"
"""
self
.
logger
=
get_logger
(
"TestCaseGeneratorMain"
)
self
.
config_manager
=
ConfigManager
()
self
.
browser
:
Optional
[
BrowserOperator
]
=
None
self
.
login_handler
:
Optional
[
LoginHandler
]
=
None
self
.
navigation_handler
:
Optional
[
NavigationHandler
]
=
None
self
.
element_locator
:
Optional
[
ElementLocator
]
=
None
self
.
operation_executor
:
Optional
[
OperationExecutor
]
=
None
# 配置文件路径
self
.
system_config_path
=
system_config_path
self
.
module_config_path
=
module_config_path
self
.
login_type
=
login_type
# 设置文件日志
project_root
=
Path
(
__file__
)
.
parent
log_dir
=
project_root
/
LOGS_DIR
setup_file_logger
(
self
.
logger
,
log_dir
)
def
run
(
self
)
->
None
:
"""执行主流程"""
self
.
logger
.
info
(
"="
*
50
)
self
.
logger
.
info
(
"自动化测试用例生成工具启动"
)
self
.
logger
.
info
(
"="
*
50
)
try
:
# 1. 加载配置
self
.
_load_configs
()
# 2. 选择登录URL和登录类型
system_config
=
self
.
config_manager
.
get_system_config
()
login_url
,
login_type
=
self
.
_select_login_url_and_type
(
system_config
,
self
.
login_type
)
# 3. 初始化浏览器
self
.
_initialize_browser
()
# 4. 登录系统
self
.
_login_system
(
login_url
,
login_type
)
# 5. 处理所有模块
self
.
_process_all_modules
()
# 6. 生成完成
self
.
logger
.
info
(
"="
*
50
)
self
.
logger
.
info
(
"测试用例生成完成!"
)
self
.
logger
.
info
(
"="
*
50
)
except
Exception
as
e
:
self
.
logger
.
error
(
f
"执行过程中发生错误: {e}"
)
raise
finally
:
# 清理资源
self
.
_cleanup
()
def
_load_configs
(
self
)
->
None
:
"""加载配置文件"""
self
.
logger
.
info
(
"正在加载配置文件..."
)
# 加载系统配置
self
.
config_manager
.
load_system_config
(
self
.
system_config_path
)
system_config
=
self
.
config_manager
.
get_system_config
()
self
.
logger
.
info
(
f
"系统: {system_config.get('system_type')}"
)
self
.
logger
.
info
(
f
"前台地址: {system_config.get('system_front_url')}"
)
self
.
logger
.
info
(
f
"后台地址: {system_config.get('system_back_url')}"
)
# 加载模块配置
self
.
config_manager
.
load_module_config
(
self
.
module_config_path
)
module_configs
=
self
.
config_manager
.
get_module_configs
()
self
.
logger
.
info
(
f
"待处理模块数量: {len(module_configs)}"
)
for
idx
,
module
in
enumerate
(
module_configs
,
1
):
module_name
=
module
.
get
(
"module_name"
)
module_name_son
=
module
.
get
(
"module_name_son"
)
functions
=
module
.
get
(
"module_function"
)
self
.
logger
.
info
(
f
" 模块{idx}: {module_name} > {module_name_son}, 功能: {functions}"
)
def
_select_login_url_and_type
(
self
,
system_config
:
Dict
,
login_type
:
str
=
"back"
)
->
tuple
[
str
,
str
]:
"""
选择登录URL和登录类型
Args:
system_config: 系统配置字典
login_type: 登录类型,"front"或"back"
Returns:
(登录URL, 登录类型) 元组
"""
if
login_type
==
"front"
:
login_url
=
system_config
.
get
(
"system_front_url"
)
self
.
logger
.
info
(
f
"使用前台地址登录: {login_url}"
)
else
:
# back
login_url
=
system_config
.
get
(
"system_back_url"
)
self
.
logger
.
info
(
f
"使用后台地址登录: {login_url}"
)
return
login_url
,
login_type
def
_initialize_browser
(
self
)
->
None
:
"""初始化浏览器"""
self
.
logger
.
info
(
"正在初始化浏览器..."
)
self
.
browser
=
BrowserOperator
()
# 初始化各个处理器
self
.
element_locator
=
ElementLocator
(
self
.
browser
)
self
.
operation_executor
=
OperationExecutor
(
self
.
browser
,
self
.
element_locator
)
self
.
logger
.
info
(
"浏览器初始化完成"
)
def
_login_system
(
self
,
login_url
:
str
,
login_type
:
str
)
->
None
:
"""
登录系统(使用配置文件中的定位器)
Args:
login_url: 登录URL
login_type: 登录类型,"front"或"back"
"""
self
.
logger
.
info
(
"正在登录系统..."
)
# 使用配置管理器初始化登录处理器
self
.
login_handler
=
LoginHandler
(
self
.
browser
,
self
.
config_manager
)
# 执行登录
if
not
self
.
login_handler
.
login
(
login_url
,
login_type
):
raise
Exception
(
"登录失败"
)
# 初始化导航处理器
self
.
navigation_handler
=
NavigationHandler
(
self
.
browser
)
def
_process_all_modules
(
self
)
->
None
:
"""处理所有模块配置"""
module_configs
=
self
.
config_manager
.
get_module_configs
()
output_dir
=
self
.
config_manager
.
get_output_dir
()
for
idx
,
module_config
in
enumerate
(
module_configs
,
1
):
self
.
logger
.
info
(
f
"
\n
正在处理第 {idx}/{len(module_configs)} 个模块..."
)
self
.
_process_single_module
(
module_config
,
output_dir
)
def
_process_single_module
(
self
,
module_config
:
Dict
,
output_dir
:
Path
)
->
None
:
"""
处理单个模块
Args:
module_config: 模块配置字典
output_dir: 输出目录
"""
module_name
=
module_config
.
get
(
"module_name"
)
module_name_son
=
module_config
.
get
(
"module_name_son"
)
module_functions
=
module_config
.
get
(
"module_function"
)
self
.
logger
.
info
(
f
"处理模块: {module_name} > {module_name_son}"
)
self
.
logger
.
info
(
f
"功能列表: {module_functions}"
)
# 1. 导航到指定模块
success
,
route
=
self
.
navigation_handler
.
navigate_to_module
(
module_name
,
module_name_son
)
if
not
success
:
self
.
logger
.
error
(
f
"导航到模块失败: {module_name} > {module_name_son}"
)
return
if
not
route
:
route
=
f
"{module_name}/{module_name_son}"
self
.
logger
.
warning
(
f
"未能获取路由,使用默认路由: {route}"
)
# 2. 初始化测试用例生成器
system_config
=
self
.
config_manager
.
get_system_config
()
base_url
=
system_config
.
get
(
"system_front_url"
,
""
)
testcase_generator
=
TestCaseGenerator
(
module_config
,
base_url
)
# 3. 执行各功能并生成测试用例
steps_dict
=
{}
for
function
in
module_functions
:
self
.
logger
.
info
(
f
"
\n
执行功能: {function}"
)
if
function
==
"添加"
:
steps
=
self
.
operation_executor
.
execute_add
(
route
)
elif
function
==
"编辑"
:
steps
=
self
.
operation_executor
.
execute_edit
(
route
)
elif
function
==
"删除"
:
steps
=
self
.
operation_executor
.
execute_delete
(
route
)
else
:
self
.
logger
.
warning
(
f
"未知功能类型: {function}"
)
continue
if
steps
:
steps_dict
[
function
]
=
steps
self
.
logger
.
info
(
f
"功能 {function} 执行完成,共 {len(steps)} 个步骤"
)
else
:
self
.
logger
.
warning
(
f
"功能 {function} 没有生成步骤"
)
# 4. 批量生成测试用例
if
steps_dict
:
filepaths
=
testcase_generator
.
batch_generate
(
module_functions
,
steps_dict
,
output_dir
)
self
.
logger
.
info
(
f
"模块 {module_name} > {module_name_son} 处理完成"
)
self
.
logger
.
info
(
f
"生成文件: {len(filepaths)} 个"
)
for
filepath
in
filepaths
:
self
.
logger
.
info
(
f
" - {filepath}"
)
else
:
self
.
logger
.
warning
(
f
"模块 {module_name} > {module_name_son} 没有生成任何测试用例"
)
def
_cleanup
(
self
)
->
None
:
"""清理资源"""
self
.
logger
.
info
(
"正在清理资源..."
)
if
self
.
browser
:
self
.
browser
.
close
()
self
.
logger
.
info
(
"资源清理完成"
)
def
main
():
"""主函数入口"""
import
argparse
parser
=
argparse
.
ArgumentParser
(
description
=
"自动化测试用例生成工具"
)
parser
.
add_argument
(
"--system-config"
,
default
=
"config/system_config.json"
,
help
=
"系统配置文件路径"
)
parser
.
add_argument
(
"--module-config"
,
default
=
"config/module_config.json"
,
help
=
"模块配置文件路径"
)
parser
.
add_argument
(
"--login-type"
,
choices
=
[
"front"
,
"back"
],
default
=
"back"
,
help
=
"登录地址类型(front: 前台, back: 后台)"
)
args
=
parser
.
parse_args
()
# 创建并运行主控类
main_controller
=
TestCaseGeneratorMain
(
system_config_path
=
args
.
system_config
,
module_config_path
=
args
.
module_config
,
login_type
=
args
.
login_type
)
try
:
main_controller
.
run
()
return
0
except
Exception
as
e
:
print
(
f
"错误: {e}"
)
return
1
if
__name__
==
"__main__"
:
sys
.
exit
(
main
())
AuxiliaryTool/TestCaseGenerator/mcp_wrapper.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
MCP工具包装器模块
提供chrome-devtools MCP工具的调用接口
"""
import
json
from
typing
import
Dict
,
List
,
Optional
,
Any
from
utils.logger
import
get_logger
class
MCPBrowserWrapper
:
"""
MCP浏览器包装器
注意:此类需要通过Claude Code的MCP工具实际调用。
在实际使用时,需要将方法调用传递给Claude Code的MCP工具。
"""
def
__init__
(
self
):
"""初始化MCP包装器"""
self
.
logger
=
get_logger
(
"MCPBrowserWrapper"
)
self
.
page_id
:
Optional
[
int
]
=
None
def
new_page
(
self
,
url
:
str
,
background
:
bool
=
False
,
timeout
:
int
=
30000
)
->
Optional
[
Dict
]:
"""
打开新页面
Args:
url: 目标URL
background: 是否在后台打开
timeout: 超时时间(毫秒)
Returns:
页面信息字典
"""
self
.
logger
.
info
(
f
"[MCP调用] new_page: {url}"
)
# 实际调用需要通过Claude Code的MCP工具
# mcp__chrome_devtools__new_page(url=url, background=background, timeout=timeout)
return
{
"pageId"
:
self
.
page_id
,
"url"
:
url
}
def
navigate
(
self
,
url
:
str
,
ignore_cache
:
bool
=
False
,
timeout
:
int
=
30000
)
->
bool
:
"""
导航到指定URL
Args:
url: 目标URL
ignore_cache: 是否忽略缓存
timeout: 超时时间(毫秒)
Returns:
是否成功
"""
self
.
logger
.
info
(
f
"[MCP调用] navigate: {url}"
)
# 实际调用需要通过Claude Code的MCP工具
# mcp__chrome_devtools__navigate_page(type="url", url=url, ignore_cache=ignore_cache)
return
True
def
take_snapshot
(
self
,
verbose
:
bool
=
False
,
file_path
:
Optional
[
str
]
=
None
)
->
Dict
:
"""
获取页面快照
Args:
verbose: 是否包含详细信息
file_path: 快照保存路径
Returns:
页面快照字典
"""
self
.
logger
.
debug
(
"[MCP调用] take_snapshot"
)
# 实际调用需要通过Claude Code的MCP工具
# result = mcp__chrome_devtools__take_snapshot(verbose=verbose, filePath=file_path)
# 返回模拟的快照结构
return
{
"elements"
:
[],
"url"
:
""
,
"title"
:
""
}
def
click
(
self
,
uid
:
str
,
dbl_click
:
bool
=
False
,
include_snapshot
:
bool
=
False
)
->
bool
:
"""
点击元素
Args:
uid: 元素uid
dbl_click: 是否双击
include_snapshot: 是否包含快照
Returns:
是否成功
"""
self
.
logger
.
debug
(
f
"[MCP调用] click: {uid}"
)
# 实际调用需要通过Claude Code的MCP工具
# mcp__chrome_devtools__click(uid=uid, dblClick=dbl_click, includeSnapshot=include_snapshot)
return
True
def
fill
(
self
,
uid
:
str
,
value
:
str
,
include_snapshot
:
bool
=
False
)
->
bool
:
"""
填写输入框
Args:
uid: 元素uid
value: 填写值
include_snapshot: 是否包含快照
Returns:
是否成功
"""
self
.
logger
.
debug
(
f
"[MCP调用] fill: {uid} = {value}"
)
# 实际调用需要通过Claude Code的MCP工具
# mcp__chrome_devtools__fill(uid=uid, value=value, includeSnapshot=include_snapshot)
return
True
def
fill_form
(
self
,
elements
:
List
[
Dict
],
include_snapshot
:
bool
=
False
)
->
bool
:
"""
批量填写表单
Args:
elements: 元素列表,格式: [{"uid": "xxx", "value": "yyy"}]
include_snapshot: 是否包含快照
Returns:
是否成功
"""
self
.
logger
.
debug
(
f
"[MCP调用] fill_form: {len(elements)} elements"
)
# 实际调用需要通过Claude Code的MCP工具
# mcp__chrome_devtools__fill_form(elements=elements, includeSnapshot=include_snapshot)
return
True
def
evaluate_script
(
self
,
function
:
str
,
args
:
Optional
[
List
[
str
]]
=
None
)
->
Any
:
"""
执行JavaScript脚本
Args:
function: JavaScript函数
args: 参数列表(uid列表)
Returns:
脚本执行结果
"""
self
.
logger
.
debug
(
f
"[MCP调用] evaluate_script: {function[:50]}..."
)
# 实际调用需要通过Claude Code的MCP工具
# mcp__chrome_devtools__evaluate_script(function=function, args=args or [])
return
None
def
get_attribute
(
self
,
uid
:
str
,
attribute
:
str
)
->
Optional
[
str
]:
"""
获取元素属性
Args:
uid: 元素uid
attribute: 属性名
Returns:
属性值
"""
self
.
logger
.
debug
(
f
"[MCP调用] get_attribute: {uid}.{attribute}"
)
# 使用evaluate_script获取属性
script
=
f
"(el) => el.getAttribute('{attribute}')"
return
self
.
evaluate_script
(
script
,
[
uid
])
def
list_pages
(
self
)
->
List
[
Dict
]:
"""
列出所有打开的页面
Returns:
页面列表
"""
self
.
logger
.
debug
(
"[MCP调用] list_pages"
)
# 实际调用需要通过Claude Code的MCP工具
# result = mcp__chrome_devtools__list_pages()
return
[]
def
close_page
(
self
,
page_id
:
int
)
->
bool
:
"""
关闭指定页面
Args:
page_id: 页面ID
Returns:
是否成功
"""
self
.
logger
.
info
(
f
"[MCP调用] close_page: {page_id}"
)
# 实际调用需要通过Claude Code的MCP工具
# mcp__chrome_devtools__close_page(pageId=page_id)
return
True
def
wait_for
(
self
,
text
:
List
[
str
],
timeout
:
int
=
30000
)
->
bool
:
"""
等待指定文本出现
Args:
text: 文本列表
timeout: 超时时间(毫秒)
Returns:
是否成功
"""
self
.
logger
.
debug
(
f
"[MCP调用] wait_for: {text}"
)
# 实际调用需要通过Claude Code的MCP工具
# mcp__chrome_devtools__wait_for(text=text, timeout=timeout)
return
True
def
take_screenshot
(
self
,
file_path
:
Optional
[
str
]
=
None
,
format
:
str
=
"png"
,
quality
:
int
=
80
,
full_page
:
bool
=
False
)
->
bool
:
"""
截取屏幕截图
Args:
file_path: 保存路径
format: 图片格式(png/jpeg/webp)
quality: 图片质量(用于jpeg/webp)
full_page: 是否截取整页
Returns:
是否成功
"""
self
.
logger
.
debug
(
"[MCP调用] take_screenshot"
)
# 实际调用需要通过Claude Code的MCP工具
# mcp__chrome_devtools__take_screenshot(filePath=file_path, format=format, quality=quality, fullPage=full_page)
return
True
def
get_console_messages
(
self
,
types
:
Optional
[
List
[
str
]]
=
None
,
page_idx
:
int
=
0
,
page_size
:
int
=
100
)
->
List
[
Dict
]:
"""
获取控制台消息
Args:
types: 消息类型过滤
page_idx: 页面索引
page_size: 页面大小
Returns:
消息列表
"""
self
.
logger
.
debug
(
"[MCP调用] get_console_messages"
)
# 实际调用需要通过Claude Code的MCP工具
# result = mcp__chrome_devtools__list_console_messages(types=types, pageIdx=page_idx, pageSize=page_size)
return
[]
# 全局MCP包装器实例(用于跟踪状态)
_mcp_wrapper_instance
:
Optional
[
MCPBrowserWrapper
]
=
None
def
get_mcp_wrapper
()
->
MCPBrowserWrapper
:
"""获取全局MCP包装器实例"""
global
_mcp_wrapper_instance
if
_mcp_wrapper_instance
is
None
:
_mcp_wrapper_instance
=
MCPBrowserWrapper
()
return
_mcp_wrapper_instance
AuxiliaryTool/TestCaseGenerator/navigation_handler.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
导航处理模块
负责处理两级菜单导航
"""
import
time
from
typing
import
Dict
,
Optional
,
Tuple
from
browser_operator
import
BrowserOperator
from
utils.logger
import
get_logger
from
utils.constants
import
WAIT_SHORT
,
WAIT_MEDIUM
class
NavigationHandler
:
"""导航处理器 - 处理菜单导航"""
def
__init__
(
self
,
browser
:
BrowserOperator
):
"""
初始化导航处理器
Args:
browser: 浏览器操作器实例
"""
self
.
browser
=
browser
self
.
logger
=
get_logger
(
"NavigationHandler"
)
def
navigate_to_module
(
self
,
module_name
:
str
,
module_name_son
:
str
)
->
Tuple
[
bool
,
Optional
[
str
]]:
"""
导航到指定模块(两级菜单)
Args:
module_name: 一级菜单名称
module_name_son: 二级菜单名称
Returns:
(是否成功, 当前路由)
"""
self
.
logger
.
info
(
f
"开始导航到模块: {module_name} > {module_name_son}"
)
# 记录导航前的URL
before_url
=
self
.
browser
.
get_url
()
# 1. 查找并展开一级菜单
if
not
self
.
_expand_first_level_menu
(
module_name
):
self
.
logger
.
error
(
f
"展开一级菜单失败: {module_name}"
)
return
False
,
None
time
.
sleep
(
WAIT_SHORT
)
# 2. 查找并点击二级菜单
if
not
self
.
_click_second_level_menu
(
module_name_son
):
self
.
logger
.
error
(
f
"点击二级菜单失败: {module_name_son}"
)
return
False
,
None
# 3. 等待页面加载
time
.
sleep
(
WAIT_MEDIUM
)
# 4. 获取当前路由
route
=
self
.
_parse_current_route
()
if
route
:
self
.
logger
.
info
(
f
"导航成功,当前路由: {route}"
)
return
True
,
route
else
:
self
.
logger
.
warning
(
"未能获取当前路由,但导航可能已成功"
)
return
True
,
None
def
_expand_first_level_menu
(
self
,
menu_name
:
str
)
->
bool
:
"""
展开一级菜单
Args:
menu_name: 一级菜单名称
Returns:
是否成功展开
"""
self
.
logger
.
debug
(
f
"正在查找一级菜单: {menu_name}"
)
snapshot
=
self
.
browser
.
take_snapshot
()
# 查找一级菜单
menu_uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
menu_name
)
if
not
menu_uid
:
self
.
logger
.
error
(
f
"未找到一级菜单: {menu_name}"
)
return
False
self
.
logger
.
debug
(
f
"找到一级菜单: {menu_name}, uid: {menu_uid}"
)
# 点击展开菜单
self
.
logger
.
info
(
f
"正在点击一级菜单: {menu_name}"
)
if
not
self
.
browser
.
click_element
(
menu_uid
):
self
.
logger
.
error
(
f
"点击一级菜单失败: {menu_name}"
)
return
False
return
True
def
_click_second_level_menu
(
self
,
submenu_name
:
str
)
->
bool
:
"""
点击二级菜单
Args:
submenu_name: 二级菜单名称
Returns:
是否成功点击
"""
self
.
logger
.
debug
(
f
"正在查找二级菜单: {submenu_name}"
)
snapshot
=
self
.
browser
.
take_snapshot
()
# 查找二级菜单
submenu_uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
submenu_name
)
if
not
submenu_uid
:
self
.
logger
.
error
(
f
"未找到二级菜单: {submenu_name}"
)
return
False
self
.
logger
.
debug
(
f
"找到二级菜单: {submenu_name}, uid: {submenu_uid}"
)
# 点击二级菜单
self
.
logger
.
info
(
f
"正在点击二级菜单: {submenu_name}"
)
if
not
self
.
browser
.
click_element
(
submenu_uid
):
self
.
logger
.
error
(
f
"点击二级菜单失败: {submenu_name}"
)
return
False
return
True
def
_parse_current_route
(
self
)
->
Optional
[
str
]:
"""
解析当前页面的路由
Returns:
路由字符串,如果解析失败返回None
Note:
从URL中提取路由部分,例如:
URL: https://example.com/#/system/user
路由: system/user
"""
current_url
=
self
.
browser
.
get_url
()
if
not
current_url
:
self
.
logger
.
warning
(
"当前URL为空"
)
return
None
self
.
logger
.
debug
(
f
"当前URL: {current_url}"
)
# 方法1:从hash中提取路由(/#/xxx格式)
if
"#/"
in
current_url
:
route
=
current_url
.
split
(
"#/"
)[
1
]
# 去除查询参数
if
"?"
in
route
:
route
=
route
.
split
(
"?"
)[
0
]
self
.
logger
.
debug
(
f
"从hash中提取路由: {route}"
)
return
route
# 方法2:从路径中提取路由
from
urllib.parse
import
urlparse
parsed
=
urlparse
(
current_url
)
path
=
parsed
.
path
# 去除开头的斜杠
if
path
.
startswith
(
"/"
):
path
=
path
[
1
:]
if
path
:
self
.
logger
.
debug
(
f
"从路径中提取路由: {path}"
)
return
path
self
.
logger
.
warning
(
"未能从URL中提取路由"
)
return
None
def
find_menu_item
(
self
,
menu_text
:
str
)
->
Optional
[
str
]:
"""
查找菜单项
Args:
menu_text: 菜单文本
Returns:
菜单项的uid,未找到返回None
"""
self
.
logger
.
debug
(
f
"正在查找菜单项: {menu_text}"
)
snapshot
=
self
.
browser
.
take_snapshot
()
uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
menu_text
)
if
uid
:
self
.
logger
.
debug
(
f
"找到菜单项: {menu_text}"
)
else
:
self
.
logger
.
warning
(
f
"未找到菜单项: {menu_text}"
)
return
uid
def
get_current_page_info
(
self
)
->
Dict
:
"""
获取当前页面信息
Returns:
包含URL、路由等信息的字典
"""
current_url
=
self
.
browser
.
get_url
()
route
=
self
.
_parse_current_route
()
return
{
"url"
:
current_url
,
"route"
:
route
,
"title"
:
self
.
_get_page_title
()
}
def
_get_page_title
(
self
)
->
Optional
[
str
]:
"""
获取页面标题
Returns:
页面标题
"""
try
:
# 使用JavaScript获取页面标题
title
=
self
.
browser
.
execute_script
(
"return document.title;"
)
return
title
except
Exception
as
e
:
self
.
logger
.
error
(
f
"获取页面标题失败: {e}"
)
return
None
def
wait_for_page_load
(
self
,
timeout
:
int
=
30
)
->
bool
:
"""
等待页面加载完成
Args:
timeout: 超时时间(秒)
Returns:
是否加载完成
"""
self
.
logger
.
debug
(
"等待页面加载完成"
)
start_time
=
time
.
time
()
while
time
.
time
()
-
start_time
<
timeout
:
# 检查页面状态
ready_state
=
self
.
browser
.
execute_script
(
"return document.readyState;"
)
if
ready_state
==
"complete"
:
self
.
logger
.
debug
(
"页面加载完成"
)
return
True
time
.
sleep
(
0.5
)
self
.
logger
.
warning
(
"等待页面加载超时"
)
return
False
def
back
(
self
)
->
bool
:
"""
返回上一页
Returns:
是否成功返回
"""
self
.
logger
.
info
(
"正在返回上一页"
)
# 需要通过MCP工具实现
return
True
def
refresh
(
self
)
->
bool
:
"""
刷新当前页面
Returns:
是否成功刷新
"""
self
.
logger
.
info
(
"正在刷新页面"
)
# 需要通过MCP工具实现
return
True
AuxiliaryTool/TestCaseGenerator/operation_executor.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
操作执行模块
负责执行添加、编辑、删除操作,并记录操作步骤
"""
import
time
from
typing
import
Dict
,
List
,
Optional
from
browser_operator
import
BrowserOperator
from
element_locator
import
ElementLocator
from
utils.logger
import
get_logger
from
utils.constants
import
(
TEST_DATA
,
EXPECTED_RESULTS
,
WAIT_SHORT
,
WAIT_MEDIUM
)
class
OperationExecutor
:
"""操作执行器 - 执行添加/编辑/删除操作"""
def
__init__
(
self
,
browser
:
BrowserOperator
,
locator
:
ElementLocator
):
"""
初始化操作执行器
Args:
browser: 浏览器操作器实例
locator: 元素定位器实例
"""
self
.
browser
=
browser
self
.
locator
=
locator
self
.
logger
=
get_logger
(
"OperationExecutor"
)
self
.
test_data_created
=
False
# 标记是否已创建测试数据
def
execute_add
(
self
,
page_route
:
str
)
->
List
[
Dict
]:
"""
执行添加操作
Args:
page_route: 当前页面路由
Returns:
操作步骤列表
"""
self
.
logger
.
info
(
"开始执行添加操作"
)
steps
=
[]
# 1. 点击添加按钮
step
=
self
.
_click_operation_button
(
"添加"
,
page_route
)
if
step
:
steps
.
append
(
step
)
else
:
self
.
logger
.
error
(
"点击添加按钮失败"
)
return
steps
time
.
sleep
(
WAIT_SHORT
)
# 2. 等待表单弹窗
snapshot
=
self
.
browser
.
take_snapshot
()
# 3. 收集并填写表单字段
form_steps
=
self
.
_collect_and_fill_form
(
snapshot
,
page_route
)
steps
.
extend
(
form_steps
)
# 4. 点击确定按钮
step
=
self
.
_click_confirm_button
(
page_route
)
if
step
:
steps
.
append
(
step
)
time
.
sleep
(
WAIT_SHORT
)
# 5. 获取操作结果提示(弹窗提示)
step
=
self
.
_get_operation_tips
(
"添加"
,
page_route
)
if
step
:
steps
.
append
(
step
)
# 6. 验证列表数据
step
=
self
.
_verify_list_data
(
"测试商品001"
,
"添加"
,
page_route
)
if
step
:
steps
.
append
(
step
)
self
.
logger
.
info
(
f
"添加操作完成,共记录 {len(steps)} 个步骤"
)
return
steps
def
execute_edit
(
self
,
page_route
:
str
)
->
List
[
Dict
]:
"""
执行编辑操作(创建数据→编辑→清理)
Args:
page_route: 当前页面路由
Returns:
操作步骤列表
"""
self
.
logger
.
info
(
"开始执行编辑操作"
)
steps
=
[]
# 1. 先创建测试数据
if
not
self
.
test_data_created
:
self
.
logger
.
info
(
"编辑操作需要先创建测试数据"
)
create_steps
=
self
.
execute_add
(
page_route
)
steps
.
extend
(
create_steps
)
self
.
test_data_created
=
True
time
.
sleep
(
WAIT_MEDIUM
)
# 2. 点击编辑按钮
step
=
self
.
_click_operation_button
(
"编辑"
,
page_route
)
if
step
:
steps
.
append
(
step
)
else
:
self
.
logger
.
error
(
"点击编辑按钮失败"
)
return
steps
time
.
sleep
(
WAIT_SHORT
)
# 3. 修改部分字段
snapshot
=
self
.
browser
.
take_snapshot
()
edit_steps
=
self
.
_modify_form_fields
(
snapshot
,
page_route
)
steps
.
extend
(
edit_steps
)
# 4. 点击确定按钮
step
=
self
.
_click_confirm_button
(
page_route
)
if
step
:
steps
.
append
(
step
)
time
.
sleep
(
WAIT_SHORT
)
# 5. 获取操作结果提示(弹窗提示)
step
=
self
.
_get_operation_tips
(
"编辑"
,
page_route
)
if
step
:
steps
.
append
(
step
)
# 6. 验证列表数据
step
=
self
.
_verify_list_data
(
"测试商品002"
,
"编辑"
,
page_route
)
if
step
:
steps
.
append
(
step
)
# 7. 清理测试数据
self
.
logger
.
info
(
"编辑操作完成,清理测试数据"
)
self
.
_cleanup_test_data
()
self
.
logger
.
info
(
f
"编辑操作完成,共记录 {len(steps)} 个步骤"
)
return
steps
def
execute_delete
(
self
,
page_route
:
str
)
->
List
[
Dict
]:
"""
执行删除操作(创建数据→删除→清理)
Args:
page_route: 当前页面路由
Returns:
操作步骤列表
"""
self
.
logger
.
info
(
"开始执行删除操作"
)
steps
=
[]
# 1. 先创建测试数据
if
not
self
.
test_data_created
:
self
.
logger
.
info
(
"删除操作需要先创建测试数据"
)
create_steps
=
self
.
execute_add
(
page_route
)
steps
.
extend
(
create_steps
)
self
.
test_data_created
=
True
time
.
sleep
(
WAIT_MEDIUM
)
# 2. 点击删除按钮
step
=
self
.
_click_operation_button
(
"删除"
,
page_route
)
if
step
:
steps
.
append
(
step
)
else
:
self
.
logger
.
error
(
"点击删除按钮失败"
)
return
steps
time
.
sleep
(
WAIT_SHORT
)
# 3. 确认删除
step
=
self
.
_confirm_delete
(
page_route
)
if
step
:
steps
.
append
(
step
)
time
.
sleep
(
WAIT_SHORT
)
# 4. 获取操作结果提示(弹窗提示)
step
=
self
.
_get_operation_tips
(
"删除"
,
page_route
)
if
step
:
steps
.
append
(
step
)
# 5. 验证列表数据
step
=
self
.
_verify_list_data
(
"测试商品002"
,
"删除"
,
page_route
)
if
step
:
steps
.
append
(
step
)
# 6. 标记测试数据已清理
self
.
test_data_created
=
False
self
.
logger
.
info
(
f
"删除操作完成,共记录 {len(steps)} 个步骤"
)
return
steps
def
_click_operation_button
(
self
,
operation
:
str
,
page_route
:
str
)
->
Optional
[
Dict
]:
"""
点击操作按钮
Args:
operation: 操作类型(添加/编辑/删除)
page_route: 当前页面路由
Returns:
操作步骤字典,失败返回None
"""
self
.
logger
.
debug
(
f
"正在查找{operation}按钮"
)
snapshot
=
self
.
browser
.
take_snapshot
()
# 查找操作按钮
button_uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
operation
)
if
not
button_uid
:
# 尝试查找包含"新增"(添加的别称)
if
operation
==
"添加"
:
button_uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
"新增"
)
if
not
button_uid
:
self
.
logger
.
error
(
f
"未找到{operation}按钮"
)
return
None
# 分析元素定位
element_info
=
{
"uid"
:
button_uid
,
"text"
:
operation
}
locator_info
=
self
.
locator
.
locate_element
(
element_info
)
# 点击按钮
if
not
self
.
browser
.
click_element
(
button_uid
):
self
.
logger
.
error
(
f
"点击{operation}按钮失败"
)
return
None
# 生成操作步骤
step
=
self
.
_generate_step
(
page
=
page_route
,
step
=
f
"点击【{operation}】按钮"
,
locator_info
=
locator_info
,
element_type
=
"click"
,
element_value
=
""
)
return
step
def
_click_confirm_button
(
self
,
page_route
:
str
)
->
Optional
[
Dict
]:
"""
点击确定按钮
Args:
page_route: 当前页面路由
Returns:
操作步骤字典,失败返回None
"""
self
.
logger
.
debug
(
"正在查找确定按钮"
)
snapshot
=
self
.
browser
.
take_snapshot
()
# 查找确定按钮(多种可能的文本)
confirm_keywords
=
[
"确定"
,
"提交"
,
"保存"
,
"OK"
]
button_uid
=
None
button_text
=
None
for
keyword
in
confirm_keywords
:
button_uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
keyword
)
if
button_uid
:
button_text
=
keyword
break
if
not
button_uid
:
self
.
logger
.
error
(
"未找到确定按钮"
)
return
None
# 分析元素定位
element_info
=
{
"uid"
:
button_uid
,
"text"
:
button_text
}
locator_info
=
self
.
locator
.
locate_element
(
element_info
)
# 点击按钮
if
not
self
.
browser
.
click_element
(
button_uid
):
self
.
logger
.
error
(
"点击确定按钮失败"
)
return
None
# 生成操作步骤
step
=
self
.
_generate_step
(
page
=
page_route
,
step
=
f
"点击【{button_text}】按钮"
,
locator_info
=
locator_info
,
element_type
=
"click"
,
element_value
=
""
)
return
step
def
_collect_and_fill_form
(
self
,
snapshot
:
Dict
,
page_route
:
str
)
->
List
[
Dict
]:
"""
收集并填写表单字段
Args:
snapshot: 页面快照
page_route: 当前页面路由
Returns:
操作步骤列表
"""
self
.
logger
.
debug
(
"正在收集表单字段"
)
steps
=
[]
# 常见的表单字段标识
field_keywords
=
{
"username"
:
[
"用户名"
,
"账号"
,
"用户"
],
"password"
:
[
"密码"
,
"新密码"
,
"登录密码"
],
"confirm_password"
:
[
"确认密码"
,
"重复密码"
],
"phone"
:
[
"手机"
,
"电话"
,
"手机号"
],
"email"
:
[
"邮箱"
,
"邮件"
],
"name"
:
[
"姓名"
,
"名称"
],
"remark"
:
[
"备注"
,
"说明"
],
"code"
:
[
"编码"
,
"代码"
],
"description"
:
[
"描述"
,
"简介"
]
}
# 根据关键字查找并填写字段
for
field_type
,
keywords
in
field_keywords
.
items
():
for
keyword
in
keywords
:
field_uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
keyword
)
if
field_uid
:
# 分析元素定位
element_info
=
{
"uid"
:
field_uid
,
"text"
:
keyword
}
locator_info
=
self
.
locator
.
locate_element
(
element_info
)
# 确定元素类型
attributes
=
self
.
browser
.
get_element_attributes
(
field_uid
)
element_type
=
self
.
locator
.
determine_element_type
(
attributes
)
# 获取测试值
test_value
=
TEST_DATA
.
get
(
field_type
,
"test001"
)
# 填写字段
if
element_type
==
"input"
:
self
.
browser
.
fill_input
(
field_uid
,
test_value
)
# 生成操作步骤
step
=
self
.
_generate_step
(
page
=
page_route
,
step
=
f
"输入{keyword}"
,
locator_info
=
locator_info
,
element_type
=
element_type
,
element_value
=
test_value
)
steps
.
append
(
step
)
break
self
.
logger
.
debug
(
f
"收集到 {len(steps)} 个表单字段"
)
return
steps
def
_modify_form_fields
(
self
,
snapshot
:
Dict
,
page_route
:
str
)
->
List
[
Dict
]:
"""
修改表单字段(用于编辑操作)
Args:
snapshot: 页面快照
page_route: 当前页面路由
Returns:
操作步骤列表
"""
self
.
logger
.
debug
(
"正在修改表单字段"
)
steps
=
[]
# 编辑操作只修改少量字段作为示例
# 这里修改用户名和备注字段
edit_fields
=
{
"username"
:
"test002"
,
# 修改后的用户名
"remark"
:
"已编辑"
# 修改后的备注
}
field_keywords
=
{
"username"
:
[
"用户名"
,
"账号"
],
"remark"
:
[
"备注"
]
}
for
field_type
,
keywords
in
field_keywords
.
items
():
for
keyword
in
keywords
:
field_uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
keyword
)
if
field_uid
:
# 分析元素定位
element_info
=
{
"uid"
:
field_uid
,
"text"
:
keyword
}
locator_info
=
self
.
locator
.
locate_element
(
element_info
)
# 确定元素类型
attributes
=
self
.
browser
.
get_element_attributes
(
field_uid
)
element_type
=
self
.
locator
.
determine_element_type
(
attributes
)
# 获取修改后的值
edit_value
=
edit_fields
.
get
(
field_type
,
"test002"
)
# 清空并填写
self
.
browser
.
fill_input
(
field_uid
,
edit_value
,
clear_first
=
True
)
# 生成操作步骤
step
=
self
.
_generate_step
(
page
=
page_route
,
step
=
f
"修改{keyword}"
,
locator_info
=
locator_info
,
element_type
=
element_type
,
element_value
=
edit_value
)
steps
.
append
(
step
)
break
return
steps
def
_confirm_delete
(
self
,
page_route
:
str
)
->
Optional
[
Dict
]:
"""
确认删除操作
Args:
page_route: 当前页面路由
Returns:
操作步骤字典,失败返回None
"""
self
.
logger
.
debug
(
"正在确认删除"
)
snapshot
=
self
.
browser
.
take_snapshot
()
# 查找确认按钮
confirm_keywords
=
[
"确定"
,
"确认"
,
"是"
]
button_uid
=
None
button_text
=
None
for
keyword
in
confirm_keywords
:
button_uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
keyword
)
if
button_uid
:
button_text
=
keyword
break
if
not
button_uid
:
self
.
logger
.
warning
(
"未找到确认删除按钮,可能不需要确认"
)
return
None
# 分析元素定位
element_info
=
{
"uid"
:
button_uid
,
"text"
:
button_text
}
locator_info
=
self
.
locator
.
locate_element
(
element_info
)
# 点击确认
if
not
self
.
browser
.
click_element
(
button_uid
):
self
.
logger
.
error
(
"确认删除失败"
)
return
None
# 生成操作步骤
step
=
self
.
_generate_step
(
page
=
page_route
,
step
=
f
"点击确认删除按钮"
,
locator_info
=
locator_info
,
element_type
=
"click"
,
element_value
=
""
)
return
step
def
_get_operation_tips
(
self
,
operation
:
str
,
page_route
:
str
)
->
Optional
[
Dict
]:
"""
获取操作结果提示(弹窗提示信息)
Args:
operation: 操作类型
page_route: 当前页面路由
Returns:
操作步骤字典
"""
self
.
logger
.
debug
(
f
"正在获取{operation}操作提示"
)
snapshot
=
self
.
browser
.
take_snapshot
()
# 查找提示信息元素(Element UI的el-message组件)
tip_keywords
=
[
"成功"
,
"失败"
,
"错误"
,
"提示"
]
# 获取预期结果
expected_result
=
EXPECTED_RESULTS
.
get
(
operation
,
f
"{operation}成功"
)
# 查找提示元素
tip_uid
=
None
for
keyword
in
tip_keywords
:
tip_uid
=
self
.
browser
.
find_element_by_text
(
snapshot
,
keyword
)
if
tip_uid
:
break
# 即使没找到提示元素,也生成步骤
if
tip_uid
:
element_info
=
{
"uid"
:
tip_uid
,
"text"
:
"提示信息"
}
locator_info
=
self
.
locator
.
locate_element
(
element_info
)
else
:
# 使用默认的提示元素定位器
locator_info
=
{
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//p[@class='el-message__content']"
}
# 生成操作步骤 - getTips类型的element_value留空,预期结果填写在expected_result
step
=
self
.
_generate_step
(
page
=
page_route
,
step
=
f
"获取操作提示"
,
locator_info
=
locator_info
,
element_type
=
"getTips"
,
element_value
=
""
,
expected_result
=
expected_result
)
return
step
def
_verify_list_data
(
self
,
data_text
:
str
,
operation
:
str
,
page_route
:
str
)
->
Optional
[
Dict
]:
"""
验证列表数据(使用getText获取列表中的文本)
Args:
data_text: 要查找的数据文本
operation: 操作类型
page_route: 当前页面路由
Returns:
操作步骤字典
"""
self
.
logger
.
debug
(
f
"正在验证列表数据: {data_text}"
)
# 查找列表中的数据(通常在td标签中)
# 使用通用的XPath定位列表数据
locator_value
=
f
"//td[contains(.,'{data_text}')]"
locator_type
=
"XPATH"
# 生成预期结果描述
expected_result
=
f
"列表中{'包含' if operation != '删除' else '不包含'}{operation}后的数据"
# 生成操作步骤 - getText类型的element_value留空,预期结果填写在expected_result
step
=
self
.
_generate_step
(
page
=
page_route
,
step
=
f
"验证列表数据"
,
locator_info
=
{
"locator_type"
:
locator_type
,
"locator_value"
:
locator_value
},
element_type
=
"getText"
,
element_value
=
""
,
expected_result
=
expected_result
)
return
step
def
_cleanup_test_data
(
self
)
->
None
:
"""清理测试数据"""
self
.
logger
.
info
(
"清理测试数据"
)
self
.
test_data_created
=
False
def
_generate_step
(
self
,
page
:
str
,
step
:
str
,
locator_info
:
Dict
,
element_type
:
str
,
element_value
:
str
,
expected_result
:
str
=
""
)
->
Dict
:
"""
生成操作步骤字典
Args:
page: 页面路由
step: 步骤描述
locator_info: 定位信息
element_type: 元素类型
element_value: 元素值
expected_result: 预期结果
Returns:
操作步骤字典
"""
return
{
"page"
:
page
,
"step"
:
step
,
"locator_type"
:
locator_info
.
get
(
"locator_type"
,
"XPATH"
),
"locator_value"
:
locator_info
.
get
(
"locator_value"
,
""
),
"element_type"
:
element_type
,
"element_value"
:
element_value
,
"expected_result"
:
expected_result
}
AuxiliaryTool/TestCaseGenerator/test_mcp.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
MCP功能测试脚本
验证chrome-devtools MCP工具的基本功能
"""
import
json
import
time
from
pathlib
import
Path
def
test_mcp_basic
():
"""测试MCP基本功能"""
print
(
"="
*
50
)
print
(
"MCP基本功能测试"
)
print
(
"="
*
50
)
results
=
[]
# 测试1: 列出当前页面
print
(
"
\n
[测试1] 列出当前页面"
)
try
:
pages
=
mcp__chrome_devtools__list_pages
()
print
(
f
"✓ 成功获取页面列表: {len(pages.get('pages', []))} 个页面"
)
if
pages
.
get
(
'pages'
):
print
(
f
" 当前页面: {pages['pages'][0]}"
)
results
.
append
((
"list_pages"
,
True
,
""
))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"list_pages"
,
False
,
str
(
e
)))
# 测试2: 导航到测试页面
print
(
"
\n
[测试2] 导航到测试页面"
)
test_url
=
"https://www.baidu.com"
try
:
result
=
mcp__chrome_devtools__navigate_page
(
type
=
"url"
,
url
=
test_url
)
time
.
sleep
(
2
)
# 等待页面加载
print
(
f
"✓ 成功导航到: {test_url}"
)
results
.
append
((
"navigate"
,
True
,
""
))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"navigate"
,
False
,
str
(
e
)))
# 测试3: 获取页面快照
print
(
"
\n
[测试3] 获取页面快照"
)
try
:
snapshot
=
mcp__chrome_devtools__take_snapshot
()
snapshot_text
=
str
(
snapshot
)[:
200
]
print
(
f
"✓ 成功获取快照"
)
print
(
f
" 快照预览: {snapshot_text}..."
)
results
.
append
((
"take_snapshot"
,
True
,
""
))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"take_snapshot"
,
False
,
str
(
e
)))
# 测试4: 查找搜索框元素
print
(
"
\n
[测试4] 查找搜索框元素"
)
try
:
snapshot
=
mcp__chrome_devtools__take_snapshot
()
# 百度搜索框的文本
search_keywords
=
[
"百度"
,
"搜索"
,
"百度一下"
]
found_uid
=
None
for
keyword
in
search_keywords
:
if
keyword
in
str
(
snapshot
):
print
(
f
"✓ 在快照中找到关键词: {keyword}"
)
found_uid
=
"search_box"
# 模拟uid
break
if
found_uid
:
results
.
append
((
"find_element"
,
True
,
f
"找到元素: {found_uid}"
))
else
:
print
(
f
"⚠ 未在快照中找到搜索关键词"
)
results
.
append
((
"find_element"
,
False
,
"未找到搜索框"
))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"find_element"
,
False
,
str
(
e
)))
# 测试5: 执行JavaScript脚本
print
(
"
\n
[测试5] 执行JavaScript脚本"
)
try
:
script
=
"() => { return document.title; }"
result
=
mcp__chrome_devtools__evaluate_script
(
function
=
script
)
print
(
f
"✓ 成功执行脚本"
)
print
(
f
" 页面标题: {result}"
)
results
.
append
((
"evaluate_script"
,
True
,
""
))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"evaluate_script"
,
False
,
str
(
e
)))
# 测试6: 获取当前URL
print
(
"
\n
[测试6] 获取当前URL"
)
try
:
script
=
"() => { return window.location.href; }"
result
=
mcp__chrome_devtools__evaluate_script
(
function
=
script
)
print
(
f
"✓ 成功获取当前URL: {result}"
)
results
.
append
((
"get_url"
,
True
,
""
))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"get_url"
,
False
,
str
(
e
)))
# 测试7: 截取屏幕截图
print
(
"
\n
[测试7] 截取屏幕截图"
)
try
:
screenshot_path
=
"E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator/test_screenshot.png"
result
=
mcp__chrome_devtools__take_screenshot
(
filePath
=
screenshot_path
,
format
=
"png"
)
print
(
f
"✓ 成功截取屏幕截图"
)
print
(
f
" 保存路径: {screenshot_path}"
)
results
.
append
((
"take_screenshot"
,
True
,
""
))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"take_screenshot"
,
False
,
str
(
e
)))
# 打印测试结果汇总
print
(
"
\n
"
+
"="
*
50
)
print
(
"测试结果汇总"
)
print
(
"="
*
50
)
passed
=
sum
(
1
for
_
,
success
,
_
in
results
if
success
)
total
=
len
(
results
)
for
test_name
,
success
,
error
in
results
:
status
=
"✓ 通过"
if
success
else
"✗ 失败"
print
(
f
"{status:10} {test_name:20} {error if error else ''}"
)
print
(
f
"
\n
总计: {passed}/{total} 通过"
)
return
results
def
test_mcp_form_interaction
():
"""测试MCP表单交互功能"""
print
(
"
\n
"
+
"="
*
50
)
print
(
"MCP表单交互测试"
)
print
(
"="
*
50
)
results
=
[]
# 导航到百度
print
(
"
\n
[步骤1] 导航到百度"
)
try
:
mcp__chrome_devtools__navigate_page
(
type
=
"url"
,
url
=
"https://www.baidu.com"
)
time
.
sleep
(
2
)
print
(
"✓ 成功导航到百度"
)
results
.
append
((
"navigate_baidu"
,
True
,
""
))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"navigate_baidu"
,
False
,
str
(
e
)))
return
results
# 获取快照并分析
print
(
"
\n
[步骤2] 获取页面快照"
)
try
:
snapshot
=
mcp__chrome_devtools__take_snapshot
()
print
(
"✓ 成功获取快照"
)
# 分析快照结构
snapshot_str
=
str
(
snapshot
)
print
(
f
" 快照长度: {len(snapshot_str)} 字符"
)
# 查找输入框相关内容
if
"input"
in
snapshot_str
.
lower
()
or
"textbox"
in
snapshot_str
.
lower
():
print
(
" ✓ 快照中包含输入框元素"
)
results
.
append
((
"analyze_snapshot"
,
True
,
"找到输入框"
))
else
:
print
(
" ⚠ 快照中未找到明显的输入框元素"
)
results
.
append
((
"analyze_snapshot"
,
False
,
"未找到输入框"
))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"analyze_snapshot"
,
False
,
str
(
e
)))
# 尝试填写搜索框(使用JavaScript)
print
(
"
\n
[步骤3] 尝试填写搜索框"
)
try
:
# 百度搜索框的ID通常是kw
script
=
"""(text) => {
const input = document.getElementById('kw');
if (input) {
input.value = text;
return {success: true, element: 'input#kw'};
}
return {success: false, error: 'input#kw not found'};
}"""
result
=
mcp__chrome_devtools__evaluate_script
(
function
=
script
,
args
=
[
"自动化测试"
])
print
(
f
"✓ 成功执行填写脚本"
)
print
(
f
" 结果: {result}"
)
results
.
append
((
"fill_input"
,
True
,
str
(
result
)))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"fill_input"
,
False
,
str
(
e
)))
# 截取填写后的截图
print
(
"
\n
[步骤4] 截取填写后的页面"
)
try
:
screenshot_path
=
"E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator/test_filled.png"
mcp__chrome_devtools__take_screenshot
(
filePath
=
screenshot_path
,
format
=
"png"
)
print
(
f
"✓ 成功截取截图: {screenshot_path}"
)
results
.
append
((
"screenshot_filled"
,
True
,
""
))
except
Exception
as
e
:
print
(
f
"✗ 失败: {e}"
)
results
.
append
((
"screenshot_filled"
,
False
,
str
(
e
)))
return
results
def
save_test_results
(
results
:
list
,
test_name
:
str
):
"""保存测试结果到文件"""
output_dir
=
Path
(
"E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator/test_results"
)
output_dir
.
mkdir
(
exist_ok
=
True
)
timestamp
=
time
.
strftime
(
"
%
Y
%
m
%
d_
%
H
%
M
%
S"
)
result_file
=
output_dir
/
f
"{test_name}_{timestamp}.json"
summary
=
{
"test_name"
:
test_name
,
"timestamp"
:
time
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
),
"total_tests"
:
len
(
results
),
"passed_tests"
:
sum
(
1
for
_
,
success
,
_
in
results
if
success
),
"results"
:
[
{
"test"
:
name
,
"success"
:
success
,
"error"
:
error
}
for
name
,
success
,
error
in
results
]
}
with
open
(
result_file
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
json
.
dump
(
summary
,
f
,
ensure_ascii
=
False
,
indent
=
2
)
print
(
f
"
\n
测试结果已保存到: {result_file}"
)
if
__name__
==
"__main__"
:
print
(
"开始MCP功能测试..."
)
# 基本功能测试
basic_results
=
test_mcp_basic
()
save_test_results
(
basic_results
,
"mcp_basic_test"
)
# 表单交互测试
form_results
=
test_mcp_form_interaction
()
save_test_results
(
form_results
,
"mcp_form_test"
)
print
(
"
\n
"
+
"="
*
50
)
print
(
"所有测试完成!"
)
print
(
"="
*
50
)
AuxiliaryTool/TestCaseGenerator/test_screenshot.png
0 → 100644
浏览文件 @
588166f7
142.4 KB
AuxiliaryTool/TestCaseGenerator/testcase_generator.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
测试用例生成模块
负责根据操作步骤生成JSON格式的测试用例文件
"""
import
json
from
pathlib
import
Path
from
typing
import
Dict
,
List
from
datetime
import
datetime
from
utils.logger
import
get_logger
from
utils.constants
import
EXPECTED_RESULTS
class
TestCaseGenerator
:
"""测试用例生成器"""
def
__init__
(
self
,
module_info
:
Dict
,
base_url
:
str
):
"""
初始化测试用例生成器
Args:
module_info: 模块信息字典
base_url: 系统基础URL
"""
self
.
module_info
=
module_info
self
.
base_url
=
base_url
self
.
logger
=
get_logger
(
"TestCaseGenerator"
)
self
.
module_name
=
module_info
.
get
(
"module_name"
,
""
)
self
.
module_name_son
=
module_info
.
get
(
"module_name_son"
,
""
)
def
generate_testcase
(
self
,
operation
:
str
,
steps
:
List
[
Dict
])
->
Dict
:
"""
生成测试用例
Args:
operation: 操作类型(添加/编辑/删除)
steps: 操作步骤列表
Returns:
测试用例字典
"""
self
.
logger
.
info
(
f
"正在生成测试用例: {self.generate_name(operation)}"
)
testcase
=
{
"name"
:
self
.
generate_name
(
operation
),
"para"
:
steps
,
"platform"
:
"web"
,
"base_url"
:
self
.
base_url
}
# 验证测试用例
if
not
self
.
_validate_testcase
(
testcase
):
self
.
logger
.
error
(
"测试用例验证失败"
)
return
{}
self
.
logger
.
info
(
f
"测试用例生成成功,共 {len(steps)} 个步骤"
)
return
testcase
def
generate_name
(
self
,
operation
:
str
)
->
str
:
"""
生成测试用例名称
Args:
operation: 操作类型
Returns:
测试用例名称
"""
return
f
"{self.module_name}_{self.module_name_son}_{operation}"
def
generate_expected_result
(
self
,
operation
:
str
)
->
str
:
"""
生成预期结果
Args:
operation: 操作类型
Returns:
预期结果字符串
"""
return
EXPECTED_RESULTS
.
get
(
operation
,
f
"{operation}成功"
)
def
save_testcase
(
self
,
testcase
:
Dict
,
output_dir
:
Path
)
->
str
:
"""
保存测试用例到JSON文件
Args:
testcase: 测试用例字典
output_dir: 输出目录
Returns:
保存的文件路径
"""
if
not
testcase
:
self
.
logger
.
error
(
"测试用例为空,无法保存"
)
return
""
# 确保输出目录存在
output_dir
=
Path
(
output_dir
)
output_dir
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
# 生成文件名
filename
=
f
"{testcase['name']}.json"
filepath
=
output_dir
/
filename
self
.
logger
.
info
(
f
"正在保存测试用例: {filepath}"
)
try
:
# 格式化JSON并保存
json_content
=
self
.
format_json
(
testcase
)
with
open
(
filepath
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
json_content
)
self
.
logger
.
info
(
f
"测试用例保存成功: {filepath}"
)
return
str
(
filepath
)
except
Exception
as
e
:
self
.
logger
.
error
(
f
"保存测试用例失败: {e}"
)
return
""
def
format_json
(
self
,
data
:
Dict
)
->
str
:
"""
格式化JSON输出
Args:
data: 要格式化的数据
Returns:
格式化后的JSON字符串
"""
return
json
.
dumps
(
data
,
ensure_ascii
=
False
,
indent
=
2
,
separators
=
(
','
,
': '
)
)
def
_validate_testcase
(
self
,
testcase
:
Dict
)
->
bool
:
"""
验证测试用例格式
Args:
testcase: 测试用例字典
Returns:
是否验证通过
"""
# 检查必填字段
required_fields
=
[
"name"
,
"para"
,
"platform"
,
"base_url"
]
for
field
in
required_fields
:
if
field
not
in
testcase
:
self
.
logger
.
error
(
f
"测试用例缺少必填字段: {field}"
)
return
False
# 检查para是否为列表
if
not
isinstance
(
testcase
[
"para"
],
list
):
self
.
logger
.
error
(
"测试用例的para字段必须是列表"
)
return
False
# 检查每个步骤的必填字段
step_required_fields
=
[
"page"
,
"step"
,
"locator_type"
,
"locator_value"
,
"element_type"
]
for
idx
,
step
in
enumerate
(
testcase
[
"para"
]):
for
field
in
step_required_fields
:
if
field
not
in
step
:
self
.
logger
.
warning
(
f
"步骤{idx+1}缺少字段: {field}"
)
return
True
def
generate_summary
(
self
,
testcases
:
List
[
Dict
])
->
Dict
:
"""
生成测试用例汇总信息
Args:
testcases: 测试用例列表
Returns:
汇总信息字典
"""
summary
=
{
"module_name"
:
self
.
module_name
,
"module_name_son"
:
self
.
module_name_son
,
"total_count"
:
len
(
testcases
),
"testcases"
:
[
tc
.
get
(
"name"
,
""
)
for
tc
in
testcases
],
"generated_time"
:
datetime
.
now
()
.
strftime
(
"
%
Y-
%
m-
%
d
%
H:
%
M:
%
S"
)
}
return
summary
def
save_summary
(
self
,
summary
:
Dict
,
output_dir
:
Path
)
->
str
:
"""
保存汇总信息
Args:
summary: 汇总信息字典
output_dir: 输出目录
Returns:
保存的文件路径
"""
filename
=
f
"{self.module_name}_{self.module_name_son}_summary.json"
filepath
=
Path
(
output_dir
)
/
filename
try
:
with
open
(
filepath
,
'w'
,
encoding
=
'utf-8'
)
as
f
:
f
.
write
(
self
.
format_json
(
summary
))
self
.
logger
.
info
(
f
"汇总信息保存成功: {filepath}"
)
return
str
(
filepath
)
except
Exception
as
e
:
self
.
logger
.
error
(
f
"保存汇总信息失败: {e}"
)
return
""
def
batch_generate
(
self
,
operations
:
List
[
str
],
steps_dict
:
Dict
[
str
,
List
[
Dict
]],
output_dir
:
Path
)
->
List
[
str
]:
"""
批量生成测试用例
Args:
operations: 操作类型列表
steps_dict: 操作步骤字典,key为操作类型,value为步骤列表
output_dir: 输出目录
Returns:
生成的文件路径列表
"""
self
.
logger
.
info
(
f
"开始批量生成测试用例,共 {len(operations)} 个操作"
)
testcases
=
[]
filepaths
=
[]
for
operation
in
operations
:
steps
=
steps_dict
.
get
(
operation
,
[])
if
not
steps
:
self
.
logger
.
warning
(
f
"操作 {operation} 没有步骤,跳过"
)
continue
# 生成测试用例
testcase
=
self
.
generate_testcase
(
operation
,
steps
)
if
testcase
:
testcases
.
append
(
testcase
)
# 保存测试用例
filepath
=
self
.
save_testcase
(
testcase
,
output_dir
)
if
filepath
:
filepaths
.
append
(
filepath
)
# 生成并保存汇总信息
if
testcases
:
summary
=
self
.
generate_summary
(
testcases
)
self
.
save_summary
(
summary
,
output_dir
)
self
.
logger
.
info
(
f
"批量生成完成,共生成 {len(filepaths)} 个文件"
)
return
filepaths
AuxiliaryTool/TestCaseGenerator/utils/__init__.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
工具函数模块
提供日志、常量等公共功能
"""
from
.logger
import
get_logger
from
.constants
import
*
__all__
=
[
'get_logger'
]
AuxiliaryTool/TestCaseGenerator/utils/constants.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
常量定义模块
定义系统中使用的各种常量
"""
# 固定测试数据
TEST_DATA
=
{
"username"
:
"test001"
,
"password"
:
"Test@123456"
,
"phone"
:
"13800138000"
,
"email"
:
"test001@example.com"
,
"name"
:
"测试用户"
,
"description"
:
"测试内容"
,
"remark"
:
"自动化测试"
,
"code"
:
"001"
}
# 预期结果映射
EXPECTED_RESULTS
=
{
"添加"
:
"添加成功"
,
"编辑"
:
"编辑成功"
,
"删除"
:
"删除成功"
,
"提交"
:
"提交成功"
,
"保存"
:
"保存成功"
,
"修改"
:
"修改成功"
}
# 支持的元素类型
ELEMENT_TYPES
=
{
# 基础类型
"click"
:
"点击按钮、链接等可点击元素"
,
"input"
:
"文本输入框"
,
"select"
:
"下拉选择框"
,
# 选择类型
"checkbox"
:
"复选框/单选框"
,
"switch"
:
"开关控件"
,
# 验证类型
"getTips"
:
"获取弹窗提示信息(如Element UI的el-message组件)"
,
"getText"
:
"获取列表数据文本(如表格td元素中的文本)"
}
# element_type 与 element_value 填写规则映射
ELEMENT_VALUE_RULES
=
{
"input"
:
"填写需输入的文本内容"
,
"getTips"
:
"留空(验证内容填写在expected_result中)"
,
"getText"
:
"留空(验证内容填写在expected_result中)"
,
"click"
:
"留空"
,
"select"
:
"留空"
,
"checkbox"
:
"留空"
,
"switch"
:
"留空"
}
# 定位策略优先级(注意:不允许使用UID)
LOCATOR_PRIORITIES
=
[
"ID"
,
# 优先级1:ID属性
"NAME"
,
# 优先级2:Name属性
"CLASS"
,
# 优先级3:Class属性
"XPATH"
,
# 备选:XPATH表达式
"CSS_SELECTOR"
# 备选:CSS选择器
]
# 禁止使用的定位类型
FORBIDDEN_LOCATOR_TYPES
=
[
"UID"
]
# XPath规范
XPATH_CONTAINS_PATTERN
=
"contains(.,'{text}')"
# 使用 . 而非 text()
XPATH_CONTAINS_OLD_PATTERN
=
"contains(text(),'{text}')"
# 旧的错误模式
# 定位策略优先级
LOCATOR_PRIORITIES
=
[
"ID"
,
# 优先级1:ID属性
"NAME"
,
# 优先级2:Name属性
"CLASS"
,
# 优先级3:Class属性
"XPATH"
,
# 备选:XPATH表达式
"CSS_SELECTOR"
# 备选:CSS选择器
]
# 等待超时时间(秒)
WAIT_TIMEOUT
=
30
WAIT_SHORT
=
5
WAIT_MEDIUM
=
10
# MCP相关常量
MCP_TIMEOUT
=
60000
# MCP操作超时时间(毫秒)
# 浏览器相关
BROWSER_WIDTH
=
1920
BROWSER_HEIGHT
=
1080
# 文件相关
CONFIG_DIR
=
"config"
TESTCASES_DIR
=
"testcases"
LOGS_DIR
=
"logs"
AuxiliaryTool/TestCaseGenerator/utils/logger.py
0 → 100644
浏览文件 @
588166f7
# -*- coding: utf-8 -*-
"""
日志工具模块
提供统一的日志输出功能
"""
import
logging
import
sys
from
pathlib
import
Path
from
datetime
import
datetime
def
get_logger
(
name
:
str
=
"TestCaseGenerator"
,
level
:
int
=
logging
.
INFO
)
->
logging
.
Logger
:
"""
获取日志记录器
Args:
name: 日志记录器名称
level: 日志级别
Returns:
配置好的日志记录器
"""
logger
=
logging
.
getLogger
(
name
)
# 避免重复添加handler
if
logger
.
handlers
:
return
logger
logger
.
setLevel
(
level
)
# 控制台输出handler
console_handler
=
logging
.
StreamHandler
(
sys
.
stdout
)
console_handler
.
setLevel
(
level
)
# 日志格式
formatter
=
logging
.
Formatter
(
'
%(asctime)
s -
%(name)
s -
%(levelname)
s -
%(message)
s'
,
datefmt
=
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
console_handler
.
setFormatter
(
formatter
)
logger
.
addHandler
(
console_handler
)
return
logger
def
setup_file_logger
(
logger
:
logging
.
Logger
,
log_dir
:
Path
)
->
None
:
"""
设置文件日志输出
Args:
logger: 日志记录器
log_dir: 日志文件目录
"""
log_dir
.
mkdir
(
parents
=
True
,
exist_ok
=
True
)
# 日志文件名:按日期命名
log_file
=
log_dir
/
f
"testcase_generator_{datetime.now().strftime('
%
Y
%
m
%
d')}.log"
# 文件输出handler
file_handler
=
logging
.
FileHandler
(
log_file
,
encoding
=
'utf-8'
)
file_handler
.
setLevel
(
logging
.
DEBUG
)
# 文件日志格式
formatter
=
logging
.
Formatter
(
'
%(asctime)
s -
%(name)
s -
%(levelname)
s - [
%(filename)
s:
%(lineno)
d] -
%(message)
s'
,
datefmt
=
'
%
Y-
%
m-
%
d
%
H:
%
M:
%
S'
)
file_handler
.
setFormatter
(
formatter
)
logger
.
addHandler
(
file_handler
)
Docs/PRD/自动化测试用例生成/_PRD_自动化测试用例生成需求文档.md
0 → 100644
浏览文件 @
588166f7
# 需求文档
## 需求背景与目标
### 1.1 背景
当前自动化测试用例仍是手动编写,效率较低,且无法批量生成,且无法自动生成JSON数据模板,无法自动生成测试用例。需要实现自动化测试用例生成。
### 1.2 目标
通过claude code+mcp,根据system_config.json获取系统登录地址、账号密码。根据提供的module_config.json自动访问系统的对应模块进行操作,收集操作步骤的元素定位类型以及元素定位值,并通过自动化测试用例JSON数据模板输出自动化测试用例。实现快速自动补充JSON数据
## 2. 功能需求
1.
根据同目录下的config.json文件,获取系统登录地址、账号密码。
-
system_config.json文件格式示例:
```
json
[
{
"system_type"
:
"new_platform"
,
"system_front_url"
:
"https://192.168.5.44"
,
"system_back_url"
:
"https://192.168.5.44/#/LoginAdmin"
,
"username"
:
"admin@xty"
,
"password"
:
"Ubains@4321"
,
"code"
:
"csba"
}
]
```
2
.
根据同目录下的module_config.json文件,获取需要收集的自动化模块名称、模块功能。
-
module_config.json文件格式示例:
```json
[
{
"module_name"
:
"区域管理"
,
"module_name_son"
:
"增值服务"
,
"module_function"
:
[
"添加"
,
"编辑"
,
"删除"
]
},
{
"module_name"
:
"区域管理"
,
"module_name_son"
:
"增值服务"
,
"module_function"
:
[
"添加"
,
"编辑"
,
"删除"
]
}
]
```
3
.
通过claude
code+mcp,自动访问系统进行模块操作,并通过自动化测试用例JSON数据模板输出自动化测试用例。
-
自动化测试用例JSON数据模板格式示例:
```json
{
"name"
:
"{module_name}_{module_name_son}_{model_function}"
,
"para"
:
[
{
"page"
:
"login/logindf"
,
"step"
:
"点击左侧【用户管理】"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//span[contains(.,'用户管理')]"
,
"element_type"
:
"click"
,
"element_value"
:
""
,
"expected_result"
:
""
},
{
"page"
:
"login/logindf"
,
"step"
:
"点击【用户列表】按钮"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//li[contains(.,'用户列表')]"
,
"element_type"
:
"click"
,
"element_value"
:
""
,
"expected_result"
:
""
},
{
"page"
:
"login/logindf"
,
"step"
:
"点击【新增】按钮"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//button[contains(.,'添加')]"
,
"element_type"
:
"click"
,
"element_value"
:
""
,
"expected_result"
:
""
},
{
"page"
:
"login/logindf"
,
"step"
:
"输入账号"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//input[@id='accountChange']"
,
"element_type"
:
"input"
,
"element_value"
:
"admin@zdh"
,
"expected_result"
:
""
},
{
"page"
:
"login/logindf"
,
"step"
:
"输入用户名"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//input[@placeholder='用户名']"
,
"element_type"
:
"input"
,
"element_value"
:
"admin@zdh"
,
"expected_result"
:
""
},
{
"page"
:
"login/logindf"
,
"step"
:
"输入新密码"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//input[@placeholder='11位及以上的大小写字母和数字且连续3位及以上不重复和不连续组合']"
,
"element_type"
:
"input"
,
"element_value"
:
"Ubains@1357"
,
"expected_result"
:
""
},
{
"page"
:
"login/logindf"
,
"step"
:
"输入确认密码"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//input[@placeholder='确认密码']"
,
"element_type"
:
"input"
,
"element_value"
:
"Ubains@1357"
,
"expected_result"
:
""
},
{
"page"
:
"login/logindf"
,
"step"
:
"点击【确定】按钮"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//button[contains(.,'确定')]"
,
"element_type"
:
"click"
,
"element_value"
:
""
,
"expected_result"
:
""
},
{
"page"
:
"login/logindf"
,
"step"
:
"获取提示文本"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//p[@class='el-message__content']"
,
"element_type"
:
"getTips"
,
"element_value"
:
""
,
"expected_result"
:
"添加成功"
},
{
"page"
:
"login/logindf"
,
"step"
:
"验证列表数据"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//td[contains(.,'admin@zdh')]"
,
"element_type"
:
"getText"
,
"element_value"
:
""
,
"expected_result"
:
"列表中包含新添加的用户"
}
],
"platform"
:
"web"
,
"base_url"
:
"https://192.168.5.44/"
}
```
-
参数说明:
-
name
:
测试用例名称,命名格式为:
{
module_name
}
_
{
module_name_son
}
_
{
model_function
}
,例如:区域管理_增值服务_添加。
-
para
:
测试步骤列表,每个步骤包含以下字段:
-
page
:
page字段为当前模块功能操作的页面路由后缀。
-
step
:
操作步骤描述,例如:点击左侧【用户管理】。
-
locator_type
:
元素定位类型,例如:ID、XPATH、CSS_SELECTOR等。优先获取ID、XPATH,**不允许用UID**。
-
locator_value
:
元素定位值,例如://button
[
contains(.
,
'确定')
]
,需要与locator_type一致。
-
**注意**:XPATH中使用
`contains(.
,
'文本')`
而非
`contains(text()
,
'文本')`,以正确匹配Element
UI等框架中span子元素内的文本。
-
element_type
:
操作类型,例如:click、input、select、checkbox、switch、getTips、getText。
-
element_value
:
操作值,**根据element_type不同有不同填写规则**:
-
`input`:填写需输入的文本内容(如
"test001"
)
-
`getTips`:**留空**,预期结果填写在expected_result字段
-
`getText`:**留空**,预期结果填写在expected_result字段
-
`click`、`select`、`checkbox`、`switch`:**留空**
-
expected_result
:
预期结果描述,根据操作类型填写,如
"添加成功"
、
"列表中包含新添加的数据"
等。
4
.
输出的自动化测试用例文件命名格式为:
{
module_name
}
_
{
module_name_son
}
_
{
model_function
}
.json,例如:区域管理_增值服务_添加.json。
-
模板参考:
[
自动化测试用例模板
]
(新统一平台/测试数据/新统一平台测试用例
-
副本.xlsx)
-
输出位置:config文件同目录下的
`testcases/`
子目录
##
3
.
需求补充说明
###
3.1
配置文件格式规范
####
3.1
.
1
system_config.json格式
采用标准JSON格式,包含以下字段:
-
`system_type`
:
系统类型标识
-
`system_front_url`
:
系统前台地址
-
`system_back_url`
:
系统后台地址
-
`username`
:
登录账号
-
`password`
:
登录密码
-
`code`
:
验证码(固定值:csba)
**注意**:登录时需用户输入选择使用前台或后台URL。
####
3.1
.
2
module_config.json格式
-
`module_name`
:
一级菜单名称
-
`module_name_son`
:
二级菜单名称
-
`module_function`
:
功能列表,支持
"添加"
、
"编辑"
、
"删除"
###
3.2
执行流程规范
####
3.2
.
1
模块导航方式
采用**两级菜单导航**:
1
.
先点击一级菜单(`module_name`)
2
.
再点击二级菜单(`module_name_son`)
####
3.2
.
2
功能操作策略
-
**添加操作**:填写通用固定测试值
-
**编辑操作**:先自动创建测试数据
→
执行编辑
→
清理测试数据
-
**删除操作**:先自动创建测试数据
→
执行删除
→
清理测试数据
####
3.2
.
3
元素定位策略
采用**智能检测**方式,按以下优先级选择定位方式:
1
.
**优先级
1
**:id属性(唯一标识)
2
.
**优先级
2
**:name属性
3
.
**优先级
3
**:class属性
4
.
**备选方案**:XPATH表达式
####
3.2
.
4
Page字段获取
通过**URL解析**获取当前功能所在页面的路由后缀。
####
3.2
.
5
预期结果生成
根据操作类型**自动生成**预期结果提示文本,例如:
-
添加操作
→
"添加成功"
-
编辑操作
→
"编辑成功"
-
删除操作
→
"删除成功"
###
3.3
支持的元素类型(element_type)
####
3.3
.
1
基础类型
-
**click**:点击按钮、链接等可点击元素
-
**input**:文本输入框(input
type=text)
-
**select**:下拉选择框(select)
####
3.3
.
2
选择类型
-
**checkbox**:复选框(checkbox)和单选框(radio)
-
**switch**:开关控件(如Element
UI的switch)
####
3.3
.
3
验证类型
-
**getTips**:获取弹窗提示信息(如Element
UI的el-message组件中的
"添加成功"
、
"删除成功"
等提示)
-
**getText**:获取列表中的文本数据(如表格td元素中的文本,用于验证数据是否存在于列表中)
###
3.4
element_value
和
expected_result
填写规范
####
3.4
.
1
element_value
填写规则
|
element_type
|
element_value
填写内容
|
示例
|
|-------------|----------------------|------|
|
`input`
|
需输入的文本内容
|
`
"test001"
`
|
|
`getTips`
|
**留空**
|
`
""
`
|
|
`getText`
|
**留空**
|
`
""
`
|
|
`click`
|
**留空**
|
`
""
`
|
|
`select`
|
**留空**
|
`
""
`
|
|
`checkbox`
|
**留空**
|
`
""
`
|
|
`switch`
|
**留空**
|
`
""
`
|
####
3.4
.
2
expected_result
填写规则
|
element_type
|
expected_result
填写内容
|
示例
|
|-------------|-------------------------|------|
|
`input`
|
通常留空(非验证步骤)
|
`
""
`
|
|
`click`
|
通常留空(非验证步骤)
|
`
""
`
|
|
`select`
|
通常留空(非验证步骤)
|
`
""
`
|
|
`getTips`
|
预期的弹窗提示文本
|
`
"添加成功"
`
|
|
`getText`
|
预期的验证描述
|
`
"列表中包含新添加的商品"
`
|
####
3.4
.
3
完整示例对比
**错误的写法**(element_value填写了验证相关内容):
```
json
{
"element_type": "getTips",
"element_value": "添加成功", // ❌ 错误
"expected_result": ""
}
```
**正确的写法**(验证内容填写在expected_result):
```
json
{
"element_type": "getTips",
"element_value": "", // ✅ 正确:留空
"expected_result": "添加成功" // ✅ 正确:填写预期结果
}
```
### 3.5 测试数据规范
#### 3.4.1 固定测试值
使用
**通用测试值**
,例如:
-
用户名:test001
-
手机号:13800138000
-
密码:Test@123456
-
邮箱:test001@example.com
### 规范文档
-
代码规范:
`Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
-
问题总结:
`Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
-
方法总结:
`Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
-
文档规范:
`Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md`
- 测试规范: `Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
---
*文档结束*
\ No newline at end of file
Docs/PRD/自动化测试用例生成/_PRD_自动化测试用例生成需求文档_计划执行.md
0 → 100644
浏览文件 @
588166f7
# _PRD_自动化测试用例生成需求文档_计划执行
> 版本:V1.0
> 创建日期:2026-03-06
> 更新日期:2026-03-06
> 适用范围:自动化测试用例生成工具
> 来源:基于《_PRD_自动化测试用例生成需求文档.md》
> 状态:待执行
---
## 1. 项目概述
### 1.1 背景
当前自动化测试用例仍是手动编写,效率较低,且无法批量生成JSON数据模板。需要通过Claude Code + MCP技术,实现自动化测试用例的自动生成,提高测试用例编写效率。
### 1.2 目标
-
使用chrome-devtools MCP工具自动访问系统
-
根据配置文件自动执行模块操作并收集元素定位信息
-
按照JSON模板自动生成测试用例文件
-
支持添加、编辑、删除三种操作的自动化生成
-
实现快速批量生成测试用例
### 1.3 技术栈
-
**Claude Code**
: AI代码生成与执行
-
**chrome-devtools MCP**
: 浏览器自动化工具
-
**Python**
: 主要开发语言
-
**JSON**
: 配置文件和输出格式
### 1.4 涉及文件
**输入文件:**
-
`system_config.json`
- 系统配置文件(登录信息)
-
`module_config.json`
- 模块配置文件(测试模块和功能)
**输出文件:**
-
`{module_name}_{module_name_son}_{function}.json`
- 生成的测试用例文件
-
输出目录:config文件同目录下的
`testcases/`
子目录
**需要创建的模块文件:**
| 序号 | 模块名称 | 文件名 | 职责 | 状态 |
|-----|---------|--------|------|------|
| 1 | 配置管理模块 | config_manager.py | 读取和解析配置文件 | ⏳ 待创建 |
| 2 | 浏览器操作模块 | browser_operator.py | 封装chrome-devtools MCP操作 | ⏳ 待创建 |
| 3 | 登录模块 | login_handler.py | 处理系统登录 | ⏳ 待创建 |
| 4 | 导航模块 | navigation_handler.py | 处理菜单导航 | ⏳ 待创建 |
| 5 | 元素定位模块 | element_locator.py | 智能检测元素定位方式 | ⏳ 待创建 |
| 6 | 操作执行模块 | operation_executor.py | 执行添加/编辑/删除操作 | ⏳ 待创建 |
| 7 | 用例生成模块 | testcase_generator.py | 生成JSON测试用例 | ⏳ 待创建 |
| 8 | 主控模块 | main.py | 统一调度各模块 | ⏳ 待创建 |
---
## 2. 功能需求分析
### 2.1 核心功能流程
```
主程序启动
├── 1) 读取system_config.json获取系统信息
├── 2) 读取module_config.json获取测试模块
├── 3) 用户选择使用前台或后台
├── 4) 初始化chrome-devtools MCP连接
├── 5) 登录系统(固定验证码csba)
├── 6) 遍历module_config中的模块配置
│ ├── 6.1) 两级菜单导航(module_name → module_name_son)
│ ├── 6.2) 执行功能操作
│ │ ├── 添加操作:填写固定测试值
│ │ ├── 编辑操作:创建→编辑→清理
│ │ └── 删除操作:创建→删除→清理
│ ├── 6.3) 智能检测元素定位
│ ├── 6.4) 从URL解析page字段
│ ├── 6.5) 自动生成expected_result
│ └── 6.6) 生成JSON测试用例文件
└── 7) 输出到testcases/目录
```
### 2.2 配置文件规范
#### 2.2.1 system_config.json格式
```
json
[
{
"system_type"
:
"new_platform"
,
"system_front_url"
:
"https://192.168.5.44"
,
"system_back_url"
:
"https://192.168.5.44/#/LoginAdmin"
,
"username"
:
"admin@xty"
,
"password"
:
"Ubains@4321"
,
"code"
:
"csba"
}
]
```
#### 2.2.2 module_config.json格式
```
json
[
{
"module_name"
:
"区域管理"
,
"module_name_son"
:
"增值服务"
,
"module_function"
:
[
"添加"
,
"编辑"
,
"删除"
]
}
]
```
### 2.3 测试用例JSON模板
```
json
{
"name"
:
"{module_name}_{module_name_son}_{function}"
,
"para"
:
[
{
"page"
:
"页面路由"
,
"step"
:
"操作步骤描述"
,
"locator_type"
:
"ID/XPATH/CSS_SELECTOR"
,
"locator_value"
:
"元素定位值"
,
"element_type"
:
"click/input/select/checkbox/switch/getTips/getText"
,
"element_value"
:
"操作值"
,
"expected_result"
:
"预期结果"
}
],
"platform"
:
"web"
,
"base_url"
:
"系统URL"
}
```
**参数填写规范:**
| 参数 | 说明 | 填写规则 |
|------|------|----------|
|
`locator_type`
| 元素定位类型 | 优先使用ID、XPATH;
**不允许使用UID**
|
|
`locator_value`
| 元素定位值 | XPATH使用
`contains(.,'text')`
而非
`contains(text(),'text')`
|
|
`element_type`
| 操作类型 | click/input/select/checkbox/switch/getTips/getText |
|
`element_value`
| 操作值 | 根据element_type填写(详见下文规范) |
|
`expected_result`
| 预期结果 | 根据element_type填写(详见下文规范) |
---
## 3. 模块详细设计
### 3.1 config_manager.py - 配置管理模块
**职责:**
-
读取system_config.json文件
-
读取module_config.json文件
-
验证配置文件格式
-
提供配置数据访问接口
**导出函数:**
```
python
def
load_system_config
(
config_path
:
str
)
->
List
[
Dict
]
def
load_module_config
(
config_path
:
str
)
->
List
[
Dict
]
def
validate_system_config
(
config
:
Dict
)
->
bool
def
validate_module_config
(
config
:
Dict
)
->
bool
```
**依赖:**
-
Python标准库:json、pathlib
---
### 3.2 browser_operator.py - 浏览器操作模块
**职责:**
-
封装chrome-devtools MCP工具调用
-
提供统一的浏览器操作接口
-
处理页面快照和元素查找
-
错误处理和重试机制
**导出函数:**
```
python
class
BrowserOperator
:
def
__init__
(
self
)
def
open_page
(
self
,
url
:
str
)
->
bool
def
take_snapshot
(
self
)
->
Dict
def
find_element
(
self
,
text
:
str
)
->
str
def
click_element
(
self
,
uid
:
str
)
->
bool
def
fill_input
(
self
,
uid
:
str
,
value
:
str
)
->
bool
def
select_option
(
self
,
uid
:
str
,
value
:
str
)
->
bool
def
get_url
(
self
)
->
str
def
wait_for_element
(
self
,
text
:
str
,
timeout
:
int
)
->
bool
```
**依赖:**
-
chrome-devtools MCP工具
-
mcp__chrome_devtools_
*
系列函数
---
### 3.3 login_handler.py - 登录模块
**职责:**
-
处理系统登录流程
-
填写用户名、密码、验证码
-
处理登录后的页面状态
-
验证登录成功
**导出函数:**
```
python
class
LoginHandler
:
def
__init__
(
self
,
browser
:
BrowserOperator
)
def
login
(
self
,
url
:
str
,
username
:
str
,
password
:
str
,
code
:
str
)
->
bool
def
verify_login_success
(
self
)
->
bool
```
**登录流程:**
1.
打开登录页面
2.
查找用户名输入框并填写
3.
查找密码输入框并填写
4.
查找验证码输入框并填写(固定csba)
5.
查找登录按钮并点击
6.
等待页面跳转
7.
验证登录成功
**依赖:**
-
browser_operator模块
---
### 3.4 navigation_handler.py - 导航模块
**职责:**
-
处理两级菜单导航
-
根据module_name和module_name_son定位菜单
-
处理菜单展开和点击
-
验证导航成功
**导出函数:**
```
python
class
NavigationHandler
:
def
__init__
(
self
,
browser
:
BrowserOperator
)
def
navigate_to_module
(
self
,
module_name
:
str
,
module_name_son
:
str
)
->
bool
def
find_menu_item
(
self
,
menu_text
:
str
)
->
str
def
expand_menu
(
self
,
menu_uid
:
str
)
->
bool
def
click_submenu
(
self
,
submenu_text
:
str
)
->
bool
def
get_current_route
(
self
)
->
str
```
**导航流程:**
1.
获取页面快照
2.
查找一级菜单(module_name)
3.
如果菜单未展开,点击展开
4.
查找二级菜单(module_name_son)
5.
点击二级菜单
6.
等待页面加载
7.
从URL解析并返回路由
**依赖:**
-
browser_operator模块
---
### 3.5 element_locator.py - 元素定位模块
**职责:**
-
智能检测元素定位方式
-
按优先级选择最优定位策略
-
生成多种定位方式作为备选
-
记录元素属性信息
**导出函数:**
```
python
class
ElementLocator
:
def
__init__
(
self
,
browser
:
BrowserOperator
)
def
locate_element
(
self
,
element_info
:
Dict
)
->
Dict
def
get_locator_strategy
(
self
,
element
:
Dict
)
->
str
def
generate_locator_value
(
self
,
element
:
Dict
,
strategy
:
str
)
->
str
def
analyze_element_attributes
(
self
,
uid
:
str
)
->
Dict
```
**定位策略优先级:**
1.
**ID属性**
:如果元素有唯一id,优先使用
2.
**Name属性**
:如果元素有唯一name
3.
**Class属性**
:如果元素有唯一class
4.
**XPATH表达式**
:作为备选方案
-
**注意**
:
**不允许使用UID**
作为定位类型
-
**注意**
:XPATH中使用
`contains(.,'text')`
而非
`contains(text(),'text')`
,以正确匹配Element UI等框架中span子元素内的文本
**输出格式:**
```
python
{
"locator_type"
:
"ID/XPATH/CSS_SELECTOR"
,
"locator_value"
:
"//input[@id='username']"
,
"backup_locators"
:
[
...
]
# 备选定位方式
}
```
**依赖:**
-
browser_operator模块
---
### 3.6 operation_executor.py - 操作执行模块
**职责:**
-
执行添加操作
-
执行编辑操作(创建→编辑→清理)
-
执行删除操作(创建→删除→清理)
-
记录操作步骤和元素信息
-
填写固定测试值
**导出函数:**
```
python
class
OperationExecutor
:
def
__init__
(
self
,
browser
:
BrowserOperator
,
locator
:
ElementLocator
)
def
execute_add
(
self
)
->
List
[
Dict
]
def
execute_edit
(
self
)
->
List
[
Dict
]
def
execute_delete
(
self
)
->
List
[
Dict
]
def
fill_test_data
(
self
,
form_elements
:
List
[
Dict
])
->
None
def
click_operation_button
(
self
,
operation
:
str
)
->
bool
def
create_test_data
(
self
)
->
bool
def
cleanup_test_data
(
self
)
->
bool
```
**固定测试值:**
```
python
TEST_DATA
=
{
"username"
:
"test001"
,
"password"
:
"Test@123456"
,
"phone"
:
"13800138000"
,
"email"
:
"test001@example.com"
,
"name"
:
"测试用户"
,
"description"
:
"测试内容"
}
```
**操作流程:**
**添加操作:**
1.
点击"添加"按钮
2.
等待表单弹窗
3.
收集表单字段
4.
填写固定测试值
5.
点击"确定"按钮
6.
**获取操作提示**
(getTips - 弹窗提示)
7.
**验证列表数据**
(getText - 列表数据验证)
8.
记录所有步骤
**编辑操作:**
1.
执行添加操作创建测试数据
2.
点击"编辑"按钮
3.
修改部分字段
4.
点击"确定"按钮
5.
**获取操作提示**
(getTips - 弹窗提示)
6.
**验证列表数据**
(getText - 列表数据验证)
7.
删除测试数据
**删除操作:**
1.
执行添加操作创建测试数据
2.
点击"删除"按钮
3.
确认删除
4.
**获取操作提示**
(getTips - 弹窗提示)
5.
**验证列表数据**
(getText - 列表数据验证)
**依赖:**
-
browser_operator模块
-
element_locator模块
---
### 3.7 testcase_generator.py - 用例生成模块
**职责:**
-
根据操作步骤生成JSON测试用例
-
生成测试用例名称
-
填充page、step、expected_result字段
-
输出JSON文件到指定目录
**导出函数:**
```
python
class
TestCaseGenerator
:
def
__init__
(
self
,
module_info
:
Dict
,
base_url
:
str
)
def
generate_testcase
(
self
,
operation
:
str
,
steps
:
List
[
Dict
])
->
Dict
def
generate_name
(
self
,
operation
:
str
)
->
str
def
generate_expected_result
(
self
,
operation
:
str
)
->
str
def
save_testcase
(
self
,
testcase
:
Dict
,
output_dir
:
str
)
->
str
def
format_json
(
self
,
data
:
Dict
)
->
str
```
**预期结果生成规则:**
```
python
EXPECTED_RESULTS
=
{
"添加"
:
"添加成功"
,
"编辑"
:
"编辑成功"
,
"删除"
:
"删除成功"
,
"提交"
:
"提交成功"
,
"保存"
:
"保存成功"
}
```
**依赖:**
-
Python标准库:json、pathlib
---
### 3.8 main.py - 主控模块
**职责:**
-
统一调度各模块
-
处理用户输入(前台/后台选择)
-
控制整体执行流程
-
错误处理和日志记录
**导出函数:**
```
python
class
TestCaseGeneratorMain
:
def
__init__
(
self
,
system_config
:
str
,
module_config
:
str
)
def
run
(
self
)
->
None
def
select_login_url
(
self
,
system
:
Dict
)
->
str
def
process_modules
(
self
)
->
None
def
process_single_module
(
self
,
module
:
Dict
)
->
None
def
log
(
self
,
message
:
str
,
level
:
str
=
"INFO"
)
->
None
```
**主流程:**
```
python
def
main
():
# 1. 加载配置
system_config
=
load_system_config
(
"system_config.json"
)
module_config
=
load_module_config
(
"module_config.json"
)
# 2. 选择登录URL
login_url
=
select_login_url
(
system_config
[
0
])
# 3. 初始化浏览器
browser
=
BrowserOperator
()
browser
.
open_page
(
login_url
)
# 4. 登录系统
login_handler
=
LoginHandler
(
browser
)
login_handler
.
login
(
...
)
# 5. 处理每个模块
for
module
in
module_config
:
process_single_module
(
module
)
# 6. 输出报告
print
(
"测试用例生成完成!"
)
```
**依赖:**
-
所有其他模块
---
## 4. 目录结构设计
```
ubains-module-test/
├── AuxiliaryTool/
│ └── TestCaseGenerator/ # 测试用例生成工具目录
│ ├── main.py # 主程序入口
│ ├── config_manager.py # 配置管理模块
│ ├── browser_operator.py # 浏览器操作模块
│ ├── login_handler.py # 登录模块
│ ├── navigation_handler.py # 导航模块
│ ├── element_locator.py # 元素定位模块
│ ├── operation_executor.py # 操作执行模块
│ ├── testcase_generator.py # 用例生成模块
│ ├── config/ # 配置文件目录
│ │ ├── system_config.json
│ │ └── module_config.json
│ ├── testcases/ # 输出测试用例目录
│ │ ├── 区域管理_增值服务_添加.json
│ │ ├── 区域管理_增值服务_编辑.json
│ │ └── 区域管理_增值服务_删除.json
│ └── utils/ # 工具函数
│ ├── __init__.py
│ ├── logger.py # 日志工具
│ └── constants.py # 常量定义
```
---
## 5. 支持的元素类型
### 5.1 基础类型
| element_type | 说明 | 操作方式 | 示例 |
|-------------|------|---------|------|
| click | 点击按钮、链接等 | 点击元素 | 点击【确定】按钮 |
| input | 文本输入框 | 输入文本 | 输入用户名 |
| select | 下拉选择框 | 选择选项 | 选择状态【启用】 |
### 5.2 选择类型
| element_type | 说明 | 操作方式 | 示例 |
|-------------|------|---------|------|
| checkbox | 复选框/单选框 | 勾选/取消勾选 | 勾选【启用】复选框 |
| switch | 开关控件 | 打开/关闭 | 打开【自动同步】开关 |
### 5.3 验证类型
| element_type | 说明 | 操作方式 | element_value | expected_result | 示例 |
|-------------|------|---------|-------------|----------------|------|
| getTips | 获取弹窗提示信息 | 读取Element UI等组件的提示文本 | 留空
`""`
| 填写预期提示文本 |
`"添加成功"`
|
| getText | 获取列表数据文本 | 读取表格td等元素中的文本 | 留空
`""`
| 填写验证描述 |
`"列表中包含新添加的数据"`
|
---
## 5.5 element_value 和 expected_result 填写规范
### 5.5.1 element_value 填写规则
| element_type | element_value 填写内容 | 示例 |
|-------------|----------------------|------|
|
`input`
| 填写需输入的文本内容 |
`"test001"`
|
|
`getTips`
|
**留空**
`""`
|
`""`
|
|
`getText`
|
**留空**
`""`
|
`""`
|
|
`click`
|
**留空**
`""`
|
`""`
|
|
`select`
|
**留空**
`""`
|
`""`
|
|
`checkbox`
|
**留空**
`""`
|
`""`
|
|
`switch`
|
**留空**
`""`
|
`""`
|
### 5.5.2 expected_result 填写规则
| element_type | expected_result 填写内容 | 示例 |
|-------------|-------------------------|------|
|
`input`
| 通常留空(非验证步骤) |
`""`
|
|
`click`
| 通常留空(非验证步骤) |
`""`
|
|
`select`
| 通常留空(非验证步骤) |
`""`
|
|
`getTips`
| 填写预期的弹窗提示文本 |
`"添加成功"`
|
|
`getText`
| 填写预期的验证描述 |
`"列表中包含新添加的商品"`
|
### 5.5.3 完整示例对比
**错误的写法**
(element_value填写了验证相关内容):
```
json
{
"element_type"
:
"getTips"
,
"element_value"
:
"添加成功"
,
//
❌
错误
"expected_result"
:
""
}
```
**正确的写法**
(验证内容填写在expected_result):
```
json
{
"element_type"
:
"getTips"
,
"element_value"
:
""
,
//
✅
正确:留空
"expected_result"
:
"添加成功"
//
✅
正确:填写预期结果
}
```
### 5.5.4 XPath 定位规范
**错误的写法**
(无法匹配Element UI按钮span子元素内的文本):
```
json
{
"locator_value"
:
"//button[contains(text(),'确定')]"
//
❌
错误
}
```
**正确的写法**
(使用
`contains(.,'text')`
匹配整个元素文本内容):
```
json
{
"locator_value"
:
"//button[contains(.,'确定')]"
//
✅
正确
}
```
**多条件定位的简化**
(移除不必要的多条件组合):
```
json
//
❌
错误:多条件组合,定位器过长
"locator_value"
:
"//div[contains(@class, 'el-dialog__footer')]//button[contains(text(), '确定') and contains(@class, 'el-button--primary')]"
//
✅
正确:简化为唯一条件
"locator_value"
:
"//button[contains(.,'确定')]"
```
---
## 6. 执行计划
### 6.1 阶段划分
| 阶段 | 任务 | 预计工作量 | 状态 |
|-----|------|----------|------|
|
**阶段1**
| 创建项目目录结构 | 0.5天 | ⏳ 待执行 |
|
**阶段2**
| 实现config_manager模块 | 0.5天 | ⏳ 待执行 |
|
**阶段3**
| 实现browser_operator模块 | 1-2天 | ⏳ 待执行 |
|
**阶段4**
| 实现login_handler模块 | 1天 | ⏳ 待执行 |
|
**阶段5**
| 实现navigation_handler模块 | 1天 | ⏳ 待执行 |
|
**阶段6**
| 实现element_locator模块 | 1-2天 | ⏳ 待执行 |
|
**阶段7**
| 实现operation_executor模块 | 2-3天 | ⏳ 待执行 |
|
**阶段8**
| 实现testcase_generator模块 | 1天 | ⏳ 待执行 |
|
**阶段9**
| 实现main主控模块 | 1天 | ⏳ 待执行 |
|
**阶段10**
| 集成测试 | 2-3天 | ⏳ 待执行 |
|
**阶段11**
| 文档编写 | 1天 | ⏳ 待执行 |
### 6.2 里程碑
| 里程碑 | 完成标准 | 状态 |
|-------|---------|------|
| M1: 基础模块完成 | config_manager、browser_operator、login_handler 完成 | ⏳ 待达成 |
| M2: 核心功能完成 | navigation、element_locator、operation_executor 完成 | ⏳ 待达成 |
| M3: 生成功能完成 | testcase_generator、main 完成 | ⏳ 待达成 |
| M4: 测试完成 | 端到端测试通过 | ⏳ 待达成 |
---
## 7. 测试计划
### 7.1 单元测试
每个模块需要测试:
-
函数正常执行
-
参数校验
-
异常处理
-
返回值格式
### 7.2 集成测试
测试场景:
1.
新平台系统完整流程
2.
多模块批量生成
3.
不同元素类型处理
4.
添加/编辑/删除操作
5.
JSON输出格式验证
### 7.3 测试用例示例
**输入配置:**
```
json
{
"module_name"
:
"用户管理"
,
"module_name_son"
:
"用户列表"
,
"module_function"
:
[
"添加"
]
}
```
**预期输出:**
```
json
{
"name"
:
"用户管理_用户列表_添加"
,
"para"
:
[
{
"page"
:
"system/user"
,
"step"
:
"点击左侧【用户管理】"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//span[contains(.,'用户管理')]"
,
"element_type"
:
"click"
,
"element_value"
:
""
,
"expected_result"
:
""
},
{
"page"
:
"system/user"
,
"step"
:
"点击【用户列表】"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//li[contains(.,'用户列表')]"
,
"element_type"
:
"click"
,
"element_value"
:
""
,
"expected_result"
:
""
},
{
"page"
:
"system/user"
,
"step"
:
"点击【添加】按钮"
,
"locator_type"
:
"ID"
,
"locator_value"
:
"addButton"
,
"element_type"
:
"click"
,
"element_value"
:
""
,
"expected_result"
:
""
},
{
"page"
:
"system/user"
,
"step"
:
"输入用户名"
,
"locator_type"
:
"ID"
,
"locator_value"
:
"username"
,
"element_type"
:
"input"
,
"element_value"
:
"test001"
,
"expected_result"
:
""
},
{
"page"
:
"system/user"
,
"step"
:
"点击【确定】按钮"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//button[contains(.,'确定')]"
,
"element_type"
:
"click"
,
"element_value"
:
""
,
"expected_result"
:
""
},
{
"page"
:
"system/user"
,
"step"
:
"获取操作提示"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//p[@class='el-message__content']"
,
"element_type"
:
"getTips"
,
"element_value"
:
""
,
"expected_result"
:
"添加成功"
},
{
"page"
:
"system/user"
,
"step"
:
"验证列表数据"
,
"locator_type"
:
"XPATH"
,
"locator_value"
:
"//td[contains(.,'test001')]"
,
"element_type"
:
"getText"
,
"element_value"
:
""
,
"expected_result"
:
"列表中包含新添加的用户"
}
],
"platform"
:
"web"
,
"base_url"
:
"https://192.168.5.44/"
}
```
---
## 8. 风险与应对
| 风险 | 影响 | 概率 | 应对措施 |
|-----|------|------|---------|
| MCP连接不稳定 | 高 | 中 | 添加重试机制和超时处理 |
| 元素定位失败 | 高 | 中 | 提供多种定位方式备选 |
| 页面加载超时 | 中 | 中 | 添加等待和重试逻辑 |
| 登录验证码变化 | 中 | 低 | 支持手动输入或API获取 |
| 菜单结构差异 | 中 | 中 | 支持配置化导航路径 |
| 表单字段识别错误 | 中 | 中 | 添加字段类型识别 |
---
## 9. 验收标准
### 9.1 功能验收
-
[
]
能够正确读取配置文件
-
[
]
能够自动登录系统
-
[
]
能够正确导航到指定模块
-
[
]
能够智能检测元素定位方式
-
[
]
能够执行添加/编辑/删除操作
-
[
]
能够生成符合格式的JSON测试用例
-
[
]
能够批量处理多个模块
### 9.2 代码质量验收
-
[
]
符合代码规范要求(中文注释)
-
[
]
每个模块职责单一
-
[
]
函数有完整的类型注解
-
[
]
异常处理完善
-
[
]
日志记录详细
### 9.3 输出质量验收
-
[
]
JSON格式正确
-
[
]
元素定位准确
-
[
]
不使用UID作为定位类型
-
[
]
XPATH使用
`contains(.,'text')`
而非
`contains(text(),'text')`
-
[
]
定位器简洁,无多余的多条件组合
-
[
]
操作步骤完整
-
[
]
添加/编辑/删除操作包含getTips验证步骤
-
[
]
添加/编辑/删除操作包含getText列表验证步骤
-
[
]
element_value填写正确
-
[
]
只有input类型填写输入内容
-
[
]
getTips/getText类型element_value为空
-
[
]
click/select类型element_value为空
-
[
]
expected_result填写正确
-
[
]
getTips类型填写预期提示文本
-
[
]
getText类型填写验证描述
-
[
]
文件命名规范
---
## 10. 参考文档
-
代码规范:
`Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
-
问题总结:
`Docs/PRD/01规范文档/_PRD_问题总结_记录文档.md`
-
方法总结:
`Docs/PRD/01规范文档/_PRD_方法总结_记录文档.md`
-
文档规范:
`Docs/PRD/01规范文档/_PRD_规范文档_文档规范.md`
-
测试规范:
`Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
-
需求文档:
`Docs/PRD/自动化测试用例生成/_PRD_自动化测试用例生成需求文档.md`
---
## 11. 执行结果记录
### 11.1 代码实现完成(2026-03-06)
**完成内容:**
-
✅ 阶段1:创建项目目录结构
-
✅ 阶段2:实现config_manager模块
-
✅ 阶段3:实现browser_operator模块
-
✅ 阶段4:实现login_handler模块
-
✅ 阶段5:实现navigation_handler模块
-
✅ 阶段6:实现element_locator模块
-
✅ 阶段7:实现operation_executor模块
-
✅ 阶段8:实现testcase_generator模块
-
✅ 阶段9:实现main主控模块
**创建的文件:**
```
AuxiliaryTool/TestCaseGenerator/
├── main.py # 主程序入口(253行)
├── config_manager.py # 配置管理模块(227行)
├── browser_operator.py # 浏览器操作模块(277行)
├── login_handler.py # 登录处理模块(227行)
├── navigation_handler.py # 导航处理模块(221行)
├── element_locator.py # 元素定位模块(326行)
├── operation_executor.py # 操作执行模块(567行)
├── testcase_generator.py # 测试用例生成模块(249行)
├── config/
│ ├── system_config.json # 系统配置示例
│ └── module_config.json # 模块配置示例
├── utils/
│ ├── __init__.py
│ ├── logger.py # 日志工具
│ └── constants.py # 常量定义
├── testcases/ # 输出目录
└── README.md # 使用说明
```
**代码统计:**
-
总代码行数:约2300行
-
模块数量:8个核心模块
-
注释覆盖率:100%(所有函数包含中文注释)
**里程碑达成:**
-
✅ M1: 基础模块完成(config_manager、browser_operator、login_handler)
-
✅ M2: 核心功能完成(navigation、element_locator、operation_executor)
-
✅ M3: 生成功能完成(testcase_generator、main)
**状态:**
⏳ 待集成测试
### 11.2 问题修复记录(2026-03-06)
#### 问题1:XPath定位器文本匹配层级问题
**问题描述**
:
-
`contains(text(), '文本')`
无法匹配Element UI按钮span子元素内的文本
-
定位器存在多条件组合,不够简洁
**修复方案**
:
| 修复项 | 修复前 | 修复后 |
|--------|--------|--------|
| 文本匹配 |
`contains(text(), '确定')`
|
`contains(., '确定')`
|
| 按钮定位 |
`//button[contains(text(), '添加') and contains(@class, 'el-button--primary')]`
|
`//button[contains(., '添加')]`
|
| 确定按钮 |
`//div[contains(@class, 'el-dialog__footer')]//button[contains(text(), '确定')]`
|
`//button[contains(., '确定')]`
|
| 图标按钮 |
`//span[contains(@id, 'edit') and .//i[contains(@class, 'el-icon-edit')]]`
|
`//i[contains(@class, 'el-icon-edit')]`
|
**修复文件**
:
-
`element_locator.py`
- XPath生成逻辑
-
`区域管理_增值服务_添加.json`
- 步骤3、8
-
`区域管理_增值服务_编辑.json`
- 步骤3、5
-
`区域管理_增值服务_删除.json`
- 步骤3、4
#### 问题2:element_value 填写规则不统一
**问题描述**
:
-
`getTips`
和
`getText`
类型的
`element_value`
填写了验证内容
-
规范要求验证内容应填写在
`expected_result`
字段中
**修复方案**
:
| element_type | element_value | expected_result |
|-------------|---------------|-----------------|
|
`input`
| 填写输入内容 | 通常留空 |
|
`getTips`
|
**留空**
✅ | 填写预期提示文本 |
|
`getText`
|
**留空**
✅ | 填写验证描述 |
|
`click`
/
`select`
|
**留空**
✅ | 通常留空 |
**修复文件**
:
-
`operation_executor.py`
-
`_get_operation_tips()`
和
`_verify_list_data()`
方法
-
`区域管理_增值服务_添加.json`
- 添加getTips步骤,修正element_value
-
`区域管理_增值服务_编辑.json`
- 添加getTips步骤,修正element_value
-
`区域管理_增值服务_删除.json`
- 添加getTips步骤,修正element_value
#### 问题3:新增getText类型区分验证场景
**说明**
:
-
`getTips`
:获取弹窗提示信息(如Element UI的el-message组件)
-
`getText`
:获取列表中的文本数据(如表格td元素)
**代码更新**
:
-
`operation_executor.py`
- 新增
`_verify_list_data()`
方法
-
各操作流程中添加列表数据验证步骤
#### 问题4:文档规范同步更新
**更新内容**
:
1.
需求文档新增3.4节:element_value 和 expected_result 填写规范
2.
需求文档更新示例:XPATH使用
`contains(.,'text')`
3.
计划执行文档新增5.5节:详细填写规范
4.
更新测试用例示例符合新规范
---
## 12. 优化功能回填
*本节将在执行过程中记录优化内容*
---
*文档结束*
Docs/PRD/自动化测试用例生成/config.json
0 → 100644
浏览文件 @
588166f7
[
{
"system_type"
:
"new_platform"
,
"system_front_url"
:
"https://192.168.5.44"
,
"system_back_url"
:
"https://192.168.5.44/#/LoginAdmin"
,
"username"
:
"admin@xty"
,
"password"
:
"Ubains@4321"
,
"code"
:
"csba"
},
{
"system_type"
:
"new_platform"
,
"system_front_url"
:
"https://192.168.5.44"
,
"system_back_url"
:
"https://192.168.5.44/#/LoginAdmin"
,
"username"
:
"admin@xty"
,
"password"
:
"Ubains@4321"
,
"code"
:
"csba"
}
]
\ No newline at end of file
Docs/PRD/自动化测试用例生成/问题修复/_PRD_XPATH定位器无法匹配元素_问题记录_计划执行.md
0 → 100644
浏览文件 @
588166f7
# _PRD_XPATH定位器无法匹配元素_问题记录_计划执行
> 版本:V1.1
> 创建日期:2026-03-06
> 更新日期:2026-03-06
> 适用范围:自动化测试用例生成工具
> 来源:基于《_PRD_运行脚本无法登录系统_问题记录_.md》
> 状态:已完成
---
## 1. 问题概述
### 1.1 问题描述
使用配置文件中的XPATH定位器仍然无法找到登录输入框元素。
### 1.2 问题日志
```
2026-03-06 13:41:51 - LoginHandler - WARNING - 未找到匹配元素: [XPATH] //input[@placeholder='请输入账号或手机号或邮箱号']
2026-03-06 13:41:51 - LoginHandler - ERROR - 使用定位器未找到元素: //input[@placeholder='请输入账号或手机号或邮箱号']
2026-03-06 13:41:51 - LoginHandler - ERROR - 填写用户名失败
2026-03-06 13:41:51 - TestCaseGeneratorMain - ERROR - 执行过程中发生错误: 登录失败
```
---
## 2. 问题分析
### 2.1 根本原因
1.
**browser_operator.py 返回空快照**
:
`take_snapshot()`
方法返回
`{"elements": [], "url": "", "title": ""}`
2.
**MCP工具未实际调用**
:mcp_wrapper.py 中的方法只是模拟实现,没有真正调用 MCP chrome-devtools 工具
3.
**XPath匹配逻辑不完整**
:
`_match_xpath_locator`
只实现了简单的 placeholder 和 id 匹配
### 2.2 问题定位
| 文件 | 行号 | 问题 |
|------|------|------|
| browser_operator.py | 71-101 | take_snapshot 返回空字典 |
| mcp_wrapper.py | 68-90 | take_snapshot 返回空元素列表 |
| login_handler.py | 170-199 | XPath匹配逻辑过于简单 |
---
## 3. 解决方案
### 3.1 方案一:使用MCP chrome-devtools工具(推荐)
通过Claude Code的MCP chrome-devtools工具直接操作浏览器,获取元素信息并执行操作。
#### 3.1.1 架构调整
```
TestCaseGeneratorMain
└── BrowserOperator (MCP集成)
├── mcp__chrome-devtools__take_snapshot
├── mcp__chrome-devtools__fill
├── mcp__chrome-devtools__click
└── mcp__chrome-devtools__evaluate_script
```
#### 3.1.2 实现方式
修改
`browser_operator.py`
和
`mcp_wrapper.py`
,通过Claude Code的Skill工具调用MCP chrome-devtools工具。
### 3.2 方案二:增强本地XPath匹配
如果无法使用MCP工具,需要增强本地的XPath匹配逻辑。
#### 3.2.1 实现完整的XPath解析器
-
支持
`//tag[@attr='value']`
语法
-
支持多种属性匹配
-
支持通配符匹配
---
## 4. 修复执行计划
### Phase 1: 修复MCP工具调用(优先级:P0)✅ 已完成
| 序号 | 任务 | 文件 | 预计工时 | 状态 |
|-----|------|------|----------|------|
| 1 | 修改 browser_operator.py 使用Selenium WebDriver获取真实快照 | browser_operator.py | 30分钟 | ✅ 完成 |
| 2 | 更新 login_handler.py 中的元素匹配逻辑 | login_handler.py | 20分钟 | ✅ 完成 |
### Phase 2: 测试验证(优先级:P0)⏳ 待测试
| 序号 | 任务 | 预计工时 | 状态 |
|-----|------|----------|------|
| 3 | 测试后台登录功能 | 20分钟 | ⏳ 待测试 |
| 4 | 测试前台登录功能 | 20分钟 | ⏳ 待测试 |
---
## 5. 详细修复方案
### 5.1 问题根因
MCP chrome-devtools工具返回的快照格式:
```
json
{
"elements"
:
[
{
"uid"
:
"123"
,
"role"
:
"textbox"
,
"name"
:
"请输入账号或手机号或邮箱号"
,
"attributes"
:
{
"placeholder"
:
"请输入账号或手机号或邮箱号"
,
"type"
:
"text"
}
}
],
"url"
:
"https://192.168.5.44/#/LoginAdmin"
,
"title"
:
"登录页"
}
```
当前代码问题:
1.
`browser_operator.take_snapshot()`
返回空字典
2.
`login_handler._find_element_by_locator()`
从空元素列表中查找
3.
`_match_xpath_locator()`
匹配逻辑不完整
### 5.2 修复代码
#### 5.2.1 browser_operator.py
需要通过Claude Code MCP工具获取真实快照:
```
python
def
take_snapshot
(
self
,
verbose
:
bool
=
False
)
->
Dict
:
"""
获取页面快照(通过MCP chrome-devtools)
"""
self
.
logger
.
debug
(
"正在获取页面快照"
)
try
:
# 通过Claude Code的MCP工具获取快照
# 注意:实际执行时需要Claude Code环境支持
from
mcp_wrapper
import
get_mcp_wrapper
mcp
=
get_mcp_wrapper
()
snapshot
=
mcp
.
take_snapshot
(
verbose
=
verbose
)
if
not
snapshot
or
"elements"
not
in
snapshot
:
self
.
logger
.
warning
(
"快照为空,返回默认结构"
)
return
{
"elements"
:
[],
"url"
:
""
,
"title"
:
""
}
self
.
logger
.
debug
(
f
"页面快照获取成功,共 {len(snapshot.get('elements', []))} 个元素"
)
return
snapshot
except
Exception
as
e
:
self
.
logger
.
error
(
f
"获取页面快照失败: {e}"
)
return
{
"elements"
:
[],
"url"
:
""
,
"title"
:
""
}
```
#### 5.2.2 login_handler.py
增强XPath匹配逻辑,支持从快照中正确提取元素属性:
```
python
def
_match_xpath_locator
(
self
,
element
:
Dict
,
xpath
:
str
)
->
bool
:
"""
增强的XPath匹配(支持placeholder属性)
Args:
element: 元素字典(来自MCP快照)
xpath: XPath表达式
Returns:
是否匹配
"""
import
re
# 获取元素属性
element_attrs
=
element
.
get
(
"attributes"
,
{})
# 匹配 placeholder 属性
if
"@placeholder="
in
xpath
:
match
=
re
.
search
(
r"@placeholder='([^']+)'"
,
xpath
)
if
match
:
placeholder_value
=
match
.
group
(
1
)
element_placeholder
=
element_attrs
.
get
(
"placeholder"
,
""
)
# 使用in进行部分匹配,允许placeholder包含定位值
return
placeholder_value
in
element_placeholder
# 匹配 id 属性
if
"@id="
in
xpath
:
match
=
re
.
search
(
r"@id='([^']+)'"
,
xpath
)
if
match
:
id_value
=
match
.
group
(
1
)
element_id
=
element_attrs
.
get
(
"id"
,
""
)
return
element_id
==
id_value
# 匹配 name 属性
if
"@name="
in
xpath
:
match
=
re
.
search
(
r"@name='([^']+)'"
,
xpath
)
if
match
:
name_value
=
match
.
group
(
1
)
element_name
=
element_attrs
.
get
(
"name"
,
""
)
return
element_name
==
name_value
# 匹配 type 属性
if
"@type="
in
xpath
:
match
=
re
.
search
(
r"@type='([^']+)'"
,
xpath
)
if
match
:
type_value
=
match
.
group
(
1
)
element_type
=
element_attrs
.
get
(
"type"
,
""
)
return
element_type
==
type_value
# 如果没有匹配到任何已知模式,尝试通过元素的name字段匹配
# MCP快照中,输入框的name字段通常显示placeholder文本
element_name
=
element
.
get
(
"name"
,
""
)
if
element_name
:
# 提取XPath中的目标值(placeholder后的内容)
placeholder_match
=
re
.
search
(
r"@placeholder='([^']+)'"
,
xpath
)
if
placeholder_match
:
target
=
placeholder_match
.
group
(
1
)
return
target
in
element_name
return
False
```
### 5.3 备选方案:使用JavaScript直接定位元素
如果XPath匹配仍然有问题,可以使用
`evaluate_script`
直接通过JavaScript查找元素:
```
python
def
_find_element_by_javascript
(
self
,
xpath
:
str
)
->
Optional
[
str
]:
"""
使用JavaScript通过XPath查找元素
Args:
xpath: XPath表达式
Returns:
元素的uid(使用DOM路径作为临时uid)
"""
script
=
f
'''
() => {{
const result = document.evaluate("{xpath}", document, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
return result.singleNodeValue ? result.singleNodeValue.toString() : null;
}}
'''
# 通过MCP evaluate_script执行
# 这需要实际的MCP工具支持
return
None
```
---
## 6. 验收标准
### 6.1 功能验收
-
[
]
能够通过XPATH定位器找到用户名输入框
-
[
]
能够通过XPATH定位器找到密码输入框
-
[
]
能够通过XPATH定位器找到验证码输入框(如果存在)
-
[
]
后台登录能够成功
-
[
]
前台登录能够成功
### 6.2 日志验收
-
[
]
不再出现"未找到匹配元素"警告
-
[
]
正确显示元素定位成功的日志
-
[
]
登录成功后显示"登录成功"日志
---
## 7. 实际修复内容
### 7.1 browser_operator.py 完全重写
使用Selenium WebDriver替代空的MCP包装实现:
-
添加Chrome浏览器初始化(支持无头模式)
-
实现真实的
`take_snapshot()`
方法,从DOM提取元素信息
-
实现真实的
`fill_input()`
、
`click_element()`
方法
-
实现真实的
`execute_script()`
方法支持JavaScript执行
-
新增
`_extract_element_info()`
方法提取元素属性到快照
快照格式(与MCP chrome-devtools兼容):
```
python
{
"elements"
:
[
{
"uid"
:
"input-0"
,
"role"
:
"textbox"
,
"name"
:
"请输入账号或手机号或邮箱号"
,
"attributes"
:
{
"id"
:
"..."
,
"placeholder"
:
"请输入账号或手机号或邮箱号"
,
"type"
:
"text"
,
"tag"
:
"input"
}
}
],
"url"
:
"https://192.168.5.44/#/LoginAdmin"
,
"title"
:
"..."
}
```
### 7.2 login_handler.py 增强XPath匹配
1.
**增强`_match_xpath_locator()`方法**
:
-
支持从
`attributes`
字典中读取属性值
-
支持placeholder、id、name、type属性匹配
-
添加备选匹配策略(从name字段匹配placeholder)
2.
**新增`_fill_input_by_javascript_xpath()`方法**
:
-
当快照匹配失败时,使用JavaScript直接通过XPath填写输入框
-
支持触发input和change事件
3.
**新增`_click_login_button_by_javascript()`方法**
:
-
当快照中找不到登录按钮时,使用JavaScript直接点击
-
支持多种常见的登录按钮XPath
### 7.3 修复总结
| 问题 | 原因 | 解决方案 |
|------|------|----------|
| 快照为空 | browser_operator返回空字典 | 使用Selenium WebDriver获取真实DOM |
| XPath无法匹配 | _match_xpath_locator逻辑太简单 | 增强属性匹配,支持attributes字典 |
| 元素找不到 | 快照属性不完整 | 添加JavaScript直接操作DOM的降级方案 |
---
*文档结束*
Docs/PRD/自动化测试用例生成/问题修复/_PRD_定位值存在多条件_问题记录.md
0 → 100644
浏览文件 @
588166f7
# 自动化测试用例生成 - 问题描述文档
## 文档信息
-
**创建日期**
: 2026-03-06
-
**验证轮次**
: 第二轮
-
**验证范围**
: 添加操作测试用例
-
**状态**
: 待修复
---
## 问题概述
在用户更新了消息定位器后,重新执行添加操作测试用例时,发现了新的关键问题:
1.
XPath文本匹配层级问题导致按钮定位失败
2.
确定按钮定位器类名和文本匹配都存在问题
3.
消息元素验证仍然失败(存在时间太短)
---
## 问题清单
### 问题1: 添加按钮XPath定位失败 ⚠️ 严重
**问题描述**
测试用例中的XPath
`//button[contains(text(), '添加') and contains(@class, 'el-button--primary')]`
无法定位到添加按钮。
**根因分析**
XPath的
`text()`
函数只匹配元素的直接文本节点,不包含子元素的文本。Element UI按钮的实际HTML结构为:
```
html
<button
type=
"button"
class=
"el-button el-button--primary"
id=
"set_room_list.value_add-create"
>
<span>
添加
</span>
</button>
```
"添加"文本在
`<span>`
子元素内,不是
`<button>`
的直接文本,因此
`contains(text(), '添加')`
无法匹配。
**错误定位器**
```
xpath
//button[contains(text(), '添加') and contains(@class, 'el-button--primary')]
```
**正确做法**
```
xpath
// 使用点号(.)匹配所有后代文本
//button[contains(@class, 'el-button--primary') and contains(., '添加')]
// 或使用 descendant-or-self axis
//button[contains(@class, 'el-button--primary') and .//text()='添加']
// 或直接定位span子元素
//button[contains(@class, 'el-button--primary')]//span[text()='添加']
```
**影响范围**
-
`区域管理_增值服务_添加.json`
- 步骤3
-
所有使用
`contains(text(), 'xxx')`
格式匹配Element UI按钮的场景
**测试结果**
```
json
{
"step"
:
3
,
"success"
:
false
,
"xpath"
:
"//button[contains(text(), '添加') and contains(@class, 'el-button--primary')]"
,
"message"
:
"XPath定位失败 - 未找到添加按钮"
}
```
---
### 问题2: 确定按钮XPath定位失败 ⚠️ 严重
**问题描述**
测试用例中的XPath
`//div[contains(@class, 'el-dialog__footer')]//button[contains(text(), '确定') and contains(@class, 'el-button--primary')]`
无法定位到确定按钮。
**根因分析**
存在两个问题:
1.
**类名问题**
:
`el-dialog__footer`
类名在Element UI中可能不使用
`el-`
前缀,实际可能是
`dialog-footer`
2.
**文本匹配问题**
:与问题1相同,
`contains(text(), '确定')`
无法匹配span内的文本
**错误定位器**
```
xpath
//div[contains(@class, 'el-dialog__footer')]//button[contains(text(), '确定') and contains(@class, 'el-button--primary')]
```
**修复建议**
```
xpath
// 简化定位器,直接查找确定按钮
//button[contains(@class, 'el-button--primary') and contains(., '确定')]
// 或使用更通用的定位方式
//button[contains(@class, 'el-button--primary')]//span[text()='确定']
```
**影响范围**
-
`区域管理_增值服务_添加.json`
- 步骤8
-
`区域管理_增值服务_编辑.json`
- 步骤5
-
所有对话框确定按钮的定位
**测试结果**
```
json
{
"step"
:
8
,
"success"
:
true
,
"message"
:
"使用备用定位器成功点击确定按钮"
,
"note"
:
"原始XPath定位失败"
}
```
---
### 问题3: 消息元素验证失败 ⚠️ 中等
**问题描述**
更新后的定位器
`//p[@class='el-message__content']`
仍然无法捕获到消息元素。
**根因分析**
Element UI的
`el-message`
组件默认显示时间约为1-2秒,当测试执行到验证步骤时,消息元素已经从DOM中移除。
**当前定位器**
```
xpath
//p[@class='el-message__content']
```
**问题表现**
-
操作成功执行(列表数据正确更新)
-
成功消息确实显示过
-
但验证时消息已消失
**影响范围**
-
`区域管理_增值服务_添加.json`
- 步骤9
-
`区域管理_增值服务_编辑.json`
- 步骤6
-
`区域管理_增值服务_删除.json`
- 步骤5
-
所有消息验证步骤
**测试结果**
```
json
{
"step"
:
9
,
"success"
:
false
,
"xpath"
:
"//p[@class='el-message__content']"
,
"message"
:
"XPath定位失败 - 未找到提示信息"
}
```
---
## 测试执行详情
### 添加操作测试用例执行记录
| 步骤 | 操作 | 定位器 | 结果 | 备注 |
|------|-----------|--------------|--------|-------|
| 1-2 | 导航到页面 | ID | ✅ 跳过 | 已在页面 |
| 3 | 点击【添加】按钮 | XPATH | ❌ 失败 | 问题1 - 使用UID通过 |
| 4 | 输入商品名称 | XPATH | ✅ 成功 | placeholder定位正常 |
| 5 | 点击商品分类选择框 | XPATH | ✅ 成功 | placeholder定位正常 |
| 6 | 选择分类 | XPATH | ✅ 成功 | 文本定位正常 |
| 7 | 输入商品描述 | XPATH | ✅ 成功 | placeholder定位正常 |
| 8 | 点击【确定】按钮 | XPATH | ⚠️ 备用 | 问题2 - 使用UID通过 |
| 9 | 获取提示文本 | XPATH | ❌ 失败 | 问题3 - 消息已消失 |
**实际操作结果**
:
-
✅ "测试商品001"成功添加到列表
-
✅ 列表计数从4条变为5条
-
✅ 成功消息"添加成功"确实显示过
-
❌ 验证步骤失败
---
## 技术分析
### XPath文本匹配机制
在XPath中,
`text()`
的行为:
```
javascript
// 只匹配直接文本节点
text
()
// 元素的直接子文本节点
// 匹配所有文本(包括后代)
.
// 当前元素的所有文本内容
text
()
// 第一个文本节点
//text() // 所有后代文本节点
// 实际示例
<
button
><
span
>
添加
<
/span></
button
>
text
()
=
""
// 空,无直接文本
.
=
"添加"
// 包含所有后代文本
.
//text() = "添加" // 后代文本
span
/
text
()
=
"添加"
// 子元素文本
```
### Element UI消息组件生命周期
```
javascript
// Element UI 消息默认配置
{
duration
:
3000
,
// 显示3秒(但实际可能更短)
showClose
:
false
,
// 不显示关闭按钮
center
:
false
,
// 不居中
onClose
:
null
// 关闭回调
}
// 消息出现后会在指定时间后自动移除DOM
// 因此测试验证必须在短时间内完成
```
---
## 影响评估
### 严重性评估
| 问题 | 严重性 | 可执行性 | 数据准确性 |
|------|-------|---------|-----------|
| 问题1: 添加按钮定位失败 | 严重 | ❌ 无法执行 | - |
| 问题2: 确定按钮定位失败 | 严重 | ⚠️ 需变通 | ✅ |
| 问题3: 消息验证失败 | 中等 | ✅ 可执行 | ✅ |
### 影响范围统计
-
**测试用例文件数量**
: 3个(添加、编辑、删除)
-
**受影响的步骤**
: 约9个步骤
-
**必须修复的问题**
: 2个(问题1、问题2)
-
**可选优化的问题**
: 1个(问题3)
---
## 附录
### 相关文档
-
第一轮问题修复文档:
`_PRD_执行问题修复.md`
-
测试用例文件:
`AuxiliaryTool/TestCaseGenerator/testcases/`
### 验证环境
-
系统地址:https://192.168.5.44
-
测试时间:2026-03-06
-
浏览器:Chrome (via chrome-devtools MCP)
---
**文档结束**
Docs/PRD/自动化测试用例生成/问题修复/_PRD_定位值存在多条件_问题记录_计划执行.md
0 → 100644
浏览文件 @
588166f7
# 自动化测试用例生成 - 问题修复计划执行文档
## 文档信息
-
**创建日期**
: 2026-03-06
-
**版本**
: v1.0
-
**状态**
: 待执行
---
## 修复目标
修复第二轮验证中发现的XPath文本匹配层级问题和消息验证问题,确保测试用例能够稳定执行。
---
## 修复计划清单
### 任务1: 修复按钮定位器的XPath表达式 ✅ 必须修复
**优先级**
: P0 - 严重
**预计工时**
: 1小时
**问题描述**
`contains(text(), '文本')`
无法匹配Element UI按钮span子元素内的文本
**修复步骤**
1.
**修改添加按钮定位器**
```
diff
文件: 区域管理_增值服务_添加.json
步骤: 3 - 点击【添加】按钮
- "locator_value": "//button[contains(text(), '添加') and contains(@class, 'el-button--primary')]"
+ "locator_value": "//button[contains(@class, 'el-button--primary') and contains(., '添加')]"
```
2. **修改确定按钮定位器 - 添加用例**
```diff
文件: 区域管理_增值服务_添加.json
步骤: 8 - 点击【确定】按钮
- "locator_value": "//div[contains(@class, 'el-dialog__footer')]//button[contains(text(), '确定') and contains(@class, 'el-button--primary')]"
+ "locator_value": "//button[contains(@class, 'el-button--primary') and contains(., '确定')]"
```
3. **修改确定按钮定位器 - 编辑用例**
```diff
文件: 区域管理_增值服务_编辑.json
步骤: 5 - 点击【确定】按钮
- "locator_value": "//div[contains(@class, 'el-dialog__footer')]//button[contains(text(), '确定') and contains(@class, 'el-button--primary')]"
+ "locator_value": "//button[contains(@class, 'el-button--primary') and contains(., '确定')]"
```
4. **检查其他按钮定位器**
- 搜索所有使用 `contains(text(),` 的XPath
- 确认是否需要修复
**验证方法**
```
bash
# 重新执行添加操作测试用例
# 期望:步骤3、8的XPath定位成功
```
**验收标准**
- ✅ 添加按钮定位成功
- ✅ 确定按钮定位成功
- ✅ 无需使用UID等变通方式
---
### 任务2: 优化消息验证方案 ⚠️ 可选优化
**优先级**: P1 - 中等
**预计工时**: 2小时
**问题描述**
el-message元素存在时间太短(1-2秒),XPath验证时已消失
**可选方案**
#### 方案A: 使用数据验证替代消息验证(推荐)
将消息验证改为验证列表数据的变化:
```
diff
文件: 区域管理_增值服务_添加.json
步骤: 9 - 验证添加结果
-
{
-
"step": "获取提示文本",
-
"locator_type": "XPATH",
-
"locator_value": "//p
[
@class='el-message__content'
]
",
-
"element_type": "getTips",
-
"element_value": "添加成功",
-
"expected_result": "添加成功"
-
}
+
{
+
"step": "验证列表数据",
+
"locator_type": "XPATH",
+
"locator_value": "//td
[
contains(text(), '测试商品001')
]
",
+
"element_type": "verify",
+
"element_value": "存在",
+
"expected_result": "列表中包含新添加的商品"
+
}
```
#### 方案B: 在测试执行框架中添加显式等待
```
python
def wait_for_message(driver, text, timeout=3):
"""等待消息出现并返回"""
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
try:
element = WebDriverWait(driver, timeout).until(
EC.presence_of_element_located((By.XPATH, f"//p[@class='el-message__content' and contains(text(), '{text}')]"))
)
return element.text
except:
return None
```
#### 方案C: 延长消息显示时间(需要开发支持)
修改Element UI全局配置:
```
javascript
// main.js 或相关配置文件
Vue.prototype.$message = Message
Message.defaults.duration = 5000 // 延长到5秒
```
**推荐方案**: 方案A(数据验证)
- 更可靠
- 不依赖消息显示时间
- 直接验证业务结果
**验收标准**
- ✅ 验证步骤能够成功执行
- ✅ 验证结果准确反映操作是否成功
---
## 代码修复清单
### 需要修改的文件
| 文件路径 | 修改内容 | 影响步骤 |
|---------|---------|---------|
| `testcases/区域管理_增值服务_添加.json` | 修复步骤3、8的XPath | 2个步骤 |
| `testcases/区域管理_增值服务_编辑.json` | 修复步骤5的XPath | 1个步骤 |
| `testcases/区域管理_增值服务_删除.json` | 检查确定按钮XPath | 1个步骤 |
### 测试用例生成器修复(可选)
如果要从根本上避免此类问题,需要在测试用例生成器中添加:
```
python
# 在生成XPath时,使用 . 而不是 text()
def generate_button_xpath(text, class_name=None):
"""
生成按钮的XPath定位器
Args:
text: 按钮文本
class_name: 按钮类名
"""
if class_name:
# 使用 contains(., 'text') 而不是 contains(text(), 'text')
return f"//button[contains(@class, '{class_name}') and contains(., '{text}')]"
return f"//button[contains(., '{text}')]"
```
---
## 修复执行计划
### Phase 1: 紧急修复(必须) - 预计1小时
1.
[
]
修复添加操作测试用例的XPath(步骤3、8)
2.
[
]
修复编辑操作测试用例的XPath(步骤5)
3.
[
]
检查删除操作测试用例的XPath
4.
[
]
重新执行添加操作测试用例验证
### Phase 2: 验证测试 - 预计30分钟
1.
[
]
完整执行添加操作测试用例
2.
[
]
完整执行编辑操作测试用例
3.
[
]
完整执行删除操作测试用例
4.
[
]
记录测试结果
### Phase 3: 优化改进(可选) - 预计1.5小时
1.
[
]
实施数据验证方案(替代消息验证)
2.
[
]
更新所有三个测试用例的验证步骤
3.
[
]
再次验证所有测试用例
4.
[
]
更新问题文档
---
## XPath修复规范
### 修复前 vs 修复后对比
| 场景 | 错误写法 | 正确写法 |
|------|---------|---------|
| 按钮文本匹配 |
`contains(text(), '确定')`
|
`contains(., '确定')`
|
| 按钮精确定位 |
`//button[contains(text(), '确定')]`
|
`//button[contains(., '确定')]`
|
| 组合定位 |
`//button[contains(text(), '确定') and contains(@class, 'primary')]`
|
`//button[contains(@class, 'primary') and contains(., '确定')]`
|
### 关键规则
1.
**匹配包含的文本**
:使用
`contains(., 'text')`
而不是
`contains(text(), 'text')`
2.
**精确匹配文本**
:使用
`.//text()='text'`
而不是
`text()='text'`
3.
**简化定位器**
:优先使用直接定位而非多层嵌套
---
## 验证计划
### 修复后验证步骤
1.
**单步验证**
-
重新执行添加操作测试用例
-
逐步验证每个XPath定位器是否工作
-
记录每步的执行结果
2.
**完整验证**
-
连续执行添加→编辑→删除操作
-
验证测试用例的连续性和稳定性
-
确认所有操作都能成功完成
3.
**回归验证**
-
检查之前的修复是否仍然有效
-
确保没有引入新的问题
### 成功标准
-
✅ 所有XPath定位器能够正确找到元素
-
✅ 无需使用UID等变通方式
-
✅ 添加、编辑、删除操作都能成功执行
-
✅ 验证步骤能够准确反映操作结果
---
## 风险评估
### 潜在风险
1.
**其他按钮定位器可能存在相同问题**
-
风险等级:中
-
缓解措施:全局搜索并检查所有按钮定位器
2.
**编辑/删除操作可能遇到类似问题**
-
风险等级:高
-
缓解措施:先验证这两个测试用例
3.
**数据验证方案可能不够全面**
-
风险等级:低
-
缓解措施:可以保留消息验证作为可选验证
---
## 附录
### 相关文档
-
问题描述文档:
`_PRD_问题描述_问题记录_20260306_第二轮.md`
-
第一轮修复文档:
`_PRD_执行问题修复.md`
### 修复历史
| 日期 | 轮次 | 修复内容 | 状态 |
|------|------|---------|------|
| 2026-03-06 | 第一轮 | ID重复、XPath索引、消息定位 | 已完成 |
| 2026-03-06 | 第二轮 | XPath文本匹配层级 | 待执行 |
---
**文档结束**
Docs/PRD/自动化测试用例生成/问题修复/_PRD_运行脚本无法登录系统_问题记录_.md
0 → 100644
浏览文件 @
588166f7
# 自动化测试用例生成 - 执行问题修复文档
## 文档信息
-
**创建日期**
: 2026-03-06
-
**版本**
: v1.1
-
**最后更新**
:
-
**状态**
:
---
## 问题概述
通过main.py运行时,日志打印未找到元素用户、账号、username、account、用户,登录失败。
---
## 发现的问题清单
### 问题1: 无法登录系统
**问题描述**
-
登录失败,未找到用户名输入框
**问题日志**
-
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: 用户名
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: 账号
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: username
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: account
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: 用户
2026-03-06 13:25:37 - LoginHandler - ERROR - 未找到用户名输入框
2026-03-06 13:25:37 - TestCaseGeneratorMain - ERROR - 执行过程中发生错误: 登录失败
2026-03-06 13:25:37 - TestCaseGeneratorMain - INFO - 正在清理资源...
2026-03-06 13:25:37 - BrowserOperator - INFO - 正在关闭浏览器
2026-03-06 13:25:37 - BrowserOperator - INFO - 浏览器已关闭
2026-03-06 13:25:37 - TestCaseGeneratorMain - INFO - 资源清理完成
2026-03-06 13:41:51 - LoginHandler - WARNING - 未找到匹配元素:
[
XPATH
]
//input
[
@placeholder='请输入账号或手机号或邮箱号'
]
2026-03-06 13:41:51 - LoginHandler - ERROR - 使用定位器未找到元素: //input
[
@placeholder='请输入账号或手机号或邮箱号'
]
2026-03-06 13:41:51 - LoginHandler - ERROR - 填写用户名失败
2026-03-06 13:41:51 - TestCaseGeneratorMain - ERROR - 执行过程中发生错误: 登录失败
2026-03-06 13:41:51 - TestCaseGeneratorMain - INFO - 正在清理资源...
2026-03-06 13:41:51 - BrowserOperator - INFO - 正在关闭浏览器
2026-03-06 13:41:51 - BrowserOperator - INFO - 浏览器已关闭
2026-03-06 13:41:51 - TestCaseGeneratorMain - INFO - 资源清理完成
错误: 登录失败
---
**文档结束**
Docs/PRD/自动化测试用例生成/问题修复/_PRD_运行脚本无法登录系统_问题记录_计划执行.md
0 → 100644
浏览文件 @
588166f7
# _PRD_运行脚本无法登录系统_问题记录_计划执行
> 版本:V2.0
> 创建日期:2026-03-06
> 更新日期:2026-03-06
> 适用范围:自动化测试用例生成工具
> 来源:基于《_PRD_运行脚本无法登录系统_问题记录_.md》
> 状态:待执行
---
## 1. 问题概述
### 1.1 问题描述
运行 main.py 时,登录失败,日志显示未找到用户名输入框。
### 1.2 问题日志
```
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: 用户名
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: 账号
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: username
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: account
2026-03-06 13:25:37 - BrowserOperator - WARNING - 未找到元素: 用户
2026-03-06 13:25:37 - LoginHandler - ERROR - 未找到用户名输入框
2026-03-06 13:25:37 - TestCaseGeneratorMain - ERROR - 执行过程中发生错误: 登录失败
```
---
## 2. 解决方案
### 2.1 新方案:配置文件指定登录定位器
**设计变更**
:不再自动查找输入框,而是在
`system_config.json`
中直接指定登录元素的定位器。
#### 2.1.1 新的 system_config.json 格式
```
json
[
{
"system_type"
:
"new_platform"
,
"system_front_url"
:
"https://192.168.5.44"
,
"system_back_url"
:
"https://192.168.5.44/#/LoginAdmin"
,
"back_username"
:
[
"XPATH"
,
"//input[@placeholder='请输入账号或手机号或邮箱号']"
,
"admin@xty"
],
"back_password"
:
[
"XPATH"
,
"//input[@placeholder='请输入密码']"
,
"Ubains@4321"
],
"back_code"
:
[
"XPATH"
,
"//input[@placeholder='请输入图形验证码']"
,
"csba"
],
"front_username"
:
[
"XPATH"
,
"//input[@placeholder='手机号/用户名/邮箱']"
,
"admin@xty"
],
"front_password"
:
[
"XPATH"
,
"//input[@placeholder='密码']"
,
"Ubains@4321"
],
"front_code"
:
[
"XPATH"
,
"//input[@placeholder='图形验证']"
,
"csba"
]
}
]
```
#### 2.1.2 定位器配置格式
**格式**
:
`["定位类型", "定位值", "输入值"]`
| 字段 | 说明 | 示例 |
|------|------|------|
| 定位类型 | XPATH/ID/CSS_SELECTOR等 |
`"XPATH"`
|
| 定位值 | 元素定位表达式 |
`"//input[@placeholder='请输入账号']"`
|
| 输入值 | 要填写的值 |
`"admin@xty"`
|
---
## 3. 修复执行计划
### Phase 1: 更新配置文件(优先级:P0)
| 序号 | 任务 | 文件 | 预计工时 |
|-----|------|------|----------|
| 1 | 更新 system_config.json 格式 | config/system_config.json | 10分钟 |
### Phase 2: 更新代码实现(优先级:P0)
| 序号 | 任务 | 文件 | 预计工时 |
|-----|------|------|----------|
| 2 | 更新 config_manager.py 解析新格式 | config_manager.py | 30分钟 |
| 3 | 重写 login_handler.py 使用配置定位器 | login_handler.py | 30分钟 |
| 4 | 更新 main.py 传递登录类型参数 | main.py | 15分钟 |
### Phase 3: 测试验证(优先级:P1)
| 序号 | 任务 | 预计工时 |
|-----|------|----------|
| 5 | 测试后台登录 | 15分钟 |
| 6 | 测试前台登录 | 15分钟 |
---
## 4. 详细修复代码
### 4.1 system_config.json
```
json
[
{
"system_type"
:
"new_platform"
,
"system_front_url"
:
"https://192.168.5.44"
,
"system_back_url"
:
"https://192.168.5.44/#/LoginAdmin"
,
"back_username"
:
[
"XPATH"
,
"//input[@placeholder='请输入账号或手机号或邮箱号']"
,
"admin@xty"
],
"back_password"
:
[
"XPATH"
,
"//input[@placeholder='请输入密码']"
,
"Ubains@4321"
],
"back_code"
:
[
"XPATH"
,
"//input[@placeholder='请输入图形验证码']"
,
"csba"
],
"front_username"
:
[
"XPATH"
,
"//input[@placeholder='手机号/用户名/邮箱']"
,
"admin@xty"
],
"front_password"
:
[
"XPATH"
,
"//input[@placeholder='密码']"
,
"Ubains@4321"
],
"front_code"
:
[
"XPATH"
,
"//input[@placeholder='图形验证']"
,
"csba"
]
}
]
```
### 4.2 config_manager.py 更新
```
python
def
_validate_system_config
(
self
,
config
:
List
[
Dict
])
->
bool
:
"""验证系统配置格式(更新版)"""
if
not
isinstance
(
config
,
list
):
self
.
logger
.
error
(
"系统配置必须是列表格式"
)
return
False
required_fields
=
[
"system_type"
,
"system_front_url"
,
"system_back_url"
]
login_fields
=
[
"back_username"
,
"back_password"
,
"back_code"
,
"front_username"
,
"front_password"
,
"front_code"
]
for
idx
,
system
in
enumerate
(
config
):
if
not
isinstance
(
system
,
dict
):
self
.
logger
.
error
(
f
"系统配置第{idx+1}项必须是字典格式"
)
return
False
# 检查必填字段
for
field
in
required_fields
:
if
field
not
in
system
:
self
.
logger
.
error
(
f
"系统配置第{idx+1}项缺少必填字段: {field}"
)
return
False
# 检查登录字段格式
for
field
in
login_fields
:
if
field
in
system
:
field_value
=
system
[
field
]
if
not
isinstance
(
field_value
,
list
)
or
len
(
field_value
)
!=
3
:
self
.
logger
.
error
(
f
"登录字段 {field} 格式错误,应为 [定位类型, 定位值, 输入值]"
)
return
False
return
True
```
### 4.3 login_handler.py 更新
```
python
def
login
(
self
,
url
:
str
,
login_type
:
str
=
"back"
)
->
bool
:
"""
执行登录操作(使用配置的定位器)
Args:
url: 登录页面URL
login_type: 登录类型,"back"或"front"
Returns:
是否登录成功
"""
# 从配置中获取登录信息
system_config
=
self
.
config_manager
.
get_system_config
()
# 根据登录类型选择配置
username_config
=
system_config
.
get
(
f
"{login_type}_username"
)
password_config
=
system_config
.
get
(
f
"{login_type}_password"
)
code_config
=
system_config
.
get
(
f
"{login_type}_code"
)
# 使用配置的定位器进行登录
locator_type
,
locator_value
,
username_value
=
username_config
# ... 填写用户名
locator_type
,
locator_value
,
password_value
=
password_config
# ... 填写密码
# ...
```
---
## 5. 验收标准
### 5.1 功能验收
-
[
]
能够成功解析新的配置格式
-
[
]
能够使用配置的定位器找到输入框
-
[
]
后台登录能够成功
-
[
]
前台登录能够成功
### 5.2 配置验收
-
[
]
system_config.json 格式正确
-
[
]
所有登录字段配置完整
-
[
]
定位器能够正确定位元素
---
*文档结束*
Docs/PRD/自动化生成功能测试报告/_PRD_自动化生成功能测试报告.md
0 → 100644
浏览文件 @
588166f7
新统一平台/测试数据/新统一平台测试用例.xlsx
浏览文件 @
588166f7
No preview for this file type
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论