Appium Python Client扩展实战:自定义命令与连接管理
1. 项目概述:为什么需要扩展Appium Python Client?
如果你已经用Appium Python Client写过一段时间的自动化测试脚本,大概率会遇到过这样的场景:官方库提供的 click 、 send_keys 、 find_element 这些标准命令用起来很顺手,但一到某些特定需求,比如想直接调用一个WebDriver协议里支持但Client库没封装的底层命令,或者想精细化管理设备连接的生命周期,就会感觉有点“使不上劲”。官方Client库为了保持通用性和稳定性,通常只封装最常用、最稳定的那部分功能。而实际项目中,尤其是面对碎片化严重的安卓设备群、需要与内部测试平台对接,或者有特殊性能监控需求时,原生的Client就显得不够灵活了。
这就是我们今天要聊的核心: 扩展Appium Python Client 。这不是简单地调用几个API,而是深入到Client库的架构层面,去定制和增强它的能力。具体来说,主要围绕两个方向:一是 自定义命令(Custom Commands) ,让你能像调用 driver.find_element 一样,轻松执行任何符合WebDriver协议或甚至是你自己服务端定义的指令;二是 连接管理(Connection Management) ,这关乎测试脚本的健壮性和执行效率,如何优雅地处理连接建立、重试、复用和销毁,尤其是在多设备并行或长时间运行的稳定性测试中。
我经历过不少因为连接超时导致测试用例莫名失败,或者因为某个特殊操作没有现成方法而不得不写一堆底层HTTP请求代码的情况。后来发现,与其每次“打补丁”,不如系统地掌握扩展Client的方法。这不仅能提升脚本的复用性和可读性,更能让你对Appium的运作机制有更深的理解,从“使用者”转变为“定制者”。接下来,我会结合代码,把自定义命令和连接管理这两块硬骨头拆开、揉碎,讲清楚里面的门道和实操中容易踩的坑。
2. 理解Appium Python Client的架构与扩展点
在动手写代码之前,我们必须先搞清楚Appium Python Client是怎么工作的。它不是一个黑盒子,而是一个清晰的分层结构。知其然,更要知其所以然,这样我们扩展的时候才能找到正确的“穴位”。
2.1 核心架构:从WebDriver到你的脚本
Appium Python Client(以下简称 appium-python-client )本质上是Selenium Python Client的一个扩展。它的核心是 webdriver.Remote 类。当你执行 driver = webdriver.Remote(command_executor, desired_capabilities) 时,发生的事情是这样的:
- 命令翻译层 :你调用的所有方法,如
driver.find_element(By.ID, “button”),都会被Remote对象转换成一个标准的JSON Wire Protocol或W3C WebDriver协议请求。 - HTTP通信层 :这个请求通过HTTP POST发送到你指定的
command_executor(通常是Appium Server的地址,如http://localhost:4723)。 - 服务端处理 :Appium Server接收到请求,解析命令,并通过对应的设备驱动(如UiAutomator2 for Android, XCUITest for iOS)在真实设备或模拟器上执行操作。
- 响应返回 :操作结果被封装成HTTP响应返回给Client,Client再解析响应,可能返回一个WebElement对象,也可能只是返回一个状态。
appium-python-client 在这个链条中做了什么?它做了两件关键事:一是 扩展了Desired Capabilities ,增加了 appium:appPackage 、 appium:automationName 等Appium特有的配置项;二是 混入(Mixin)了一系列Helper类 ,比如 AppiumBy 、 FindsByImage 等,为 Remote 对象添加了 find_element_by_image 、 start_activity 等移动端特有方法。
2.2 关键扩展点:Command和Connection
我们的扩展工作,主要针对两个内部机制:
- Command(命令)机制 :这是
webdriver.Remote内部维护的一个命令字典(_commands属性)。它定义了方法名(如FIND_ELEMENT)到具体远程请求路径(如/session/:session_id/element)和HTTP方法(POST)的映射。当我们想添加一个官方库没有的命令时,就需要操作这个机制。 - RemoteConnection(远程连接)类 :这是真正负责发送HTTP请求、处理响应的类。它管理着连接超时、请求重试、错误处理等底层网络细节。我们要增强连接稳定性、添加日志或重试逻辑,就需要从这里入手。
理解这两点,就像拿到了扩展Client的“地图”。自定义命令让我们可以绘制新的“目的地”(命令),而自定义连接管理则让我们可以优化“交通工具”(网络连接)的性能和可靠性。
3. 实战一:开发自定义命令(Custom Commands)
自定义命令是扩展Client最直接、最常用的方式。它允许你将任何HTTP端点封装成一个直观的Python方法。
3.1 自定义命令的原理与步骤
原理很简单:在 webdriver.Remote 实例的 _commands 字典中注册一个新的映射,然后定义一个对应的方法来发起请求。
假设我们需要一个官方库未提供的命令: 获取当前设备的屏幕分辨率 。虽然可以通过 driver.get_window_size() 获取,但假设Appium Server提供了一个更直接的内部端点 /session/:session_id/appium/device/screen_info 。
步骤拆解:
- 定义命令常量 :给它起个唯一的名字,比如
GET_SCREEN_INFO。 - 注册命令映射 :告诉Client,这个命令常量对应哪个HTTP路径和方法。
- 实现命令方法 :在自定义的WebDriver类中添加一个方法,该方法内部会调用注册的命令。
3.2 完整代码示例与逐行解析
下面我们创建一个自定义的WebDriver类。
# custom_driver.py
from appium.webdriver.webdriver import WebDriver as AppiumWebDriver
from selenium.webdriver.remote.command import Command as SeleniumCommand
# 1. 定义自定义命令常量。为了避免冲突,建议使用独特的前缀。
class CustomCommand:
GET_SCREEN_INFO = ("GET", "/session/:session_id/appium/device/screen_info")
class CustomAppiumDriver(AppiumWebDriver):
def __init__(self, *args, **kwargs):
# 首先调用父类的初始化方法,建立标准连接和命令集
super().__init__(*args, **kwargs)
# 2. 在初始化时,将自定义命令注册到驱动程序的命令执行器中。
# `self._commands` 是一个字典,键是命令名,值是 (method, url) 元组。
self._commands[CustomCommand.GET_SCREEN_INFO] = CustomCommand.GET_SCREEN_INFO
def get_screen_info(self):
"""
自定义方法:获取设备屏幕的详细信息(分辨率、密度等)。
返回一个包含屏幕信息的字典。
"""
# 3. 使用 `self.execute` 方法执行自定义命令。
# `execute` 方法会查找 `self._commands` 中的映射,构造并发送HTTP请求。
# 第一个参数是我们注册的命令常量。
screen_info = self.execute(CustomCommand.GET_SCREEN_INFO, {})
# 通常,响应体中的 `value` 字段包含了服务端返回的主要数据。
return screen_info.get('value', {}) if isinstance(screen_info, dict) else screen_info
# 再举一个例子:一个需要传递参数的复杂命令,比如设置设备网络连接状态。
# 假设端点:POST /session/:session_id/appium/device/network_connection
SET_NETWORK_CONNECTION = ("POST", "/session/:session_id/appium/device/network_connection")
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._commands[CustomCommand.GET_SCREEN_INFO] = CustomCommand.GET_SCREEN_INFO
# 注册第二个命令
self._commands['SET_NETWORK_CONNECTION'] = self.SET_NETWORK_CONNECTION
def set_network_connection(self, connection_type):
"""
设置设备网络连接类型。
:param connection_type: 整数,代表网络类型(如 0: 无网络, 1: 飞行模式, 2: WIFI only, 4: 数据 only, 6: 所有网络)
"""
# 构建请求体参数
params = {'type': connection_type}
# 执行命令,第二个参数是请求体数据
result = self.execute('SET_NETWORK_CONNECTION', params)
return result
代码解析与注意事项:
- 命令常量格式 :
(HTTP_METHOD, “URL_PATH”)。URL中的:session_id是占位符,execute方法会自动用当前会话的真实ID替换它。这是Selenium/Appium Client内部的标准约定。 - 注册时机 :必须在
__init__中,调用super().__init__()之后 进行注册。因为父类的初始化会建立基础的命令字典,我们是在此基础上做扩展。 -
execute方法 :这是核心。它接收命令名和参数字典。参数字典会被序列化为JSON,作为请求体(对于POST/PUT)或查询参数(对于GET/DELETE,但Appium命令多为POST)发送。 - 错误处理 :
execute方法内部已经包含了基本的HTTP错误和WebDriver错误处理。但如果你的服务端端点返回非标准格式,可能需要在这个自定义方法里额外处理响应。
注意 :在注册命令时,键(如
‘SET_NETWORK_CONNECTION’)只是一个内部标识符,理论上可以任意字符串,但为了清晰,通常与常量或方法名保持一致。而值必须是(method, url)元组。
3.3 使用自定义Driver
使用起来和原生Driver完全一样:
from appium import webdriver
from custom_driver import CustomAppiumDriver # 导入我们自定义的类
desired_caps = {
'platformName': 'Android',
'appium:deviceName': 'emulator-5554',
'appium:appPackage': 'com.example.app',
'appium:appActivity': '.MainActivity'
}
# 关键:使用 custom_driver 模块中的 CustomAppiumDriver
driver = CustomAppiumDriver('http://localhost:4723', desired_caps)
try:
# 使用原生方法
element = driver.find_element(by=AppiumBy.ID, value='login_button')
element.click()
# 使用我们自定义的方法!
screen_info = driver.get_screen_info()
print(f"屏幕分辨率: {screen_info.get('width')}x{screen_info.get('height')}")
# 设置网络为仅WIFI
driver.set_network_connection(2)
finally:
driver.quit()
通过这种方式,你将业务相关的、重复的底层HTTP调用封装成了语义清晰的方法,大大提升了代码的可维护性。
4. 实战二:实现自定义连接管理(Connection Management)
连接管理关乎测试的稳定性和资源效率。默认的 RemoteConnection 在简单场景下够用,但在复杂网络环境或追求高稳定性的自动化流水线中,往往需要定制。
4.1 为什么需要自定义连接管理?
默认连接可能存在的痛点:
- 超时策略僵化 :默认的读写超时可能不适用于所有操作。例如,安装一个大型APK需要更长的超时时间。
- 无自动重试 :一次网络抖动或Appium Server的短暂GC停顿就可能导致命令失败,测试用例被误判。
- 日志信息不足 :出问题时,只有简单的错误信息,难以定位是网络问题、服务端问题还是脚本问题。
- 连接无法复用 :对于需要频繁创建销毁Session的测试套件,每次都建立全新的HTTP连接会有开销。
4.2 创建自定义RemoteConnection类
我们将创建一个自定义的连接类,主要增加 重试机制 和 更详细的日志 。
# custom_connection.py
import logging
import time
from selenium.common.exceptions import WebDriverException
from selenium.webdriver.remote.remote_connection import RemoteConnection
import json
class RetryableRemoteConnection(RemoteConnection):
"""
自定义远程连接类,支持失败重试和增强日志。
"""
def __init__(self, remote_server_addr, keep_alive=True, retry_count=3, retry_delay=1, timeout=30):
"""
初始化自定义连接。
:param remote_server_addr: Appium Server地址
:param keep_alive: 是否保持HTTP长连接
:param retry_count: 命令失败后的重试次数
:param retry_delay: 重试之间的延迟(秒)
:param timeout: 默认超时时间(秒)
"""
# 调用父类初始化,注意父类可能需要`timeout`参数
super().__init__(remote_server_addr, keep_alive=keep_alive)
self.retry_count = retry_count
self.retry_delay = retry_delay
self._timeout = timeout
# 设置一个专门的logger
self.logger = logging.getLogger(__name__)
def execute(self, command, params=None):
"""
重写execute方法,加入重试逻辑。
:param command: 命令名
:param params: 参数字典
:return: 远程服务器返回的JSON响应
"""
params = params or {}
last_exception = None
# 重试循环
for attempt in range(self.retry_count + 1): # +1 包括第一次尝试
try:
self.logger.debug(f"尝试执行命令 [{command}], 参数: {json.dumps(params)[:200]}... (尝试 {attempt + 1}/{self.retry_count + 1})")
# 调用父类的execute方法执行实际的HTTP请求
response = super().execute(command, params)
self.logger.debug(f"命令 [{command}] 执行成功。")
return response
except (WebDriverException, IOError) as e:
last_exception = e
self.logger.warning(f"命令 [{command}] 第{attempt + 1}次尝试失败: {str(e)[:100]}")
if attempt < self.retry_count: # 如果不是最后一次尝试,则等待后重试
time.sleep(self.retry_delay)
else:
self.logger.error(f"命令 [{command}] 在{self.retry_count + 1}次尝试后均失败。")
raise # 重试耗尽,抛出最后的异常
# 理论上不会执行到这里,因为上面循环内会raise
raise last_exception
# 可选:重写_request方法以添加更底层的日志(如HTTP状态码、响应头)
# def _request(self, *args, **kwargs):
# self.logger.debug(f"发起HTTP请求: {args}")
# response = super()._request(*args, **kwargs)
# self.logger.debug(f"收到HTTP响应,状态码: {response.status}")
# return response
4.3 将自定义连接注入到Driver中
仅仅定义了 RetryableRemoteConnection 还不够,我们需要让 CustomAppiumDriver 使用它。这需要重写Driver的 create_connection 类方法。
# 在 custom_driver.py 中更新 CustomAppiumDriver 类
from custom_connection import RetryableRemoteConnection
class CustomAppiumDriver(AppiumWebDriver):
def __init__(self, *args, **kwargs):
# 可以从kwargs中提取自定义的连接参数,如重试次数
self.retry_count = kwargs.pop('retry_count', 3)
self.retry_delay = kwargs.pop('retry_delay', 1)
super().__init__(*args, **kwargs)
self._commands[CustomCommand.GET_SCREEN_INFO] = CustomCommand.GET_SCREEN_INFO
@classmethod
def create_connection(cls, keep_alive=True, timeout=30):
"""
重写此方法,返回我们自定义的连接类实例。
这个方法会被父类的 __init__ 调用。
"""
# 注意:这里无法直接获取到实例的 retry_count 参数,因为这是类方法。
# 一种方案是通过类属性或全局配置传递,另一种更灵活的方式是在 __init__ 中替换连接对象。
# 这里演示第二种更直接的方式:在 __init__ 中替换。
pass # 我们先保留空实现,实际工作在 __init__ 中做。
def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub',
desired_capabilities=None, browser_profile=None, proxy=None,
keep_alive=True, direct_connection=False, retry_count=3, retry_delay=1, **kwargs):
"""
扩展初始化方法,支持重试参数,并替换连接对象。
"""
# 1. 保存重试参数到实例变量
self._retry_count = retry_count
self._retry_delay = retry_delay
# 2. 调用父类初始化(此时会使用默认的RemoteConnection)
super().__init__(command_executor, desired_capabilities, browser_profile,
proxy, keep_alive, direct_connection, **kwargs)
# 3. 初始化完成后,替换掉self._command_executor内部的连接对象
# `self._command_executor` 是一个 `RemoteConnection` 实例。
# 我们创建一个新的自定义连接实例来替换它。
custom_conn = RetryableRemoteConnection(
remote_server_addr=command_executor,
keep_alive=keep_alive,
retry_count=self._retry_count,
retry_delay=self._retry_delay,
timeout=self._command_executor._timeout # 继承原有的超时设置
)
self._command_executor = custom_conn
# 4. 注册自定义命令
self._commands[CustomCommand.GET_SCREEN_INFO] = CustomCommand.GET_SCREEN_INFO
关键点解析:
- 参数传递 :我们将
retry_count和retry_delay作为Driver初始化参数传入,并在内部传递给RetryableRemoteConnection。这样使用起来非常直观。 - 替换时机 :在父类
__init__执行完毕后替换_command_executor。因为父类初始化过程中已经创建了一个默认的RemoteConnection实例,我们需要用增强版覆盖它。 - 保持兼容性 :我们复制了原有连接的
keep_alive和timeout设置,确保行为一致。
4.4 使用增强版的Driver
现在,你可以创建一个具备自动重试能力的Driver了:
from custom_driver import CustomAppiumDriver
desired_caps = {...} # 你的能力配置
# 创建Driver时指定重试参数
driver = CustomAppiumDriver(
command_executor='http://localhost:4723',
desired_capabilities=desired_caps,
retry_count=2, # 失败后重试2次
retry_delay=1.5, # 每次重试间隔1.5秒
keep_alive=True
)
try:
# 现在,所有通过这个driver发出的命令(包括find_element, click等)都自带重试机制!
el = driver.find_element(by=AppiumBy.ACCESSIBILITY_ID, value='Submit')
el.click() # 如果点击因网络问题失败,会自动重试最多2次
info = driver.get_screen_info() # 自定义命令也享受重试机制
print(info)
finally:
driver.quit()
5. 高级技巧与集成实践
掌握了基本扩展方法后,我们来看看如何将这些技巧应用到更实际的复杂场景中。
5.1 封装常用操作组合为高阶命令
自定义命令不限于单个HTTP端点。你可以封装一系列操作,形成一个高阶的“业务命令”。
例如,一个常见的场景是 等待某个元素出现并点击,如果失败则截图 。我们可以把它封装起来:
class CustomAppiumDriver(AppiumWebDriver):
... # 之前的代码
def wait_and_click(self, by, selector, timeout=10, screenshot_on_fail=True):
"""
等待元素出现并点击,失败时可选截图。
:param by: 定位策略 (AppiumBy.ID, AppiumBy.XPATH等)
:param selector: 定位器
:param timeout: 等待超时时间(秒)
:param screenshot_on_fail: 失败时是否截图
:return: 被点击的WebElement对象
"""
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
try:
self.logger.info(f"等待元素 [{by}: {selector}] 出现,超时 {timeout}秒")
element = WebDriverWait(self, timeout).until(
EC.presence_of_element_located((by, selector))
)
self.logger.info(f"元素找到,准备点击。")
element.click()
return element
except TimeoutException:
self.logger.error(f"等待元素 [{by}: {selector}] 超时。")
if screenshot_on_fail:
screenshot_path = f"screenshot_fail_{int(time.time())}.png"
self.save_screenshot(screenshot_path)
self.logger.error(f"已保存失败截图至: {screenshot_path}")
raise # 重新抛出异常,让调用者处理
5.2 与Pytest/Unittest测试框架深度集成
在自动化测试框架中,我们通常希望Driver的创建和销毁由框架管理(如 setup / teardown )。我们可以创建一个 Driver Fixture(Pytest) 或 setUpClass方法(Unittest) ,并在这里注入我们的自定义Driver。
Pytest示例:
# conftest.py
import pytest
from custom_driver import CustomAppiumDriver
@pytest.fixture(scope="function") # 每个测试函数一个driver
def appium_driver(request):
"""
提供一个配置好的自定义Appium Driver fixture。
"""
desired_caps = {
'platformName': 'Android',
'appium:platformVersion': '11',
'appium:deviceName': 'Android Emulator',
'appium:app': '/path/to/your/app.apk',
'appium:automationName': 'UiAutomator2',
'appium:noReset': False
}
# 使用自定义Driver,并传入重试参数
driver = CustomAppiumDriver(
command_executor='http://localhost:4723',
desired_capabilities=desired_caps,
retry_count=2,
retry_delay=1
)
yield driver # 将driver提供给测试用例
# 测试结束后,无论成功失败,都退出driver
driver.quit()
# 在测试用例中直接使用
def test_login(appium_driver): # pytest会自动注入fixture
driver = appium_driver
# 使用自定义方法
driver.wait_and_click(AppiumBy.ID, 'com.example:id/username_field')
driver.find_element(AppiumBy.ID, 'com.example:id/username_field').send_keys('testuser')
# ...
screen_info = driver.get_screen_info()
assert screen_info['width'] == 1080
5.3 性能考量与连接池化(高级)
对于大规模并行测试,频繁创建销毁HTTP连接( RemoteConnection )会有开销。虽然HTTP/1.1的 keep_alive 已经帮我们复用了TCP连接,但 RemoteConnection 对象本身以及其内部的 HTTPConnection 对象仍然可能被频繁创建。
一个更高级的优化是 实现一个简单的连接池 。但请注意,由于 RemoteConnection 与Driver Session紧密绑定(尤其是包含 session_id ),通常 不建议池化 RemoteConnection 实例本身 。更可行的优化是池化 Driver实例 ,特别是对于需要快速执行大量独立测试套件的场景。这涉及到更复杂的生命周期管理,通常需要与测试框架和任务调度器(如pytest-xdist, Celery)结合,超出了本篇基础教程的范围。一个简单的起点是,在 conftest.py 的fixture中使用 scope="session" ,让所有测试用例共享同一个Driver实例(需确保测试用例之间不会相互干扰)。
6. 常见问题排查与调试技巧
扩展开发过程中,难免会遇到问题。这里记录几个我踩过的坑和解决方法。
6.1 自定义命令执行失败:404或405错误
- 问题 :调用自定义命令时,Appium Server返回
404 Not Found或405 Method Not Allowed。 - 排查 :
- 检查URL和Method :首先确认你注册的命令元组
(METHOD, URL)是否正确。与Appium Server官方文档或源码中的端点定义仔细比对。注意URL路径是相对于Server根路径的。 - 检查Session ID :确保URL中的
:session_id占位符格式正确。Client会自动替换它。如果你手动拼接URL,可能会出错。 - 使用抓包工具 :用 mitmproxy 、 Charles 或 Fiddler 抓包,查看实际发出的HTTP请求的URL和方法,与Server期望的是否一致。
- 检查URL和Method :首先确认你注册的命令元组
- 示例 :如果你定义的命令是
(“GET”, “/session/:session_id/appium/device/info”),但Server实际端点可能是/wd/hub/session/:session_id/appium/device/info(如果你把Appium Server放在/wd/hub路径下)。这时需要调整URL。
6.2 连接重试机制导致测试执行时间过长
- 问题 :设置了重试后,某个本来会快速失败的命令(如元素找不到)现在会等待多次重试后才报错,拖慢了整体测试速度。
- 解决 : 区分可重试异常和不可重试异常 。不是所有异常都值得重试。
- 可重试异常 :
ConnectionRefusedError,TimeoutError,socket.error等网络层异常,或WebDriverException中表示临时服务端错误的(如UnknownError,但需谨慎)。 - 不可重试异常 :
NoSuchElementException(元素找不到)、InvalidSelectorException(选择器错误)等业务逻辑错误,重试没有意义。
- 可重试异常 :
- 优化重试逻辑 :修改
RetryableRemoteConnection.execute方法,在except块中判断异常类型。
def execute(self, command, params=None):
params = params or {}
last_exception = None
for attempt in range(self.retry_count + 1):
try:
return super().execute(command, params)
except (socket.error, ConnectionRefusedError, TimeoutError) as e:
# 网络相关异常,重试
last_exception = e
self.logger.warning(f"网络异常,第{attempt + 1}次重试...")
if attempt < self.retry_count:
time.sleep(self.retry_delay)
else:
raise
except WebDriverException as e:
# WebDriver异常,需要根据消息判断是否可重试
if "unknown error" in str(e).lower() or "internal server error" in str(e).lower():
# 可能是服务端临时错误,重试
last_exception = e
self.logger.warning(f"服务端异常,第{attempt + 1}次重试...")
if attempt < self.retry_count:
time.sleep(self.retry_delay)
else:
raise
else:
# 其他WebDriver异常(如NoSuchElement),直接抛出,不重试
raise
6.3 日志过于冗长或找不到日志
- 问题 :自定义连接类的日志没有输出,或者输出太多干扰信息。
- 解决 :
- 配置Python Logging :在你的测试脚本入口或
conftest.py中配置logging级别和格式。import logging logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') # 如果只想看自定义连接的日志,可以设置特定logger的级别 # logging.getLogger('custom_connection').setLevel(logging.DEBUG) - 控制日志级别 :在
RetryableRemoteConnection中,对于不同的操作使用不同的级别。DEBUG用于详细请求/响应,INFO用于重要步骤,WARNING用于重试,ERROR用于最终失败。 - 使用日志文件 :将日志输出到文件,便于持续集成(CI)环境查看。
file_handler = logging.FileHandler('appium_test.log') file_handler.setLevel(logging.DEBUG) formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') file_handler.setFormatter(formatter) logging.getLogger().addHandler(file_handler)
- 配置Python Logging :在你的测试脚本入口或
6.4 与Page Object Model (POM) 模式结合
自定义的Driver如何优雅地用在POM中?很简单,在你的Page类中,直接使用这个自定义Driver即可。
# base_page.py
class BasePage:
def __init__(self, driver: CustomAppiumDriver): # 类型注解提示使用自定义Driver
self.driver = driver
self.logger = logging.getLogger(self.__class__.__name__)
def find_and_click(self, by, selector):
""" 使用Driver的自定义等待点击方法 """
return self.driver.wait_and_click(by, selector)
# login_page.py
class LoginPage(BasePage):
USERNAME_FIELD = (AppiumBy.ID, 'com.example:id/username')
PASSWORD_FIELD = (AppiumBy.ID, 'com.example:id/password')
LOGIN_BUTTON = (AppiumBy.ID, 'com.example:id/login_btn')
def login(self, username, password):
self.driver.find_element(*self.USERNAME_FIELD).send_keys(username)
self.driver.find_element(*self.PASSWORD_FIELD).send_keys(password)
# 使用基类封装的方法,它内部调用了driver的自定义方法
self.find_and_click(*self.LOGIN_BUTTON)
# 也可以直接使用driver的其他自定义方法
screen_info = self.driver.get_screen_info()
self.logger.info(f"登录时屏幕信息: {screen_info}")
通过将自定义Driver作为Page类的依赖注入,你可以在整个页面对象体系中无缝使用所有扩展功能,保持代码的清晰和可维护性。
更多推荐
所有评论(0)