08ea5fbe98
添加用户管理视图、API和状态管理文件
215 lines
7.5 KiB
Python
215 lines
7.5 KiB
Python
"""
|
|
基础页面类
|
|
|
|
所有页面对象的基类,提供通用方法。
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Optional, List
|
|
from playwright.sync_api import Page, Locator, expect
|
|
|
|
|
|
class BasePage(ABC):
|
|
"""基础页面类"""
|
|
|
|
def __init__(self, page: Page, base_url: str = ""):
|
|
self.page = page
|
|
self.base_url = base_url
|
|
|
|
@abstractmethod
|
|
def navigate(self, path: str = "") -> None:
|
|
"""导航到指定路径"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def is_loaded(self) -> bool:
|
|
"""检查页面是否加载完成"""
|
|
pass
|
|
|
|
def wait_for_load(self, timeout: int = 30000) -> None:
|
|
"""等待页面加载完成"""
|
|
self.page.wait_for_load_state("networkidle", timeout=timeout)
|
|
|
|
def wait_for_selector(
|
|
self, selector: str, timeout: int = 10000, state: str = "visible"
|
|
) -> Locator:
|
|
"""等待元素出现"""
|
|
return self.page.wait_for_selector(selector, timeout=timeout, state=state)
|
|
|
|
def click_element(self, selector: str, timeout: int = 10000) -> None:
|
|
"""点击元素"""
|
|
self.wait_for_selector(selector, timeout=timeout).click()
|
|
|
|
def fill_input(self, selector: str, value: str, timeout: int = 10000) -> None:
|
|
"""填充输入框"""
|
|
element = self.wait_for_selector(selector, timeout=timeout)
|
|
element.fill(value)
|
|
|
|
def get_text(self, selector: str, timeout: int = 10000) -> str:
|
|
"""获取元素文本"""
|
|
return (
|
|
self.wait_for_selector(selector, timeout=timeout).text_content() or ""
|
|
)
|
|
|
|
def get_input_value(self, selector: str, timeout: int = 10000) -> str:
|
|
"""获取输入框值"""
|
|
return (
|
|
self.wait_for_selector(selector, timeout=timeout).input_value() or ""
|
|
)
|
|
|
|
def is_element_visible(self, selector: str, timeout: int = 5000) -> bool:
|
|
"""检查元素是否可见"""
|
|
try:
|
|
self.page.wait_for_selector(
|
|
selector, timeout=timeout, state="visible"
|
|
)
|
|
return True
|
|
except:
|
|
return False
|
|
|
|
def is_element_enabled(self, selector: str, timeout: int = 5000) -> bool:
|
|
"""检查元素是否可用"""
|
|
try:
|
|
element = self.page.wait_for_selector(
|
|
selector, timeout=timeout, state="visible"
|
|
)
|
|
return element.is_enabled()
|
|
except:
|
|
return False
|
|
|
|
def get_elements_count(self, selector: str) -> int:
|
|
"""获取元素数量"""
|
|
return len(self.page.locator(selector).all())
|
|
|
|
def select_option(self, selector: str, value: str, timeout: int = 10000) -> None:
|
|
"""选择下拉选项"""
|
|
self.wait_for_selector(selector, timeout=timeout).select_option(value)
|
|
|
|
def check_checkbox(self, selector: str, timeout: int = 10000) -> None:
|
|
"""勾选复选框"""
|
|
element = self.wait_for_selector(selector, timeout=timeout)
|
|
if not element.is_checked():
|
|
element.check()
|
|
|
|
def uncheck_checkbox(self, selector: str, timeout: int = 10000) -> None:
|
|
"""取消勾选复选框"""
|
|
element = self.wait_for_selector(selector, timeout=timeout)
|
|
if element.is_checked():
|
|
element.uncheck()
|
|
|
|
def scroll_to_element(self, selector: str, timeout: int = 10000) -> None:
|
|
"""滚动到元素"""
|
|
element = self.wait_for_selector(selector, timeout=timeout)
|
|
element.scroll_into_view_if_needed()
|
|
|
|
def take_screenshot(self, name: str, full_page: bool = False) -> str:
|
|
"""截图"""
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
screenshot_dir = Path("reports/screenshots")
|
|
screenshot_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
filename = f"{name}_{timestamp}.png"
|
|
filepath = screenshot_dir / filename
|
|
|
|
self.page.screenshot(path=str(filepath), full_page=full_page)
|
|
return str(filepath)
|
|
|
|
def get_current_url(self) -> str:
|
|
"""获取当前URL"""
|
|
return self.page.url
|
|
|
|
def go_back(self) -> None:
|
|
"""返回上一页"""
|
|
self.page.go_back()
|
|
|
|
def reload(self) -> None:
|
|
"""刷新页面"""
|
|
self.page.reload()
|
|
|
|
def wait_for_timeout(self, timeout: int) -> None:
|
|
"""等待指定时间(毫秒)"""
|
|
self.page.wait_for_timeout(timeout)
|
|
|
|
def assert_element_text(
|
|
self, selector: str, expected_text: str, timeout: int = 10000
|
|
) -> None:
|
|
"""断言元素文本"""
|
|
element = self.wait_for_selector(selector, timeout=timeout)
|
|
expect(element).to_have_text(expected_text)
|
|
|
|
def assert_element_contains_text(
|
|
self, selector: str, expected_text: str, timeout: int = 10000
|
|
) -> None:
|
|
"""断言元素包含文本"""
|
|
element = self.wait_for_selector(selector, timeout=timeout)
|
|
expect(element).to_contain_text(expected_text)
|
|
|
|
def assert_url_contains(self, text: str) -> None:
|
|
"""断言URL包含文本"""
|
|
expect(self.page).to_have_url(lambda url: text in url)
|
|
|
|
def assert_element_visible(self, selector: str, timeout: int = 10000) -> None:
|
|
"""断言元素可见"""
|
|
element = self.wait_for_selector(selector, timeout=timeout)
|
|
expect(element).to_be_visible()
|
|
|
|
def assert_element_hidden(self, selector: str, timeout: int = 10000) -> None:
|
|
"""断言元素隐藏"""
|
|
element = self.page.locator(selector)
|
|
expect(element).to_be_hidden(timeout=timeout)
|
|
|
|
def hover_element(self, selector: str, timeout: int = 10000) -> None:
|
|
"""悬停在元素上"""
|
|
self.wait_for_selector(selector, timeout=timeout).hover()
|
|
|
|
def drag_and_drop(
|
|
self, source_selector: str, target_selector: str, timeout: int = 10000
|
|
) -> None:
|
|
"""拖拽元素"""
|
|
source = self.wait_for_selector(source_selector, timeout=timeout)
|
|
target = self.wait_for_selector(target_selector, timeout=timeout)
|
|
source.drag_to(target)
|
|
|
|
def upload_file(self, selector: str, file_path: str, timeout: int = 10000) -> None:
|
|
"""上传文件"""
|
|
self.wait_for_selector(selector, timeout=timeout).set_input_files(file_path)
|
|
|
|
def press_key(self, selector: str, key: str, timeout: int = 10000) -> None:
|
|
"""按键"""
|
|
self.wait_for_selector(selector, timeout=timeout).press(key)
|
|
|
|
def get_element_attribute(
|
|
self, selector: str, attribute: str, timeout: int = 10000
|
|
) -> str:
|
|
"""获取元素属性"""
|
|
return (
|
|
self.wait_for_selector(selector, timeout=timeout).get_attribute(attribute)
|
|
or ""
|
|
)
|
|
|
|
def get_element_css_property(
|
|
self, selector: str, property_name: str, timeout: int = 10000
|
|
) -> str:
|
|
"""获取元素CSS属性"""
|
|
element = self.wait_for_selector(selector, timeout=timeout)
|
|
return element.evaluate(f"el => getComputedStyle(el).{property_name}")
|
|
|
|
def wait_for_element_to_disappear(
|
|
self, selector: str, timeout: int = 10000
|
|
) -> None:
|
|
"""等待元素消失"""
|
|
self.page.wait_for_selector(selector, state="detached", timeout=timeout)
|
|
|
|
def wait_for_response(self, url_pattern: str, timeout: int = 30000):
|
|
"""等待网络响应"""
|
|
with self.page.expect_response(url_pattern, timeout=timeout) as response_info:
|
|
pass
|
|
return response_info.value
|
|
|
|
def wait_for_navigation(self, timeout: int = 30000):
|
|
"""等待页面导航"""
|
|
self.page.wait_for_load_state("networkidle", timeout=timeout)
|