大数跨境

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

吃透POM页面对象模型,Playwright自动化脚本维护效率翻倍(下) 51Testing软件测试网
2026-08-04
0
导读:POM进阶:封装交互、拦截路由、处理弹窗,一套代码复用到底,脚本维护效率翻倍

点击蓝字

关注我们

上期我们详细探究了POM的落地方案、页面操作与断言分工,这次将继续学习封装交互操作、处理路由网络请求、浏览器事件等。


在页面类里封装交互操作

POM 有一条硬性规范:所有页面交互动作都必须封装成页面类内部函数。这样我们就能在操作前后处理各类特殊场景,不用每条用例重复写前置、后置逻辑。


单纯点击按钮看起来简单,但实际开发中总会有各种额外场景需要兼容。


拿全站通用的导航栏举例,导航栏包含多项操作能力,我们需要实现三类功能:


●获取页面标题

●查看全部语言版本并完成页面跳转

●唤起搜索弹窗


拿语言下拉菜单来讲,这个下拉框必须鼠标悬浮才会展开。如果不做 POM 封装,任何需要操作下拉菜单的用例,都要重复写悬浮、等待面板渲染、点击选项整套流程。


但用上 POM 之后,我们可以封装函数自动完成悬浮、等待下拉框展示、执行点击操作。

// header_page.ts  async allLanguages(): Promise<string[]> {  await this.LANGUAGE_DROPDOWN.hover();
  return await this.LANGUAGE_DROPDOWN_LINK_LIST.allInnerTexts();}
async getCurrentLanguage(): Promise<string> { await this.LANGUAGE_DROPDOWN.hover(); await expect.soft(this.LANGUAGE_DROPDOWN_CURRENT_ITEM).toBeVisible();  return await this.LANGUAGE_DROPDOWN_CURRENT_ITEM.innerText();}
async clickLanguage(env: Languages): Promise<void> { await this.LANGUAGE_DROPDOWN.hover(); await expect.soft(this.LANGUAGE_DROPDOWN_CURRENT_ITEM).toBeVisible();  await this.LANGUAGE_DROPDOWN_LINK_LIST.getByText(env).click();}


补充说明:上面 header_page.ts 代码中用到了软断言。软断言只会校验页面状态,就算校验失败也不会直接终止整条用例运行。它能大幅减少硬编码waitForTimeout等待,同时严格遵循规范:所有硬性断言全部写在测试脚本内部。


封装完成后,测试脚本可读性极高,逻辑一目了然:

// home_tests.spec.ts  test("Supported Languages", { tag: ["@e2e", "@home"] }, async ({ page }) => {  const header = home_page.HEADER;  expect(await header.getCurrentLanguage()).toBe(Languages.NODEJS);  expect(home_page.GET_STARTED_BUTTON).toHaveAttribute("href""/docs/intro");
  await header.clickLanguage(Languages.PYTHON);  await expect(home_page.HEADER.HEADER).toHaveText("Playwright for Python");
  expect(await header.getCurrentLanguage()).toBe(Languages.PYTHON);  expect(home_page.GET_STARTED_BUTTON).toHaveAttribute(    "href",    "/python/docs/intro" );});


这里划重点:页面类里绝对不要封装可通过 Playwright 自动重试断言实现的逻辑!不要再写isButtonVisible()这类工具函数。元素可见性校验直接写在测试用例中,调用类顶层定义的定位器变量即可。


下面是打开、关闭搜索弹窗的测试用例示例:

// home_tests.spec.ts  test(  "Open/Close Search",  { tag: ["@e2e""@home""@search"] },  async ({ page }) => {    const search_dialog = await home_page.HEADER.openSearchPage();
    await expect(search_dialog.DIALOG).toBeVisible();    await search_dialog.escSearch();    await expect(search_dialog.DIALOG).not.toBeVisible();  });


在页面类中统一处理路由网络请求

接下来我们讲解搜索模块:输入关键词后会发起网络请求拉取搜索结果。


拦截、捕获网络请求是 Playwright 最亮眼的功能之一,借助它我们能跳出单纯 UI 校验,实现更深层次的业务状态校验,精准把控页面运行情况。


但使用路由拦截有一个硬性前提:必须先注册路由监听函数,再触发页面操作。行业通用标准写法是用Promise.all([])同时包裹监听注册逻辑和触发操作逻辑。


放到 POM 架构里会出现一个问题:触发页面操作的逻辑全部封装在页面类内部方法中,我们不能把重复的路由拦截代码复制粘贴到每一个点击、输入方法内。


解决方案是借助TypeScript的lambda匿名函数,TypeScript函数支持把其他函数作为入参。


我们封装一套通用工具函数,接收「操作触发器」作为参数,内部用Promise.all统一处理监听注册和页面操作。同一段路由拦截逻辑可以在所有页面类中复用,完全不会破坏 POM 封装原则。


路由请求执行完成后,我们可以把 Response 响应对象返回给测试脚本做断言,也能把响应存为页面类的成员变量。


