"""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