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

Appium+Python+pytest移动端自动化测试框架搭建与工程实践

1. 项目概述:为什么选择 Appium+Python+pytest 这个组合?

如果你正在为移动端应用的回归测试发愁,每次版本更新都要手动点点点,那这套组合拳绝对值得你花时间研究。我最早接触移动端自动化时,也试过不少方案,最终沉淀下来的就是 Appium + Python + pytest 这套框架。它不是什么高深莫测的黑科技,而是一个经过大量项目验证、稳定且高效的工程化解决方案。

简单来说,Appium 负责搞定与手机(无论是 Android 还是 iOS)的“对话”,让你能用代码控制手机上的元素;Python 作为脚本语言,写起来快,读起来也清晰,生态丰富;而 pytest 则是测试界的“瑞士军刀”,它强大的夹具(fixture)管理、参数化测试和丰富的插件,能让你的测试用例组织得井井有条,报告也漂亮。这个组合的核心价值在于,它将自动化测试从“能跑起来”提升到了“易于维护、扩展和协作”的工业级水准。特别适合测试工程师、有一定 Python 基础的开发人员,或者任何希望将移动端测试流程标准化、自动化的团队。

2. 框架整体设计与核心思路拆解

2.1 技术选型背后的逻辑:为什么是它们三个?

很多新手会问,工具这么多,为什么偏偏是这三个?这背后是稳定性、生态和工程效率的综合考量。

首先看Appium。它的最大优势是跨平台和标准化。它基于 WebDriver 协议(没错,就是 Selenium 用的那个),这意味着你写 Android 和 iOS 的自动化脚本,在核心的查找元素、操作元素的 API 上是基本一致的。这大大降低了学习和维护成本。你不用为两个平台维护两套完全不同的脚本。虽然底层驱动不同(Android 用 UIAutomator2/iOS 用 XCUITest),但 Appium 帮你做了封装,提供统一的接口。相比之下,一些厂商提供的专用测试框架往往绑定特定平台或版本,灵活性和可持续性不足。

其次是Python。在测试自动化领域,Python 几乎是事实上的标准语言。原因很简单:语法简洁,上手快;拥有极其丰富的库支持,从 HTTP 请求到图像处理,几乎你能想到的需求都有现成的轮子;社区活跃,遇到问题容易找到解决方案。对于测试脚本这种偏“胶水”性质的任务,Python 的快速开发特性优势明显。你用 Java 或 C# 也能做,但 Python 能让你的脚本更聚焦于业务逻辑本身,而不是语言细节。

最后是pytest。这是整个框架的“骨架”和“大脑”。早期的 unittest 或 nose 框架在组织复杂测试用例、管理测试前置后置条件、生成报告方面比较吃力。pytest 通过fixture机制,可以优雅地管理测试资源(如启动 Appium 驱动、初始化应用)。它的断言写起来更符合直觉,失败信息也更清晰。更重要的是,它的插件生态极其丰富,比如pytest-html生成美观的 HTML 报告,pytest-xdist支持分布式并行测试,pytest-rerunfailures支持失败重试。这些都能直接提升测试框架的健壮性和实用性。

2.2 框架目录结构设计:清晰是维护的第一要义

一个混乱的目录结构是项目后期维护的噩梦。经过多个项目的迭代,我总结出一个清晰、可扩展的目录结构,它遵循“分离关注点”的原则。

project_root/ ├── configs/ # 配置文件 │ ├── __init__.py │ ├── config.yaml # 全局配置(如服务器地址、超时时间) │ └── capabilities.yaml # 设备能力配置(Desired Capabilities) ├── common/ # 公共模块 │ ├── __init__.py │ ├── base_page.py # 页面基类,封装公共操作 │ ├── appium_driver.py # Appium 驱动单例管理 │ └── logger.py # 日志记录模块 ├── page_objects/ # 页面对象模型(POM) │ ├── __init__.py │ ├── login_page.py # 登录页面 │ ├── home_page.py # 首页 │ └── ... # 其他页面 ├── test_cases/ # 测试用例 │ ├── __init__.py │ ├── conftest.py # pytest 夹具定义(核心!) │ ├── test_login.py # 登录模块测试 │ └── test_home.py # 首页模块测试 ├── test_data/ # 测试数据 │ ├── __init__.py │ └── login_data.yaml # 登录相关测试数据 ├── reports/ # 测试报告(运行时生成) │ └── html/ ├── logs/ # 运行日志(运行时生成) └── requirements.txt # Python 依赖包列表

