第7篇:跨端拓展!Playwright+Appium实现Web+移动端全覆盖

前言:Hello 大家好!我是励志死磕计算机~ 前面我们已经完成了Web UI自动化的企业级落地(Jenkins+Allure),但在真实企业场景中,测试需求往往不局限于Web端------移动端App、桌面端应用的测试需求同样高频。传统方案中,Web用Selenium、移动端用Appium,需要维护两套技术栈,学习成本和维护成本都很高。今天这篇文章,我们就学习当前最火的跨端自动化工具Playwright,实现"Web+移动端App"的统一测试,一套技术栈搞定多端需求,大幅提升测试效率!全程代码实战,复制即可运行,新手也能轻松上手~

本文核心目标:

  • 理解Playwright核心优势,掌握其与Selenium的差异(为什么企业越来越倾向Playwright)

  • 完成Playwright全环境搭建(Web+移动端),解决环境配置高频问题

  • 实战Playwright Web端核心操作,感受自动等待、内置截图等优势

  • 实现Playwright+Appium联动,完成移动端App(Android)自动化测试

  • 整合Playwright+Pytest+Allure,搭建跨端测试框架,复用企业级落地能力

一、前置知识:为什么选择Playwright?跨端测试核心价值

在动手操作前,先明确Playwright的核心优势和跨端测试的企业价值,避免盲目学习:

1. Playwright核心优势(对比Selenium)

对比维度 Selenium Playwright
跨端支持 仅Web端(Chrome/Firefox/Edge等) Web端、移动端(Android/iOS App)、桌面端应用(Windows/macOS/Linux)
等待机制 需手动配置显式/隐式等待,易出现不稳定问题 内置自动等待(元素可交互时才执行操作),稳定性大幅提升
驱动管理 需手动下载或用webdriver-manager管理,易出现版本不匹配 自动下载浏览器二进制文件和驱动,无需手动管理
核心功能 基础交互操作,高级功能需依赖第三方插件 内置截图/录屏、网络拦截、模拟设备、无痕模式等高级功能
学习成本 较低,但企业落地需额外封装大量工具 略高,但内置企业级功能,落地效率更高

2. 跨端测试的企业价值

  • 降低学习成本:一套技术栈搞定多端测试,无需同时掌握Selenium、Appium等工具

  • 降低维护成本:跨端框架可复用代码(如日志、报告、数据驱动模块),无需维护多套框架

  • 提升测试效率:统一的测试流程和报告,便于团队协作和项目管理

  • 契合业务需求:当前互联网产品多为"Web+App"联动,跨端测试可覆盖完整用户场景

3. 本文实战技术栈

Playwright(跨端自动化核心)+ Python(编程语言)+ Pytest(测试框架)+ Appium(移动端联动)+ Allure(测试报告)+ Android Studio(Android模拟器)

二、前置准备:全环境搭建(Web+移动端)

环境搭建是跨端测试的基础,分为"Playwright基础环境""Web端环境""移动端环境"三部分,全程图文步骤,避开配置坑:

1. Playwright基础环境搭建

Playwright的环境搭建非常简单,核心是安装Playwright库并自动下载浏览器二进制文件:

(1)安装Playwright库

打开cmd/终端,执行以下命令(建议在虚拟环境中安装,避免依赖冲突):

复制代码
# 安装Playwright(指定稳定版本)
pip install playwright==1.40.0

# 自动下载浏览器二进制文件(Chrome、Firefox、Edge)和驱动
playwright install

小技巧:如果下载缓慢,可配置国内镜像源: pip install playwright==1.40.0 -i https://pypi.tuna.tsinghua.edu.cn/simple playwright install 命令也可指定镜像:playwright install --proxy-server https://pypi.tuna.tsinghua.edu.cn/simple

(2)验证Playwright安装

创建test_playwright_install.py文件,执行以下代码,若能成功启动Chrome并访问百度,则基础环境搭建成功:

复制代码
from playwright.sync_api import sync_playwright

# 启动Playwright
with sync_playwright() as p:
    # 启动Chrome浏览器(headless=False表示显示浏览器界面)
    browser = p.chromium.launch(headless=False)
    # 创建新页面
    page = browser.new_page()
    # 访问百度
    page.goto("https://www.baidu.com")
    # 打印页面标题
    print("页面标题:", page.title())
    # 关闭浏览器
    browser.close()

