Airtest自动化测试第五篇:小程序与Web测试——跨平台自动化全覆盖

上一篇我们完成了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. 等待安装完成(约1-2分钟)
  3. 看到"安装成功"提示

注意 :如果 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持续集成,以及生产级落地的全部实践经验,敬请期待!

相关推荐
IT_陈寒1 小时前
SpringBoot自动配置不是你以为的那样的智能
前端·人工智能·后端
yume_sibai2 小时前
大屏数据可视化 - 边框红绿呼吸灯实现详解
前端·信息可视化·typescript
测试老哥2 小时前
Pytest自动化测试详解
自动化测试·软件测试·python·测试工具·测试用例·pytest·接口测试
Hyyy2 小时前
很多Desktop都在上的Computer Use是什么
前端·llm
慢功夫2 小时前
开篇:VS Code 为什么不是一个普通 React App
前端·visual studio code
你挚爱的强哥3 小时前
Vue2 实现 1.5s 十连击监听(连续点击若干次),封装通用可复用点击检测工具,不用 data!Vue 封装通用连击监听方法,支持自定义时长与点击次数
前端·javascript·vue.js
huashengzsj3 小时前
Shopify个人建站如何搭建独立站:从零开始的完整指南
前端·网络·人工智能
CRMEB定制开发3 小时前
开源商城源码下载后怎么搭建?五步走通完整流程
小程序·开源·商城系统·私域运营
程序猿小泓3 小时前
从 Claude Code 学 Agent Harness:一个前端工程师的 AI Agent 学习笔记
前端·人工智能·学习