这样设计的好处:

  1. configs/test_data/分离:将易变的配置和数据从代码中抽离,修改环境或测试数据无需改动代码。
  2. page_objects/核心:严格实践 POM 模式,每个页面的元素定位和操作都封装在对应的类中。测试用例里只包含业务逻辑和断言,极大提高了代码的可读性和可维护性。当 UI 元素发生变化时,通常只需要修改对应的 Page 类。
  3. conftest.py是关键:这是 pytest 的魔力所在。你可以在这里定义全局或特定目录范围的fixture,比如初始化 Appium 驱动、安装卸载 APP、登录用户等。这些fixture可以被所有测试用例按需调用,实现了测试资源的共享和生命周期管理。
  4. common/存放通用工具:比如驱动管理确保整个测试会话中只有一个驱动实例;日志模块统一格式,方便问题追溯。

3. 核心细节解析与实操要点

3.1 Appium 环境配置与驱动初始化:避开第一个大坑

环境配置是新手的第一道坎。Appium 涉及 Node.js、Appium Server、客户端库以及手机端的开发设置,环节较多。

对于 Android 环境:

  1. 安装 Node.js 和 Appium Server:建议通过npm install -g appium安装。同时,我强烈建议安装appium-doctor(npm install -g appium-doctor) 来检查环境是否完整。它会提示你缺少 Android SDK 或 JAVA_HOME 等配置。
  2. 配置 Android 开发环境:下载 Android Studio 或 Command Line Tools,确保ANDROID_HOME环境变量正确设置,并且platform-tools(包含 adb) 和build-tools目录在系统 PATH 中。
  3. 准备测试设备与 APP:连接真机或启动模拟器,通过adb devices确认设备已连接。获取被测 APP 的安装包(.apk 文件),并知道其主 Activity 名称(可通过adb shell dumpsys window | grep mCurrentFocus查看)。

驱动初始化的代码,我们通常封装在common/appium_driver.py中:

from appium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import yaml import os class AppiumDriver: _instance = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super().__new__(cls) return cls._instance def __init__(self): if not hasattr(self, 'driver'): config_path = os.path.join(os.path.dirname(__file__), '../configs/capabilities.yaml') with open(config_path, 'r', encoding='utf-8') as f: caps = yaml.safe_load(f) # 动态获取设备UDID,避免硬编码 device_list = os.popen('adb devices').read().strip().split('\n')[1:] if device_list: # 取第一个连接的设备 udid = device_list[0].split('\t')[0] caps['desiredCapabilities']['udid'] = udid else: raise Exception("未检测到连接的 Android 设备或模拟器") self.driver = webdriver.Remote('http://localhost:4723/wd/hub', caps['desiredCapabilities']) self.driver.implicitly_wait(10) # 设置隐式等待 def get_driver(self): return self.driver

对应的capabilities.yaml示例:

desiredCapabilities: platformName: "Android" platformVersion: "10" # 根据你的设备调整 deviceName: "Android Emulator" # 自定义名称,用于报告识别 appPackage: "com.example.myapp" appActivity: ".MainActivity" automationName: "UiAutomator2" noReset: false # 是否在会话前重置应用状态 fullReset: false # 是否在会话前卸载重装应用 unicodeKeyboard: true # 支持Unicode输入 resetKeyboard: true # 测试后重置键盘

注意:udid通过adb命令动态获取,这比在配置文件中写死要灵活得多,特别适合有多台测试设备或 CI/CD 环境。noResetfullReset是关键参数,根据测试需要选择。如果测试需要干净的登录状态,用fullReset: true;如果只是想重用已登录的缓存,用noReset: true

3.2 页面对象模型(POM)的深度实践:不仅仅是封装定位符

