Files
smart-management-auto-test/zhyy/test_case/Resource/UI/contract_management_page.py

209 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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}"
)