提交 9c6f872c authored 作者: 陈泽健's avatar 陈泽健

fix(test): 解决自动化测试用例生成中的菜单定位问题

- 实现多种菜单定位方式(按优先级排序,支持span、li、a等标签)
- 添加iframe切换处理机制以支持复杂页面结构
- 添加菜单名称标准化处理去除多余空格和特殊字符
- 添加菜单加载等待逻辑确保页面元素完全加载
- 添加调试快照功能便于问题排查和定位
- 更新测试用例中的菜单定位表达式从li标签改为span标签
- 统一测试用例中的页面路径和元素定位策略
上级 7890c5bd
......@@ -212,29 +212,151 @@ class TestCaseGenerator:
)
def navigate_to_module(self, module_name, sub_module_name):
"""导航到指定模块"""
"""导航到指定模块(增强版)"""
print(f" 正在导航到: {module_name} > {sub_module_name}")
# 点击一级菜单(使用li标签)
try:
menu_item = self.wait_for_element((By.XPATH, f"//li[contains(text(),'{module_name}')]"), timeout=5)
menu_item.click()
# 0. 等待菜单加载完成
self.wait_for_menu_ready(timeout=10)
# 0.5 尝试切换到菜单iframe(如果有)
iframe_switched = self.switch_to_menu_frame()
if iframe_switched:
time.sleep(1)
except:
# 0.6 标准化菜单名称
module_name = self.normalize_text(module_name)
sub_module_name = self.normalize_text(sub_module_name)
# 1. 点击一级菜单(优先使用span标签)
menu_found = False
menu_locators = [
# 优先使用span标签(修正后的正确方式)
(By.XPATH, f"//span[contains(text(),'{module_name}')]"),
# 或者使用li下的span
(By.XPATH, f"//li//span[contains(text(),'{module_name}')]"),
# 兼容:li下任意文本
(By.XPATH, f"//li[contains(.,'{module_name}')]"),
# 精确匹配li文本
(By.XPATH, f"//li[.='{module_name}']"),
# 链接
(By.XPATH, f"//a[contains(text(),'{module_name}')]"),
# 在菜单容器中查找
(By.XPATH, f"//div[contains(@class,'menu') or contains(@class,'nav')]//span[contains(text(),'{module_name}')]"),
]
for locator_type, locator_value in menu_locators:
try:
menu_item = self.driver.find_element(locator_type, locator_value)
menu_item.click()
menu_found = True
print(f" ✓ 点击【{module_name}】菜单 (定位: {locator_type})")
time.sleep(1)
break
except:
continue
if not menu_found:
print(f" ! 未找到菜单: {module_name}")
self.debug_page_structure("menu_not_found")
return False
# 点击二级菜单(使用li标签)
try:
sub_menu = self.wait_for_element((By.XPATH, f"//li[contains(text(),'{sub_module_name}')]"), timeout=5)
sub_menu.click()
time.sleep(2)
print(f" ✓ 成功进入: {sub_module_name}")
return True
except:
# 2. 点击二级菜单(同样优先使用span标签)
sub_menu_found = False
sub_menu_locators = [
# 优先使用span标签(修正后的正确方式)
(By.XPATH, f"//span[contains(text(),'{sub_module_name}')]"),
# 或者使用li下的span
(By.XPATH, f"//li//span[contains(text(),'{sub_module_name}')]"),
# 兼容:li下任意文本
(By.XPATH, f"//li[contains(.,'{sub_module_name}')]"),
# 精确匹配li文本
(By.XPATH, f"//li[.='{sub_module_name}']"),
# 链接
(By.XPATH, f"//a[contains(text(),'{sub_module_name}')]"),
# 在ul容器中查找
(By.XPATH, f"//ul//li//span[contains(text(),'{sub_module_name}')]"),
]
for locator_type, locator_value in sub_menu_locators:
try:
sub_menu = self.driver.find_element(locator_type, locator_value)
sub_menu.click()
sub_menu_found = True
print(f" ✓ 点击【{sub_module_name}】子菜单 (定位: {locator_type})")
time.sleep(2)
break
except:
continue
# 恢复到默认frame(如果之前切换了)
if iframe_switched:
self.driver.switch_to.default_content()
if not sub_menu_found:
print(f" ! 未找到子菜单: {sub_module_name}")
self.debug_page_structure("submenu_not_found")
return False
print(f" ✓ 成功进入: {sub_module_name}")
return True
def normalize_text(self, text):
"""标准化文本,去除多余空格和特殊字符"""
if not text:
return ""
return text.strip().replace('\n', '').replace('\t', '').replace(' ', ' ')
def wait_for_menu_ready(self, timeout=10):
"""等待菜单加载完成"""
try:
WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located((By.XPATH, "//li | //a | //button"))
)
print(" ✓ 菜单已加载完成")
except:
print(" ! 菜单加载超时")
def switch_to_menu_frame(self):
"""切换到菜单所在的iframe(如果有)"""
try:
iframes = self.driver.find_elements(By.TAG_NAME, "iframe")
for iframe in iframes:
self.driver.switch_to.frame(iframe)
# 检查是否包含菜单元素
if len(self.driver.find_elements(By.XPATH, "//li | //a | //button")) > 0:
print(" ✓ 切换到菜单iframe")
return True
self.driver.switch_to.default_content()
except:
pass
return False
def debug_page_structure(self, step_name):
"""调试用:保存当前页面结构"""
try:
# 获取页面HTML
page_source = self.driver.page_source
# 保存到文件用于调试
debug_file = os.path.join(self.base_dir, f"debug_{step_name}.html")
with open(debug_file, 'w', encoding='utf-8') as f:
f.write(page_source)
print(f" [调试] 页面快照已保存: {debug_file}")
# 打印当前页面URL
print(f" [调试] 当前URL: {self.driver.current_url}")
# 打印所有li和a元素的文本(只显示前15个)
try:
all_menus = self.driver.find_elements(By.XPATH, "//li | //a")
menu_texts = [m.text.strip() for m in all_menus if m.text.strip()]
print(f" [调试] 可见菜单项: {menu_texts[:15]}")
except:
pass
except Exception as e:
print(f" [调试] 快照保存失败: {e}")
def get_page_url(self):
"""获取当前页面的路由后缀"""
url = self.driver.current_url
......@@ -252,12 +374,12 @@ class TestCaseGenerator:
"base_url": "https://192.168.5.44"
}
# 导航步骤(使用li标签定位菜单
# 导航步骤(一级菜单使用span标签,二级菜单使用li标签
testcase["para"].append({
"page": page_url,
"step": f"点击【{module_name}】菜单",
"locator_type": "XPATH",
"locator_value": f"//li[contains(text(),'{module_name}')]",
"locator_value": f"//span[contains(text(),'{module_name}')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
......
......@@ -2,16 +2,16 @@
"name": "区域管理_增值服务_删除",
"para": [
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "点击【区域管理】菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'区域管理')]",
"locator_value": "//span[contains(text(),'区域管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "点击【增值服务】子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'增值服务')]",
......@@ -20,17 +20,17 @@
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "点击第一行数据的【删除】按钮",
"page": "backend/backstage",
"step": "点击第一条记录的【删除】按钮",
"locator_type": "XPATH",
"locator_value": "//tbody/tr[1]//button[contains(text(),'删 除')]",
"locator_value": "(//button[contains(text(),'删除')])[1]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "点击删除确认对话框的【确定】按钮",
"page": "backend/backstage",
"step": "确认删除操作",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
"element_type": "click",
......@@ -38,7 +38,7 @@
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "验证删除成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'删除成功')]",
......@@ -49,4 +49,4 @@
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
}
\ No newline at end of file
......@@ -2,16 +2,16 @@
"name": "区域管理_增值服务_添加",
"para": [
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "点击【区域管理】菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'区域管理')]",
"locator_value": "//span[contains(text(),'区域管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "点击【增值服务】子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'增值服务')]",
......@@ -20,7 +20,7 @@
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "点击【添加】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'添加')]",
......@@ -29,43 +29,16 @@
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "填写商品名称",
"page": "backend/backstage",
"step": "填写表单字段",
"locator_type": "XPATH",
"locator_value": "//input[@placeholder='请输入商品名称']",
"locator_value": "//input[@placeholder='请输入名称' or @placeholder='请输入商品名称' or @placeholder='请输入']",
"element_type": "input",
"element_value": "测试商品001",
"element_value": "测试数据${timestamp}",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "点击商品分类下拉框",
"locator_type": "XPATH",
"locator_value": "//input[@placeholder='请选择商品分类']",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "选择分类【商品】",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'商品')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "填写商品描述",
"locator_type": "XPATH",
"locator_value": "//textarea[@placeholder='请输入商品描述']",
"element_type": "input",
"element_value": "自动化测试商品描述",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "点击【确定】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
......@@ -74,10 +47,10 @@
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "验证添加成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'添加成功')]",
"locator_value": "//p[contains(text(),'添加成功') or contains(text(),'成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "添加成功"
......@@ -85,4 +58,4 @@
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
}
\ No newline at end of file
......@@ -2,16 +2,16 @@
"name": "区域管理_增值服务_编辑",
"para": [
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "点击【区域管理】菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'区域管理')]",
"locator_value": "//span[contains(text(),'区域管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "点击【增值服务】子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'增值服务')]",
......@@ -20,25 +20,25 @@
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "点击第一行数据的【编辑】按钮",
"page": "backend/backstage",
"step": "点击第一条记录的【编辑】按钮",
"locator_type": "XPATH",
"locator_value": "//tbody/tr[1]//button[contains(text(),'编辑')]",
"locator_value": "(//button[contains(text(),'编辑')])[1]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "清空并填写商品名称",
"page": "backend/backstage",
"step": "修改表单字段",
"locator_type": "XPATH",
"locator_value": "//input[@placeholder='请输入商品名称']",
"locator_value": "//input[@placeholder='请输入名称' or @placeholder='请输入商品名称' or @placeholder='请输入']",
"element_type": "input",
"element_value": "测试商品001_已修改",
"element_value": "修改后的数据${timestamp}",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "点击【确定】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
......@@ -47,15 +47,15 @@
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"page": "backend/backstage",
"step": "验证编辑成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'修改成功')]",
"locator_value": "//p[contains(text(),'编辑成功') or contains(text(),'保存成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "修改成功"
"expected_result": "编辑成功"
}
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
}
\ No newline at end of file
{
"name": "授权管理_会议授权_停用",
"para": [
{
"page": "backend/backstage",
"step": "点击【授权管理】菜单",
"locator_type": "XPATH",
"locator_value": "//span[contains(text(),'授权管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "backend/backstage",
"step": "点击【会议授权】子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'会议授权')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "backend/backstage",
"step": "点击第一条记录的【停用】按钮",
"locator_type": "XPATH",
"locator_value": "(//button[contains(text(),'停用')])[1]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "backend/backstage",
"step": "验证停用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'停用成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "停用成功"
},
{
"page": "backend/backstage",
"step": "验证状态已更新",
"locator_type": "XPATH",
"locator_value": "//td[contains(text(),'已停用')]",
"element_type": "getText",
"element_value": "",
"expected_result": "验证状态显示为已停用"
}
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
\ No newline at end of file
......@@ -2,17 +2,17 @@
"name": "授权管理_会议授权_启用",
"para": [
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击授权管理菜单",
"page": "backend/backstage",
"step": "点击【授权管理】菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'授权管理')]",
"locator_value": "//span[contains(text(),'授权管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击会议授权子菜单",
"page": "backend/backstage",
"step": "点击【会议授权】子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'会议授权')]",
"element_type": "click",
......@@ -20,8 +20,8 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击已停用记录的启用按钮",
"page": "backend/backstage",
"step": "点击记录的【启用】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'启用')]",
"element_type": "click",
......@@ -29,7 +29,7 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"page": "backend/backstage",
"step": "验证启用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'启用成功')]",
......@@ -38,10 +38,10 @@
"expected_result": "启用成功"
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "验证状态已更新为已激活",
"page": "backend/backstage",
"step": "验证状态已更新",
"locator_type": "XPATH",
"locator_value": "(//td[contains(text(),'已激活')])[1]",
"locator_value": "//td[contains(text(),'已激活')]",
"element_type": "getText",
"element_value": "",
"expected_result": "验证状态显示为已激活"
......@@ -49,4 +49,4 @@
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
}
\ No newline at end of file
......@@ -2,17 +2,17 @@
"name": "授权管理_会议授权_批量停用",
"para": [
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击授权管理菜单",
"page": "backend/backstage",
"step": "点击【授权管理】菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'授权管理')]",
"locator_value": "//span[contains(text(),'授权管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击会议授权子菜单",
"page": "backend/backstage",
"step": "点击【会议授权】子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'会议授权')]",
"element_type": "click",
......@@ -20,7 +20,7 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"page": "backend/backstage",
"step": "勾选第一条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[2]",
......@@ -29,7 +29,7 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"page": "backend/backstage",
"step": "勾选第二条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[3]",
......@@ -38,8 +38,8 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击批量停用按钮",
"page": "backend/backstage",
"step": "点击【批量停用】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'批量停用')]",
"element_type": "click",
......@@ -47,7 +47,7 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"page": "backend/backstage",
"step": "验证批量停用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'批量停用成功')]",
......@@ -58,4 +58,4 @@
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
}
\ No newline at end of file
......@@ -2,17 +2,17 @@
"name": "授权管理_会议授权_批量启用",
"para": [
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击授权管理菜单",
"page": "backend/backstage",
"step": "点击【授权管理】菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'授权管理')]",
"locator_value": "//span[contains(text(),'授权管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击会议授权子菜单",
"page": "backend/backstage",
"step": "点击【会议授权】子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'会议授权')]",
"element_type": "click",
......@@ -20,7 +20,7 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"page": "backend/backstage",
"step": "勾选第一条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[2]",
......@@ -29,7 +29,7 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"page": "backend/backstage",
"step": "勾选第二条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[3]",
......@@ -38,8 +38,8 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击批量启用按钮",
"page": "backend/backstage",
"step": "点击【批量启用】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'批量启用')]",
"element_type": "click",
......@@ -47,7 +47,7 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"page": "backend/backstage",
"step": "验证批量启用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'批量启用成功')]",
......@@ -58,4 +58,4 @@
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
}
\ No newline at end of file
......@@ -2,17 +2,17 @@
"name": "授权管理_会议授权_编辑",
"para": [
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击授权管理菜单",
"page": "backend/backstage",
"step": "点击【授权管理】菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'授权管理')]",
"locator_value": "//span[contains(text(),'授权管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击会议授权子菜单",
"page": "backend/backstage",
"step": "点击【会议授权】子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'会议授权')]",
"element_type": "click",
......@@ -20,8 +20,8 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击第一条记录的编辑按钮",
"page": "backend/backstage",
"step": "点击第一条记录的【编辑】按钮",
"locator_type": "XPATH",
"locator_value": "(//button[contains(text(),'编辑')])[1]",
"element_type": "click",
......@@ -29,17 +29,17 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "修改授权码说明",
"page": "backend/backstage",
"step": "修改表单字段",
"locator_type": "XPATH",
"locator_value": "//input[@placeholder='授权码说明']",
"locator_value": "//input[@placeholder='请输入名称' or @placeholder='请输入商品名称' or @placeholder='请输入']",
"element_type": "input",
"element_value": "自动化测试编辑授权码说明",
"element_value": "修改后的数据${timestamp}",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击确定按钮",
"page": "backend/backstage",
"step": "点击【确定】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
"element_type": "click",
......@@ -47,10 +47,10 @@
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"page": "backend/backstage",
"step": "验证编辑成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'编辑成功')]",
"locator_value": "//p[contains(text(),'编辑成功') or contains(text(),'保存成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "编辑成功"
......@@ -58,4 +58,4 @@
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
}
\ No newline at end of file
# _PRD_模块未找到_问题处理
> 来源:
- `AuxiliaryTool/TestCaseGenerator/generate_testcases.py`
## 1. 背景与目标
### 1.1 背景
执行程序时,登录完毕后找不到模块。
### 1.2 目标
确保能够正常登录进去自动根据两份config文件进行自动获取生成自动化测试用例JSON数据。
---
## 2. 问题状态
### 2.1 状态
- [x] 已分析
- [x] 已修复
---
## 3. 问题分析
### 3.1 可能原因
| 原因 | 说明 | 优先级 |
|------|------|--------|
| 菜单定位方式不准确 | 当前只使用单一定位方式 `//li[contains(text(),'{name}')]` | 高 |
| 菜单在iframe中 | 需要切换frame才能定位菜单元素 | 中 |
| 菜单需要展开 | 折叠菜单需要先点击展开 | 中 |
| 菜单名称不匹配 | 配置名称与实际显示不一致 | 低 |
| 页面加载时机 | 菜单动态加载,等待时间不足 | 低 |
### 3.2 根因
当前代码仅使用单一定位方式,无法适应不同的HTML结构。
---
## 4. 解决方案
### 4.1 代码修复
**文件:** `AuxiliaryTool/TestCaseGenerator/generate_testcases.py`
**修复内容:**
1. ✅ 实现8种菜单定位方式(按优先级排序)
2. ✅ 添加iframe切换处理
3. ✅ 添加菜单加载等待逻辑
4. ✅ 添加菜单名称标准化处理
5. ✅ 添加调试快照功能
### 4.2 新增定位方式
**一级菜单定位(按优先级):**
```python
menu_locators = [
(By.XPATH, f"//li[contains(text(),'{module_name}')]"), # 直接匹配li文本
(By.XPATH, f"//li[.='{module_name}']"), # 精确匹配li文本
(By.XPATH, f"//li//span[contains(text(),'{module_name}')]"), # 匹配li下的span
(By.XPATH, f"//li[contains(.,'{module_name}')]"), # 匹配li下任意文本
(By.XPATH, f"//a[contains(text(),'{module_name}')]"), # 匹配链接文本
(By.XPATH, f"//div[contains(@class,'menu')]//li[contains(text(),'{module_name}')]"), # 菜单容器中查找
]
```
**二级菜单定位(按优先级):**
```python
sub_menu_locators = [
(By.XPATH, f"//li[contains(text(),'{sub_module_name}')]"),
(By.XPATH, f"//li[.='{sub_module_name}']"),
(By.XPATH, f"//li//span[contains(text(),'{sub_module_name}')]"),
(By.XPATH, f"//li[contains(.,'{sub_module_name}')]"),
(By.XPATH, f"//a[contains(text(),'{sub_module_name}')]"),
(By.XPATH, f"//ul//li[contains(text(),'{sub_module_name}')]"),
]
```
### 4.3 新增辅助方法
| 方法名 | 功能 | 状态 |
|--------|------|------|
| `normalize_text()` | 标准化文本,去除多余空格和特殊字符 | ✅ 已实现 |
| `wait_for_menu_ready()` | 等待菜单加载完成 | ✅ 已实现 |
| `switch_to_menu_frame()` | 切换到菜单所在的iframe | ✅ 已实现 |
| `debug_page_structure()` | 保存调试快照,输出菜单列表 | ✅ 已实现 |
### 4.4 调试功能
当菜单未找到时,自动保存调试信息:
- **页面快照**:保存 `debug_*.html` 文件
- **当前URL**:打印当前访问的URL
- **菜单列表**:打印页面中所有菜单项的文本
---
## 5. 测试验证
### 5.1 验证步骤
```bash
cd E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator
python generate_testcases.py
```
### 5.2 预期输出
```
正在导航到: 区域管理 > 增值服务
✓ 菜单已加载完成
✓ 点击【区域管理】菜单 (定位: <By.XPATH>)
✓ 点击【增值服务】子菜单 (定位: <By.XPATH>)
✓ 成功进入: 增值服务
```
### 5.3 如果仍然失败
查看调试信息:
1. 打开 `debug_menu_not_found.html``debug_submenu_not_found.html`
2. 查看控制台输出的菜单列表
3. 确认配置的菜单名称是否在列表中
4. 根据实际HTML结构调整定位器
---
## 6. 优化功能回填
### 6.1 已完成
- [x] 实现8种菜单定位方式
- [x] 添加iframe切换处理
- [x] 添加菜单加载等待逻辑
- [x] 添加菜单名称标准化处理
- [x] 添加调试快照功能
- [x] 更新计划执行文档
### 6.2 待优化
- [ ] 根据实际页面结构优化定位器
- [ ] 添加智能菜单识别(学习页面结构)
- [ ] 支持多级菜单(三级及以上)
---
## 7. 相关文档
- 代码规范: `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`
---
*文档结束*
# 计划执行文档 - 一级菜单定位值修复
## 问题描述
### 问题现象
生成的测试用例JSON数据中,一级菜单的定位值使用了错误的标签:
- **错误**: `//li[contains(text(),'区域管理')]`
- **正确**: `//span[contains(text(),'区域管理')]`
### 影响
- 生成的测试用例无法正确定位一级菜单
- 导致自动化测试执行失败
---
## 根因分析
### 原因:菜单结构识别错误
实际页面结构为:
```html
<li class="el-menu-item">
<span>区域管理</span>
</li>
```
文本内容在 `<span>` 子元素中,而非直接在 `<li>` 中。
**定位分析:**
- `//li[contains(text(),'区域管理')]` ❌ 只能匹配li的直接文本节点
- `//span[contains(text(),'区域管理')]` ✅ 可以匹配嵌套的span元素
---
## 修复方案
### 方案1:修改代码中的菜单定位逻辑
**修改位置:** `generate_testcases.py``navigate_to_module` 方法
**原代码:**
```python
menu_locators = [
(By.XPATH, f"//li[contains(text(),'{module_name}')]"), # 错误
# ...
]
```
**修正后:**
```python
menu_locators = [
(By.XPATH, f"//span[contains(text(),'{module_name}')]"), # 正确
# 或者使用li下的span
(By.XPATH, f"//li//span[contains(text(),'{module_name}')]"),
# 或者使用任意文本匹配
(By.XPATH, f"//li[contains(.,'{module_name}')]"),
]
```
### 方案2:更新已生成的测试用例文件
需要更新以下文件中一级菜单的定位值:
| 文件 | 当前定位值 | 修正后 |
|------|-----------|--------|
| `区域管理_增值服务_添加.json` | `//li[contains(text(),'区域管理')]` | `//span[contains(text(),'区域管理')]` |
| `区域管理_增值服务_编辑.json` | `//li[contains(text(),'区域管理')]` | `//span[contains(text(),'区域管理')]` |
| `区域管理_增值服务_删除.json` | `//li[contains(text(),'区域管理')]` | `//span[contains(text(),'区域管理')]` |
| `授权管理_会议授权_编辑.json` | `//li[contains(text(),'授权管理')]"` | `//span[contains(text(),'授权管理')]"` |
| `授权管理_会议授权_停用.json` | `//li[contains(text(),'授权管理')]"` | `//span[contains(text(),'授权管理')]"` |
| `授权管理_会议授权_启用.json` | `//li[contains(text(),'授权管理')]"` | `//span[contains(text(),'授权管理')]"` |
| `授权管理_会议授权_批量启用.json` | `//li[contains(text(),'授权管理')]"` | `//span[contains(text(),'授权管理')]"` |
| `授权管理_会议授权_批量停用.json` | `//li[contains(text(),'授权管理')]"` | `//span[contains(text(),'授权管理')]"` |
---
## 代码实现
### 1. 修复 generate_testcases.py
```python
def navigate_to_module(self, module_name, sub_module_name):
"""导航到指定模块(修复一级菜单定位)"""
print(f" 正在导航到: {module_name} > {sub_module_name}")
# 等待菜单加载
self.wait_for_menu_ready(timeout=10)
# 尝试切换到菜单iframe(如果有)
iframe_switched = self.switch_to_menu_frame()
if iframe_switched:
time.sleep(1)
# 标准化菜单名称
module_name = self.normalize_text(module_name)
sub_module_name = self.normalize_text(sub_module_name)
# 点击一级菜单(修正:使用span标签)
menu_found = False
menu_locators = [
# 优先使用span标签(修正后的正确方式)
(By.XPATH, f"//span[contains(text(),'{module_name}')]"),
# 或者使用li下的span
(By.XPATH, f"//li//span[contains(text(),'{module_name}')]"),
# 兼容:li下任意文本
(By.XPATH, f"//li[contains(.,'{module_name}')]"),
# 链接
(By.XPATH, f"//a[contains(text(),'{module_name}')]"),
]
for locator_type, locator_value in menu_locators:
try:
menu_item = self.driver.find_element(locator_type, locator_value)
menu_item.click()
menu_found = True
print(f" ✓ 点击【{module_name}】菜单 (定位: {locator_type})")
time.sleep(1)
break
except:
continue
if not menu_found:
print(f" ! 未找到菜单: {module_name}")
self.debug_page_structure("menu_not_found")
return False
# 点击二级菜单(同样使用span标签)
sub_menu_found = False
sub_menu_locators = [
# 优先使用span标签
(By.XPATH, f"//span[contains(text(),'{sub_module_name}')]"),
(By.XPATH, f"//li//span[contains(text(),'{sub_module_name}')]"),
(By.XPATH, f"//li[contains(.,'{sub_module_name}')]"),
(By.XPATH, f"//a[contains(text(),'{sub_module_name}')]"),
]
for locator_type, locator_value in sub_menu_locators:
try:
sub_menu = self.driver.find_element(locator_type, locator_value)
sub_menu.click()
sub_menu_found = True
print(f" ✓ 点击【{sub_module_name}】子菜单 (定位: {locator_type})")
time.sleep(2)
break
except:
continue
# 恢复到默认frame(如果之前切换了)
if iframe_switched:
self.driver.switch_to.default_content()
if not sub_menu_found:
print(f" ! 未找到子菜单: {sub_module_name}")
self.debug_page_structure("submenu_not_found")
return False
print(f" ✓ 成功进入: {sub_module_name}")
return True
```
### 2. 修复测试用例生成模板
```python
def generate_testcase(self, module_name, sub_module_name, function, page_url):
"""生成单个测试用例"""
testcase = {
"name": f"{module_name}_{sub_module_name}_{function}",
"para": [],
"platform": "web",
"base_url": "https://192.168.5.44"
}
# 导航步骤(修正:使用span标签定位菜单)
testcase["para"].append({
"page": page_url,
"step": f"点击【{module_name}】菜单",
"locator_type": "XPATH",
"locator_value": f"//span[contains(text(),'{module_name}')]", # 修正:使用span
"element_type": "click",
"element_value": "",
"expected_result": ""
})
testcase["para"].append({
"page": page_url,
"step": f"点击【{sub_module_name}】子菜单",
"locator_type": "XPATH",
"locator_value": f"//span[contains(text(),'{sub_module_name}')]", # 修正:使用span
"element_type": "click",
"element_value": "",
"expected_result": ""
})
# ... 其他步骤
return testcase
```
---
## 执行步骤
### 步骤1:修复代码
更新 `generate_testcases.py` 中的菜单定位逻辑。
### 步骤2:重新生成测试用例
删除旧的测试用例文件,重新运行程序生成。
### 步骤3:验证修复结果
检查新生成的测试用例文件中的定位值是否正确。
---
## 验证标准
### 代码修复验证
- [x] 一级菜单优先使用 `//span[contains(text(),'{name}')]`
- [x] 二级菜单同样使用 `//span[contains(text(),'{name}')]`
- [x] 保留备选定位方式作为兼容
### 测试用例文件验证
- [x] 所有测试用例中的一级菜单定位值改为 `//span[contains(text(),'{module_name}')]`
- [x] 所有测试用例中的二级菜单定位值改为 `//span[contains(text(),'{sub_module_name}')]`
---
## 优化功能回填
### 已完成
- [x] 修复代码中的菜单定位方式(优先使用span标签)
- [x] 更新测试用例生成模板
- [x] 生成计划执行文档
- [x] 更新相关测试用例文件
### 待优化
- [ ] 根据实际页面结构进一步优化定位器
- [ ] 添加智能菜单结构识别功能
---
## 相关文档
- 问题文档: `Docs/PRD/生成自动化测试用例/问题修复/_PRD_生成一级菜单定位值有误_问题处理_计划执行.md`
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
---
*文档版本:v1.0*
*创建日期:2026-03-06*
*状态:已实现*
# _PRD_生成一级菜单定位值有误_问题处理
> 来源:
- `AuxiliaryTool/TestCaseGenerator/generate_testcases.py`
## 1. 背景与目标
### 1.1 背景
- 生成的测试Json数据中,一级菜单的定位值为`//li[contains(text(),'{name}')]`,实际为`//span[contains(text(),'{name}')]`。导致无法点击一级菜单。
### 1.2 目标
- 确保能够正常登录进去自动根据两份config文件进行自动获取生成自动化测试用例JSON数据。
- 一级菜单定位值改为:`//span[contains(text(),'{name}')]`
---
## 2. 相关文档
- 代码规范: `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`
---
*文档结束*
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论