POM 模式大家可能都听过,但实践中很容易流于形式,变成简单的“元素定位符仓库”。真正的 POM 应该体现业务逻辑的封装。

page_objects/login_page.py为例:

from appium.webdriver.common.mobileby import MobileBy from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from common.base_page import BasePage class LoginPage(BasePage): # 1. 元素定位符 USERNAME_INPUT = (MobileBy.ID, 'com.example.myapp:id/et_username') PASSWORD_INPUT = (MobileBy.ID, 'com.example.myapp:id/et_password') LOGIN_BUTTON = (MobileBy.ID, 'com.example.myapp:id/btn_login') ERROR_TOAST = (MobileBy.XPATH, '//android.widget.Toast') # 2. 页面操作方法 def input_username(self, username): """输入用户名""" self.find_element(*self.USERNAME_INPUT).clear() self.find_element(*self.USERNAME_INPUT).send_keys(username) return self # 支持链式调用 def input_password(self, password): """输入密码""" self.find_element(*self.PASSWORD_INPUT).send_keys(password) return self def click_login(self): """点击登录按钮""" self.find_element(*self.LOGIN_BUTTON).click() # 3. 业务场景组合方法 def login(self, username, password): """完整的登录业务流""" self.input_username(username).input_password(password).click_login() return HomePage(self.driver) # 返回下一个页面的对象,实现流程衔接 # 4. 页面状态断言方法 def get_error_toast_text(self, timeout=5): """获取Toast提示文本,Toast是Android特有的短暂消息提示""" try: # 显式等待Toast出现 toast_element = WebDriverWait(self.driver, timeout).until( EC.presence_of_element_located(self.ERROR_TOAST) ) return toast_element.text except: return None

这里的几个关键点:

  1. 继承BasePageBasePage封装了最基础的find_elementclickswipe等操作,并可以加入日志、截图等通用逻辑。让具体的 Page 类更专注于自身业务。
  2. 链式调用:像self.input_username().input_password()这样的写法,让代码更流畅。
  3. 业务组合方法login()方法将多个操作步骤组合成一个有业务意义的方法。测试用例中直接调用login_page.login('user', 'pass'),可读性极高。
  4. 返回下一个页面对象login()方法返回HomePage对象。这样在测试用例中,可以写成home_page = login_page.login(...),逻辑连贯,符合实际用户操作流程。
  5. 封装页面特有的断言:像get_error_toast_text这种获取特定提示信息的方法,封装在 Page 类里是最合适的,因为只有这个页面关心这个 Toast。

3.3 pytest 夹具(fixture)的精妙运用:管理测试生命周期

fixture是 pytest 的灵魂,它用于准备测试环境、提供测试数据以及清理工作。在test_cases/conftest.py中定义:

import pytest from common.appium_driver import AppiumDriver from page_objects.login_page import LoginPage import yaml import os @pytest.fixture(scope="session") def app_driver(): """会话级别的fixture,整个测试会话只启动一次驱动""" driver = AppiumDriver().get_driver() print("启动 Appium 驱动...") yield driver # yield之前是setup,之后是teardown print("关闭 Appium 驱动...") driver.quit() @pytest.fixture(scope="function") def to_login_page(app_driver): """函数级别的fixture,每个测试函数开始前,都回到登录页""" # 假设通过重启APP来回到登录页,简单粗暴但有效 app_driver.close_app() app_driver.launch_app() return LoginPage(app_driver) @pytest.fixture(params=[ {"username": "correct_user", "password": "correct_pass", "expected": "success"}, {"username": "wrong_user", "password": "wrong_pass", "expected": "fail_toast"}, ]) def login_data(request): """参数化fixture,提供多组登录测试数据""" return request.param

fixturescope参数是关键:

  • session:整个 pytest 执行过程只运行一次。适合初始化 Appium 驱动这种重量级、耗时的操作。
  • function:默认值,每个测试函数都运行一次。适合需要干净测试环境的操作,如回到登录页。
  • class:每个测试类运行一次。
  • module:每个.py文件运行一次。

yield的用法:yield之前的代码是“设置”,yield返回的是提供给测试用例的值,yield之后的代码是“清理”。这比传统的setup/teardown方法更清晰。

