大数跨境

吃透POM页面对象模型,Playwright自动化脚本维护效率翻倍

吃透POM页面对象模型,Playwright自动化脚本维护效率翻倍 51Testing软件测试网
2026-07-28
3
导读:测试脚本乱成一锅粥?POM帮你理清页面操作与断言分工,一处改处处省心,维护成本直线下降。

点击蓝字

关注我们

页面对象模型(POM)给自动化测试代码搭建了一套清晰好用的设计规范。


如果你有一点写代码的经验、正在搭建自动化测试框架,或是被一堆杂乱难读的测试脚本折腾了很久,想找一套更省心的落地方案,那 POM 刚好适配你的需求,这篇文章会完整带你吃透这套思路。


什么是 POM?

POM 是适配自动化测试架构、清爽简洁的代码设计模式,一句话就能分清两类代码的分工:测试脚本负责做校验,页面对象负责执行页面操作。


说得更细致一点:测试脚本把控整体业务流程、校验应用运行状态;页面对象负责执行页面各类操作、反馈页面实时状态。


测试脚本下达指令:“选中这个按钮”

页面对象执行按钮选中操作

接着测试脚本发起校验:“弹窗有没有弹出来?”

页面对象负责获取弹窗元素


它还有一个很实用的优势:所有测试脚本共用一套可复用的页面类库。


把元素定位器、页面操作逻辑全部收拢到公共类文件之后,测试代码会更好维护,页面微调也不会大面积跑崩。


你还会发现大量高频复用逻辑,可以抽成工具类,或是直接放进全局页面类中。


上面这张架构图完整展示了 POM 四大模块各自的职责:


测试类(Test Class)

把控业务流程走向

管理页面运行状态

编写各类断言校验逻辑


全局页面类(Global Page Class)

存放全局页面对象实例

处理 iframe 嵌套页面

封装全局通用工具、超时等待方法


页面类(Page Class)

执行各类页面交互动作

统一存放所有元素定位器

处理页面特殊交互场景


工具辅助类(Helper Class)

封装可复用通用工具函数

处理各类网络请求

绑定浏览器事件监听


POM 的由来

POM 是自动化测试框架领域成熟通用的设计模式与行业标准最佳实践,诞生的初衷就是解决大型自动化测试套件难以扩容、维护成本居高不下的痛点。


早些年写自动化脚本都是逐条单独开发,几乎没有配套框架和分层架构。每条用例内部都硬编码写死元素定位、自定义操作逻辑、超时等待代码,写法和 Playwright 官方示例完全一致:

test('get started link'async ({ page }) => { await page.goto('https://playwright.dev/');
// Click the get started link.      await page.getByRole('link', { name: 'Get started' }).click();
// Expects page to have a heading with the name of Installation.      await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();});


这种写法里,每条脚本都要自己维护专属定位器和操作逻辑,项目规模一扩大根本扛不住。举个很直观的例子:


几十条用例都需要点击「Get started」链接,如果后续产品迭代把这段文案改成「Click Here!」,几十条用例会全部执行失败,你只能逐个打开脚本修改定位文本。


这会带来毁灭性的维护负担:每条测试用例几乎等同于一套独立代码工程,里面所有自定义交互逻辑都要单独调整,前端页面随便改一处内容,就要批量修改几十条测试脚本。


之后整洁代码架构、GRASP 通用职责分配设计原则慢慢渗透到自动化测试领域,行业沉淀出三条核心开发准则:


DRY(不要重复造轮子):把固定操作封装成独立函数,全量测试用例直接复用


SOLID 设计原则:按页面维度拆分独立类,类内存放该页面全部交互逻辑;严格划分权责,测试脚本只做断言、页面对象只做页面操作


KISS(保持简单易懂):制定高可读性代码标准,每个函数的功能一眼就能看懂


遵循这套规范之后,产品页面逻辑发生变更时,我们只需要修改对应页面类一处代码,不用改动成千上万条测试用例。


自动化套件可以平稳支撑超大型系统迭代,脚本稳定性大幅提升,逻辑修改全部集中处理,新增功能也能基于现有代码扩展,不用重复堆砌冗余代码。


这套适配自动化测试场景的规范合集,就是我们所说的页面对象模型(POM)。它依托面向对象设计思路,用名为「页面」的各类类文件对被测系统做建模。


我们把上面那段原生示例改写成 POM 写法,差异一眼就能看清:只需要新建统一的HomePage类,收拢全部定位器和页面操作。后续前端页面改版,仅需修改HomePage这一个文件即可。

test('get started link'async ({ page }) => {const home_page = new HomePage(page);
 await home_page.goto();
// Click the get started link.      await home_page.clickGetStarted();
// Expects page to have a heading with the name of Installation.      await expect(home_page.header).toBeVisible();});


一套更合理的落地思路

Playwright 官方标注的最佳实践,完全没有适配 POM 分层思路,甚至里面不少写法都属于POM 反模式。


Playwright 自带自动重试断言能力,官方推荐直接在测试脚本内部定义元素定位器


官方给出的定位写法,经常把断言逻辑和元素定位代码混杂在一起


官方建议网络请求、浏览器事件直接写在测试用例内部,还要求在触发页面操作前提前注册捕获监听函数


撰写本文时,Playwright 文档虽然提供了一段 POM 示例,但不仅内容写得十分简略,示例代码本身还存在多处不符合 POM 规范的问题:


在测试用例内部调用page.locator,将页面业务逻辑混入测试脚本

在函数内部修改定位器(规范要求定位器必须静态定义)

在构造函数内实例化定位器(定位器应当直接写在类顶层,才能完整触发代码智能提示)


下面我们来搭建一套分层更规范的实操示例。


Playwright 本身是一款非常优秀的现代化自动化工具,而 POM 依旧能解决绝大多数框架维护难题。


我们完全可以把两者优势结合,兼顾工具原生能力与分层架构,大幅降低长期维护成本。


拆分Playwright官网页面(划分页面类)

本次示例以 Playwright 官网作为被测站点,行业内没有硬性标准界定什么是独立「页面」,划分的核心准则只有两点:封装性、可复用性


按照这个标准,我把 Playwright 官网首页拆分成 3 个组件用于演示:


home_page.ts:存放首页全部专属页面内容与交互逻辑


header_page.ts:导航栏全站通用,首页类可以引用该文件,复用导航栏所有操作方法


search_dialog_page.ts:和导航栏一致,搜索弹窗全站通用,会弹出独立浮层,和主页面解耦


当然你也可以拆分底部栏 footer_page.ts 等其他页面组件,但本次演示用这三类就足够清晰。


上面的截图展示了 pages 文件夹目录,能看到 header_page、home_page、search_dialog_page 三个页面类文件。


搭建通用页面基类

我们先封装一套规范的页面对象构造器。老旧自动化框架会直接扩展、覆写全局页面类,但 Playwright 自带的全局 Page 对象,不建议随意修改、覆盖。


我们换一种实现方案:自定义一个通用页面基类,所有业务页面类都继承这个基类。


这个基类会完成三件核心工作:

封装 Playwright 原生全局 Page 实例,让所有子页面都能调用页面内置全部方法


优化定位器的智能提示、代码重构跳转体验


存放各类全局通用工具函数,很多子页面都会用到,比如全局统一等待超时方法

// model_page.ts import { FrameLocator, Page } from "@playwright/test";
exportclassModel_Page { readonly pagePage; readonly iFrameFrameLocator | Page;
 constructor(pagePageiFrame?: FrameLocator | Page) {this.page = page;this.iFrame = iFrame;
if (!iFrame) {this.iFrame = page; } }}


编写带静态定位器的业务页面类

接下来我们编写业务页面类。所有页面类都要继承Model_Page通用页面基类,并且必须在构造函数中调用super()完成初始化。


因为基类实例已经提前创建完成,TypeScript 支持我们直接在类顶层定义定位器常量,不用在构造函数里批量赋值只读变量。这么写有两个直观好处:


鼠标悬浮对应变量时,会直接展示完整定位器表达式


按下 F12 可以一键跳转到定位器定义位置,不用在冗长的构造函数里翻找代码


我们的首页页面类写法很简洁,包含直接跳转对应网址的逻辑,以及「Get started」按钮的定位器。


导航栏全站通用,我们可以直接在首页类中实例化导航页面对象,方便后续调用。

// home_page.ts import { Model_Page } from "@pages/model_page";import { FrameLocatorPage } from "@playwright/test";import { Header_Page } from "./header_page";
exportclassHome_PageextendsModel_Page { readonly HEADERHeader_Page; readonly GET_STARTED_BUTTON = this.page.getByRole("link", { name"Get started", });
 constructor(pagePageiFrame?: FrameLocator | Page) { super(page, iFrame);
this.HEADER = new Header_Page(this.pagethis.iFrame); }
async gotoHome(): Promise<Home_Page> { await this.page.goto("/");
returnthis; }}


依托这个页面类,我们就能写出极简的基础测试用例,完成页面打开、按钮链接地址校验:

// home_basic_tests.spec.ts import { Home_Page } from "@pages/playwright-dev/home_page";import { expect, test } from "@playwright/test";
test("Basic Home Page Test", { tag: ["@e2e""@home"] }, async ({ page }) => {const home_page = new Home_Page(page);
 await home_page.gotoHome();
 expect(home_page.GET_STARTED_BUTTON).toHaveAttribute("href""/docs/intro");});

未完待续

下期我们继续学习封装交互操作、处理路由网络请求、浏览器事件等。


【声明】内容源于网络
0
0
51Testing软件测试网
博为峰51Testing软件测试网提供各种线上招聘、线上课程等网络服务,出版软件测试系列丛书及电子杂志,组织线上技术交流活动;同时还举办多种线下公益活动,如软件测试沙龙、软件测试专场招聘会等。
内容 3939
粉丝 0
51Testing软件测试网 博为峰51Testing软件测试网提供各种线上招聘、线上课程等网络服务,出版软件测试系列丛书及电子杂志,组织线上技术交流活动;同时还举办多种线下公益活动,如软件测试沙龙、软件测试专场招聘会等。
总阅读2.8k
粉丝0
内容3.9k