运行命令:python test_playwright_install.py,输出"页面标题:百度一下,你就知道"即成功。

2. 移动端环境搭建(Android为例)

移动端测试需要依赖Android Studio(模拟器)和Appium(设备管理),步骤如下:

(1)安装Android Studio并配置模拟器
  1. 下载Android Studio:https://developer.android.google.cn/studio,按默认步骤安装(Windows需勾选"Android Virtual Device")

  2. 配置Android SDK: ① 打开Android Studio,进入"Configure"→"SDK Manager" ② 勾选"Android SDK Platform 33"(或最新稳定版本)和"Android SDK Build-Tools 33.0.2" ③ 记住SDK安装路径(如D:\Android\Sdk),后续配置环境变量用

  3. 创建Android模拟器: ① 进入"Configure"→"AVD Manager"→"Create Virtual Device" ② 选择设备类型(如Pixel 6)→ 选择系统镜像(推荐API 33)→ 完成创建 ③ 点击"启动"按钮,验证模拟器是否能正常启动

  4. 配置Android环境变量(Windows): ① 此电脑→属性→高级系统设置→环境变量→系统变量→新建 ② 变量名:ANDROID_HOME,变量值:Android SDK安装路径(如D:\Android\Sdk) ③ 编辑Path变量,添加:%ANDROID_HOME%\platform-tools 和 %ANDROID_HOME%\tools ④ 验证:打开新cmd,输入adb --version,输出版本信息即成功

(2)安装Appium并配置
  1. 下载Appium:https://github.com/appium/appium-desktop/releases,选择最新稳定版本(如1.22.3),按默认步骤安装

  2. 启动Appium:打开Appium,默认端口4723,点击"Start Server"启动服务(显示"Appium REST http interface listener started on 0.0.0.0:4723"即成功)

(3)安装移动端相关依赖
复制代码
# Appium Python客户端(Playwright联动Appium需要)
pip install appium-python-client==2.10.0

# 用于解析AndroidManifest.xml(获取App包名和Activity)
pip install xmltodict==0.13.0

3. 项目依赖更新

在之前的企业级框架根目录,更新requirements.txt文件,添加Playwright及移动端相关依赖:

复制代码
pytest==7.4.0
pytest-html==3.2.0
selenium==4.11.2
webdriver-manager==4.0.0
python-dotenv==1.0.0
openpyxl==3.1.2
pytest-rerunfailures==12.0
pytest-timeout==2.1.0
allure-pytest==2.13.5
playwright==1.40.0
appium-python-client==2.10.0
xmltodict==0.13.0

将更新后的requirements.txt推送到Git仓库,便于后续Jenkins集成。

三、核心实战一:Playwright Web端自动化测试

Playwright Web端操作与Selenium类似,但API更简洁,且内置自动等待机制,稳定性更高。我们以"百度搜索+淘宝商品搜索"为实战场景,覆盖核心操作:

1. Playwright核心API快速上手

先掌握Playwright最常用的核心API,后续实战会反复用到:

复制代码
from playwright.sync_api import sync_playwright, expect

# 1. 启动Playwright(上下文管理器模式,自动关闭资源)
with sync_playwright() as p:
    # 2. 启动浏览器(chromium/firefox/webkit,headless=False显示界面)
    browser = p.chromium.launch(headless=False, slow_mo=500)  # slow_mo=500:慢动作执行,便于观察
    # 3. 创建上下文(可设置cookie、本地存储等)
    context = browser.new_context()
    # 4. 创建新页面
    page = context.new_page()
    
    # 5. 核心操作示例
    # 访问页面
    page.goto("https://www.baidu.com")
    # 输入文本(定位方式:id选择器,自动等待元素可交互)
    page.locator("#kw").fill("Playwright 教程")
    # 点击元素(定位方式:id选择器)
    page.locator("#su").click()
    
    # 6. 断言(Playwright内置expect,更强大)
    # 断言页面标题包含"Playwright 教程"
    expect(page).to_have_title("Playwright 教程_百度搜索")
    # 断言搜索结果中包含"Playwright 官方文档"
    expect(page.locator('text="Playwright 官方文档"')).to_be_visible()
    
    # 7. 内置截图(全屏截图)
    page.screenshot(path="baidu_search_result.png", full_page=True)
    # 8. 录屏(需在创建context时开启)
    # context = browser.new_context(record_video_dir="videos/")  # 开启录屏,保存到videos目录
    # page.video.save_as("baidu_search_video.mp4")  # 保存录屏
    
    # 9. 关闭资源(上下文管理器自动关闭,也可手动关闭)
    page.close()
    context.close()
    browser.close()