在测试用例中,你只需要将fixture的函数名作为参数传入,即可使用:

# test_cases/test_login.py class TestLogin: def test_login_success(self, to_login_page, login_data): """使用参数化数据测试登录""" login_page = to_login_page if login_data['expected'] == 'success': home_page = login_page.login(login_data['username'], login_data['password']) # 断言:登录成功后,首页的某个特定元素应该出现 assert home_page.is_welcome_displayed() is True else: login_page.login(login_data['username'], login_data['password']) toast_text = login_page.get_error_toast_text() # 断言:登录失败后,Toast提示应包含特定文本 assert "用户名或密码错误" in toast_text

4. 实操过程与核心环节实现

4.1 编写第一个端到端(E2E)测试用例

让我们串联起所有部分,实现一个从启动 APP 到完成某个核心业务的完整测试。假设我们测试一个电商 APP 的“加入购物车”流程。

步骤 1:定义页面对象首先,在page_objects/下创建product_detail_page.pyshopping_cart_page.py

步骤 2:在conftest.py中添加业务流fixture

@pytest.fixture def product_detail_page(app_driver): # 这里简化处理,实际项目中可能需要先搜索或浏览到商品详情页 # 我们可以通过 Deep Link 或者 Mock 数据直接打开某个商品页面 app_driver.get("myapp://product/12345") # 假设支持Deep Link return ProductDetailPage(app_driver)

步骤 3:编写测试用例test_cases/test_shopping.py

import pytest from page_objects.shopping_cart_page import ShoppingCartPage class TestShoppingCart: def test_add_to_cart(self, product_detail_page): """ 测试添加商品到购物车 步骤:1. 进入商品详情页 2. 点击加入购物车 3. 进入购物车验证 """ # 1. 在商品详情页执行加入购物车操作 product_detail_page.select_specification("红色", "L码") # 选择规格 product_detail_page.click_add_to_cart() # 2. 获取添加成功提示(可能是Toast或页面内提示) success_msg = product_detail_page.get_add_success_message() assert "添加成功" in success_msg # 3. 进入购物车页面 cart_page = product_detail_page.go_to_shopping_cart() # 4. 验证购物车中是否存在该商品 cart_items = cart_page.get_cart_item_list() assert len(cart_items) == 1 target_item = cart_items[0] assert target_item['name'] == "测试商品名称" assert target_item['spec'] == "红色 L码" assert target_item['price'] == "99.99" # 更严谨的做法:对比商品ID

这个用例体现了良好的测试设计:

  1. 可读性:方法名和变量名清晰地表达了意图。
  2. 模块化:页面操作封装在 Page 类中。
  3. 断言充分:不仅断言操作成功,还断言了最终的业务状态(购物车里的商品信息)。
  4. 流程完整:模拟了真实用户的完整操作路径。

4.2 测试数据驱动:让用例更灵活

硬编码的测试数据不利于维护和扩展。我们使用pytest@pytest.mark.parametrize装饰器或通过fixture实现数据驱动。

方法一:使用@pytest.mark.parametrize(适合简单数据)

import pytest class TestLoginWithParam: @pytest.mark.parametrize("username, password, expected", [ ("user1", "pass1", True), ("user1", "wrong", False), ("", "pass1", False), ]) def test_login_parametrize(self, to_login_page, username, password, expected): login_page = to_login_page login_page.login(username, password) if expected: assert login_page.is_login_success() else: assert not login_page.is_login_success()

方法二:从外部文件加载数据(推荐,适合复杂数据)test_data/login_data.yaml中定义:

- username: "standard_user" password: "secret_sauce" expected: "success" - username: "locked_out_user" password: "secret_sauce" expected: "fail_toast" toast_msg: "此用户已被锁定" - username: "" password: "secret_sauce" expected: "fail_toast" toast_msg: "用户名不能为空"

在测试用例中读取:

import yaml import os import pytest def load_login_data(): data_path = os.path.join(os.path.dirname(__file__), '../test_data/login_data.yaml') with open(data_path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) @pytest.mark.parametrize("data", load_login_data()) def test_login_with_yaml_data(to_login_page, data): login_page = to_login_page login_page.login(data['username'], data['password']) # ... 根据 data['expected'] 进行断言

外部文件管理数据的优势在于,非技术人员(如产品经理)也可以在不接触代码的情况下,维护和添加测试用例数据。

5. 常见问题与排查技巧实录

移动端自动化测试,尤其是基于 Appium,会遇到各种光怪陆离的问题。这里记录几个高频且棘手的问题及我的排查思路。

5.1 元素定位失败:自动化测试的“头号公敌”

超过 70% 的自动化脚本问题源于元素定位失败。表现是NoSuchElementException

排查步骤:

  1. 确认页面是否加载完成:在操作前加入显式等待,等待某个关键元素出现。
    from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC wait = WebDriverWait(driver, 10) element = wait.until(EC.presence_of_element_located((MobileBy.ID, "some_id")))
  2. 验证定位符是否正确:使用Appium InspectorUIAutomatorViewer(Android) /Xcode Accessibility Inspector(iOS) 重新检查元素属性。注意:绝对不要依赖appium-desktop录制的 XPath,它生成的路径往往又长又脆弱。优先使用resource-id(Android) 或accessibility-id(iOS),其次是textcontent-desc,最后才考虑 XPath。
  3. 检查是否有原生/WebView/Hybrid 上下文切换:如果你的 APP 内嵌了 H5 页面,需要在原生(NATIVE_APP)和 WebView(如WEBVIEW_com.example.myapp)上下文之间切换。使用driver.contexts获取所有上下文,然后用driver.switch_to.context(context_name)切换。
  4. 检查是否为动态元素:有些元素的resource-idtext包含动态部分(如时间戳、订单号)。这时需要使用 XPath 的contains()starts-with()函数或正则表达式进行模糊匹配。
    # 使用 contains 匹配部分文本 dynamic_element = (MobileBy.XPATH, '//android.widget.TextView[contains(@text, "订单")]')
  5. 终极武器:截图+Page Source:在定位失败的地方,让脚本自动截图并打印当前页面的 XML 结构(driver.page_source)。对比截图和源码,能最直观地发现问题。

5.2 测试执行不稳定:偶发性失败

这是 UI 自动化,特别是移动端 UI 自动化的顽疾。

应对策略:

  1. 增加智能等待,减少固定等待:用显式等待(WebDriverWait)替代time.sleep()。显式等待更高效,只在条件不满足时才等待。
  2. 启用重试机制:使用pytest-rerunfailures插件。在命令行执行时添加--reruns 2表示失败后重跑2次。或者在conftest.py中全局配置。
    pytest test_cases/ --reruns 2 --reruns-delay 1
  3. 优化操作逻辑:有些操作需要更“人性化”。例如,点击前先判断元素是否可点击(EC.element_to_be_clickable),滑动列表时判断是否已滑动到底部。
  4. 隔离测试环境:确保测试开始时,APP 处于预期状态(如已登出)。使用fixturesetup部分来重置应用(driver.reset()driver.close_app(); driver.launch_app())。

5.3 如何在 CI/CD 流水线中集成?

自动化测试的价值在于持续反馈。集成到 Jenkins、GitLab CI 等工具中是必由之路。

关键步骤:

  1. 环境准备:在 CI 服务器上安装好 JDK、Android SDK、Node.js、Appium Server 以及必要的模拟器或连接真机池。
  2. 脚本适配
    • 将设备信息(如 UDID、系统版本)通过环境变量或配置文件传入,而不是写死在代码里。
    • 确保测试脚本是无状态的,可以独立运行。
    • conftest.pyapp_driverfixture 中,根据环境变量选择启动本地 Appium 服务还是连接远程的 Appium 服务器(如 Selenium Grid 或云测平台)。
  3. 生成并归档报告:使用pytest-html插件生成 HTML 报告,并在 CI 任务结束后将其保存为产物。
    pytest test_cases/ --html=reports/html/report.html --self-contained-html
    --self-contained-html参数会将 CSS 等资源内联,生成单个 HTML 文件,便于传输和查看。
  4. 失败通知:配置 CI 工具,当测试失败时,通过邮件、钉钉、企业微信等渠道通知相关负责人。

