""" 页面对象基类 提供页面对象模式的基础框架 """ from typing import Any, Dict, List, Optional, Tuple, Union from urllib.parse import urljoin, urlparse from playwright.sync_api import Page, Locator, FrameLocator from playwright.sync_api import Error as PlaywrightError, TimeoutError as PlaywrightTimeoutError from config.settings import get_settings from utils.logger import get_logger from utils.helpers import ElementHelper, PageHelper, AssertionHelper, UrlHelper class BasePage: """页面对象基类""" def __init__(self, page: Page, base_url: Optional[str] = None): """ 初始化页面对象 Args: page: Playwright Page实例 base_url: 基础URL """ self.page = page self.base_url = base_url or get_settings().get_base_url() self.logger = get_logger() # 初始化辅助类 self.element = ElementHelper(page) self.page_helper = PageHelper(page) self.assertion = AssertionHelper(page) self.url_helper = UrlHelper() # 页面URL路径(子类覆盖) self.path: Optional[str] = None # 页面标题(子类覆盖) self.title: Optional[str] = None # 页面元素选择器(子类覆盖) self.selectors: Dict[str, str] = {} def _resolve_selector(self, selector: str) -> str: """解析选择器名称为实际选择器字符串""" if selector in self.selectors: return self.selectors[selector] return selector def _get_full_url(self, path: str) -> str: """获取完整URL""" if self.url_helper.is_absolute_url(path): return path return urljoin(self.base_url, path) def navigate(self, path: Optional[str] = None, **kwargs) -> 'BasePage': """ 导航到页面 Args: path: 页面路径,如果为None则使用self.path **kwargs: 传递给page.goto的参数 Returns: self """ path = path or self.path if not path: raise ValueError("页面路径未指定") url = self._get_full_url(path) self.logger.log_action(f"导航到页面: {url}") self.page_helper.navigate(url, **kwargs) return self def reload(self) -> 'BasePage': """刷新页面""" self.page_helper.reload_page() return self def go_back(self) -> 'BasePage': """返回上一页""" self.page_helper.go_back() return self def go_forward(self) -> 'BasePage': """前进到下一页""" self.page_helper.go_forward() return self def get_url(self) -> str: """获取当前URL""" return self.page_helper.get_current_url() def get_title(self) -> str: """获取页面标题""" return self.page_helper.get_page_title() def wait_for_load(self, state: str = "networkidle") -> 'BasePage': """等待页面加载完成""" self.page_helper.wait_for_load_state(state) return self def wait_for_selector( self, selector: str, timeout: Optional[int] = None, state: str = "visible" ) -> Locator: """等待选择器""" return self.element.wait_for_selector(selector, timeout, state) def scroll_to_top(self) -> 'BasePage': """滚动到页面顶部""" self.page_helper.scroll_to_top() return self def scroll_to_bottom(self) -> 'BasePage': """滚动到页面底部""" self.page_helper.scroll_to_bottom() return self def scroll_to_element(self, selector: str) -> 'BasePage': """滚动到指定元素""" self.page_helper.scroll_to_element(selector) return self def take_screenshot( self, name: str, full_page: bool = False ) -> str: """截取截图""" return self.page_helper.take_screenshot( f"{name}_{self._get_timestamp()}.png", full_page=full_page ) def execute_js(self, script: str, *args) -> Any: """执行JavaScript""" return self.page_helper.execute_javascript(script, *args) def _get_timestamp(self) -> str: """获取时间戳""" from datetime import datetime return datetime.now().strftime("%Y%m%d_%H%M%S") def _find(self, selector: str, timeout: Optional[int] = None) -> Locator: """查找元素""" resolved_selector = self._resolve_selector(selector) return self.element.find_element(resolved_selector, timeout) def _find_all(self, selector: str) -> List[Locator]: """查找所有匹配的元素""" resolved_selector = self._resolve_selector(selector) return self.element.find_elements(resolved_selector) def _click(self, selector: str, **kwargs) -> 'BasePage': """点击元素""" resolved_selector = self._resolve_selector(selector) self.element.click_element(resolved_selector, **kwargs) return self def _fill(self, selector: str, value: str, **kwargs) -> 'BasePage': """填充输入框""" resolved_selector = self._resolve_selector(selector) self.element.fill_input(resolved_selector, value, **kwargs) return self def _type(self, selector: str, text: str, **kwargs) -> 'BasePage': """输入文本""" resolved_selector = self._resolve_selector(selector) self.element.type_text(resolved_selector, text, **kwargs) return self def _get_text(self, selector: str, **kwargs) -> str: """获取元素文本""" resolved_selector = self._resolve_selector(selector) return self.element.get_element_text(resolved_selector, **kwargs) def _get_attr(self, selector: str, attribute: str, **kwargs) -> Optional[str]: """获取元素属性""" resolved_selector = self._resolve_selector(selector) return self.element.get_element_attribute(resolved_selector, attribute, **kwargs) def _is_visible(self, selector: str, **kwargs) -> bool: """检查元素是否可见""" resolved_selector = self._resolve_selector(selector) return self.element.is_element_visible(resolved_selector, **kwargs) def _is_enabled(self, selector: str, **kwargs) -> bool: """检查元素是否可用""" resolved_selector = self._resolve_selector(selector) return self.element.is_element_enabled(resolved_selector, **kwargs) # 断言方法 def assert_title_contains(self, expected: str, message: Optional[str] = None) -> 'BasePage': """断言标题包含预期文本""" self.assertion.assert_page_title_contains(expected, message) return self def assert_url_contains(self, expected: str, message: Optional[str] = None) -> 'BasePage': """断言URL包含预期文本""" self.assertion.assert_url_contains(expected, message) return self def assert_url_equals(self, expected: str, message: Optional[str] = None) -> 'BasePage': """断言URL等于预期URL""" self.assertion.assert_url_equals(expected, message) return self def assert_element_visible(self, selector: str, **kwargs) -> 'BasePage': """断言元素可见""" resolved_selector = self._resolve_selector(selector) self.assertion.assert_element_visible(resolved_selector, **kwargs) return self def assert_element_hidden(self, selector: str, **kwargs) -> 'BasePage': """断言元素隐藏""" self.assertion.assert_element_hidden(selector, **kwargs) return self def assert_element_text_contains( self, selector: str, expected: str, **kwargs ) -> 'BasePage': """断言元素文本包含预期文本""" self.assertion.assert_element_text_contains(selector, expected, **kwargs) return self def assert_element_text_equals( self, selector: str, expected: str, **kwargs ) -> 'BasePage': """断言元素文本等于预期文本""" self.assertion.assert_element_text_equals(selector, expected, **kwargs) return self def assert_element_count( self, selector: str, expected: int, message: Optional[str] = None ) -> 'BasePage': """断言元素数量""" self.assertion.assert_element_count(selector, expected, message) return self def assert_element_attribute_equals( self, selector: str, attribute: str, expected: str, **kwargs ) -> 'BasePage': """断言元素属性等于预期值""" self.assertion.assert_element_attribute_equals( selector, attribute, expected, **kwargs ) return self def should_have_url(self, url: str, **kwargs) -> 'BasePage': """检查URL""" self.assert_url_equals(url, **kwargs) return self def should_have_title(self, title: str, **kwargs) -> 'BasePage': """检查标题""" self.assert_title_contains(title, **kwargs) return self def should_contain_text(self, text: str, **kwargs) -> 'BasePage': """检查页面包含文本""" self.page.wait_for_load_state("domcontentloaded") content = self.page_helper.get_page_source() assert text in content, f"页面不包含文本: {text}" return self def __repr__(self) -> str: return f"<{self.__class__.__name__}: {self.path or 'unknown'}>" class PageRegistry: """页面注册表,用于管理页面对象""" _instance: Optional['PageRegistry'] = None _pages: Dict[str, BasePage] = {} def __new__(cls) -> 'PageRegistry': if cls._instance is None: cls._instance = super().__new__(cls) return cls._instance def register(self, name: str, page: BasePage) -> None: """注册页面""" self._pages[name] = page def get(self, name: str) -> Optional[BasePage]: """获取页面""" return self._pages.get(name) def clear(self) -> None: """清空注册表""" self._pages.clear() def get_page_registry() -> PageRegistry: """获取页面注册表""" return PageRegistry()