2. 实战:淘宝商品搜索全流程

实现"访问淘宝→输入商品关键词→点击搜索→筛选销量排序→断言结果"全流程,覆盖Playwright高级操作(如iframe切换、悬浮操作):

复制代码
from playwright.sync_api import sync_playwright, expect
import time

def test_taobao_product_search():
    with sync_playwright() as p:
        # 启动Chrome,慢动作500ms
        browser = p.chromium.launch(headless=False, slow_mo=500)
        context = browser.new_context()
        page = context.new_page()
        
        # 1. 访问淘宝首页
        page.goto("https://www.taobao.com")
        # 等待页面加载完成(Playwright自动等待,也可手动设置等待条件)
        page.wait_for_load_state("networkidle")  # 等待网络空闲(无网络请求)
        
        # 2. 处理淘宝首页的iframe(搜索框在iframe中)
        # 切换到iframe(通过name定位)
        page.frame_locator("iframe[name='s_iframe']").locator("#q").fill("Python编程书籍")
        
        # 3. 点击搜索按钮
        page.frame_locator("iframe[name='s_iframe']").locator("button[type='submit']").click()
        
        # 4. 筛选销量排序(悬浮操作)
        # 定位销量排序元素并悬浮
        sales_sort = page.locator("a[href*='sort=sale-desc']")
        page.hover(sales_sort)
        time.sleep(1)  # 短暂等待,便于观察
        sales_sort.click()
        
        # 5. 等待搜索结果加载完成
        page.wait_for_load_state("networkidle")
        
        # 6. 断言:搜索结果中包含"Python编程书籍"
        expect(page.locator("div[class='item J_MouserOnverReq  ']")).to_have_count_greater_than(10)
        expect(page.locator("text='Python编程书籍'")).to_be_visible()
        
        # 7. 截图保存结果
        page.screenshot(path="taobao_search_result.png", full_page=True)
        
        # 关闭资源
        page.close()
        context.close()
        browser.close()

if __name__ == "__main__":
    test_taobao_product_search()

运行命令:python test_taobao_search.py,可看到浏览器自动完成整个搜索流程,截图保存在当前目录。

3. Playwright Web端核心优势总结

  • 自动等待:无需手动配置显式等待,元素可交互时才执行操作,大幅降低不稳定问题

  • 简洁的定位方式:支持CSS、XPath、文本、id等多种定位方式,API更直观(如locator("#kw"))

  • 内置高级功能:截图、录屏、iframe切换、网络拦截等功能开箱即用,无需额外封装

  • 上下文隔离:每个context独立,可实现多用户并发测试,不相互干扰

四、核心实战二:Playwright+Appium实现移动端App测试

Playwright本身支持移动端WebView测试,若需测试原生App,需与Appium联动。我们以"电商App登录+商品浏览"为实战场景,完成移动端自动化测试:

1. 前置准备:获取App包名和启动Activity

测试原生App前,需获取App的包名(package)和启动Activity(入口页面),步骤如下:

  1. 启动Android模拟器,安装测试App(如京东App,可从应用商店下载)

  2. 打开cmd,执行以下命令,启动App并获取相关信息:

    启动App(以京东App为例,包名和Activity需自行获取)

    adb shell am start -n com.jingdong.app.mall/.main.MainActivity

    获取当前运行App的包名和Activity

    adb shell dumpsys window | findstr mCurrentFocus

输出结果中,"com.jingdong.app.mall"是包名,"/.main.MainActivity"是启动Activity。

2. Playwright+Appium联动配置

