Files

275 lines
9.7 KiB
Python
Raw Permalink 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.
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 或查询条件"