aade9e39d0
- tests/config: 环境配置 (.yaml) + 配置加载器 - tests/utils: 日志工具 + 测试数据生成器 - tests/web: Playwright conftest + Page Object 基类 + 登录页 + 导航栏组件 + 登录用例(6条) - tests/app: Appium conftest + Page Object 基类 + 司机端首页 + 登录用例(3条) - pytest.ini: markers(p0/p1/p2/smoke/web/app) + 运行配置 - 所有用例按最佳实践采用 Page Object 模式 + data-testid 优先定位 + 失败自动截图
98 lines
3.7 KiB
Python
98 lines
3.7 KiB
Python
"""App Page Object 基类 — 封装移动端页面共享的通用操作。
|
|
|
|
每个 App 页面应继承 AppBasePage,在 __init__ 中声明所有定位器。
|
|
|
|
Usage:
|
|
class DriverHomePage(AppBasePage):
|
|
def __init__(self, driver, platform):
|
|
super().__init__(driver, platform)
|
|
self.order_tab = driver.find_element(AppiumBy.ACCESSIBILITY_ID, "tab_orders")
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
from appium.webdriver.common.appiumby import AppiumBy
|
|
from appium.webdriver.webdriver import WebDriver
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
from selenium.webdriver.support import expected_conditions as EC
|
|
|
|
SCREENSHOTS_DIR = Path("output/screenshots")
|
|
|
|
|
|
class AppBasePage:
|
|
"""App Page Object 基类。"""
|
|
|
|
def __init__(self, driver: WebDriver, platform: str, timeout_ms: int = 30_000):
|
|
self.driver = driver
|
|
self.platform = platform # "android" | "ios"
|
|
self.timeout = timeout_ms / 1000 # WebDriverWait 使用秒
|
|
|
|
# ── 通用等待 ──
|
|
|
|
def wait_visible_by_id(self, accessibility_id: str, timeout_s: float | None = None) -> None:
|
|
"""等待 accessibility_id 对应的元素可见。"""
|
|
wait = WebDriverWait(self.driver, timeout=timeout_s or self.timeout)
|
|
wait.until(
|
|
EC.visibility_of_element_located((AppiumBy.ACCESSIBILITY_ID, accessibility_id))
|
|
)
|
|
|
|
def wait_visible_by_text(self, text: str, timeout_s: float | None = None) -> None:
|
|
"""等待页面上出现指定文本。"""
|
|
wait = WebDriverWait(self.driver, timeout=timeout_s or self.timeout)
|
|
|
|
locator = (
|
|
AppiumBy.XPATH, f"//*[@text='{text}']"
|
|
if self.platform == "android"
|
|
else (AppiumBy.XPATH, f"//*[@label='{text}']")
|
|
)
|
|
wait.until(EC.visibility_of_element_located(locator))
|
|
|
|
# ── 通用操作 ──
|
|
|
|
def tap_by_id(self, accessibility_id: str) -> None:
|
|
"""按 accessibility_id 点击元素(推荐优先使用)。"""
|
|
self.wait_visible_by_id(accessibility_id)
|
|
self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, accessibility_id).click()
|
|
|
|
def tap_by_text(self, text: str) -> None:
|
|
"""按文本点击元素。"""
|
|
strategy = AppiumBy.XPATH
|
|
locator = (
|
|
f"//*[@text='{text}']"
|
|
if self.platform == "android"
|
|
else f"//*[@label='{text}']"
|
|
)
|
|
wait = WebDriverWait(self.driver, timeout=self.timeout)
|
|
wait.until(EC.visibility_of_element_located((strategy, locator)))
|
|
self.driver.find_element(strategy, locator).click()
|
|
|
|
def fill_by_id(self, accessibility_id: str, value: str) -> None:
|
|
"""按 accessibility_id 找到输入框并填入内容。"""
|
|
self.wait_visible_by_id(accessibility_id)
|
|
element = self.driver.find_element(AppiumBy.ACCESSIBILITY_ID, accessibility_id)
|
|
element.clear()
|
|
element.send_keys(value)
|
|
|
|
def scroll_down(self) -> None:
|
|
"""向下滑动(从屏幕 70% 滑到 30%)。"""
|
|
size = self.driver.get_window_size()
|
|
self.driver.swipe(
|
|
start_x=size["width"] // 2,
|
|
start_y=int(size["height"] * 0.7),
|
|
end_x=size["width"] // 2,
|
|
end_y=int(size["height"] * 0.3),
|
|
duration=500,
|
|
)
|
|
|
|
# ── 截图 ──
|
|
|
|
def screenshot(self, name: str) -> str:
|
|
"""关键节点截图,返回文件路径。"""
|
|
scenario_dir = SCREENSHOTS_DIR / self.__class__.__name__
|
|
scenario_dir.mkdir(parents=True, exist_ok=True)
|
|
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
path = str(scenario_dir / f"{name}_{self.platform}_{ts}.png")
|
|
self.driver.save_screenshot(path)
|
|
return path
|