import os import allure from playwright.sync_api import Page, Locator, expect class BasePage: """公共页面基类:封装稳定等待、截图、候选定位器选择等通用能力。""" def __init__(self, page: Page, screenshot_dir: str): self.page = page self.screenshot_dir = screenshot_dir os.makedirs(self.screenshot_dir, exist_ok=True) def goto(self, url: str): self.page.goto(url, wait_until="domcontentloaded") try: self.page.wait_for_load_state("networkidle", timeout=5000) except Exception: pass def screenshot(self, name: str) -> str: path = os.path.join(self.screenshot_dir, name) self.page.screenshot(path=path, full_page=True) allure.attach.file(path, name=name, attachment_type=allure.attachment_type.PNG) return path def first_visible_locator( self, selectors: list[str], timeout: int = 5000, description: str = "元素", ) -> Locator: """ 从候选 CSS/XPath/Text selector 中返回第一个可见元素。 注意:页面侦察结果为空时,候选 selector 需在项目落地时根据真实 DOM 调整。 """ last_error = None for selector in selectors: try: locator = self.page.locator(selector).first locator.wait_for(state="visible", timeout=timeout) return locator except Exception as exc: last_error = exc self.screenshot(f"not_found_{description}.png") raise AssertionError(f"未找到可见{description},请根据真实页面 DOM 更新 selector。候选:{selectors}") from last_error def click_first_visible( self, selectors: list[str], timeout: int = 5000, description: str = "按钮", ): locator = self.first_visible_locator(selectors, timeout=timeout, description=description) locator.click() def fill_first_visible( self, selectors: list[str], value: str, timeout: int = 5000, description: str = "输入框", ): locator = self.first_visible_locator(selectors, timeout=timeout, description=description) locator.fill(value) def wait_network_idle(self): try: self.page.wait_for_load_state("networkidle", timeout=5000) except Exception: pass def assert_url_contains(self, expected_part: str): expect(self.page).to_have_url(lambda url: expected_part in url) def wait_for_timeout(self, ms: int): self.page.wait_for_timeout(ms)