Update UI automation test files

This commit is contained in:
2026-05-19 18:04:34 +08:00
parent e0e22b895e
commit 6d7dba25d8
14 changed files with 1076 additions and 52 deletions

View File

@@ -0,0 +1,208 @@
import re
from typing import Optional
import allure
from playwright.sync_api import Page, expect
from zhyy.test_case.Resource.UI.base_page import BasePage
class ContractManagementPage(BasePage):
"""合同管理页面对象。
注意:
- 当前未提供页面侦察结果,以下 selector 采用“候选定位 + TODO”方式。
- 落地执行前,建议通过侦察脚本确认真实 DOM 后替换 TODO selector。
"""
def __init__(self, page: Page, screenshot_dir: str):
super().__init__(page, screenshot_dir)
def open(self, url: str) -> None:
with allure.step("打开合同管理被测页面"):
self.goto(url)
self.attach_screenshot("01_open_contract_management_page.png")
def login_if_needed(self, username: str, password: str) -> None:
"""如页面跳转到登录页,则执行登录。
TODO:
请根据真实登录页 DOM 替换用户名、密码、登录按钮 selector。
当前保留常见中文系统候选定位,避免在无侦察结果时硬编码单一 selector。
"""
with allure.step("如需要则登录系统"):
username_candidates = [
self.page.get_by_placeholder(re.compile("用户名|账号|请输入用户名|请输入账号")),
self.page.locator("input[name='username']"),
self.page.locator("input[type='text']").first,
# TODO: 替换为真实用户名输入框 selector例如self.page.locator("#username")
]
password_candidates = [
self.page.get_by_placeholder(re.compile("密码|请输入密码")),
self.page.locator("input[name='password']"),
self.page.locator("input[type='password']"),
# TODO: 替换为真实密码输入框 selector例如self.page.locator("#password")
]
login_button_candidates = [
self.page.get_by_role("button", name=re.compile("登录|登 录|Login", re.I)),
self.page.locator("button[type='submit']"),
self.page.locator("text=登录"),
# TODO: 替换为真实登录按钮 selector
]
login_form_visible = any(
locator.first.is_visible()
for locator in username_candidates + password_candidates
)
if not login_form_visible:
return
self.fill_first_visible(username_candidates, username, description="用户名输入框")
self.fill_first_visible(password_candidates, password, description="密码输入框")
self.attach_screenshot("02_before_login.png")
self.click_first_visible(login_button_candidates, description="登录按钮")
self.wait_network_idle()
self.attach_screenshot("03_after_login.png")
def ensure_contract_menu_selected(self) -> None:
"""进入/确认合同菜单。
若 URL 已直接进入合同管理页面,本方法不会强制点击菜单。
TODO:
如系统必须通过左侧菜单进入,请用页面真实菜单 selector 替换候选定位。
"""
with allure.step("找到并选择合同菜单"):
menu_candidates = [
self.page.get_by_role("menuitem", name=re.compile("合同管理|合同")),
self.page.get_by_text(re.compile("^合同管理$|^合同$"), exact=False),
self.page.locator("text=合同管理"),
self.page.locator("text=合同"),
# TODO: 替换为真实合同菜单 selector
]
try:
menu = self.first_visible_locator(
menu_candidates,
timeout=3000,
description="合同菜单",
)
menu.click()
self.wait_network_idle()
except AssertionError:
# 直接 URL 进入时可能没有可见菜单或菜单已选中,不阻断用例。
pass
self.attach_screenshot("04_contract_menu_selected.png")
def search_by_contract_no_fuzzy(self, partial_contract_no: str) -> None:
"""按合同编号进行模糊查询。"""
with allure.step(f"输入合同编号部分字符并点击查询:{partial_contract_no}"):
contract_no_input_candidates = [
self.page.get_by_placeholder(re.compile("合同编号|请输入合同编号")),
self.page.locator("input[placeholder*='合同编号']"),
self.page.locator("label:has-text('合同编号')").locator("xpath=following::input[1]"),
self.page.locator("text=合同编号").locator("xpath=following::input[1]"),
# TODO: 替换为真实合同编号搜索框 selector
]
query_button_candidates = [
self.page.get_by_role("button", name=re.compile("^查询$|搜索")),
self.page.locator("button:has-text('查询')"),
self.page.locator("text=查询"),
# TODO: 替换为真实查询按钮 selector
]
self.fill_first_visible(
contract_no_input_candidates,
partial_contract_no,
description="合同编号搜索框",
)
self.attach_screenshot("05_filled_contract_no.png")
self.click_first_visible(
query_button_candidates,
description="查询按钮",
)
self.wait_network_idle()
# 等待常见表格渲染完成
self.wait_for_result_table()
self.attach_screenshot("06_after_contract_no_search.png")
def wait_for_result_table(self) -> None:
"""等待查询结果表格出现。
TODO:
根据真实表格框架替换为唯一稳定 selector。
"""
table_candidates = [
self.page.locator(".el-table__body-wrapper"),
self.page.locator(".el-table__body"),
self.page.locator(".ant-table-tbody"),
self.page.locator("table tbody"),
self.page.locator("[role='table']"),
# TODO: 替换为真实结果表格 selector
]
self.first_visible_locator(table_candidates, timeout=10000, description="合同结果表格")
def result_rows_locator(self):
"""结果表格行候选。
TODO:
建议替换为合同列表真实数据行 selector。
"""
candidates = [
self.page.locator(".el-table__body tbody tr"),
self.page.locator(".ant-table-tbody tr"),
self.page.locator("table tbody tr"),
self.page.locator("[role='row']"),
# TODO: 替换为真实合同列表行 selector
]
for locator in candidates:
try:
if locator.count() > 0:
return locator
except Exception:
continue
raise AssertionError("未找到合同列表数据行,请根据真实页面补充 result_rows_locator selector。")
def get_result_row_texts(self) -> list[str]:
with allure.step("获取合同列表查询结果"):
rows = self.result_rows_locator()
texts = self.text_contents(rows)
# 过滤空行、表头、加载占位行
return [
text
for text in texts
if text and "暂无数据" not in text and "No Data" not in text
]
def assert_contract_no_fuzzy_match(self, partial_contract_no: str) -> None:
"""断言查询结果与合同编号模糊查询条件一致。
当前无页面侦察结果,无法确认“合同编号”列的真实 selector。
因此先断言每条结果行文本中包含输入的合同编号部分字符。
如果后续确认合同编号列 selector请改为仅校验合同编号列文本。
"""
with allure.step("断言列表仅展示合同编号与输入内容模糊匹配的数据"):
row_texts = self.get_result_row_texts()
assert row_texts, "查询结果为空,无法验证合同编号模糊匹配。请确认测试数据或查询条件。"
unmatched_rows = [
row_text
for row_text in row_texts
if partial_contract_no not in row_text
]
assert not unmatched_rows, (
f"存在与合同编号查询条件不匹配的结果。\n"
f"查询条件:{partial_contract_no}\n"
f"不匹配行:{unmatched_rows}"
)