Playwright UI 自动化数据治理实践:基于 POM 的幂等、可重跑架构设计

一张图看懂,一段代码落地。本文以一个"数据工场---数据资源页"的增删改查场景为例,讲解如何用 POM 分层 + 测试数据生命周期自治,解决 UI 自动化中"数据不存在、重跑报错、用例耦合、环境脏数据"等核心痛点。

图:POM + 数据生命周期自治的分层架构


一、痛点:UI 自动化不稳定的根源往往不是定位

很多团队写 UI 自动化的体验是:

"昨天能跑,今天报错;本地能跑,CI 失败;跑一次可以,跑两次就炸。"

排查一圈,最后发现问题不在 selector,而在测试数据

常见症状 根因
修改用例 报"数据不存在" 用例依赖了"别人"或"上次"留下的数据
新增用例 第二次跑报"资源名重复" 没有幂等设计,违反唯一约束
用例之间互相影响 数据未隔离、未清理
CI 越跑越慢、数据越堆越多 缺少生命周期管理
UI 断言通过但数据是假的 只验前端、未验后端

结论

UI 自动化稳定性的天花板,取决于测试数据的可控程度,而非定位技术的优劣。


二、设计原则(先立规矩)

在动手前,先约定四条铁律:

  1. 谁创建,谁销毁:每个用例对自己产生的数据全权负责。
  2. 用例间零耦合:任意单条用例都能独立、任意顺序执行。
  3. 幂等优先:重复执行 N 次,结果与执行 1 次一致。
  4. UI 交互、API/DB 校验:UI 负责"用户视角",API/DB 负责"数据真实性"。

三、目录结构

采用 pytest + Playwright 的经典分层结构:

text 复制代码
ui_test/
├── pages/
│   ├── login_page.py
│   └── data_resource_page.py     # Page Object
├── tests/
│   └── test_data_resource.py     # 测试用例
├── utils/
│   ├── data_factory.py           # 数据工厂
│   └── db_helper.py              # DB / API 清理与校验
├── test_data/
│   └── resource_data.yaml        # 参数化数据
├── conftest.py                   # fixture / 浏览器 / 登录
└── pytest.ini

各层职责清晰分离,后续维护和扩展都集中、可控。


四、POM 层:只封装行为与定位

DataResourcePage 只做元素定位 + 页面操作 + 查询不包含测试逻辑和断言(断言留给用例)。

python 复制代码
from playwright.sync_api import Page

class DataResourcePage:
    def __init__(self, page: Page):
        self.page = page

    def goto(self):
        self.page.goto("/data-resource")
        self.page.wait_for_load_state("networkidle")

    def add_resource(self, name, desc):
        self.page.click("text=新增")
        self.page.fill("#resourceName", name)
        self.page.fill("#resourceDesc", desc)
        self.page.click("text=确定")

    def edit_resource(self, old_name, new_name):
        self.page.fill("#searchInput", old_name)
        self.page.click("text=查询")
        self.page.click(f"text={old_name}")
        self.page.click("text=编辑")
        self.page.fill("#resourceName", new_name)
        self.page.click("text=确定")

    def delete_resource(self, name):
        self.page.fill("#searchInput", name)
        self.page.click("text=查询")
        self.page.click(f"text={name}")
        self.page.click("text=删除")
        self.page.click("text=确定")

    def resource_exists(self, name) -> bool:
        self.page.fill("#searchInput", name)
        self.page.click("text=查询")
        return self.page.is_visible(f"text={name}")

要点:把"判断资源是否存在"封装成方法,是后续幂等设计的关键基础设施。


五、数据工厂:统一构造入口

所有测试数据只允许从一个地方"出生",天然避免硬编码与冲突。

python 复制代码
# utils/data_factory.py
import time, uuid

class DataResourceFactory:
    @staticmethod
    def unique_name(prefix="auto_res"):
        ts = int(time.time())
        rand = uuid.uuid4().hex[:6]
        return f"{prefix}_{ts}_{rand}"

    @staticmethod
    def default_resource():
        return {
            "name": DataResourceFactory.unique_name(),
            "desc": "ui_auto_test",
            "owner": "test_user",
        }
  • 唯一后缀时间戳 + uuid)保证并发、重跑都不冲突;
  • 复杂数据集中在此构造,便于维护。

六、生命周期自治:谁创建谁销毁

利用 pytest 的 fixture,实现用例级的数据生命周期管理:创建的数据自动登记,用例结束(含失败)后统一清理。

python 复制代码
# conftest.py
import pytest
from utils.db_helper import delete_resource_by_name
from utils.data_factory import DataResourceFactory

@pytest.fixture
def resource_lifecycle():
    created_names = []          # 登记本用例产生的数据

    def builder(data=None):
        res = data or DataResourceFactory.default_resource()
        created_names.append(res["name"])
        return res

    yield builder

    # teardown:无论如何都清理
    for name in created_names:
        delete_resource_by_name(name)

db_helper.delete_resource_by_name 可走 DB 直连或内部清理接口,保证即使 UI 删除失败也能兜底清理。


七、幂等性:四大场景策略