通过Appium管理Android设备,Playwright通过Appium的WebDriver协议控制App,核心配置如下:

复制代码
from playwright.sync_api import sync_playwright
from appium import webdriver
from appium.options.android import UiAutomator2Options
from appium.webdriver.common.appiumby import AppiumBy
import time

def test_ecommerce_app_login():
    # 1. 配置Appium选项
    options = UiAutomator2Options()
    # 设备名称(在Android Studio的AVD Manager中查看)
    options.set_capability("deviceName", "Pixel 6 API 33")
    # 平台名称
    options.set_capability("platformName", "Android")
    # 平台版本(模拟器的Android版本)
    options.set_capability("platformVersion", "13")
    # App包名
    options.set_capability("appPackage", "com.jingdong.app.mall")
    # 启动Activity
    options.set_capability("appActivity", ".main.MainActivity")
    # 不重置App状态(避免每次启动都重新安装)
    options.set_capability("noReset", True)
    
    # 2. 连接Appium服务
    appium_driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", options=options)
    appium_driver.implicitly_wait(10)
    
    try:
        # 3. 模拟用户操作(登录京东App)
        # 点击"我的"tab(通过xpath定位)
        my_tab = appium_driver.find_element(AppiumBy.XPATH, "//*[@text='我的']")
        my_tab.click()
        time.sleep(2)
        
        # 点击"登录/注册"按钮
        login_btn = appium_driver.find_element(AppiumBy.XPATH, "//*[@text='登录/注册']")
        login_btn.click()
        time.sleep(2)
        
        # 输入手机号(通过id定位)
        phone_input = appium_driver.find_element(AppiumBy.ID, "com.jingdong.app.mall:id/et_phone")
        phone_input.send_keys("13800138000")  # 测试手机号
        time.sleep(1)
        
        # 点击"获取验证码"按钮
        verify_code_btn = appium_driver.find_element(AppiumBy.XPATH, "//*[@text='获取验证码']")
        verify_code_btn.click()
        time.sleep(2)
        
        # 输入验证码(测试环境,假设验证码为123456)
        code_input = appium_driver.find_element(AppiumBy.ID, "com.jingdong.app.mall:id/et_code")
        code_input.send_keys("123456")
        time.sleep(1)
        
        # 点击"登录"按钮
        confirm_login_btn = appium_driver.find_element(AppiumBy.XPATH, "//*[@text='登录']")
        confirm_login_btn.click()
        time.sleep(3)
        
        # 4. 断言登录成功(通过"我的订单"元素是否可见判断)
        my_order = appium_driver.find_element(AppiumBy.XPATH, "//*[@text='我的订单']")
        assert my_order.is_displayed(), "登录失败,未找到'我的订单'元素"
        print("登录成功!")
        
        # 5. 截图保存结果
        appium_driver.save_screenshot("app_login_success.png")
        
        # 6. 商品浏览(点击"首页"tab→搜索商品)
        home_tab = appium_driver.find_element(AppiumBy.XPATH, "//*[@text='首页']")
        home_tab.click()
        time.sleep(2)
        
        # 点击搜索框
        search_input = appium_driver.find_element(AppiumBy.ID, "com.jingdong.app.mall:id/search_text")
        search_input.click()
        time.sleep(1)
        
        # 输入商品关键词
        search_input.send_keys("Python编程书籍")
        time.sleep(1)
        
        # 点击搜索按钮
        search_btn = appium_driver.find_element(AppiumBy.XPATH, "//*[@text='搜索']")
        search_btn.click()
        time.sleep(3)
        
        # 断言搜索结果
        product_item = appium_driver.find_element(AppiumBy.XPATH, "//*[@class='android.widget.TextView' and contains(@text,'Python编程书籍')]")
        assert product_item.is_displayed(), "商品搜索失败"
        print("商品搜索成功!")
        
    except Exception as e:
        print(f"测试失败:{str(e)}")
        appium_driver.save_screenshot("app_test_failed.png")
        raise
    finally:
        # 7. 关闭Appium连接
        time.sleep(2)
        appium_driver.quit()

if __name__ == "__main__":
    test_ecommerce_app_login()

