提交 7890c5bd authored 作者: 陈泽健's avatar 陈泽健

docs(test): 更新自动化测试用例生成工具文档并修复登录定位问题

- 添加登录按钮定位错误问题处理文档
- 更新操作手册增加Python脚本执行方式
- 修正module_config.json配置示例
- 扩展Claude工具能力配置
- 生成区域管理增值服务测试用例文件
- 创建计划执行文档详述技术方案
上级 b6ce7d77
......@@ -120,7 +120,9 @@
"mcp__chrome-devtools__wait_for",
"mcp__chrome-devtools__press_key",
"mcp__chrome-devtools__new_page",
"mcp__chrome-devtools__close_page"
"mcp__chrome-devtools__close_page",
"mcp__chrome-devtools__list_pages",
"mcp__chrome-devtools__fill_form"
]
}
}
......@@ -6,9 +6,9 @@
"module_function": ["添加","编辑","删除"]
},
{
"system_type": "台系统",
"module_name": "会议室管理",
"module_name_son": "会议室列表",
"module_function": ["添加","编辑"]
"system_type": "台系统",
"module_name": "授权管理",
"module_name_son": "会议授权",
"module_function": ["编辑","停用","启用","批量启用","批量停用"]
}
]
\ No newline at end of file
]
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
自动化测试用例生成器
根据 module_config.json 和 system_config.json 自动生成测试用例JSON文件
"""
import json
import time
import os
from datetime import datetime
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from selenium.common.exceptions import TimeoutException, NoSuchElementException
class TestCaseGenerator:
"""测试用例生成器"""
def __init__(self):
self.base_dir = os.path.dirname(os.path.abspath(__file__))
self.config_dir = os.path.join(self.base_dir, 'config')
self.testcases_dir = os.path.join(self.base_dir, 'testcases')
self.driver = None
# 确保目录存在
os.makedirs(self.config_dir, exist_ok=True)
os.makedirs(self.testcases_dir, exist_ok=True)
def load_config(self):
"""加载配置文件"""
with open(os.path.join(self.config_dir, 'system_config.json'), 'r', encoding='utf-8') as f:
self.system_config = json.load(f)
with open(os.path.join(self.config_dir, 'module_config.json'), 'r', encoding='utf-8') as f:
self.module_config = json.load(f)
print(f"✓ 加载系统配置: {len(self.system_config)} 个")
print(f"✓ 加载模块配置: {len(self.module_config)} 个")
def init_driver(self):
"""初始化浏览器驱动"""
options = webdriver.ChromeOptions()
# options.add_argument('--headless') # 取消注释可无头模式运行
options.add_argument('--no-sandbox')
options.add_argument('--disable-dev-shm-usage')
options.add_argument('--disable-gpu')
# 忽略SSL证书错误
options.add_argument('--ignore-certificate-errors')
options.add_argument('--ignore-ssl-errors')
options.add_argument('--ignore-certificate-errors-spki-list')
options.add_argument('--disable-web-security')
options.set_capability('acceptInsecureCerts', True)
self.driver = webdriver.Chrome(options=options)
self.driver.implicitly_wait(10)
print("✓ 浏览器驱动初始化完成")
def handle_ssl_warning(self):
"""处理SSL证书警告页面"""
try:
# 等待页面加载
time.sleep(2)
# 检查是否为SSL警告页面(通过URL或页面标题判断)
current_url = self.driver.current_url
page_title = self.driver.title
# 常见的SSL警告页面特征
is_ssl_warning = (
'chrome-error://' in current_url or
'您的连接不是私密连接' in page_title or
'Your connection is not private' in page_title or
'NET::ERR_CERT' in self.driver.page_source
)
if is_ssl_warning:
print(" ! 检测到SSL证书警告,正在处理...")
# 方法1: 点击"高级"按钮
try:
advanced_btn = WebDriverWait(self.driver, 3).until(
EC.element_to_be_clickable((By.XPATH, "//button[contains(text(),'高级') or contains(text(),'Advanced')]"))
)
advanced_btn.click()
time.sleep(1)
except:
pass
# 方法2: 点击"继续访问"按钮
try:
proceed_btn = WebDriverWait(self.driver, 3).until(
EC.element_to_be_clickable((By.XPATH, "//a[contains(text(),'继续访问') or contains(text(),'Proceed') or contains(text(),'不安全')]"))
)
proceed_btn.click()
time.sleep(2)
print(" ✓ SSL警告已处理,继续访问")
except:
pass
# 方法3: 尝试通过链接处理
try:
proceed_link = self.driver.find_element(By.XPATH, "//a[@id='proceed-link'] or //a[contains(@href, 'proceed')]")
proceed_link.click()
time.sleep(2)
print(" ✓ SSL警告已处理,继续访问")
except:
pass
except Exception as e:
# 如果不是SSL警告页面,忽略错误
pass
def login(self, system_info):
"""登录系统"""
url = system_info.get('system_back_url')
username = system_info.get('username')
password = system_info.get('password')
code = system_info.get('code')
print(f" 正在登录: {url}")
self.driver.get(url)
time.sleep(1)
# 处理SSL证书警告
self.handle_ssl_warning()
time.sleep(2)
# 输入账号
try:
username_input = self.wait_for_element((By.XPATH, "//input[contains(@placeholder,'账号') or contains(@placeholder,'邮箱') or contains(@placeholder,'手机号')]"))
username_input.clear()
username_input.send_keys(username)
except:
print(" ! 账号输入框未找到,尝试其他定位方式")
self.driver.find_element(By.CSS_SELECTOR, "input[type='text']").send_keys(username)
# 输入密码
try:
password_input = self.driver.find_element(By.XPATH, "//input[@type='password']")
password_input.clear()
password_input.send_keys(password)
except:
pass
# 输入验证码
try:
code_input = self.driver.find_elements(By.CSS_SELECTOR, "input[type='text']")[-1]
code_input.clear()
code_input.send_keys(code)
print(f" ✓ 输入验证码: {code}")
except:
print(" ! 验证码输入失败,跳过")
# 点击登录按钮 - 使用多重定位策略
login_success = False
# 多种登录按钮定位方式(按优先级排序)
login_locators = [
# 通过文本内容(使用正确的XPATH语法 | 管道符)
(By.XPATH, "//button[contains(text(),'登')] | //button[contains(text(),'Login')]"),
# 通过span标签和文本内容
(By.XPATH, "(//span[contains(text(),'登录')])[1]"),
# 通过input标签的value属性
(By.XPATH, "//input[@value='登 录']"),
# 通过input标签的value属性(英文)
(By.XPATH, "//input[@value='Login']"),
# 通过class属性
(By.XPATH, "//button[contains(@class,'login')]"),
# 通过type属性
(By.XPATH, "//button[@type='submit']"),
# 通过CSS选择器
(By.CSS_SELECTOR, "button.el-button--primary"),
(By.CSS_SELECTOR, "button[type='submit']"),
# 通过任意包含登录class的按钮
(By.CSS_SELECTOR, "button[class*='login']"),
(By.CSS_SELECTOR, "button[class*='Login']"),
# 通过任意包含primary class的按钮
(By.CSS_SELECTOR, "button[class*='primary']"),
]
for locator_type, locator_value in login_locators:
try:
login_btn = self.driver.find_element(locator_type, locator_value)
login_btn.click()
login_success = True
print(f" ✓ 点击登录按钮 (定位: {locator_type})")
break
except:
continue
if not login_success:
print(" ! 登录按钮定位失败,尝试JavaScript点击")
try:
# 最后的备选方案:通过JavaScript点击第一个按钮
self.driver.execute_script("document.querySelector('button').click()")
login_success = True
print(" ✓ 通过JavaScript点击登录按钮")
except Exception as e:
print(f" ✗ 登录失败: {e}")
raise Exception("无法定位登录按钮,请检查登录页面结构")
time.sleep(3)
print(" ✓ 登录成功")
def wait_for_element(self, locator, timeout=10):
"""等待元素出现"""
return WebDriverWait(self.driver, timeout).until(
EC.presence_of_element_located(locator)
)
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()
time.sleep(1)
except:
print(f" ! 未找到菜单: {module_name}")
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:
print(f" ! 未找到子菜单: {sub_module_name}")
return False
def get_page_url(self):
"""获取当前页面的路由后缀"""
url = self.driver.current_url
# 提取路由部分
if '#/' in url:
return url.split('#/')[-1].split('?')[0]
return url
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"
}
# 导航步骤(使用li标签定位菜单)
testcase["para"].append({
"page": page_url,
"step": f"点击【{module_name}】菜单",
"locator_type": "XPATH",
"locator_value": f"//li[contains(text(),'{module_name}')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
})
testcase["para"].append({
"page": page_url,
"step": f"点击【{sub_module_name}】子菜单",
"locator_type": "XPATH",
"locator_value": f"//li[contains(text(),'{sub_module_name}')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
})
# 根据功能类型添加步骤
if function == "添加":
self._add_add_steps(testcase, page_url)
elif function == "编辑":
self._add_edit_steps(testcase, page_url)
elif function == "删除":
self._add_delete_steps(testcase, page_url)
elif function == "停用":
self._add_disable_steps(testcase, page_url)
elif function == "启用":
self._add_enable_steps(testcase, page_url)
elif function == "批量启用":
self._add_batch_enable_steps(testcase, page_url)
elif function == "批量停用":
self._add_batch_disable_steps(testcase, page_url)
return testcase
def _add_add_steps(self, testcase, page_url):
"""添加功能的步骤"""
testcase["para"].extend([
{
"page": page_url,
"step": "点击【添加】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'添加')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "填写表单字段",
"locator_type": "XPATH",
"locator_value": "//input[@placeholder='请输入名称' or @placeholder='请输入商品名称' or @placeholder='请输入']",
"element_type": "input",
"element_value": "测试数据${timestamp}",
"expected_result": ""
},
{
"page": page_url,
"step": "点击【确定】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "验证添加成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'添加成功') or contains(text(),'成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "添加成功"
}
])
def _add_edit_steps(self, testcase, page_url):
"""编辑功能的步骤"""
testcase["para"].extend([
{
"page": page_url,
"step": "点击第一条记录的【编辑】按钮",
"locator_type": "XPATH",
"locator_value": "(//button[contains(text(),'编辑')])[1]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "修改表单字段",
"locator_type": "XPATH",
"locator_value": "//input[@placeholder='请输入名称' or @placeholder='请输入商品名称' or @placeholder='请输入']",
"element_type": "input",
"element_value": "修改后的数据${timestamp}",
"expected_result": ""
},
{
"page": page_url,
"step": "点击【确定】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "验证编辑成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'编辑成功') or contains(text(),'保存成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "编辑成功"
}
])
def _add_delete_steps(self, testcase, page_url):
"""删除功能的步骤"""
testcase["para"].extend([
{
"page": page_url,
"step": "点击第一条记录的【删除】按钮",
"locator_type": "XPATH",
"locator_value": "(//button[contains(text(),'删除')])[1]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "确认删除操作",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "验证删除成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'删除成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "删除成功"
}
])
def _add_disable_steps(self, testcase, page_url):
"""停用功能的步骤"""
testcase["para"].extend([
{
"page": page_url,
"step": "点击第一条记录的【停用】按钮",
"locator_type": "XPATH",
"locator_value": "(//button[contains(text(),'停用')])[1]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "验证停用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'停用成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "停用成功"
},
{
"page": page_url,
"step": "验证状态已更新",
"locator_type": "XPATH",
"locator_value": "//td[contains(text(),'已停用')]",
"element_type": "getText",
"element_value": "",
"expected_result": "验证状态显示为已停用"
}
])
def _add_enable_steps(self, testcase, page_url):
"""启用功能的步骤"""
testcase["para"].extend([
{
"page": page_url,
"step": "点击记录的【启用】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'启用')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "验证启用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'启用成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "启用成功"
},
{
"page": page_url,
"step": "验证状态已更新",
"locator_type": "XPATH",
"locator_value": "//td[contains(text(),'已激活')]",
"element_type": "getText",
"element_value": "",
"expected_result": "验证状态显示为已激活"
}
])
def _add_batch_enable_steps(self, testcase, page_url):
"""批量启用功能的步骤"""
testcase["para"].extend([
{
"page": page_url,
"step": "勾选第一条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[2]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "勾选第二条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[3]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "点击【批量启用】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'批量启用')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "验证批量启用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'批量启用成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "批量启用成功"
}
])
def _add_batch_disable_steps(self, testcase, page_url):
"""批量停用功能的步骤"""
testcase["para"].extend([
{
"page": page_url,
"step": "勾选第一条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[2]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "勾选第二条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[3]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "点击【批量停用】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'批量停用')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": page_url,
"step": "验证批量停用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'批量停用成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "批量停用成功"
}
])
def save_testcase(self, testcase):
"""保存测试用例到文件"""
filename = f"{testcase['name']}.json"
filepath = os.path.join(self.testcases_dir, filename)
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(testcase, f, ensure_ascii=False, indent=2)
print(f" ✓ 生成: {filename}")
def run(self):
"""执行生成流程"""
print("=" * 60)
print("自动化测试用例生成器")
print("=" * 60)
# 加载配置
self.load_config()
# 初始化驱动
self.init_driver()
# 获取系统配置(使用第一个)
system_info = self.system_config[0]
# 登录系统
self.login(system_info)
# 遍历模块配置
total_count = 0
for idx, module in enumerate(self.module_config, 1):
module_name = module.get('module_name')
sub_module_name = module.get('module_name_son')
functions = module.get('module_function', [])
print(f"\n[{idx}/{len(self.module_config)}] 处理模块: {module_name} > {sub_module_name}")
# 导航到模块
if not self.navigate_to_module(module_name, sub_module_name):
print(f" ✗ 跳过该模块")
continue
# 获取页面URL
page_url = self.get_page_url()
print(f" 页面路由: {page_url}")
# 生成每个功能的测试用例
for function in functions:
testcase = self.generate_testcase(
module_name, sub_module_name, function, page_url
)
self.save_testcase(testcase)
total_count += 1
print("\n" + "=" * 60)
print(f"✓ 生成完成!共生成 {total_count} 个测试用例文件")
print(f"✓ 文件保存在: {self.testcases_dir}")
print("=" * 60)
def close(self):
"""关闭浏览器"""
if self.driver:
self.driver.quit()
def main():
"""主函数"""
generator = TestCaseGenerator()
try:
generator.run()
except Exception as e:
print(f"\n✗ 执行出错: {e}")
import traceback
traceback.print_exc()
finally:
generator.close()
if __name__ == "__main__":
main()
{
"name": "区域管理_增值服务_删除",
"para": [
{
"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": "//li[contains(text(),'增值服务')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "点击第一行数据的【删除】按钮",
"locator_type": "XPATH",
"locator_value": "//tbody/tr[1]//button[contains(text(),'删 除')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "点击删除确认对话框的【确定】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "验证删除成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'删除成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "删除成功"
}
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
{
"name": "区域管理_增值服务_添加",
"para": [
{
"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": "//li[contains(text(),'增值服务')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "点击【添加】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'添加')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "填写商品名称",
"locator_type": "XPATH",
"locator_value": "//input[@placeholder='请输入商品名称']",
"element_type": "input",
"element_value": "测试商品001",
"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",
"step": "点击【确定】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "验证添加成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'添加成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "添加成功"
}
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
{
"name": "区域管理_增值服务_编辑",
"para": [
{
"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": "//li[contains(text(),'增值服务')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "点击第一行数据的【编辑】按钮",
"locator_type": "XPATH",
"locator_value": "//tbody/tr[1]//button[contains(text(),'编辑')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "清空并填写商品名称",
"locator_type": "XPATH",
"locator_value": "//input[@placeholder='请输入商品名称']",
"element_type": "input",
"element_value": "测试商品001_已修改",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "点击【确定】按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "Backend/MeetingRoom/ServiceManage",
"step": "验证编辑成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'修改成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "修改成功"
}
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
{
"name": "授权管理_会议授权_启用",
"para": [
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击授权管理菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'授权管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击会议授权子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'会议授权')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击已停用记录的启用按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'启用')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "验证启用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'启用成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "启用成功"
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "验证状态已更新为已激活",
"locator_type": "XPATH",
"locator_value": "(//td[contains(text(),'已激活')])[1]",
"element_type": "getText",
"element_value": "",
"expected_result": "验证状态显示为已激活"
}
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
{
"name": "授权管理_会议授权_批量停用",
"para": [
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击授权管理菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'授权管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击会议授权子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'会议授权')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "勾选第一条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[2]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "勾选第二条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[3]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击批量停用按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'批量停用')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "验证批量停用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'批量停用成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "批量停用成功"
}
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
{
"name": "授权管理_会议授权_批量启用",
"para": [
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击授权管理菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'授权管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击会议授权子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'会议授权')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "勾选第一条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[2]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "勾选第二条记录复选框",
"locator_type": "XPATH",
"locator_value": "(//input[@type='checkbox'])[3]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击批量启用按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'批量启用')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "验证批量启用成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'批量启用成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "批量启用成功"
}
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
{
"name": "授权管理_会议授权_编辑",
"para": [
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击授权管理菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'授权管理')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击会议授权子菜单",
"locator_type": "XPATH",
"locator_value": "//li[contains(text(),'会议授权')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击第一条记录的编辑按钮",
"locator_type": "XPATH",
"locator_value": "(//button[contains(text(),'编辑')])[1]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "修改授权码说明",
"locator_type": "XPATH",
"locator_value": "//input[@placeholder='授权码说明']",
"element_type": "input",
"element_value": "自动化测试编辑授权码说明",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "点击确定按钮",
"locator_type": "XPATH",
"locator_value": "//button[contains(text(),'确定')]",
"element_type": "click",
"element_value": "",
"expected_result": ""
},
{
"page": "#/Backend/AuthorizationCode/AuthCode",
"step": "验证编辑成功提示",
"locator_type": "XPATH",
"locator_value": "//p[contains(text(),'编辑成功')]",
"element_type": "getTips",
"element_value": "",
"expected_result": "编辑成功"
}
],
"platform": "web",
"base_url": "https://192.168.5.44"
}
# 计划执行文档 - 自动化测试用例生成器
## 需求概述
### 背景与目标
- **背景**:当前自动化测试用例手动编写效率低,无法批量生成JSON数据模板
- **目标**:通过Python + Selenium实现自动化测试用例生成器,根据配置文件自动访问系统、收集元素定位信息、生成标准JSON格式的测试用例
### 核心功能
1. 读取 `system_config.json` 获取系统登录配置
2. 读取 `module_config.json` 获取需要生成的模块和功能
3. 自动登录系统并导航到指定模块
4. 执行配置的功能操作(添加/编辑/删除等)
5. 收集元素定位信息
6. 生成标准JSON格式的测试用例文件
---
## 技术方案
### 技术栈
| 技术 | 版本 | 用途 |
|------|------|------|
| Python | 3.10+ | 主要开发语言 |
| Selenium | 4.x | 浏览器自动化 |
| ChromeDriver | 最新版 | Chrome浏览器驱动 |
### 目录结构
```
AuxiliaryTool/TestCaseGenerator/
├── config/
│ ├── system_config.json # 系统登录配置
│ └── module_config.json # 模块配置(输入)
├── testcases/ # 测试用例输出目录
├── generate_testcases.py # 主程序
└── utils/
├── locator_helper.py # 元素定位辅助类
└── template_builder.py # 测试用例模板构建类
```
---
## 实现计划
### 阶段一:基础框架搭建 ✓ 已完成
- [x] 创建 `generate_testcases.py` 主程序文件
- [x] 实现配置文件读取功能
- [x] 实现浏览器驱动初始化
- [x] 实现SSL证书警告处理
- [x] 实现系统登录功能
- [x] 实现模块导航功能
- [x] 更新XPATH定位规范(使用具体标签)
#### SSL证书警告处理
**问题**:访问自签名SSL证书的网站时,Chrome浏览器会显示"您的连接不是私密连接"警告页面。
**解决方案**:采用双重保障机制
**方案1:浏览器启动参数(预防)**
```python
options = webdriver.ChromeOptions()
options.add_argument('--ignore-certificate-errors')
options.add_argument('--ignore-ssl-errors')
options.add_argument('--disable-web-security')
options.set_capability('acceptInsecureCerts', True)
```
**方案2:页面点击处理(兜底)**
```python
def handle_ssl_warning(self):
"""处理SSL证书警告页面"""
# 检测SSL警告页面
# 点击"高级"按钮
# 点击"继续访问"按钮
```
| 处理方式 | 触发时机 | 状态 |
|---------|---------|------|
| Chrome参数 | 浏览器启动时 | ✅ 已实现 |
| 页面点击 | 访问URL后 | ✅ 已实现 |
### 阶段二:元素定位收集(待优化)
#### 2.1 定位策略实现
按以下优先级实现元素定位检测:
| 优先级 | 定位类型 | 实现方式 | 状态 |
|--------|----------|----------|------|
| 1 | ID | `element.get_attribute('id')` | 待实现 |
| 2 | XPATH | 使用具体标签生成XPATH | 待实现 |
**定位策略**:优先使用ID,如果没有ID则使用XPATH。
**XPATH规范**
-**正确**`//li[contains(text(),'用户列表')]`
-**正确**`//button[contains(text(),'添加')]`
-**正确**`//input[@placeholder='请输入商品名称']`
-**错误**`//span[contains(.,'增值服务')]`(不明确的span标签)
**实现方案**
```python
class LocatorHelper:
"""元素定位辅助类"""
@staticmethod
def get_best_locator(element):
"""获取最佳定位方式"""
# 优先级1: ID
element_id = element.get_attribute('id')
if element_id:
return 'ID', element_id
# 优先级2: XPATH(使用具体标签)
xpath = LocatorHelper.generate_specific_xpath(element)
return 'XPATH', xpath
@staticmethod
def generate_specific_xpath(element):
"""生成具体的XPATH表达式"""
tag_name = element.tag_name
# 按钮元素
if tag_name == 'button':
text = element.text.strip()
if text:
return f"//button[contains(text(),'{text}')]"
# 链接/菜单项元素
elif tag_name in ['li', 'a']:
text = element.text.strip()
if text:
return f"//{tag_name}[contains(text(),'{text}')]"
# 输入框元素
elif tag_name == 'input':
placeholder = element.get_attribute('placeholder')
if placeholder:
return f"//input[@placeholder='{placeholder}']"
input_id = element.get_attribute('id')
if input_id:
return f"//input[@id='{input_id}']"
# 其他元素:根据文本内容定位
text = element.text.strip()
if text:
return f"//{tag_name}[contains(text(),'{text}')]"
# 默认:通过属性定位
return f"//{tag_name}"
```
#### 2.2 XPATH智能生成
- 使用父元素关系生成相对XPATH
- 支持 `contains(.,'文本')` 语法匹配Element UI框架
- 避免使用UID定位
### 阶段三:功能步骤模板化(待实现)
#### 3.1 功能类型映射表
| 功能类型 | 主要步骤 | 验证方式 | 状态 |
|----------|----------|----------|------|
| 添加 | 点击添加按钮 → 填写表单 → 点击确定 | getTips验证成功提示 | 待实现 |
| 编辑 | 点击编辑按钮 → 修改表单 → 点击确定 | getTips验证成功提示 | 待实现 |
| 删除 | 点击删除按钮 → 确认删除 | getTips验证成功提示 | 待实现 |
| 停用 | 点击停用按钮 | getText验证状态 | 待实现 |
| 启用 | 点击启用按钮 | getText验证状态 | 待实现 |
| 批量启用 | 勾选多条记录 → 点击批量启用 | getTips验证成功提示 | 待实现 |
| 批量停用 | 勾选多条记录 → 点击批量停用 | getTips验证成功提示 | 待实现 |
#### 3.2 表单自动识别
- 自动检测输入框类型
- 智能填充测试数据
- 支持动态值(如 `${timestamp}`
### 阶段四:测试用例生成(待完善)
#### 4.1 JSON模板规范
```json
{
"name": "{module_name}_{module_name_son}_{function}",
"para": [
{
"page": "真实路由后缀(从URL提取)",
"step": "操作步骤描述",
"locator_type": "ID/XPATH/NAME/CLASS",
"locator_value": "定位值",
"element_type": "click/input/select/...",
"element_value": "操作值(根据类型填写)",
"expected_result": "预期结果"
}
],
"platform": "web",
"base_url": "系统URL"
}
```
#### 4.2 element_type 填写规范
| element_type | element_value | expected_result | 状态 |
|--------------|---------------|-----------------|------|
| input | 填写输入内容 | 通常留空 | 待实现 |
| click | 留空 | 通常留空 | 待实现 |
| getTips | **留空** | 填写预期提示文本 | 待实现 |
| getText | **留空** | 填写验证描述 | 待实现 |
| select | 留空 | 通常留空 | 待实现 |
| checkbox | 留空 | 通常留空 | 待实现 |
| switch | 留空 | 通常留空 | 待实现 |
#### 4.3 文件命名规范
- 格式:`{module_name}_{module_name_son}_{function}.json`
- 示例:`区域管理_增值服务_添加.json`
- 输出目录:`testcases/`
---
## 详细实现步骤
### Step 1: 创建辅助类模块
#### 1.1 LocatorHelper(元素定位辅助类)
```python
# utils/locator_helper.py
class LocatorHelper:
"""元素定位辅助类"""
@staticmethod
def get_best_locator(element):
"""获取最佳定位方式(按优先级)"""
pass
@staticmethod
def generate_xpath(element):
"""智能生成XPATH表达式"""
pass
@staticmethod
def is_ui_framework_text(element):
"""检测是否为UI框架文本元素(如Element UI)"""
pass
```
#### 1.2 TemplateBuilder(测试用例模板构建类)
```python
# utils/template_builder.py
class TemplateBuilder:
"""测试用例模板构建类"""
def __init__(self, page_url):
self.page_url = page_url
self.steps = []
def add_navigate_step(self, module_name, sub_module_name):
"""添加导航步骤"""
pass
def add_function_steps(self, function_type, locators):
"""根据功能类型添加步骤"""
pass
def build(self, name):
"""构建完整测试用例"""
pass
```
### Step 2: 优化主程序逻辑
#### 2.1 配置读取增强
- 支持system_type判断(前台/后台URL)
- 配置文件校验
- 错误提示优化
#### 2.2 元素收集流程
```
1. 导航到目标模块
2. 获取当前页面URL → 提取路由后缀
3. 定位目标元素
4. 按优先级获取最佳定位方式
5. 构建测试步骤
6. 生成JSON文件
```
#### 2.3 功能操作实现
| 功能 | 操作流程 | 数据清理 |
|------|----------|----------|
| 添加 | 填写表单 → 提交 | 需要清理(删除创建的数据) |
| 编辑 | 先创建 → 编辑 → 验证 | 需要清理 |
| 删除 | 先创建 → 删除 → 验证 | 已删除 |
| 停用 | 点击停用 → 验证状态 | 需要恢复(启用) |
| 启用 | 点击启用 → 验证状态 | 需要恢复(停用) |
### Step 3: 测试数据管理
#### 3.1 固定测试值
```python
TEST_DATA = {
'username': 'test001',
'phone': '13800138000',
'password': 'Test@123456',
'email': 'test001@example.com',
'name': '测试数据${timestamp}'
}
```
#### 3.2 动态值支持
- `${timestamp}` - 时间戳
- `${random}` - 随机数
- `${uuid}` - 唯一标识
---
## 待优化项
### 当前实现的问题
| 问题 | 影响 | 优化方案 | 状态 |
|------|------|----------|------|
| 元素定位简单 | 定位不够精确 | 实现智能定位优先级 | 待实现 |
| 缺少元素收集 | 步骤不够完整 | 自动检测表单元素 | 待实现 |
| Page字段固定 | 不符合实际需求 | 从URL动态提取 | 待实现 |
| 功能类型有限 | 不支持所有功能 | 扩展功能类型 | 待实现 |
| 错误处理不足 | 调试困难 | 添加详细日志 | 待实现 |
### 优化计划
#### 优化1:智能元素定位收集
- [ ] 实现LocatorHelper类
- [ ] 按优先级检测定位方式
- [ ] 智能生成XPATH表达式
#### 优化2:表单自动识别
- [ ] 自动检测表单中的输入元素
- [ ] 识别元素类型(input/select/checkbox等)
- [ ] 智能填充测试数据
#### 优化3:URL路由提取
- [ ] 从当前URL提取路由后缀
- [ ] 支持多种URL格式
- [ ] 处理特殊字符
#### 优化4:错误处理增强
- [ ] 添加异常捕获
- [ ] 记录详细日志
- [ ] 失败重试机制
---
## 测试验证计划
### 单元测试
| 测试项 | 测试内容 | 状态 |
|--------|----------|------|
| 配置读取 | 验证JSON配置正确解析 | 待测试 |
| 元素定位 | 验证定位优先级正确 | 待测试 |
| 模板生成 | 验证JSON格式正确 | 待测试 |
| 文件输出 | 验证文件正确保存 | 待测试 |
### 集成测试
| 测试场景 | 预期结果 | 状态 |
|----------|----------|------|
| 完整流程 | 生成正确的测试用例文件 | 待测试 |
| 多模块批量 | 批量处理多个模块配置 | 待测试 |
| 错误恢复 | 配置错误时正确提示 | 待测试 |
---
## 交付物清单
### 代码文件
- [x] `generate_testcases.py` - 主程序
- [ ] `utils/locator_helper.py` - 元素定位辅助类
- [ ] `utils/template_builder.py` - 模板构建类
- [ ] `utils/test_data.py` - 测试数据管理
### 文档文件
- [x] 操作手册(已更新)
- [x] 计划执行文档(本文档)
- [ ] 使用说明
- [ ] API文档
### 配置文件
- [x] `config/system_config.json` - 系统配置模板
- [x] `config/module_config.json` - 模块配置模板
---
## 时间规划
| 阶段 | 任务 | 预计工时 | 状态 |
|------|------|----------|------|
| 阶段一 | 基础框架搭建 | 2h | ✓ 已完成 |
| 阶段二 | 元素定位收集 | 4h | 待开始 |
| 阶段三 | 功能步骤模板化 | 3h | 待开始 |
| 阶段四 | 测试用例生成优化 | 2h | 待开始 |
| 测试验证 | 完整流程测试 | 2h | 待开始 |
| **总计** | | **13h** | 进行中 |
---
## 备注
### 重要约束
1. **不允许使用UID定位**
2. **XPATH使用`contains(.,'文本')`语法**
3. **element_value和expected_result按规范填写**
4. **page字段必须从URL提取真实路由**
### 参考文档
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
- 测试规范: `Docs/PRD/01规范文档/_PRD_规范文档_测试规范.md`
- 需求文档: `Docs/PRD/生成自动化测试用例/_PRD_生成自动化测试用例需求文档.md`
---
*文档版本:v1.0*
*创建日期:2026-03-06*
*状态:执行中*
......@@ -2,13 +2,16 @@
## 前置条件
### 1. 确保Chrome DevTools MCP工具已启动
### 1. Python环境准备
确保已安装必要的Python依赖:
```bash
# 检查MCP服务是否正常运行
# 确保 Claude Code 可以访问 chrome-devtools MCP 工具
pip install selenium
```
### 2. 准备配置文件
### 2. ChromeDriver
确保已安装ChromeDriver,并且版本与Chrome浏览器匹配。
### 3. 准备配置文件
确保以下配置文件已正确配置:
- `AuxiliaryTool/TestCaseGenerator/config/system_config.json` - 系统登录配置
- `AuxiliaryTool/TestCaseGenerator/config/module_config.json` - 模块配置
......@@ -17,7 +20,58 @@
## 操作步骤
### 使用Claude Code对话式生成
## 方式一:运行Python脚本(推荐)⭐
**推荐使用此方式**,无需对话交互,可直接运行,支持CI/CD集成。
### Step 1: 更新模块配置文件
编辑 `module_config.json`,添加新模块配置:
```json
[
{
"system_type": "后台系统",
"module_name": "一级菜单名称",
"module_name_son": "二级菜单名称",
"module_function": ["添加", "编辑", "删除"]
}
]
```
**配置说明:**
- `system_type`: 包含"后台"或"admin"使用后台URL,否则使用前台URL
- `module_name`: 一级菜单名称(如:区域管理、用户管理)
- `module_name_son`: 二级菜单名称(如:增值服务、用户列表)
- `module_function`: 需要生成的功能类型,支持:
- `添加` - 新增数据操作
- `编辑` - 修改数据操作
- `删除` - 删除数据操作
- `停用` - 停用操作
- `启用` - 启用操作
- `批量启用` - 批量启用操作
- `批量停用` - 批量停用操作
### Step 2: 运行生成脚本
```bash
# 进入目录
cd E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator
# 运行脚本
python generate_testcases.py
```
### Step 3: 查看生成的文件
生成的文件位于:
```
AuxiliaryTool/TestCaseGenerator/testcases/{模块名}_{子模块名}_{功能}.json
```
---
## 方式二:使用Claude Code对话式生成
#### Step 1: 更新模块配置文件
......@@ -294,9 +348,11 @@ mkdir -p "E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator/confi
## 附录:快速开始模板
### Step 1: 确保配置文件存在
### 使用Python脚本方式(推荐)
**创建config目录(如果不存在):**
**Step 1: 确保配置文件存在**
创建config目录(如果不存在):
```bash
mkdir -p "E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator/config"
```
......@@ -319,18 +375,43 @@ mkdir -p "E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator/confi
"system_type": "后台系统",
"module_name": "一级菜单",
"module_name_son": "二级菜单",
"module_function": ["添加"]
"module_function": ["添加", "编辑", "删除"]
}]
```
### Step 2: 在Claude Code中执行
**Step 2: 运行生成脚本**
```bash
cd E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator
python generate_testcases.py
```
**Step 3: 查看结果**
生成的文件位于:
```
AuxiliaryTool/TestCaseGenerator/testcases/
```
### 使用Claude Code对话方式
**Step 1: 在Claude Code中执行**
直接输入指令:
```
请根据module_config.json和system_config.json生成自动化测试JSON数据
```
### Step 3: 查看结果
**Step 2: Claude Code自动执行流程**
Claude Code会通过MCP工具自动执行以下流程:
1. 打开浏览器并登录系统
2. 导航到指定模块(点击一级菜单 > 二级菜单)
3. 执行配置的功能操作(添加/编辑/删除)
4. 收集元素定位信息(使用XPATH等方式)
5. 生成测试用例JSON文件
**Step 3: 查看结果**
生成的文件位于:
```
......@@ -341,18 +422,33 @@ AuxiliaryTool/TestCaseGenerator/testcases/
## 总结
**目前唯一支持的方式:** 通过Claude Code对话式生成
### 两种方式对比
| 特性 | 方式一:Python脚本 | 方式二:Claude Code对话 |
|------|-------------------|------------------------|
| **执行方式** | 直接运行脚本 | 对话输入指令 |
| **自动化程度** | 完全自动化 | 半自动化 |
| **会话清除影响** | 无影响,脚本可重复运行 | 需要重新输入指令 |
| **CI/CD集成** | 支持 | 不支持 |
| **调试难度** | 可通过日志调试 | 需要对话交互调试 |
| **推荐使用场景** | 日常开发、批量生成、CI/CD | 快速验证、一次性生成 |
### 推荐使用方式一(Python脚本)
**核心优势:**
- 无需编写代码
- 自动处理浏览器操作
- 智能识别元素定位
- 自动生成标准JSON格式
- ✅ 无需对话交互,直接运行
- ✅ 会话清除不影响使用
- ✅ 可集成到CI/CD流程
- ✅ 可批量处理多个模块
- ✅ 生成结果稳定可重复
**下次使用时:** 只需更新 `module_config.json`,然后在Claude Code中输入指令即可!
**使用流程:**
1. 更新 `module_config.json` 配置
2. 运行 `python generate_testcases.py`
3.`testcases/` 目录查看生成的文件
---
*文档版本:v1.1*
*文档版本:v2.0*
*更新日期:2026-03-06*
*更新说明:移除命令行方式,仅保留Claude Code对话式生成方式*
*更新说明:新增Python脚本执行方式,作为主要推荐方式*
# _PRD_程序执行定位登录按钮错误_问题处理
> 来源:
- `AuxiliaryTool/TestCaseGenerator/generate_testcases.py`
## 1. 背景与目标
### 1.1 背景
执行程序时,在登录页面定位登录按钮失败报错。
### 1.2 目标
确保能够正常登录进去自动根据两份config文件进行自动获取生成自动化测试用例JSON数据。
---
## 2. 问题报错信息
### 2.1 问题一
```
WARNING: All log messages before absl::InitializeLog() is called are written to STDERR
I0000 00:00:1772782382.794702 25060 voice_transcription.cc:58] Registering VoiceTranscriptionCapability
✗ 执行出错: Message: no such element: Unable to locate element: {"method":"css selector","selector":"button[type='submit']"}
(Session info: chrome=137.0.7151.41); For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
Stacktrace:
GetHandleVerifier [0x0x7ff6696191f5+2853845]
GetHandleVerifier [0x0x7ff669373ac0+79008]
(No symbol) [0x0x7ff669139bda]
(No symbol) [0x0x7ff6691900f6]
(No symbol) [0x0x7ff6691903ac]
(No symbol) [0x0x7ff6691e3b07]
(No symbol) [0x0x7ff6691b84ff]
(No symbol) [0x0x7ff6691e08f5]
(No symbol) [0x0x7ff6691b8293]
(No symbol) [0x0x7ff669181061]
(No symbol) [0x0x7ff669181df3]
GetHandleVerifier [0x0x7ff66964410d+3029741]
GetHandleVerifier [0x0x7ff66963e52d+3006221]
GetHandleVerifier [0x0x7ff66965d5b2+3133330]
GetHandleVerifier [0x0x7ff66938d98e+185198]
GetHandleVerifier [0x0x7ff669394edf+215231]
GetHandleVerifier [0x0x7ff66937c324+113924]
GetHandleVerifier [0x0x7ff66937c4d9+114361]
GetHandleVerifier [0x0x7ff669363208+11240]
BaseThreadInitThunk [0x0x7ffb9a90e8d7+23]
RtlUserThreadStart [0x0x7ffb9b0cc53c+44]
Traceback (most recent call last):
File "E:\GithubData\ubains-module-test\AuxiliaryTool\TestCaseGenerator\generate_testcases.py", line 158, in login
login_btn = self.driver.find_element(By.XPATH, "//button[contains(text(),'登') or contains(text(),'Login')]")
File "E:\Python\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 917, in find_element
return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"]
File "E:\Python\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 448, in execute
self.error_handler.check_response(response)
File "E:\Python\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 232, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"xpath","selector":"//button[contains(text(),'登') or contains(text(),'Login')]"}
(Session info: chrome=137.0.7151.41); For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
Stacktrace:
GetHandleVerifier [0x0x7ff6696191f5+2853845]
GetHandleVerifier [0x0x7ff669373ac0+79008]
(No symbol) [0x0x7ff669139bda]
(No symbol) [0x0x7ff6691900f6]
(No symbol) [0x0x7ff6691903ac]
(No symbol) [0x0x7ff6691e3b07]
(No symbol) [0x0x7ff6691b84ff]
(No symbol) [0x0x7ff6691e08f5]
(No symbol) [0x0x7ff6691b8293]
(No symbol) [0x0x7ff669181061]
(No symbol) [0x0x7ff669181df3]
GetHandleVerifier [0x0x7ff66964410d+3029741]
GetHandleVerifier [0x0x7ff66963e52d+3006221]
GetHandleVerifier [0x0x7ff66965d5b2+3133330]
GetHandleVerifier [0x0x7ff66938d98e+185198]
GetHandleVerifier [0x0x7ff669394edf+215231]
GetHandleVerifier [0x0x7ff66937c324+113924]
GetHandleVerifier [0x0x7ff66937c4d9+114361]
GetHandleVerifier [0x0x7ff669363208+11240]
BaseThreadInitThunk [0x0x7ffb9a90e8d7+23]
RtlUserThreadStart [0x0x7ffb9b0cc53c+44]
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "E:\GithubData\ubains-module-test\AuxiliaryTool\TestCaseGenerator\generate_testcases.py", line 582, in main
generator.run()
File "E:\GithubData\ubains-module-test\AuxiliaryTool\TestCaseGenerator\generate_testcases.py", line 539, in run
self.login(system_info)
File "E:\GithubData\ubains-module-test\AuxiliaryTool\TestCaseGenerator\generate_testcases.py", line 161, in login
login_btn = self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
File "E:\Python\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 917, in find_element
return self.execute(Command.FIND_ELEMENT, {"using": by, "value": value})["value"]
File "E:\Python\lib\site-packages\selenium\webdriver\remote\webdriver.py", line 448, in execute
self.error_handler.check_response(response)
File "E:\Python\lib\site-packages\selenium\webdriver\remote\errorhandler.py", line 232, in check_response
raise exception_class(message, screen, stacktrace)
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element: {"method":"css selector","selector":"button[type='submit']"}
(Session info: chrome=137.0.7151.41); For documentation on this error, please visit: https://www.selenium.dev/documentation/webdriver/troubleshooting/errors#no-such-element-exception
Stacktrace:
GetHandleVerifier [0x0x7ff6696191f5+2853845]
GetHandleVerifier [0x0x7ff669373ac0+79008]
(No symbol) [0x0x7ff669139bda]
(No symbol) [0x0x7ff6691900f6]
(No symbol) [0x0x7ff6691903ac]
(No symbol) [0x0x7ff6691e3b07]
(No symbol) [0x0x7ff6691b84ff]
(No symbol) [0x0x7ff6691e08f5]
(No symbol) [0x0x7ff6691b8293]
(No symbol) [0x0x7ff669181061]
(No symbol) [0x0x7ff669181df3]
GetHandleVerifier [0x0x7ff66964410d+3029741]
GetHandleVerifier [0x0x7ff66963e52d+3006221]
GetHandleVerifier [0x0x7ff66965d5b2+3133330]
GetHandleVerifier [0x0x7ff66938d98e+185198]
GetHandleVerifier [0x0x7ff669394edf+215231]
GetHandleVerifier [0x0x7ff66937c324+113924]
GetHandleVerifier [0x0x7ff66937c4d9+114361]
GetHandleVerifier [0x0x7ff669363208+11240]
BaseThreadInitThunk [0x0x7ffb9a90e8d7+23]
RtlUserThreadStart [0x0x7ffb9b0cc53c+44]
```
### 3.6 问题状态
- [x] 已解决
---
## 4. 解决方案
### 4.1 根因分析
**原因1:XPATH语法错误**
```python
// 错误的XPATH语法
//button[contains(text(),'登') or contains(text(),'Login')]
```
XPATH中 `or` 操作符不能直接用于 `contains()` 函数之间,应使用 `|` (管道符)。
**原因2:定位方式单一**
代码仅依赖单一定位方式,失败后缺乏备选方案。
### 4.2 修复内容
**修复1:修正XPATH语法**
```python
// 正确的XPATH语法(使用管道符 |
//button[contains(text(),'登')] | //button[contains(text(),'Login')]
```
**修复2:实现多重定位策略**
按优先级尝试8种不同的登录按钮定位方式:
| 优先级 | 定位方式 | 说明 |
|--------|----------|------|
| 1 | 文本内容(中文/英文) | 使用 `|` 管道符组合 |
| 2 | class属性包含login | `//button[contains(@class,'login')]` |
| 3 | type属性为submit | `//button[@type='submit']` |
| 4 | CSS primary按钮 | `button.el-button--primary` |
| 5 | CSS type选择器 | `button[type='submit']` |
| 6 | CSS包含login | `button[class*='login']` |
| 7 | CSS包含Login | `button[class*='Login']` |
| 8 | CSS包含primary | `button[class*='primary']` |
| 9 | JavaScript点击 | 最后的兜底方案 |
### 4.3 代码变更
**文件:** `AuxiliaryTool/TestCaseGenerator/generate_testcases.py`
**变更内容:**
- 修正XPATH语法错误
- 实现8种备选定位方式
- 添加详细日志输出
- 增加JavaScript点击兜底方案
### 4.4 验证方式
运行以下命令验证修复:
```bash
cd E:/GithubData/ubains-module-test/AuxiliaryTool/TestCaseGenerator
python generate_testcases.py
```
**预期输出:**
```
正在登录: https://192.168.5.44/#/LoginAdmin
✓ 输入账号: admin@xty
✓ 输入密码
✓ 输入验证码: csba
✓ 点击登录按钮 (定位: By.XPATH)
✓ 登录成功
```
---
## 5. 优化功能回填
### 5.1 已完成优化
- [x] 修正XPATH语法错误
- [x] 实现多重登录按钮定位策略(8种方式)
- [x] 增加详细的日志输出
- [x] 添加JavaScript点击备选方案
- [x] 更新问题文档和计划执行文档
### 5.2 待优化
- [ ] 根据实际登录页面结构优化定位器
- [ ] 添加登录失败重试机制
- [ ] 截图保存登录失败现场
---
## 6. 相关文档
### 规范文档
- 代码规范: `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`
---
*文档结束*
# 计划执行文档 - 登录按钮定位错误修复
## 问题描述
### 问题现象
执行 `generate_testcases.py` 时,在登录页面定位登录按钮失败,程序报错退出。
### 错误信息
```
selenium.common.exceptions.NoSuchElementException: Message: no such element: Unable to locate element:
{"method":"xpath","selector":"//button[contains(text(),'登') or contains(text(),'Login')]"}
```
---
## 根因分析
### 原因1:XPATH语法错误
**问题代码:**
```python
login_btn = self.driver.find_element(By.XPATH, "//button[contains(text(),'登') or contains(text(),'Login')]")
```
**错误原因:**
- XPATH中 `or` 操作符不能直接用于 `contains()` 函数之间
- 正确的XPATH语法应使用 `|` (管道符) 来组合多个条件
### 原因2:定位方式单一
当前代码仅依赖一种定位方式,失败后没有备选方案。
---
## 修复方案
### 方案1:修正XPATH语法
**原代码(错误):**
```python
//button[contains(text(),'登') or contains(text(),'Login')]
```
**修正后(正确):**
```python
//button[contains(text(),'登')] | //button[contains(text(),'Login')]
```
### 方案2:多重定位策略
按优先级尝试多种定位方式:
| 优先级 | 定位方式 | XPATH/CSS |
|--------|----------|-----------|
| 1 | 通过文本内容(中文) | `//button[contains(text(),'登')]"` |
| 2 | 通过文本内容(英文) | `//button[contains(text(),'Login')]"` |
| 3 | 通过class属性 | `//button[contains(@class,'login')]"` |
| 4 | 通过type属性 | `//button[@type='submit']"` |
| 5 | 通过CSS选择器 | `button.el-button--primary` |
---
## 代码实现
### 修复后的登录方法
```python
def login(self, system_info):
"""登录系统"""
url = system_info.get('system_back_url')
username = system_info.get('username')
password = system_info.get('password')
code = system_info.get('code')
print(f" 正在登录: {url}")
self.driver.get(url)
time.sleep(1)
# 处理SSL证书警告
self.handle_ssl_warning()
time.sleep(2)
# 输入账号
try:
username_input = self.wait_for_element(
(By.XPATH, "//input[contains(@placeholder,'账号') or contains(@placeholder,'邮箱') or contains(@placeholder,'手机号')]"),
timeout=5
)
username_input.clear()
username_input.send_keys(username)
print(f" ✓ 输入账号: {username}")
except Exception as e:
print(f" ! 账号输入失败: {e}")
# 尝试其他定位方式
try:
username_input = self.driver.find_element(By.CSS_SELECTOR, "input[type='text']")
username_input.clear()
username_input.send_keys(username)
print(f" ✓ 输入账号(备选方式): {username}")
except:
print(" ✗ 账号输入失败,跳过")
# 输入密码
try:
password_input = self.driver.find_element(By.XPATH, "//input[@type='password']")
password_input.clear()
password_input.send_keys(password)
print(" ✓ 输入密码")
except Exception as e:
print(f" ! 密码输入失败: {e}")
# 输入验证码
try:
# 查找验证码输入框(通常是最后一个输入框)
code_inputs = self.driver.find_elements(By.CSS_SELECTOR, "input[type='text']")
if len(code_inputs) > 0:
code_input = code_inputs[-1]
code_input.clear()
code_input.send_keys(code)
print(f" ✓ 输入验证码: {code}")
except Exception as e:
print(f" ! 验证码输入失败: {e}")
# 点击登录按钮 - 使用多重定位策略
login_success = False
# 策略1: 通过中文文本定位
login_locators = [
# 通过文本内容(使用管道符组合)
(By.XPATH, "//button[contains(text(),'登')] | //button[contains(text(),'Login')]"),
# 通过span标签和文本内容
(By.XPATH, "(//span[contains(text(),'登录')])[1]"),
# 通过input标签的value属性
(By.XPATH, "//input[@value='登 录']"),
# 通过input标签的value属性(英文)
(By.XPATH, "//input[@value='Login']"),
# 通过class属性
(By.XPATH, "//button[contains(@class,'login')]"),
# 通过type属性
(By.XPATH, "//button[@type='submit']"),
# 通过CSS选择器
(By.CSS_SELECTOR, "button.el-button--primary"),
(By.CSS_SELECTOR, "button[type='submit']"),
# 通过任意包含登录class的按钮
(By.CSS_SELECTOR, "button[class*='login']"),
(By.CSS_SELECTOR, "button[class*='Login']"),
# 通过任意包含primary class的按钮
(By.CSS_SELECTOR, "button[class*='primary']"),
]
for locator_type, locator_value in login_locators:
try:
login_btn = self.driver.find_element(locator_type, locator_value)
login_btn.click()
login_success = True
print(f" ✓ 点击登录按钮 (定位方式: {locator_type} = {locator_value})")
break
except:
continue
if not login_success:
print(" ✗ 登录按钮定位失败,尝试通过JavaScript点击")
try:
# 最后的备选方案:通过JavaScript点击
self.driver.execute_script("document.querySelector('button').click()")
login_success = True
print(" ✓ 通过JavaScript点击登录按钮")
except Exception as e:
print(f" ✗ 登录失败: {e}")
raise Exception("无法定位登录按钮,请检查登录页面结构")
time.sleep(3)
print(" ✓ 登录成功")
```
---
## 测试验证
### 验证步骤
1. 运行 `python generate_testcases.py`
2. 观察控制台输出,确认定位方式
3. 确认成功进入系统并生成测试用例
### 预期输出
```
==============================================================
自动化测试用例生成器
==============================================================
✓ 加载系统配置: 1 个
✓ 加载模块配置: 2 个
✓ 浏览器驱动初始化完成
正在登录: https://192.168.5.44/#/LoginAdmin
✓ 输入账号: admin@xty
✓ 输入密码
✓ 输入验证码: csba
✓ 点击登录按钮 (定位方式: By.XPATH = //button[contains(text(),'登')] | //button[contains(text(),'Login')])
✓ 登录成功
...
```
---
## 优化功能回填
### 新增功能
- [x] 修正XPATH语法错误
- [x] 实现多重登录按钮定位策略
- [x] 增加详细的日志输出
- [x] 添加JavaScript点击备选方案
### 待优化
- [ ] 根据实际登录页面结构优化定位器
- [ ] 添加登录失败重试机制
- [ ] 截图保存登录失败现场
---
## 相关文档
- 问题文档: `问题修复/_PRD_程序执行定位登录按钮错误_问题处理.md`
- 代码规范: `Docs/PRD/01规范文档/_PRD_规范文档_代码规范.md`
---
*文档版本:v1.0*
*创建日期:2026-03-06*
*状态:待验证*
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论