当前位置: 首页 > news >正文

【Appium 系列】第04节-Page Object 模式 — BasePage 基类设计

对应代码:配套代码base/base_page.py

说明:本节代码示例与配套代码中的BasePage完全对应。


这节讲什么

Page Object 模式(POM)说白了就是一个规则:每个页面写成一个类,页面上有什么元素、能做什么操作,都放在这个类里。测试用例只调页面类的方法,不直接写driver.find_element()

没有 POM 的时候,测试代码长这样:

def test_login(driver): driver.find_element("id", "username").send_keys("admin") driver.find_element("id", "password").send_keys("admin123") driver.find_element("id", "login-btn").click()

一个测试里写几行find_element还行。但如果有 50 个测试都用到了登录按钮,登录按钮的 id 一改,50 个测试全要改。

有了 POM,就变成了:

login_page = LoginPage(driver) login_page.login("admin", "admin123")

登录按钮的 id 改了,只改LoginPage这一个文件就行。


BasePage 的设计

配套代码的base_page.py有 823 行,封装了移动端所有基础操作。这节只看核心部分。

1. 智能元素定位

def find_element_smart(self, locators: list, timeout: int = 10): """ 按优先级依次尝试多个定位方式。 前面的不行就试后面的,全部失败才报错。 """ last_exception = None for idx, (locator_type, locator_value) in enumerate(locators): try: element = self.find_element(locator_type, locator_value, timeout=timeout) return element except (TimeoutException, NoSuchElementException) as e: last_exception = e continue raise TimeoutException(f"全部定位失败,最后错误: {last_exception}")

使用方式