4. 运行移动端测试用例

  1. 启动Android模拟器

  2. 启动Appium服务(点击"Start Server")

  3. 运行测试脚本:python test_ecommerce_app_login.py

  4. 观察模拟器:自动启动京东App,完成登录、商品搜索流程,截图保存在当前目录

常见问题:若出现"设备未找到"错误,检查:① 模拟器是否启动;② adb是否能识别设备(执行adb devices查看);③ Appium配置的deviceName、platformVersion是否与模拟器一致。

五、核心实战三:跨端测试框架整合(Playwright+Pytest+Allure)

前面我们分别实现了Web端和移动端的测试,现在将两者整合到之前的企业级框架中,实现"一套框架、多端测试、统一报告"的目标。框架目录结构如下(基于第4篇的PO模式框架扩展):

复制代码
ui_auto_framework/  # 项目根目录
├── config/          # 配置层
│   └── config.py    # 全局配置(Web地址、App包名、Activity等)
├── pages/           # 页面对象层(按端划分)
│   ├── web/         # Web端页面对象
│   │   ├── base_page.py
│   │   ├── baidu_page.py
│   │   └── taobao_page.py
│   └── app/         # 移动端页面对象
│       ├── base_page.py
│       └── jd_page.py
├── tests/           # 测试用例层(按端划分)
│   ├── web/
│   │   └── test_web_search.py
│   └── app/
│       └── test_app_login.py
├── utils/           # 工具层(复用之前的工具)
│   ├── excel_utils.py
│   ├── json_utils.py
│   ├── logger.py
│   ├── screenshot_utils.py
│   └── appium_utils.py  # 新增:Appium工具封装
├── testdata/        # 测试数据层
├── logs/            # 日志输出目录
├── screenshots/     # 截图输出目录
├── allure-results/  # Allure报告原始数据
├── conftest.py      # Pytest Fixture配置(扩展跨端驱动初始化)
├── pytest.ini       # Pytest配置
└── requirements.txt # 依赖清单

1. 封装跨端驱动Fixture(conftest.py)

扩展conftest.py,添加Web端和移动端的驱动初始化Fixture,支持按标记筛选测试用例:

复制代码
# 文件名:conftest.py
import pytest
from playwright.sync_api import sync_playwright
from appium import webdriver
from appium.options.android import UiAutomator2Options
from config.config import config
from utils.logger import logger
from utils.screenshot_utils import take_screenshot

# ---------------------- Web端驱动Fixture ----------------------
@pytest.fixture(scope="function")
def web_driver():
    logger.info("开始初始化Web端驱动(Playwright)")
    with sync_playwright() as p:
        # 启动Chrome浏览器
        browser = p.chromium.launch(headless=config.HEADLESS, slow_mo=config.SLOW_MO)
        context = browser.new_context()
        page = context.new_page()
        logger.info("Web端驱动初始化完成")
        
        yield page
        
        # 后置操作:关闭驱动
        logger.info("开始关闭Web端驱动")
        page.close()
        context.close()
        browser.close()
        logger.info("Web端驱动已关闭")

# ---------------------- 移动端驱动Fixture ----------------------
@pytest.fixture(scope="function")
def app_driver():
    logger.info("开始初始化移动端驱动(Appium+Playwright)")
    # 配置Appium选项
    options = UiAutomator2Options()
    options.set_capability("deviceName", config.DEVICE_NAME)
    options.set_capability("platformName", config.PLATFORM_NAME)
    options.set_capability("platformVersion", config.PLATFORM_VERSION)
    options.set_capability("appPackage", config.APP_PACKAGE)
    options.set_capability("appActivity", config.APP_ACTIVITY)
    options.set_capability("noReset", True)
    
    # 连接Appium服务
    driver = webdriver.Remote(config.APPIUM_SERVER_URL, options=options)
    driver.implicitly_wait(config.IMPLICITLY_WAIT)
    logger.info("移动端驱动初始化完成")
    
    yield driver
    
    # 后置操作:关闭驱动
    logger.info("开始关闭移动端驱动")
    driver.quit()
    logger.info("移动端驱动已关闭")

# ---------------------- 失败截图Fixture ----------------------
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    rep = outcome.get_result()
    setattr(item, "rep_call", rep)

