上一篇我们完成了APP自动化测试实战。本篇将能力边界扩展到微信小程序和Web端,教你用Airtest一套工具打通移动端、小程序、Web端三大平台。
一、跨平台测试全景图
1.1 Airtest支持的平台一览
┌──────────────────────────────────────────────────────────┐
│ Airtest 跨平台能力 │
├──────────────────────────────────────────────────────────┤
│ │
│ Android APP ──── Airtest + Poco ──── 原生支持 ✅ │
│ iOS APP ──── Airtest + Poco ──── 需WDA ✅ │
│ Windows APP ──── Airtest 图像识别 ──── 原生支持 ✅ │
│ │
│ 微信小程序 ──── Poco + TBS调试 ──── 需配置 ⚙️ │
│ 微信小游戏 ──── Airtest 图像识别 ──── 原生支持 ✅ │
│ │
│ Web/Chrome ──── Airtest-Selenium ──── 插件支持 🔌 │
│ Web/IE等 ──── Selenium底层 ──── 需额外配置 ⚙️ │
│ │
│ 微信H5 ──── WebView调试 ──── 需切换内核 ⚙️ │
│ APP内嵌H5 ──── Poco WebView ──── 需配置 ⚙️ │
│ │
└──────────────────────────────────────────────────────────┘
1.2 本篇学习目标
| 目标 | 内容 |
|---|---|
| 微信小程序自动化 | 环境配置、TBS调试、Poco定位、实战脚本 |
| 微信小游戏自动化 | 图像识别方案、截图策略 |
| Web端自动化 | Airtest-Selenium安装、Chrome连接、API使用 |
| 微信H5自动化 | WebView内核切换、元素识别 |
| 跨平台脚本复用 | 统一框架设计、平台适配层 |
二、微信小程序自动化
2.1 为什么小程序测试有门槛
小程序运行在微信的封闭环境中,默认使用腾讯X5内核,与标准Android WebView不同:
普通APP: APP → Android系统 → 标准UI控件(Poco可直接识别)
小程序: 微信 → X5内核 → 小程序页面 → 自定义渲染(Poco默认无法识别)
核心问题:X5内核下的元素树与标准Android UI树不兼容,需要切换到系统WebView内核。
2.2 环境配置(关键步骤)
第一步:安装线上内核
在微信任意聊天窗口输入以下网址并打开:
http://debugtbs.qq.com/
进入TBS调试页面后:
- 点击「安装线上内核」
- 等待安装完成(约1-2分钟)
- 看到"安装成功"提示
注意 :如果
debugtbs.qq.com打不开,先用这个地址:
http://debugxweb.qq.com/?inspector=true勾选「TBS Inspector调试」选项
第二步:重启微信
1. 完全退出微信(从任务列表划掉)
2. 重新打开微信
3. 再次进入 debugtbs.qq.com 验证
4. 确认「内核信息」显示为 0 (null) 或其他非X5内核
第三步:开启小程序调试模式
在微信中:
微信 → 发现 → 小程序 → 搜索目标小程序 → 进入
→ 点击右上角「···」 → 开发调试 → 开启调试
或者在小程序代码中:
javascript
// 小程序代码中开启(开发版/体验版)
wx.setEnableDebug({
enableDebug: true
})
第四步:Airtest IDE连接
1. 用USB连接手机,确保 adb devices 可见
2. 打开Airtest IDE,连接Android设备
3. 在手机上打开目标小程序
4. 在IDE中打开Poco辅助窗
5. 选择模式:Android
6. 如果UI树加载正常,说明配置成功
2.3 Poco定位小程序元素
配置成功后,Poco辅助窗可以看到小程序的UI树:
python
# 小程序Poco初始化
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
poco = AndroidUiautomationPoco()
# 小程序元素通常是 WebView 类型
# 在UI树中会看到类似这样的结构:
# android.webkit.WebView
# └── view
# └── text
# └── text("首页")
# 定位示例
poco(text="首页").click()
poco("android.widget.Button", text="登录").click()
poco("input").set_text("hello")
2.4 小程序专属注意事项
python
# 1. 小程序页面切换是异步的,需要等待
poco(text="我的").click()
# 小程序的页面切换比原生APP慢
import time
time.sleep(2) # 等待页面切换
poco(text="订单").wait_for_appearance(timeout=15)
# 2. 小程序中的滚动用小程序专用方式
# 不要用 swipe(),用小程序页面的 scroll-view
poco("scroll-view").swipe([0.5, 0.8], [0.5, 0.2])
# 3. 小程序返回上一页
# 点击小程序导航栏返回按钮
poco(desc="返回").click()
# 或使用微信的返回
keyevent("BACK")
# 4. 输入框可能需要特殊处理
# 小程序中的 input/textarea 元素
input_element = poco("input")
input_element.click()
input_element.set_text("搜索内容")
# 5. 小程序弹窗处理
# 小程序的弹窗通常也是WebView渲染的
poco(text="确定").click()
2.5 小程序完整测试脚本
python
# -*- encoding=utf8 -*-
"""
微信小程序自动化测试 - 电商小程序示例
"""
from airtest.core.api import *
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
import time
auto_setup(__file__)
poco = AndroidUiautomationPoco()
# ========== 通用等待时间 ==========
PAGE_WAIT = 3
ELEMENT_WAIT = 15
# ========== 工具函数 ==========
def wait_for_miniprogram_page(keyword, timeout=15):
"""等待小程序页面加载"""
try:
poco(text=keyword).wait_for_appearance(timeout=timeout)
return True
except Exception:
print(f"小程序页面未加载:未找到 '{keyword}'")
return False
def handle_miniprogram_auth():
"""处理小程序授权弹窗"""
auth_buttons = [
poco(text="允许"),
poco(text="确定"),
poco(text="授权"),
poco(text="我知道了"),
]
for btn in auth_buttons:
if btn.exists():
btn.click()
time.sleep(1)
return True
return False
def switch_to_miniprogram_tab(tab_name):
"""切换小程序底部Tab"""
poco(text=tab_name).click()
time.sleep(PAGE_WAIT)
handle_miniprogram_auth()
# ========== 测试用例 ==========
class TestMiniProgram:
def setup(self):
"""前置:打开微信 → 进入小程序"""
print("=" * 40)
print("启动小程序测试")
# 方法一:通过微信首页下拉进入小程序
# 确保微信在前台
start_app("com.tencent.mm")
time.sleep(3)
# 处理微信可能的弹窗
handle_miniprogram_auth()
# 下拉打开小程序列表
swipe([0.5, 0.1], [0.5, 0.5])
time.sleep(1)
# 点击目标小程序(需要提前截图)
touch(Template(r"tpl_miniprogram_icon.png"))
time.sleep(PAGE_WAIT)
# 处理小程序授权
handle_miniprogram_auth()
print("小程序启动完成")
def teardown(self):
"""后置:返回微信首页"""
keyevent("BACK")
time.sleep(1)
keyevent("BACK")
print("已退出小程序")
def test_home_page(self):
"""用例1:验证首页加载"""
print("\n>>> 用例1:首页加载验证")
# 等待首页元素
assert wait_for_miniprogram_page("首页"), "首页加载失败"
# 验证关键元素
assert poco(text="搜索").exists(), "搜索框不存在"
assert poco(textMatches=".*推荐.*").exists(), "推荐模块不存在"
snapshot(msg="小程序首页")
print("首页加载验证通过 ✅")
def test_search(self):
"""用例2:搜索功能"""
print("\n>>> 用例2:搜索功能")
# 点击搜索框
poco(textMatches=".*搜索.*").click()
time.sleep(1)
# 输入关键词
search_input = poco("input")
search_input.set_text("手机")
# 点击搜索按钮
poco(text="搜索").click()
time.sleep(2)
# 验证结果
assert poco(textMatches=".*结果.*").wait_for_appearance(timeout=10), \
"搜索结果未加载"
# 验证有商品
products = poco("view") # 小程序中商品列表的容器
assert products.exists(), "搜索结果为空"
snapshot(msg="搜索结果页")
print("搜索功能验证通过 ✅")
def test_product_detail(self):
"""用例3:商品详情"""
print("\n>>> 用例3:商品详情")
# 点击第一个商品
first_product = poco("view")[0]
if first_product.exists():
first_product.click()
time.sleep(PAGE_WAIT)
# 验证详情页
assert poco(textMatches=".*价格.*").exists(), "商品详情页加载失败"
# 加入购物车
cart_btn = poco(textMatches=".*(加入购物车|购买).*")
if cart_btn.exists():
cart_btn.click()
time.sleep(1)
# 验证加入成功
if poco(textMatches=".*(成功|已加入).*").exists():
print("加入购物车成功 ✅")
snapshot(msg="商品详情页")
def test_cart(self):
"""用例4:购物车"""
print("\n>>> 用例4:购物车")
# 切换到购物车Tab
switch_to_miniprogram_tab("购物车")
# 验证购物车页面
assert wait_for_miniprogram_page("购物车"), "购物车页面加载失败"
# 验证有商品
cart_items = poco("view") # 购物车商品列表
if cart_items.exists():
print(f"购物车有商品")
snapshot(msg="购物车页面")
print("购物车验证通过 ✅")
def test_profile(self):
"""用例5:个人中心"""
print("\n>>> 用例5:个人中心")
switch_to_miniprogram_tab("我的")
assert wait_for_miniprogram_page("我的"), "个人中心页面加载失败"
# 验证关键元素
assert poco(textMatches=".*(订单|收藏|地址).*").exists(), \
"个人中心关键元素缺失"
snapshot(msg="个人中心页面")
print("个人中心验证通过 ✅")
def test_page_switch(self):
"""用例6:Tab切换"""
print("\n>>> 用例6:Tab切换测试")
tabs = ["首页", "分类", "购物车", "我的"]
for tab in tabs:
switch_to_miniprogram_tab(tab)
assert wait_for_miniprogram_page(tab), f"切换到 {tab} 失败"
print(f" 切换到 {tab} ✅")
print("Tab切换验证通过 ✅")
def run_all(self):
"""运行所有用例"""
self.setup()
test_cases = [
self.test_home_page,
self.test_search,
self.test_product_detail,
self.test_cart,
self.test_profile,
self.test_page_switch,
]
passed = 0
failed = 0
for test_case in test_cases:
try:
test_case()
passed += 1
except Exception as e:
failed += 1
print(f" ❌ {test_case.__name__} 失败: {e}")
snapshot(msg=f"失败_{test_case.__name__}")
self.teardown()
print("\n" + "=" * 40)
print(f"测试完成: 通过 {passed}, 失败 {failed}")
print("=" * 40)
if __name__ == "__main__":
test = TestMiniProgram()
test.run_all()
2.6 小程序常见问题排坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| UI树为空/看不到元素 | X5内核未切换 | 进入 debugtbs.qq.com 安装线上内核并重启微信 |
| 元素偶尔找不到 | 小程序页面渲染慢 | 增加等待时间,用 wait_for_appearance() |
| Poco辅助窗看不到WebView | 未开启小程序调试 | 小程序右上角 → 开发调试 → 开启 |
| 输入框无法输入 | 小程序input是WebView渲染 | 先click聚焦,再set_text |
| 截图识别不稳定 | 小程序不同版本UI有差异 | 优先用Poco,截图做兜底 |
| 切换Tab后元素找不到 | 页面未完全加载 | 等待2-3秒 + 处理可能的授权弹窗 |
| 微信更新后Poco失效 | 微信版本变更 | 重新配置TBS内核 |
| 多小程序间切换 | 微信小程序栈管理 | 用keyevent("BACK")逐层退出 |
三、微信小游戏自动化
3.1 小游戏测试方案
微信小游戏没有标准UI控件 ,全部是Canvas/WebGL渲染,所以只能用图像识别:
python
# 小游戏只能用Airtest图像识别
from airtest.core.api import *
# 连接设备
connect_device("Android:///")
# 启动微信 → 进入小游戏
start_app("com.tencent.mm")
sleep(3)
# 下拉打开小程序列表
swipe([0.5, 0.1], [0.5, 0.5])
sleep(1)
# 点击小游戏图标(需要提前截图)
touch(Template(r"tpl_game_icon.png"))
sleep(5)
# 游戏内的操作全部用图像识别
# 点击"开始游戏"
touch(Template(r"tpl_start_btn.png"))
sleep(2)
# 游戏操作
touch(Template(r"tpl_play_area.png"))
swipe([0.3, 0.5], [0.7, 0.5])
# 验证游戏画面
assert_exists(Template(r"tpl_score.png"), "验证分数显示")
3.2 小游戏截图策略
python
# 小游戏图像识别注意事项
# 1. 截图要包含足够的特征区域
# 好的截图:包含独特背景+按钮
# 坏的截图:只截按钮本身(容易误匹配)
# 2. 适当降低threshold(游戏画面变化大)
touch(Template(r"tpl_btn.png", threshold=0.6)) # 默认0.7,游戏可降到0.6
# 3. 使用RGB模式(游戏画面颜色关键)
touch(Template(r"tpl_enemy.png", rgb=True))
# 4. 弹窗和过渡动画处理
# 游戏弹窗出现时机不固定,需要循环检测
for i in range(10):
if exists(Template(r"tpl_popup.png")):
touch(Template(r"tpl_close.png"))
break
sleep(1)
四、Web端自动化(Airtest-Selenium)
4.1 Airtest-Selenium 简介
Airtest-Selenium 是 Airtest 官方推出的Web测试插件,底层封装了Selenium,但额外支持:
-
图像识别断言(截取页面区域做验证)
-
与Airtest统一报告格式
-
IDE内Selenium辅助窗
Airtest-Selenium = Selenium(元素定位 + 浏览器控制)
+ Airtest(图像识别 + 统一报告)
4.2 环境搭建
bash
# 1. 安装airtest-selenium
pip install airtest-selenium
# 2. 安装selenium
pip install selenium
# 3. 下载ChromeDriver
# 方式一:手动下载 https://chromedriver.chromium.org/
# 方式二:用webdriver-manager自动管理
pip install webdriver-manager
# 4. 确认Chrome版本与ChromeDriver匹配
# Chrome → 设置 → 关于Chrome → 查看版本号
4.3 初始化与连接
python
# 方式一:IDE连接(推荐新手)
# 在Airtest IDE中:窗口 → Selenium Window → 输入URL → 连接
# 方式二:代码连接
from airtest_selenium import WebChrome
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
# 创建Chrome浏览器驱动
driver = WebChrome()
# 或者用webdriver-manager自动管理
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service
service = Service(ChromeDriverManager().install())
driver = webdriver.Chrome(service=service)
4.4 核心API
python
from airtest_selenium import WebChrome
from airtest.core.api import *
# ========== 页面操作 ==========
# 打开网页
driver.get("https://www.baidu.com")
driver.get("https://example.com/login")
# 获取当前URL
print(driver.current_url)
# 获取页面标题
print(driver.title)
# 前进/后退/刷新
driver.back()
driver.forward()
driver.refresh()
# 窗口操作
driver.maximize_window()
driver.set_window_size(1920, 1080)
driver.close() # 关闭当前窗口
driver.quit() # 关闭浏览器
# ========== 元素定位 ==========
# 基本定位方式
from selenium.webdriver.common.by import By
driver.find_element(By.ID, "username")
driver.find_element(By.NAME, "password")
driver.find_element(By.CLASS_NAME, "login-btn")
driver.find_element(By.TAG_NAME, "input")
driver.find_element(By.CSS_SELECTOR, "#login-form .submit-btn")
driver.find_element(By.XPATH, "//button[@type='submit']")
driver.find_element(By.LINK_TEXT, "忘记密码")
driver.find_element(By.PARTIAL_LINK_TEXT, "忘记")
# 查找多个元素
driver.find_elements(By.CLASS_NAME, "product-item")
# ========== 元素操作 ==========
element = driver.find_element(By.ID, "username")
# 输入文本
element.send_keys("admin")
# 清空
element.clear()
# 点击
element.click()
# 提交表单
element.submit()
# 获取属性
element.get_attribute("value")
element.get_attribute("class")
element.get_attribute("href")
# 获取文本
element.text
# 判断状态
element.is_displayed() # 是否可见
element.is_enabled() # 是否可用
element.is_selected() # 是否选中
# ========== 等待机制 ==========
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((By.ID, "login-btn"))
)
element = wait.until(
EC.element_to_be_clickable((By.XPATH, "//button"))
)
element = wait.until(
EC.visibility_of_element_located((By.CLASS_NAME, "result"))
)
# 隐式等待(全局设置)
driver.implicitly_wait(10)
# ========== 下拉框操作 ==========
from selenium.webdriver.support.ui import Select
select = Select(driver.find_element(By.ID, "city"))
select.select_by_value("beijing")
select.select_by_visible_text("北京")
select.select_by_index(1)
# ========== 弹窗处理 ==========
# Alert弹窗
alert = driver.switch_to.alert
print(alert.text)
alert.accept() # 确定
alert.dismiss() # 取消
# ========== 窗口/iframe切换 ==========
# 切换窗口
handles = driver.window_handles
driver.switch_to.window(handles[-1])
# 切换iframe
driver.switch_to.frame("iframe_id")
driver.switch_to.frame(driver.find_element(By.TAG_NAME, "iframe"))
driver.switch_to.default_content() # 回到主页面
# ========== 截图 ==========
# Selenium截图
driver.save_screenshot("screenshot.png")
# Airtest截图(可写入报告)
snapshot(msg="页面截图")
# 元素截图
element.screenshot("element.png")
# ========== Cookie操作 ==========
# 获取所有Cookie
cookies = driver.get_cookies()
# 添加Cookie
driver.add_cookie({"name": "token", "value": "abc123"})
# 删除Cookie
driver.delete_cookie("token")
driver.delete_all_cookies()
4.5 Web自动化实战脚本
python
# -*- encoding=utf8 -*-
"""
Web自动化测试 - 电商网站登录与搜索
"""
from airtest.core.api import *
from selenium 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
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time
class WebTestBase:
"""Web测试基类"""
def __init__(self):
self.driver = None
self.wait = None
def setup(self):
"""初始化浏览器"""
print("启动Chrome浏览器...")
service = Service(ChromeDriverManager().install())
self.driver = webdriver.Chrome(service=service)
self.driver.maximize_window()
self.wait = WebDriverWait(self.driver, 15)
self.driver.implicitly_wait(10)
def teardown(self):
"""关闭浏览器"""
print("关闭浏览器...")
if self.driver:
self.driver.quit()
def find_and_click(self, by, value, msg=""):
"""等待元素可点击并点击"""
element = self.wait.until(
EC.element_to_be_clickable((by, value))
)
element.click()
if msg:
print(f"[点击] {msg}")
time.sleep(1)
return element
def find_and_input(self, by, value, text, msg=""):
"""等待元素并输入"""
element = self.wait.until(
EC.presence_of_element_located((by, value))
)
element.clear()
element.send_keys(text)
if msg:
print(f"[输入] {msg} = {text}")
return element
def get_text(self, by, value):
"""获取元素文本"""
element = self.wait.until(
EC.presence_of_element_located((by, value))
)
return element.text
def assert_text_contains(self, by, value, expected, msg=""):
"""断言元素文本包含期望值"""
actual = self.get_text(by, value)
assert expected in actual, \
f"{msg}: 期望包含'{expected}',实际'{actual}'"
print(f"[断言通过] {msg}: {actual}")
# ========== 具体测试类 ==========
class TestShoppingWebsite(WebTestBase):
def test_login(self):
"""用例1:登录测试"""
print("\n>>> 用例1:登录测试")
# 打开登录页
self.driver.get("https://example.com/login")
time.sleep(2)
# 输入用户名
self.find_and_input(By.ID, "username", "testuser", "用户名")
# 输入密码
self.find_and_input(By.ID, "password", "123456", "密码")
# 点击登录
self.find_and_click(By.CLASS_NAME, "login-btn", "登录按钮")
# 验证登录成功
self.assert_text_contains(
By.CLASS_NAME, "user-name",
"testuser",
"验证用户名显示"
)
snapshot(msg="登录成功页面")
print("登录测试通过 ✅")
def test_search(self):
"""用例2:搜索测试"""
print("\n>>> 用例2:搜索测试")
self.driver.get("https://example.com")
# 输入搜索关键词
self.find_and_input(By.ID, "search-box", "手机", "搜索关键词")
# 点击搜索
self.find_and_click(By.CLASS_NAME, "search-btn", "搜索按钮")
# 等待搜索结果
time.sleep(2)
# 验证搜索结果
results = self.driver.find_elements(By.CLASS_NAME, "product-item")
assert len(results) > 0, "搜索结果不应为空"
print(f"找到 {len(results)} 个商品")
# 验证结果包含关键词
first_name = results[0].find_element(By.CLASS_NAME, "product-name").text
assert "手机" in first_name, f"搜索结果不包含关键词:{first_name}"
snapshot(msg="搜索结果页")
print("搜索测试通过 ✅")
def test_add_to_cart(self):
"""用例3:加入购物车"""
print("\n>>> 用例3:加入购物车")
# 点击第一个商品
products = self.driver.find_elements(By.CLASS_NAME, "product-item")
products[0].click()
time.sleep(2)
# 加入购物车
self.find_and_click(By.CLASS_NAME, "add-to-cart", "加入购物车")
# 验证成功提示
self.assert_text_contains(
By.CLASS_NAME, "toast-message",
"已加入",
"加入购物车提示"
)
# 进入购物车
self.find_and_click(By.CLASS_NAME, "cart-icon", "购物车图标")
time.sleep(2)
# 验证购物车不为空
cart_items = self.driver.find_elements(By.CLASS_NAME, "cart-item")
assert len(cart_items) > 0, "购物车为空"
print(f"购物车有 {len(cart_items)} 个商品")
snapshot(msg="购物车页面")
print("加入购物车测试通过 ✅")
def test_form_validation(self):
"""用例4:表单验证"""
print("\n>>> 用例4:表单验证")
self.driver.get("https://example.com/register")
time.sleep(2)
# 不填写直接提交
self.find_and_click(By.CLASS_NAME, "submit-btn", "提交按钮")
# 验证错误提示
error_msgs = self.driver.find_elements(By.CLASS_NAME, "error-message")
assert len(error_msgs) > 0, "应该有表单验证错误提示"
for msg in error_msgs:
if msg.is_displayed():
print(f"验证提示: {msg.text}")
snapshot(msg="表单验证错误")
print("表单验证测试通过 ✅")
def run_all(self):
"""运行所有用例"""
self.setup()
test_cases = [
("登录测试", self.test_login),
("搜索测试", self.test_search),
("加入购物车", self.test_add_to_cart),
("表单验证", self.test_form_validation),
]
passed = 0
failed = 0
for name, test_func in test_cases:
try:
test_func()
passed += 1
print(f" ✅ {name}")
except Exception as e:
failed += 1
print(f" ❌ {name} 失败: {e}")
snapshot(msg=f"失败_{name}")
self.teardown()
print("\n" + "=" * 40)
print(f"Web测试完成: 通过 {passed}, 失败 {failed}")
print("=" * 40)
if __name__ == "__main__":
test = TestShoppingWebsite()
test.run_all()
五、微信H5自动化
5.1 H5测试的特殊性
微信内置浏览器打开的H5页面,本质上也是WebView,但需要额外配置:
微信H5测试路径:
微信 → 公众号文章/菜单 → H5页面(微信内置浏览器)
5.2 H5环境配置
python
# 第一步:切换WebView内核(同小程序配置)
# 在微信中访问 http://debugtbs.qq.com/
# 安装线上内核 → 重启微信
# 第二步:开启WebView调试
# 在微信中访问 http://debugxweb.qq.com/?inspector=true
# 勾选「TBS Inspector调试」
# 第三步:Chrome DevTools查看
# 在PC Chrome浏览器访问 chrome://inspect
# 可以看到微信中的WebView页面
# 点击 inspect 可以调试
# 第四步:Poco定位
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
poco = AndroidUiautomationPoco(use_webview=True)
# use_webview=True 是H5定位的关键
5.3 H5测试脚本
python
# -*- encoding=utf8 -*-
"""微信H5页面自动化测试"""
from airtest.core.api import *
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
import time
auto_setup(__file__)
# 注意:use_webview=True
poco = AndroidUiautomationPoco(use_webview=True)
# 1. 打开微信
start_app("com.tencent.mm")
time.sleep(3)
# 2. 进入公众号(或通过聊天消息打开H5链接)
# 方式一:搜索公众号
poco(text="通讯录").click()
time.sleep(1)
poco(text="公众号").click()
time.sleep(1)
poco(text="目标公众号").click()
time.sleep(2)
# 方式二:通过聊天中的链接
# 找到包含链接的聊天 → 点击链接
# 3. 进入H5页面
# 点击公众号菜单或文章中的链接
poco(text="进入商城").click()
time.sleep(5) # H5加载较慢
# 4. H5页面操作
# H5页面中的元素现在可以被Poco识别了
poco(text="登录").click()
time.sleep(2)
# 输入表单
poco("input[name='username']").set_text("testuser")
poco("input[name='password']").set_text("123456")
poco(text="提交").click()
time.sleep(2)
# 5. H5页面滚动
# H5页面内的滚动
poco("body").swipe([0.5, 0.8], [0.5, 0.2])
# 6. 截图验证
snapshot(msg="H5页面截图")
5.4 H5常见问题
| 问题 | 解决方案 |
|---|---|
| 元素无法定位 | 确认 use_webview=True |
| H5加载慢超时 | 增加timeout到30-60秒 |
| iframe中的元素找不到 | 先切换到iframe:poco("iframe").child("button") |
| 微信版本更新后失效 | 重新配置TBS内核 |
| 混合页面(原生+H5) | 需要两个Poco实例或灵活切换 |
六、跨平台统一框架设计
6.1 统一框架架构
python
"""
跨平台统一测试框架
┌──────────────┐
│ run.py │ 统一入口
└──────┬───────┘
│
┌──────▼───────┐
│ TestRunner │ 测试运行器(平台路由)
└──────┬───────┘
│
┌─────────────────┼─────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ AppDriver │ │ MiniDriver │ │ WebDriver │
│ (APP平台) │ │ (小程序) │ │ (Web平台) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ BasePage │ │ BasePage │ │ BasePage │
│ (Poco+Airtest)│ │ (Poco+Airtest)│ │ (Selenium) │
└─────────────┘ └─────────────┘ └─────────────┘
"""
6.2 平台适配器实现
python
# common/platform_adapter.py
"""
跨平台适配器:根据平台类型返回对应的驱动和操作方式
"""
from enum import Enum
class Platform(Enum):
ANDROID_APP = "android_app"
IOS_APP = "ios_app"
MINIPROGRAM = "miniprogram"
MINIGAME = "minigame"
WEB_CHROME = "web_chrome"
WECHAT_H5 = "wechat_h5"
class PlatformAdapter:
"""平台适配器"""
def __init__(self, platform: Platform):
self.platform = platform
self.driver = None
self.poco = None
self._init_platform()
def _init_platform(self):
"""根据平台初始化不同的驱动"""
if self.platform in [Platform.ANDROID_APP, Platform.IOS_APP]:
from airtest.core.api import connect_device
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
connect_device("Android:///")
self.poco = AndroidUiautomationPoco()
elif self.platform == Platform.MINIPROGRAM:
from airtest.core.api import connect_device
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
connect_device("Android:///")
# 小程序需要在手机上先打开
self.poco = AndroidUiautomationPoco()
elif self.platform == Platform.MINIGAME:
from airtest.core.api import connect_device
connect_device("Android:///")
# 小游戏只能用图像识别,不需要Poco
elif self.platform == Platform.WEB_CHROME:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
service = Service(ChromeDriverManager().install())
self.driver = webdriver.Chrome(service=service)
self.driver.maximize_window()
elif self.platform == Platform.WECHAT_H5:
from airtest.core.api import connect_device
from poco.drivers.android.uiautomation import AndroidUiautomationPoco
connect_device("Android:///")
self.poco = AndroidUiautomationPoco(use_webview=True)
def click(self, element_info):
"""统一点击方法"""
if self.platform in [Platform.ANDROID_APP, Platform.MINIPROGRAM, Platform.WECHAT_H5]:
# Poco定位
if isinstance(element_info, str):
self.poco(text=element_info).click()
else:
element_info.click()
elif self.platform == Platform.MINIGAME:
# 图像识别
from airtest.core.api import touch, Template
touch(Template(element_info))
elif self.platform == Platform.WEB_CHROME:
# Selenium定位
if isinstance(element_info, tuple):
by, value = element_info
self.driver.find_element(by, value).click()
def input_text(self, element_info, text):
"""统一输入方法"""
if self.platform in [Platform.ANDROID_APP, Platform.MINIPROGRAM, Platform.WECHAT_H5]:
if isinstance(element_info, str):
self.poco(text=element_info).set_text(text)
else:
element_info.set_text(text)
elif self.platform == Platform.WEB_CHROME:
if isinstance(element_info, tuple):
by, value = element_info
elem = self.driver.find_element(by, value)
elem.clear()
elem.send_keys(text)
def wait_for_page(self, keyword, timeout=30):
"""统一等待页面加载"""
if self.platform in [Platform.ANDROID_APP, Platform.MINIPROGRAM, Platform.WECHAT_H5]:
return self.poco(text=keyword).wait_for_appearance(timeout=timeout)
elif self.platform == Platform.WEB_CHROME:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(self.driver, timeout)
wait.until(EC.presence_of_element_located((By.TAG_NAME, "body")))
return keyword.lower() in self.driver.page_source.lower()
def teardown(self):
"""统一清理"""
if self.platform == Platform.WEB_CHROME and self.driver:
self.driver.quit()
# APP和小程序不需要特别清理
# ========== 使用示例 ==========
def create_test_for_platform(platform: Platform):
"""工厂函数:根据平台创建对应的测试"""
adapter = PlatformAdapter(platform)
if platform == Platform.ANDROID_APP:
# 返回APP测试实例
pass
elif platform == Platform.MINIPROGRAM:
# 返回小程序测试实例
pass
elif platform == Platform.WEB_CHROME:
# 返回Web测试实例
pass
七、Airtest vs 其他工具选型指南
7.1 各平台工具对比
| 平台 | Airtest方案 | 替代方案 | 推荐场景 |
|---|---|---|---|
| Android APP | Airtest+Poco | Appium | 新手友好、快速上手、游戏测试 |
| iOS APP | Airtest+Poco+WDA | Appium+XCUITest | 与Android共用脚本 |
| 微信小程序 | Airtest+Poco | Minium(微信官方) | 跨平台统一框架 |
| 微信小游戏 | Airtest(图像识别) | Minium | 唯一成熟方案 |
| Web Chrome | Airtest-Selenium | Selenium/Playwright | 需要图像断言 |
| Web 多浏览器 | Selenium原生 | Playwright/Cypress | 跨浏览器兼容 |
7.2 何时选择Airtest
选 Airtest 的场景:
✅ 新手入门自动化测试
✅ APP + 小程序 + Web 统一管理
✅ 游戏/动画/Canvas渲染的测试
✅ 需要图像识别验证UI效果
✅ 团队有Python基础
✅ 快速搭建,不需要复杂框架
不选 Airtest 的场景:
❌ 纯Web端大规模测试(推荐Playwright)
❌ 需要精细的浏览器兼容性测试(推荐Selenium Grid)
❌ 团队以Java为主(推荐Appium Java)
❌ 需要分布式大规模并发(需要额外搭建)
八、小程序云测(MiniTest)简介
8.1 什么是MiniTest
微信官方推出的小程序云测服务,与Airtest可以互补使用:
| 对比维度 | Airtest小程序测试 | 微信MiniTest |
|---|---|---|
| 定位方式 | Poco控件 + 图像识别 | Minium框架 |
| 运行环境 | 本地真机/模拟器 | 云端真机 |
| 设备覆盖 | 自己的设备 | 微信云端设备矩阵 |
| 成本 | 免费 | 按量计费 |
| 上手难度 | 中等 | 中等 |
| 报告 | Airtest报告 | 微信云测报告 |
8.2 结合使用策略
推荐策略:
1. 日常开发测试 → Airtest本地快速验证
2. 提测/上线前 → MiniTest云端多机型兼容
3. 两者脚本可以互相参考和复用
九、总结
核心知识速查
| 平台 | 核心技术 | 关键配置 | 定位方式 |
|---|---|---|---|
| Android APP | Airtest+Poco | adb连接 | Poco控件 + 图像识别 |
| 微信小程序 | Poco+WebView | TBS内核切换 | Poco(WebView元素) |
| 微信小游戏 | Airtest图像识别 | 无需Poco | 纯图像识别 |
| Web Chrome | Airtest-Selenium | ChromeDriver | Selenium + 图像断言 |
| 微信H5 | Poco WebView | use_webview=True | Poco(WebView元素) |
技能自检清单
- 能配置微信小程序自动化环境(TBS内核切换)
- 能开启小程序调试模式并定位WebView元素
- 能用图像识别测试微信小游戏
- 能搭建Airtest-Selenium Web自动化环境
- 能使用Selenium核心API(定位/等待/操作/断言)
- 能配置微信H5自动化环境(use_webview=True)
- 能设计跨平台统一测试框架
- 能根据场景选择合适的测试工具
记忆口诀
小程序测试三步走:TBS换内核、调试要开启、Poco定位WebView
小游戏测试一招鲜:图像识别打天下、截图要清晰、阈值可降低
Web测试两个库:Selenium管交互、Airtest管报告
H5测试一个参数:use_webview=True 就搞定
跨平台统一框架:适配器模式分平台、接口统一好复用
下一篇是本专题的终篇------批量运行与CI集成,教你多设备并发、命令行批量执行、Jenkins/GitHub Actions持续集成,以及生产级落地的全部实践经验,敬请期待!