重点提醒:必须从 Playwright 导入 Response 类型,如果使用 IDE 默认推荐的 Node 原生 Response,会直接报类型错误。

// network_request_handler.ts import { Page, Response } from "@playwright/test";
export type Fulfill = {  status?: number;  headers?: {};  body?: string;};
exportconst captureQueries = async (  pagePage,  triggerPromise<void>): Promise<Response> => {  const [response] = await Promise.all([    page.waitForResponse(      (response) =>        response.url().includes("/queries") && response.request().method() === "POST" ), trigger,  ]);
  return response;};
exportconst fulfillQueries = async (  pagePage,  triggerPromise<void>,  fulfillmentFulfill): Promise<void> => {  const [response] = await Promise.all([    page.route("**/queries"(route) => route.fulfill(fulfillment)),    trigger,  ]);};


在搜索弹窗页面类中调用这套网络工具:

// search_dialog_page.ts async enterSearch(text: string): Promise<Response> {  return await captureQueries(this.page, this.SEARCH_INPUT.fill(text));}
async enterSearchAndMockFail(text: string): Promise<void> {  await fulfillQueries(this.page, this.SEARCH_INPUT.fill(text), {    status: 400,  });}


对应的搜索功能测试用例:

// search_tests.spec.ts  test("Search Results", { tag: ["@e2e", "@search"] }, async ({ page }) => {  const search_dialog = await home_page.HEADER.openSearchPage();  await expect(search_dialog.DIALOG).toBeVisible();  const response = await search_dialog.enterSearch("test");  expect(response.status()).toBe(200);  await expect(search_dialog.RESULTS_LIST).toBeVisible();});
test(  "Search Results Invalid",  { tag: ["@e2e""@search"] },  async ({ page }) => {    const search_dialog = await home_page.HEADER.openSearchPage();    await expect(search_dialog.DIALOG).toBeVisible();    const response = await search_dialog.enterSearchAndMockFail("test");    await expect(search_dialog.RESULTS_LIST).toBeHidden();  });


在页面类统一处理浏览器事件

浏览器事件的处理逻辑和上面的路由拦截高度相似,存在同一个限制、同一套解决思路:必须先注册事件监听,再触发页面操作。


和网络响应不同,浏览器弹窗事件不会直接返回规整结构化对象。我们可以自定义 Type 类型承载弹窗信息,返回给测试脚本做断言,用法和 Response 响应对象完全一致。


可惜 Playwright 官网找不到适配演示的弹窗事件场景,下面给一套通用可落地的事件处理工具模板:

// event_handler.ts import { Page } from "@playwright/test";
export type Popup = {  messageString;};
exportconst getPopupAndDismiss = async (  pagePage,  triggerPromise<void>): Promise<Popup> => {  let message = "";  await Promise.all([    page.once("dialog"async (dialog) => {      message = dialog.message();      await dialog.dismiss();    }),    page.waitForEvent("dialog"),    trigger,  ]);
  return { message: message };};
exportconst getPopupAndAccept = async (  pagePage,  triggerPromise<void>): Promise<Popup> => {  let message = "";  await Promise.all([    page.once("dialog"async (dialog) => {      message = dialog.message();      await dialog.accept();    }),    page.waitForEvent("dialog"),    trigger,  ]);
  return { message: message };};


收尾:POM 长久稳定的核心价值

POM 专门解决大规模自动化套件落地后的维护难题。当你的用例数量增长到几十、上百条时,POM 分层架构的优势会彻底凸显出来:


●定位器、页面交互操作全部收拢在统一页面类,前端产品页面改版时,只用修改一处代码就能完成全量更新


●所有断言逻辑严格留在测试脚本内,业务流程分层清晰,每一条用例的测试目的一目了然


●借助 lambda 匿名函数封装通用工具,接口拦截、浏览器事件监听这类深度测试能力可以轻松复用


Playwright 是当下体验极佳的现代化自动化工具,而 POM 是适配所有自动化工具、经过行业长期验证的成熟设计模式。


把过往沉淀的规范经验,结合 Playwright 这类新式工具落地,我们就能搭建一套长期稳定、便于迭代维护的自动化测试框架。


聊聊你的看法

有想法、疑问都可以留言交流。如果你不知道从哪切入思考,可以参考下面几个讨论方向:


可探讨问题

●你第一次接触自动化测试代码时,遇到过哪些棘手麻烦?

●POM 能不能解决你当前自动化项目里现存的痛点?

●POM 能不能降低新人测试工程师的上手门槛,也方便开发同学读懂自动化代码?

●POM 该如何结合测试驱动开发(TDD)流程使用?


落地执行建议

●和团队同步沟通,梳理现有自动化脚本的维护痛点

●复盘存量自动化用例,找出大量重复代码,统一抽成页面类封装方法

●如果脚本频繁随机失败,排查是否存在大量硬编码等待逻辑,这类场景用 POM 重构后稳定性会大幅提升

●POM 架构处理网络拦截十分便捷,可以考虑补充接口层相关自动化校验

E n d

图片

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