幂等的核心不是"不报错",而是**"重复执行结果一致"**。

场景 策略
新增 唯一名(时间戳+uuid) + 前置清理
修改 前置 ensure 确保数据存在
删除 删前判断是否存在,存在才删
查询 不依赖数据顺序,按唯一标识定位

修改前"确保存在"

python 复制代码
def ensure_resource(page, name):
    page_obj = DataResourcePage(page)
    if not page_obj.resource_exists(name):
        page_obj.add_resource(name, "auto create")

修改类用例不再依赖"上一条用例留下了数据",彻底解耦。


八、完整用例:增删改查 + 幂等 + 自动清理

python 复制代码
def test_resource_crud(page, resource_lifecycle):
    res = resource_lifecycle()          # 自动登记、自动清理
    new_name = res["name"] + "_edited"

    page_obj = DataResourcePage(page)
    page_obj.goto()

    # 新增
    page_obj.add_resource(res["name"], res["desc"])
    assert page_obj.resource_exists(res["name"])

    # 修改
    page_obj.edit_resource(res["name"], new_name)
    assert page_obj.resource_exists(new_name)

    # 删除
    page_obj.delete_resource(new_name)
    assert not page_obj.resource_exists(new_name")
  • 无论执行多少次,因名称唯一 + teardown 清理,绝不违反唯一约束
  • 一条用例覆盖 CRUD,且自包含、可独立运行。

九、参数化:YAML 驱动

把测试数据从代码中剥离,支持多组数据复用:

yaml 复制代码
# test_data/resource_data.yaml
- name: res_001
  desc: 描述一
- name: res_002
  desc: 描述二
python 复制代码
import yaml
import pytest

def load_data():
    with open("test_data/resource_data.yaml") as f:
        return yaml.safe_load(f)

@pytest.mark.parametrize("data", load_data())
def test_add_resource_param(page, data, resource_lifecycle):
    res = resource_lifecycle(data)      # 用 yaml 数据,但仍走生命周期
    page_obj = DataResourcePage(page)
    page_obj.add_resource(res["name"], res["desc"])
    assert page_obj.resource_exists(res["name"])

参数化也接入 resource_lifecycle,保证每组数据都会被清理。


十、UI + API 混合校验(大厂标配)

UI 断言只能证明"前端显示了",不能证明"数据真的写进去了"。核心链路建议双校验:

python 复制代码
def test_edit_resource_hybrid(page, resource_lifecycle):
    res = resource_lifecycle()
    new_name = res["name"] + "_edited"

    page_obj = DataResourcePage(page)
    page_obj.goto()
    page_obj.add_resource(res["name"], res["desc"])
    page_obj.edit_resource(res["name"], new_name)

    # API / DB 校验数据真实落库
    assert api_get_resource(new_name) is not None
校验方式 优点 缺点
UI 断言 贴近用户操作 慢、易受前端影响
API/DB 快、可信 不覆盖前端交互

最佳实践:UI 做交互、API/DB 做数据真实性校验,兼顾稳定与覆盖。


十一、CI 与重跑策略

pytest.ini 配置:

ini 复制代码
[pytest]
addopts = -s -v --reruns 2 --reruns-delay 3

重试只对"可恢复异常" (网络抖动、元素暂未出现),不对"数据冲突 / 断言失败"重试------后者说明是设计或逻辑问题,掩盖只会积累技术债。


十二、一句话总结(可直接用于简历 / 面试)

采用 POM 分层 + 数据工厂 + Fixture 生命周期自治,通过唯一标识、前置数据准备、后置自动清理与 UI/API 双校验,保证 UI 自动化用例幂等、可重跑、零耦合,可在 CI 中稳定执行。


附:核心源码结构一览

文件 职责
pages/data_resource_page.py 元素定位、页面操作
utils/data_factory.py 唯一数据构造
utils/db_helper.py 清理、API/DB 校验
conftest.py fixture 生命周期管理
tests/test_data_resource.py 用例步骤与断言

相关推荐
11路没有终点1 天前
第12篇-并行执行与 CI/CD 集成
playwright
11路没有终点3 天前
第 6 篇:自动等待机制
playwright·ui自动化测试
11路没有终点4 天前
第 1 篇:环境搭建与第一个测试用例
playwright
2601_962284505 天前
安卓APP UI自动化测试:Python + UiAutomator2 + pytest + pytest-html
python·pytest·uiautomator2·ui自动化测试·安卓app
2601_962301336 天前
Playwright Stealth插件:绕过网站反爬虫检测的实战指南
自动化测试·数据采集·playwright·stealth插件·反爬虫检测
2601_962295586 天前
Python UI自动化测试用例脚本编写指南
python·测试用例·uiautomator2·ui自动化测试·脚本编写
weixin_440730509 天前
Playwright介绍、特点、小例子还有与传统selenium的区别
selenium·测试工具·playwright
2601_9623878214 天前
环境不稳、用例乱跳?Playwright自动化避坑大全
自动化测试·性能优化·playwright·测试环境·flakytests
晴天1614 天前
Playwright 端到端测试实战-Day34
playwright