# 为Web端用例添加失败截图
@pytest.fixture(scope="function", autouse=True)
def web_failure_screenshot(request, web_driver):
    yield
    if request.node.rep_call.failed:
        take_screenshot(web_driver, request.node.name)

# 为移动端用例添加失败截图
@pytest.fixture(scope="function", autouse=True)
def app_failure_screenshot(request, app_driver):
    yield
    if request.node.rep_call.failed:
        take_screenshot(app_driver, request.node.name)

# ---------------------- 标记筛选 ----------------------
# 为Web端用例添加标记
def pytest_configure(config):
    config.addinivalue_line("markers", "web: 运行Web端测试用例")
    config.addinivalue_line("markers", "app: 运行移动端测试用例")

2. 封装页面对象(PO模式)

按端划分页面对象,封装重复操作,提升代码复用率:

(1)Web端页面对象(pages/web/taobao_page.py)
复制代码
# 文件名:pages/web/taobao_page.py
from playwright.sync_api import expect
from pages.web.base_page import BaseWebPage

class TaobaoPage(BaseWebPage):
    def __init__(self, page):
        super().__init__(page)
        self.search_iframe = page.frame_locator("iframe[name='s_iframe']")
        self.search_input = self.search_iframe.locator("#q")
        self.search_btn = self.search_iframe.locator("button[type='submit']")
        self.sales_sort_btn = page.locator("a[href*='sort=sale-desc']")
        self.product_item = page.locator("div[class='item J_MouserOnverReq  ']")
    
    # 访问淘宝首页
    def goto_taobao(self):
        self.page.goto("https://www.taobao.com")
        self.page.wait_for_load_state("networkidle")
    
    # 搜索商品
    def search_product(self, product_name):
        self.search_input.fill(product_name)
        self.search_btn.click()
        self.page.wait_for_load_state("networkidle")
    
    # 筛选销量排序
    def sort_by_sales(self):
        self.page.hover(self.sales_sort_btn)
        self.sales_sort_btn.click()
        self.page.wait_for_load_state("networkidle")
    
    # 断言搜索结果
    def assert_search_result(self, product_name, min_count=10):
        expect(self.product_item).to_have_count_greater_than(min_count)
        expect(self.page.locator(f"text='{product_name}'")).to_be_visible()
(2)移动端页面对象(pages/app/jd_page.py)
复制代码
# 文件名:pages/app/jd_page.py
from appium.webdriver.common.appiumby import AppiumBy
from pages.app.base_page import BaseAppPage

class JdPage(BaseAppPage):
    def __init__(self, driver):
        super().__init__(driver)
        self.my_tab = (AppiumBy.XPATH, "//*[@text='我的']")
        self.login_btn = (AppiumBy.XPATH, "//*[@text='登录/注册']")
        self.phone_input = (AppiumBy.ID, "com.jingdong.app.mall:id/et_phone")
        self.verify_code_btn = (AppiumBy.XPATH, "//*[@text='获取验证码']")
        self.code_input = (AppiumBy.ID, "com.jingdong.app.mall:id/et_code")
        self.confirm_login_btn = (AppiumBy.XPATH, "//*[@text='登录']")
        self.my_order = (AppiumBy.XPATH, "//*[@text='我的订单']")
        self.home_tab = (AppiumBy.XPATH, "//*[@text='首页']")
        self.search_input = (AppiumBy.ID, "com.jingdong.app.mall:id/search_text")
        self.search_confirm_btn = (AppiumBy.XPATH, "//*[@text='搜索']")
        self.product_item = (AppiumBy.XPATH, "//*[@class='android.widget.TextView' and contains(@text,'{product_name}')]")
    
    # 点击我的tab
    def click_my_tab(self):
        self.find_element(self.my_tab).click()
        self.driver.implicitly_wait(2)
    
    # 点击登录按钮
    def click_login_btn(self):
        self.find_element(self.login_btn).click()
        self.driver.implicitly_wait(2)
    
    # 输入手机号
    def input_phone(self, phone):
        self.find_element(self.phone_input).send_keys(phone)
        self.driver.implicitly_wait(1)
    
    # 点击获取验证码
    def click_get_verify_code(self):
        self.find_element(self.verify_code_btn).click()
        self.driver.implicitly_wait(2)
    
    # 输入验证码
    def input_verify_code(self, code):
        self.find_element(self.code_input).send_keys(code)
        self.driver.implicitly_wait(1)
    
    # 确认登录
    def confirm_login(self):
        self.find_element(self.confirm_login_btn).click()
        self.driver.implicitly_wait(3)
    
    # 断言登录成功
    def assert_login_success(self):
        assert self.find_element(self.my_order).is_displayed(), "登录失败"
    
    # 点击首页tab
    def click_home_tab(self):
        self.find_element(self.home_tab).click()
        self.driver.implicitly_wait(2)
    
    # 搜索商品
    def search_product(self, product_name):
        self.find_element(self.search_input).click()
        self.driver.implicitly_wait(1)
        self.find_element(self.search_input).send_keys(product_name)
        self.driver.implicitly_wait(1)
        self.find_element(self.search_confirm_btn).click()
        self.driver.implicitly_wait(3)
    
    # 断言商品搜索成功
    def assert_product_search_success(self, product_name):
        locator = (AppiumBy.XPATH, f"//*[@class='android.widget.TextView' and contains(@text,'{product_name}')]")
        assert self.find_element(locator).is_displayed(), "商品搜索失败"

