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}" )