5.4 提升脚本执行速度

当用例成百上千时,执行时间是个问题。

优化方向:

  1. 并行测试:使用pytest-xdist插件。
    pytest test_cases/ -n 3 # 启动3个worker并行执行
    前提:你需要有多台设备或模拟器,或者你的测试用例是相互独立的(没有共享状态)。通常需要配合pytestfixturescope="session"scope="module"来为每个 worker 单独初始化驱动。
  2. 减少不必要的重启:对于不需要完全干净环境的测试套件,使用noReset: true能力,避免每次测试都重装 APP。
  3. 用例选择与分组:使用pytest -m标记来只运行冒烟测试、核心功能测试等特定用例集。
    # 在测试用例上打标记 @pytest.mark.smoke def test_critical_login(self): pass
    pytest test_cases/ -m smoke

6. 进阶技巧与最佳实践

6.1 封装自定义操作与断言

除了基本的点击、输入,移动端测试常有滑动、长按、多点触控等操作。在BasePage中封装这些方法。

# common/base_page.py from appium.webdriver.common.touch_action import TouchAction class BasePage: def __init__(self, driver): self.driver = driver def swipe_up(self, duration=1000): """向上滑动""" size = self.driver.get_window_size() start_x = size['width'] * 0.5 start_y = size['height'] * 0.8 end_x = size['width'] * 0.5 end_y = size['height'] * 0.2 self.driver.swipe(start_x, start_y, end_x, end_y, duration) def swipe_to_find(self, locator, max_swipes=5): """滑动查找元素,用于处理列表懒加载""" for i in range(max_swipes): try: return self.find_element(*locator) except: self.swipe_up() raise Exception(f"元素 {locator} 未找到,已滑动 {max_swipes} 次") def assert_element_text(self, locator, expected_text): """断言元素文本,并包含更友好的错误信息""" actual_text = self.find_element(*locator).text assert actual_text == expected_text, \ f"元素文本断言失败。定位符: {locator}, 期望: '{expected_text}', 实际: '{actual_text}'"

6.2 日志与失败截图:问题定位的生命线

没有日志和截图的自动化框架是“瞎子”。在BasePage的关键操作和conftest.py的 fixture 中加入日志和自动截图。

# common/logger.py (简化示例) import logging import datetime def get_logger(name): logger = logging.getLogger(name) if not logger.handlers: logger.setLevel(logging.INFO) ch = logging.StreamHandler() formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') ch.setFormatter(formatter) logger.addHandler(ch) return logger # 在 BasePage 中集成 class BasePage: def __init__(self, driver): self.driver = driver self.logger = get_logger(self.__class__.__name__) def find_element(self, by, value): self.logger.info(f"查找元素: {by} = {value}") try: element = self.driver.find_element(by, value) return element except Exception as e: self.logger.error(f"查找元素失败: {e}") self._take_screenshot("find_element_failed") raise def _take_screenshot(self, name): timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"screenshots/{name}_{timestamp}.png" self.driver.save_screenshot(filename) self.logger.info(f"截图已保存: {filename}")

conftest.py中配置自动截图(当测试失败时):

@pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(item, call): """Hook函数,用于在测试失败时自动截图""" outcome = yield report = outcome.get_result() if report.when == "call" and report.failed: # 获取测试用例中的driver对象 for fixture_name in item.fixturenames: if "driver" in fixture_name: driver = item.funcargs[fixture_name] try: timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") screenshot_name = f"{item.name}_{timestamp}.png" screenshot_path = os.path.join("reports/screenshots", screenshot_name) driver.save_screenshot(screenshot_path) print(f"测试失败,截图已保存至: {screenshot_path}") # 还可以将截图路径附加到HTML报告中 if hasattr(report, 'extra'): from pytest_html import extras report.extra.append(extras.png(screenshot_path)) except: pass

6.3 处理权限弹窗与系统弹窗

