1. 项目背景与核心价值
在当前的测试领域,移动端和Web端的联动测试正变得越来越重要。许多业务场景需要验证用户在移动应用和网站之间的无缝体验,比如电商平台的购物流程可能从App开始,在Web端完成支付;或者社交应用需要在移动端和Web端保持消息同步。传统的测试方法往往将这两部分割裂开来,导致无法真实模拟用户跨平台的操作场景。
uiautomator2和selenium这两个框架的联动使用,恰好能解决这一痛点。uiautomator2是Android平台强大的UI自动化测试框架,而selenium则是Web自动化测试的事实标准。将它们结合起来,可以实现真正的端到端测试覆盖。
我在实际项目中多次采用这种联动方案,特别是在测试金融类App与后台管理系统、电商平台与商家后台等场景时,效果尤为显著。这种组合不仅能发现单一平台测试难以捕捉的边界问题,还能大幅提升测试效率——原本需要人工切换设备验证的流程,现在可以全自动化执行。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与框架对比
2.1 为什么选择uiautomator2
在Android自动化测试领域,常见的框架还有Appium、Espresso等。uiautomator2相比它们有几个独特优势:
- 直接访问Android框架层:不依赖WebView或混合应用环境,对纯原生App支持更好
- 无需源码:与Espresso不同,它可以在已安装的APK上直接运行测试
- 跨应用操作:能处理应用间跳转场景,比如从浏览器跳转到App
- 丰富的控件识别方式:支持resource-id、text、description等多种定位策略
实测中,uiautomator2在Android 5.0+设备上表现稳定,对复杂UI结构的解析能力较强。特别是在处理动态加载列表时,其wait机制比早期版本可靠得多。
2.2 Selenium的版本选择
Selenium目前主流有3.x和4.x两个大版本。对于联动测试场景,我推荐使用Selenium 4+版本,原因包括:
- 更好的W3C协议支持:与各浏览器驱动兼容性更佳
- 相对统一的API:减少了版本间差异带来的迁移成本
- 新增的相对定位器:对于动态元素定位更友好
- 内置的CDP支持:方便获取网络日志等调试信息
提示:如果项目需要支持老版本浏览器,可以考虑Selenium 3.141.59这个最后维护的3.x版本,但新项目建议直接从4.x起步。
2.3 联动测试的技术难点
将两个框架结合使用时,主要面临三个技术挑战:
- 会话管理:需要同时维护移动端和Web端的测试会话
- 上下文切换:测试逻辑需要在两个环境间正确切换
- 异常处理:某一边失败时如何保证另一边能正确清理
针对这些问题,我在实践中总结出了一套稳定的架构模式,将在后续章节详细说明。
3. 环境搭建与基础配置
3.1 基础环境准备
联动测试需要准备以下基础环境:
- Android测试机:建议使用Android 8.0+的真机,模拟器可能遇到性能问题
- 开发机器:安装Python 3.7+环境
- 浏览器选择:Chrome或Firefox的最新稳定版
- ADB工具:确保adb devices能正确识别设备
安装核心Python包:
bash复制pip install uiautomator2 selenium webdriver-manager
3.2 uiautomator2设备初始化
在测试代码运行前,需要初始化设备连接:
python复制import uiautomator2 as u2
# 通过设备序列号连接
d = u2.connect('123456f')
# 或者通过无线ADB连接
d = u2.connect('192.168.1.100:5555')
# 检查设备状态
print(d.info)
初始化后建议执行以下健康检查:
- 确保屏幕常亮:
d.settings['operation_delay'] = (0, 1) - 关闭动画:
d.settings['animation_delay'] = 0 - 设置隐式等待:
d.implicitly_wait(10.0)
3.3 Selenium驱动配置
使用webdriver-manager自动管理浏览器驱动:
python复制from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
options = webdriver.ChromeOptions()
options.add_argument('--ignore-certificate-errors')
options.add_argument('--disable-popup-blocking')
driver = webdriver.Chrome(
ChromeDriverManager().install(),
options=options
)
对于需要移动端浏览器测试的场景,可以启用移动模拟模式:
python复制mobile_emulation = {
"deviceMetrics": {"width": 360, "height": 640, "pixelRatio": 3.0},
"userAgent": "Mozilla/5.0 (Linux; Android 8.0.0...)"
}
options.add_experimental_option("mobileEmulation", mobile_emulation)
4. 核心联动测试模式实现
4.1 共享测试上下文设计
为了实现两个框架间的状态共享,我设计了一个TestContext类:
python复制class TestContext:
def __init__(self):
self.u2_driver = None
self.web_driver = None
self.shared_data = {}
def init_u2(self, device_id):
self.u2_driver = u2.connect(device_id)
def init_web(self, browser='chrome'):
if browser == 'chrome':
self.web_driver = webdriver.Chrome(
ChromeDriverManager().install()
)
def close_all(self):
if self.u2_driver:
self.u2_driver.app_stop_all()
if self.web_driver:
self.web_driver.quit()
这个上下文对象会在整个测试过程中维护两端的状态,包括:
- 当前活动的页面/Activity
- 需要跨平台传递的测试数据
- 统一的日志记录
4.2 典型联动场景实现
以电商应用"从App分享商品到Web"为例,演示联动测试实现:
python复制def test_share_product_to_web(context):
# 在App端操作
app = context.u2_driver
app(text="搜索").click()
app(focused=True).set_text("蓝牙耳机")
app(text="搜索").click()
app.xpath('//*[@resource-id="item_0"]').click()
# 获取商品ID用于Web端验证
product_id = app(resourceId="product_id").get_text()
context.shared_data['current_product'] = product_id
# 点击分享按钮
app(resourceId="btn_share").click()
app(text="复制链接").click()
# 切换到Web端
web = context.web_driver
web.get("https://m.example.com")
web.find_element(By.ID, "search_input").send_keys(
context.shared_data['current_product']
)
# 验证Web端是否显示相同商品
assert product_id in web.page_source
4.3 智能等待与同步机制
联动测试中最棘手的问题之一是两端操作的同步。我总结了几种有效的等待策略:
- 显式条件等待:
python复制from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
WebDriverWait(driver, 10).until(
EC.text_to_be_present_in_element(
(By.ID, "price"),
context.shared_data['expected_price']
)
)
- uiautomator2的智能等待:
python复制d(text="支付成功").wait(timeout=15.0)
- 跨框架事件通知:
python复制# 在App端完成操作后设置标记
context.shared_data['app_ready'] = True
# Web端轮询检查
while not context.shared_data.get('app_ready'):
time.sleep(