login_btn = page.find_element_smart([ ("accessibility_id", "login_button"), # 首选 ("id", "com.app:id/login_btn"), # 备选 ("xpath", "//*[@text='登录']"), # 最后才用 ])

为什么要有这么多层定位

  • accessibility_id最稳定,跨平台通用
  • resource-id(即id)在 Android 上稳定
  • xpath容易因为页面结构调整而失效,只做最后保底

2. 元素查找(带显式等待)

def find_element(self, locator_type: str, locator_value: str, timeout: int = 10): locator_map = { "id": (AppiumBy.ID, locator_value), "xpath": (AppiumBy.XPATH, locator_value), "class_name": (AppiumBy.CLASS_NAME, locator_value), "accessibility_id": (AppiumBy.ACCESSIBILITY_ID, locator_value), "android_uiautomator": (AppiumBy.ANDROID_UIAUTOMATOR, locator_value), "ios_predicate": (AppiumBy.IOS_PREDICATE, locator_value), } element = WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located(locator_map[locator_type]) ) return element

WebDriverWait做了显式等待——元素还没出现时,最多等 timeout 秒,而不是立刻报错。这比time.sleep(5)高效得多。

3. 点击操作

def click(self, locator_type: str, locator_value: str, timeout: int = 10): element = self.find_element(locator_type, locator_value, timeout) element.click() time.sleep(0.5) # 点击后稍等,避免操作过快

为什么点完要等 0.5 秒:移动端的操作比 Web 慢。点击一个按钮后,页面可能需要几百毫秒才能响应。连点两个操作之间不加延迟,第二个操作可能还没找到页面就执行了。

4. 输入文本

def input_text(self, locator_type: str, locator_value: str, text: str, clear_first: bool = True, timeout: int = 10): element = self.find_element(locator_type, locator_value, timeout) if clear_first: element.clear() element.send_keys(text)

5. 滑动操作

def swipe_up(self, duration: int = 1000, distance: Optional[int] = None): size = self.driver.get_window_size() width = size['width'] height = size['height'] start_x = width // 2 start_y = int(height * 0.8) # 从屏幕 80% 位置开始 end_y = int(height * 0.2) # 滑到屏幕 20% 位置 end_x = start_x self.driver.swipe(start_x, start_y, end_x, end_y, duration)

swipe_downswipe_leftswipe_right同理,只是坐标方向不同。


三级定位降级策略

实际使用中,一个元素可能同时有 accessibility_id、resource-id、xpath 三种定位方式。优先级原则:

accessibility_id > resource-id/id > xpath
  • accessibility_id:开发给元素加的辅助标签,除非开发主动改,否则不变
  • resource-id:Android 原生属性,比较稳定
  • xpath:依赖页面 DOM 结构,前端一改版就废

配套代码的find_element_smart就是按这个优先级依次尝试的。


踩过的坑

1. xpath 太脆弱了

第一次写 Appium 测试时,所有定位都用 xpath,因为方便——//*[@text='登录']一行搞定。 然后 App 发版了,登录按钮外面包了一层布局,xpath 路径变了,全部定位失效。 后来全部改成用 accessibility_id,发版 3 次都没动过。

2. 显式等待 vs 隐式等待

刚开始不明白两者的区别,混着用。隐式等待设了 10 秒,显式等待又设了 10 秒,结果就是 timeout 响应慢了一倍。规则:用显式等待(WebDriverWait),别用隐式等待(implicitly_wait)。

3. 点击操作太快

不加延迟连续点击两个元素,第二个点击总是报element is not attached to the page document。 加 0.5 秒延迟之后就好了。移动端不比 Web,操作之间要给页面反应时间。

4. 找不到元素就截图

find_element_smart在所有定位方式都失败后,会自动截一张图保存,文件名带element_not_found时间戳。 这个习惯特别好——看日志只能看到"元素找不到",看到截图才知道"哦,原来页面压根没加载出来"。

http://www.jsqmd.com/news/821693/

相关文章:

  • 从数据手册到面包板:手把手教你用MP2315S搭建一个可调压的迷你DC-DC电源模块
  • Mixamo动画救不了你的自定义角色?手把手教你用ADV骨骼完成完美动画重定向(附避坑指南)
  • Win11上VMware 15.5跑不起来?别急着重装,先试试关掉这个安全开关
  • not-my-job:基于代码变更自动定责的工程效能工具设计与实践
  • 桌面整理革命:NoFences如何拯救我的数字生活
  • 用C语言结构体给51单片机游戏开发‘松绑’:以TFT屏贪吃蛇为例讲透数据管理
  • 如何在3分钟内免费解锁12种加密音乐格式:重新掌控你的数字音乐资产
  • 考公想上岸,真的要死磕这 5 件事! 少一件,都容易陪跑[特殊字符]
  • Abra:轻量级自动化构建部署工具,用“咒语”简化DevOps流程
  • 基于CircuitPython的数字陀螺游戏开发:传感器交互与图形显示实践
  • 写作高手不说的秘密,文章大纲决定完读率
  • 办公自动化__获取路径下所有文件名称
  • SLAM算法评测避坑指南:如何正确使用evo计算ATE与RPE(以ORB-SLAM2单目实验为例)
  • ODA/Oracle 19c CDB/PDB 环境下报错ORA-65162:common user密码过期问题排查与处理_2026-05-15
  • NomNom:如何用最智能的存档编辑器重新定义你的《无人深空》游戏体验
  • 用Arduino与加速度计打造可编程电子万花筒:从传感器原理到光学实现
  • 终极免费B站视频下载方案:BilibiliDown完整使用指南
  • 终极视觉小说翻译解决方案:LunaTranslator从零到精通完整指南
  • 声明式文本格式化:fancy-text-formatter 库的设计、实战与优化
  • 在Node.js服务中集成Taotoken实现多模型对话能力
  • 远程开发新思路:用VNC把AutoDL/矩池云的GPU服务器变成你的“图形工作站”
  • 油皮用什么水比较清爽?夏季护肤真人实测,速吸保湿长效控油不紧绷 - 博客万
  • 多开 Claude Code / Codex 看不过来?2k Star 开源神器,实时统计 AI 代理怎么跑!
  • 5个简单步骤掌握魔兽世界GSE宏编译器的技能自动化魔法
  • 小米智能家居全面接入HomeAssistant的终极指南:hass-xiaomi-miot深度解析
  • 河北单招培训机构避坑指南:真实体验下的靠谱选择 - 奔跑123
  • 5分钟让您的PS3手柄在Windows上重获新生:DsHidMini驱动完全指南
  • 基于LLM的GitHub智能体:自动化仓库管理与代码审查实战
  • 15分钟打造高颜值小程序:ColorUI色彩组件库终极指南
  • ubuntu20.04在Vscode上配置codex