应用在运行时可能会请求位置、存储等权限,或者系统会弹出各种提示。这些弹窗元素不在你的 APP 内,需要特殊处理。

策略:在驱动初始化后,或在关键操作前,加入一个“弹窗处理”的守护逻辑。可以封装一个方法,定期检查并尝试关闭已知的弹窗。

def handle_common_popups(driver): """尝试关闭常见的权限弹窗或系统弹窗""" # 示例:处理Android权限弹窗(按钮文本可能是'允许'或'拒绝') allow_buttons = [ (MobileBy.ID, 'com.android.packageinstaller:id/permission_allow_button'), (MobileBy.XPATH, '//*[@text="允许"]'), (MobileBy.XPATH, '//*[@text="ALLOW"]'), ] for locator in allow_buttons: try: element = WebDriverWait(driver, 2).until(EC.element_to_be_clickable(locator)) element.click() print(f"点击了弹窗按钮: {locator}") return True except: continue return False # 在BasePage的初始化或关键操作前调用 # 或者写一个装饰器,装饰那些可能触发弹窗的操作方法

这套 Appium + Python + pytest 的框架,其强大之处不在于任何一个单独的组件,而在于它们组合后形成的工程化能力。它迫使你思考测试的结构、可维护性和可靠性。从环境搭建到第一个脚本,从 POM 设计到 CI 集成,每一步都可能会遇到坑,但每一步的解决都会让你对移动应用和自动化测试的理解更深一层。记住,框架是死的,人是活的。最重要的是理解其设计理念,然后根据自己项目的实际情况进行调整和优化。

http://www.gsyq.cn/news/1593931.html

相关文章:

  • 062、编写自定义 Skill:SKILL.md 规范、触发词设计与发布流程
  • 微电网混合控制架构的应用案例
  • 收藏!2026年技术小白也能看懂的大模型学习路线图,速进!
  • 深度解析iOS端U2-Net背景移除架构设计与性能优化
  • 10分钟学会ExifToolGUI:免费开源的图片元数据管理神器
  • linux内核中一个特殊宏:BUILD_BUG_ON的分析
  • NanaZip完整指南:3种方法掌握Windows平台最佳压缩工具
  • 移动端系统镜像提取革命:Payload-Dumper-Android颠覆传统工作流
  • 免费开源鼠标连点器:3分钟掌握自动化点击技巧
  • OpenCore Legacy Patcher:老旧Mac的智能适配与重生革命
  • MusicBee网易云歌词插件终极指南:3步实现完美同步歌词体验
  • HoRain云--C++ 基本语法
  • 告别网盘限速:LinkSwift 九大网盘直链下载终极指南
  • 如何用Blue-Topaz主题打造你的专属Obsidian笔记美学空间
  • macOS下Claude Code从安装到API配置全流程,小白也能照着做
  • 省属改制律所发展脉络梳理:安大法学背景带来的实务优势
  • DLSS Swapper架构深度解析:跨平台游戏DLSS版本管理引擎的技术实现
  • 从零构建Selenium+POM UI自动化测试框架:以Web聊天室为例
  • ThinkPad终极散热解决方案:TPFanCtrl2让你的笔记本性能全开
  • Nigate:开源NTFS读写工具的技术架构与实践应用
  • 用Python解锁金融数据:AKShare财经数据接口库全方位指南
  • 多轮采样下的AI品牌回答波动观察
  • 终极指南:3分钟掌握DeepL Chrome翻译插件的完整配置与高效使用技巧
  • 退化黎曼曲面上调和映射Morse指数稳定性:渐近分析与有限元计算实战
  • 企业微信OAuth2.0免登授权链路真的安全吗?怎么防止授权码泄露与篡改?
  • Navicat试用期重置技术方案深度解析:macOS系统级清理与自动化实现
  • Java毕业设计-基于 SpringBoot 的 C 语言在线学习辅导平台的设计与实现(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • 【2024年最值得投入的5大vSphere替代方案】:资深架构师亲测,成本直降47%、运维效率提升3.2倍的实战选型指南
  • 5分钟掌握AI音频修复:让任何语音重获清晰质感
  • 金属多芯自接头防爆连接器应用场景介绍