from typing import List, Optional import allure from playwright.sync_api import Page, expect from zhyy.test_case.Resource.UI.base_page import BasePage class SmartManagementLoginPage(BasePage): """智慧运营登录页。""" # TODO:如项目已有页面侦察结果,请替换为真实 selector USERNAME_INPUT_SELECTORS = [ 'input[name="username"]', 'input[id="username"]', 'input[placeholder*="用户名"]', 'input[placeholder*="账号"]', 'input[type="text"]', ] PASSWORD_INPUT_SELECTORS = [ 'input[name="password"]', 'input[id="password"]', 'input[placeholder*="密码"]', 'input[type="password"]', ] LOGIN_BUTTON_SELECTORS = [ 'button:has-text("登录")', 'button:has-text("登 录")', 'button[type="submit"]', ] def is_login_page(self) -> bool: url = self.page.url.lower() if "login" in url: return True try: self.first_visible_locator(self.PASSWORD_INPUT_SELECTORS, timeout=2000, description="密码输入框") return True except Exception: return False @allure.step("登录智慧运营系统") def login_if_required(self, username: str, password: str): if not self.is_login_page(): return self.fill_first_visible(self.USERNAME_INPUT_SELECTORS, username, description="用户名输入框") self.fill_first_visible(self.PASSWORD_INPUT_SELECTORS, password, description="密码输入框") self.click_first_visible(self.LOGIN_BUTTON_SELECTORS, description="登录按钮") self.wait_for_page_ready() class ContractManagementPage(BasePage): """合同管理页面:封装合同编号模糊查询能力。""" PAGE_URL = "https://smart-management-web-st.best-envision.com/SZPurchase/PurchaseManage/PurchaseOrderManage" # TODO:页面侦察结果为空,以下为基于业务语义的候选定位;落地时建议替换为真实稳定 selector/data-testid CONTRACT_MENU_SELECTORS = [ 'a:has-text("合同管理")', 'li:has-text("合同管理")', 'span:has-text("合同管理")', 'div:has-text("合同管理")', ] CONTRACT_NO_INPUT_SELECTORS = [ # Ant Design 常见结构:label 为“合同编号”后紧邻输入框 'xpath=//*[normalize-space()="合同编号"]/following::input[1]', 'input[placeholder*="合同编号"]', 'input[name*="contract"]', 'input[id*="contract"]', # TODO:补充页面侦察得到的合同编号输入框 selector ] QUERY_BUTTON_SELECTORS = [ 'button:has-text("查询")', 'button:has-text("搜索")', # TODO:补充页面侦察得到的查询按钮 selector ] RESET_BUTTON_SELECTORS = [ 'button:has-text("重置")', 'button:has-text("清空")', ] TABLE_ROW_SELECTORS = [ # Ant Design 表格常见结构 '.ant-table-tbody > tr:not(.ant-table-placeholder)', 'table tbody tr', # TODO:补充页面侦察得到的列表行 selector ] TABLE_HEADER_CELL_SELECTORS = [ '.ant-table-thead th', 'table thead th', # TODO:补充页面侦察得到的表头 selector ] EMPTY_SELECTORS = [ '.ant-empty', 'text=暂无数据', 'text=无数据', ] def __init__(self, page: Page): super().__init__(page) self.login_page = SmartManagementLoginPage(page) @allure.step("打开合同管理页面") def open(self, username: str, password: str): self.goto(self.PAGE_URL) self.login_page.login_if_required(username, password) # 登录后如回到首页,则再次进入目标页面 if "PurchaseOrderManage" not in self.page.url: self.goto(self.PAGE_URL) self.wait_for_page_ready() self.ensure_contract_management_page_loaded() @allure.step("确认合同管理页面加载完成") def ensure_contract_management_page_loaded(self): """ 优先确认页面 URL,其次确认页面上存在合同相关筛选项。 """ if "PurchaseOrderManage" in self.page.url: return try: self.first_visible_locator(self.CONTRACT_MENU_SELECTORS, timeout=5000, description="合同管理菜单") return except Exception as exc: raise AssertionError(f"合同管理页面未成功加载,当前 URL={self.page.url}") from exc @allure.step("点击合同管理菜单") def click_contract_menu_if_visible(self): """ 用户步骤要求“找到合同菜单”;若当前已在目标页面,则不强制点击。 """ if "PurchaseOrderManage" in self.page.url: return self.click_first_visible(self.CONTRACT_MENU_SELECTORS, timeout=5000, description="合同管理菜单") self.wait_for_page_ready() @allure.step("输入合同编号查询条件:{contract_no_keyword}") def input_contract_no_keyword(self, contract_no_keyword: str): try: self.page.get_by_label("合同编号", exact=False).fill(contract_no_keyword) except Exception: self.fill_first_visible( self.CONTRACT_NO_INPUT_SELECTORS, contract_no_keyword, description="合同编号搜索框", ) @allure.step("点击查询按钮") def click_query(self): try: self.page.get_by_role("button", name="查询").click() self.wait_for_page_ready() except Exception: self.click_first_visible(self.QUERY_BUTTON_SELECTORS, description="查询按钮") # 等待列表刷新完成 self.page.wait_for_timeout(1000) @allure.step("按合同编号模糊查询:{contract_no_keyword}") def search_by_contract_no(self, contract_no_keyword: str): self.input_contract_no_keyword(contract_no_keyword) self.click_query() def _get_contract_no_column_index(self) -> Optional[int]: """ 根据表头文本动态识别“合同编号”列索引。 识别失败时返回 None,断言时退化为行文本包含校验。 """ for selector in self.TABLE_HEADER_CELL_SELECTORS: headers = self.page.locator(selector) try: count = headers.count() except Exception: continue for index in range(count): text = headers.nth(index).inner_text(timeout=2000).strip() if "合同编号" in text: return index return None def get_result_rows_text(self) -> List[str]: for selector in self.TABLE_ROW_SELECTORS: rows = self.page.locator(selector) try: count = rows.count() except Exception: continue row_texts = [] for index in range(count): row = rows.nth(index) text = row.inner_text(timeout=3000).strip() if text: row_texts.append(text) if row_texts: return row_texts return [] def get_result_contract_numbers(self) -> List[str]: """ 返回结果列表中的合同编号列文本。 若无法识别合同编号列,则返回空列表,由上层断言决定是否退化为行文本校验。 """ column_index = self._get_contract_no_column_index() if column_index is None: return [] for selector in self.TABLE_ROW_SELECTORS: rows = self.page.locator(selector) try: row_count = rows.count() except Exception: continue contract_numbers = [] for row_index in range(row_count): row = rows.nth(row_index) cells = row.locator("td") if cells.count() > column_index: contract_no = cells.nth(column_index).inner_text(timeout=3000).strip() if contract_no: contract_numbers.append(contract_no) if contract_numbers: return contract_numbers return [] def is_empty_result(self) -> bool: for selector in self.EMPTY_SELECTORS: try: if self.page.locator(selector).first.is_visible(timeout=2000): return True except Exception: continue return False @allure.step("断言列表仅展示合同编号模糊匹配:{contract_no_keyword}") def assert_contract_no_fuzzy_match(self, contract_no_keyword: str): """ 优先按“合同编号”列断言; 如因页面侦察缺失无法识别列,则退化为断言每行文本均包含查询关键字。 """ expect(self.page.locator("body")).to_be_visible() contract_numbers = self.get_result_contract_numbers() if contract_numbers: assert all(contract_no_keyword in item for item in contract_numbers), ( f"存在合同编号不匹配查询条件:keyword={contract_no_keyword}, " f"contract_numbers={contract_numbers}" ) return row_texts = self.get_result_rows_text() if row_texts: assert all(contract_no_keyword in row_text for row_text in row_texts), ( f"无法识别合同编号列,已退化为行文本校验;存在行不包含查询条件:" f"keyword={contract_no_keyword}, rows={row_texts}" ) return assert self.is_empty_result(), "查询后列表无数据且未识别到空数据提示,请检查表格 selector 或查询条件"