3. 编写跨端测试用例

(1)Web端测试用例(tests/web/test_web_taobao_search.py)
复制代码
# 文件名:tests/web/test_web_taobao_search.py
import pytest
from pages.web.taobao_page import TaobaoPage
from utils.logger import logger

@pytest.mark.web  # 标记为Web端用例
class TestWebTaobaoSearch:
    def test_taobao_product_search(self, web_driver):
        logger.info("开始执行Web端淘宝商品搜索用例")
        taobao_page = TaobaoPage(web_driver)
        
        # 执行测试步骤
        taobao_page.goto_taobao()
        taobao_page.search_product("Python编程书籍")
        taobao_page.sort_by_sales()
        
        # 断言
        taobao_page.assert_search_result("Python编程书籍")
        
        logger.info("Web端淘宝商品搜索用例执行成功")
(2)移动端测试用例(tests/app/test_app_jd_login.py)
复制代码
# 文件名:tests/app/test_app_jd_login.py
import pytest
from pages.app.jd_page import JdPage
from utils.logger import logger

@pytest.mark.app  # 标记为移动端用例
class TestAppJdLogin:
    def test_jd_app_login_and_search(self, app_driver):
        logger.info("开始执行移动端京东App登录+搜索用例")
        jd_page = JdPage(app_driver)
        
        # 执行测试步骤
        jd_page.click_my_tab()
        jd_page.click_login_btn()
        jd_page.input_phone("13800138000")
        jd_page.click_get_verify_code()
        jd_page.input_verify_code("123456")
        jd_page.confirm_login()
        
        # 断言登录成功
        jd_page.assert_login_success()
        
        # 商品搜索
        jd_page.click_home_tab()
        jd_page.search_product("Python编程书籍")
        
        # 断言搜索成功
        jd_page.assert_product_search_success("Python编程书籍")
        
        logger.info("移动端京东App登录+搜索用例执行成功")

4. 运行跨端测试用例并生成Allure报告

使用Pytest命令运行指定端的测试用例,生成Allure报告:

复制代码
# 运行Web端用例,生成Allure报告
pytest tests/web/ -v -s -m web --alluredir=./allure-results --clean-alluredir

# 运行移动端用例,生成Allure报告
pytest tests/app/ -v -s -m app --alluredir=./allure-results --clean-alluredir

# 运行所有用例,生成Allure报告
pytest tests/ -v -s --alluredir=./allure-results --clean-alluredir

# 查看Allure报告
allure serve ./allure-results

运行完成后,打开Allure报告,可看到Web端和移动端用例的执行结果、截图、日志等信息,实现统一报告展示。

六、企业级跨端测试落地要点

将跨端测试框架落地到企业时,需注意以下要点,提升框架的实用性和稳定性:

1. 环境隔离与配置管理

  • 使用配置文件(config.py)管理不同环境的配置(如测试环境、生产环境的Web地址、App包名)

  • 通过环境变量区分测试端(如export TEST_ENV=web 或 export TEST_ENV=app),实现一键切换测试端

2. 测试数据分离

  • 将Web端和移动端的测试数据(如账号密码、商品关键词)存储在Excel/JSON文件中,通过数据驱动实现多组测试

  • 复用之前封装的excel_utils.py和json_utils.py工具,无需重复开发

3. CI/CD集成(Jenkins)

  • 在Jenkins中配置两个任务:一个运行Web端用例,一个运行移动端用例(或一个任务通过参数选择测试端)

  • 移动端测试需在Jenkins服务器配置Android模拟器或连接真实设备(推荐使用云设备,如Testin、阿里云测)

4. 稳定性优化

  • Web端:利用Playwright的自动等待机制,减少显式等待的使用

  • 移动端:处理手势操作(如滑动、缩放)时,使用Appium的TouchAction类,提升操作稳定性

  • 添加失败重试机制(pytest-rerunfailures),解决偶发失败问题

七、实战练习(巩固跨端框架)

基于整合后的跨端框架,完成以下练习,巩固所学知识:

  1. 新增Web端"百度搜索多组关键词"测试用例(数据驱动,Excel存储关键词)

  2. 新增移动端"京东App商品加入购物车"测试用例(封装购物车页面对象)

  3. 配置Jenkins任务,运行新增的测试用例,生成Allure报告并发送邮件通知

  4. 模拟测试失败场景,验证失败截图和日志是否正常生成

提示:练习过程中,重点关注"代码复用"和"跨端一致性",尽量复用框架已有的工具和模块,避免重复开发。遇到问题可查看Allure报告的日志和截图,快速定位问题。

八、总结与下一篇预告

本篇文章我们完成了跨端自动化测试的核心实战,总结重点:

  • Playwright是跨端测试的优选工具,支持Web+移动端+桌面端,内置自动等待、截图录屏等高级功能,稳定性和效率优于Selenium

  • 移动端测试需依赖Appium管理设备,Playwright通过Appium的WebDriver协议控制原生App

  • 跨端框架整合核心:基于PO模式按端划分页面对象,扩展Fixture实现跨端驱动初始化,复用日志、报告、数据驱动模块

  • 企业级落地要点:环境隔离、数据分离、CI/CD集成、稳定性优化

下一篇文章是系列的最后一篇,我们将进行"综合项目实战+面试冲刺",通过一个完整的电商平台跨端测试项目巩固全系列知识,同时梳理UI自动化面试高频考点和标准答案,帮你从"学会"到"会用",轻松应对求职面试!

如果这篇文章对你有帮助,别忘了点赞+收藏+关注,后续会持续更新UI自动化系列教程~ 有任何问题欢迎在评论区留言!

专栏地址:【保姆级实战】UI自动化从入门到企业落地全系列(持续更新)

相关推荐
进击的明明7 小时前
当AI开始“边想边干”:深入聊聊ReAct,以及如何用Python手搓一个简单Agent
python·面试·agent
全栈弄潮儿7 小时前
Python实战第8期:函数
后端·python·编程语言
卷无止境9 小时前
暗流与破局:AI时代的代码安全审查工具全景指南
后端·python
卷无止境12 小时前
一人公司的全栈选型指南:让 AI Agent 写得快,也让生意跑得稳
后端·python
众壹新能源科技13 小时前
逆变器自带无功不够用,加装5MVar SVG后功率因数从0.85回到0.99
运维·人工智能·自动化
滴啦嘟啦哒13 小时前
【具身笔记】【π0.5 微调与仿真】五、在MuJoCo中实现可视化仿真
笔记·python·vla·具身
全栈练习生14 小时前
AI安全与红队测试
python·ai
费曼学习法14 小时前
把「发送成功」当「送达」?我排查 IM 长消息被静默吞掉的完整过程
python·ai编程·自动化运维
鸽鸽程序猿14 小时前
【项目】【在线判题系统】b端题目增删改
开发语言·python
NetX行者14 小时前
Python 工程化入门:从脚本到规范的 Python 项目结构
网络·python