首页 / Playwright 入门教程 / Page Object Model 页面对象模型

Playwright 入门教程

Page Object Model 页面对象模型

本教程共 59 篇 · 第 52 篇 · 更新于 2026-08-04 · 约 8 分钟阅读

PlaywrightPOM页面对象模型PageObject可维护性选择器收敛测试结构

52. Page Object Model 页面对象模型

本节目标:学完你能用页面对象模型把「页面怎么操作、元素在哪」收拢到一个类,测试写起来短、改起来快。

测试一多,满屏都是 page.locator(...) 和点击逻辑。哪天设计师改了个按钮文案,几十个测试全要改。页面对象模型(Page Object Model,常缩写为 POM)就是来治这个的:把「页面长啥样、能干啥」包进一个类。

POM 解决什么

POM 把页面(或页面的一块)抽象成一个类。它做两件事:

  • 简化编写:给你一套贴合自己业务的更高级 API,比如 login()search()
  • 简化维护:把所有定位器集中在一处,改页面只改这一个类。
Note

POM 不是 Playwright 独有的概念,是测试圈的老套路。Python 版 Playwright 也这么用,思路一致(Python 版见第 57 章)。

一个 TS 版的实现

拿 playwright.dev 首页举例。我们建一个 PlaywrightDevPage 类,内部持有 page,把常用元素和动作封进去:

// playwright-dev-page.ts
import { expect, type Locator, type Page } from '@playwright/test';

export class PlaywrightDevPage {
  readonly page: Page;
  readonly getStartedLink: Locator;
  readonly gettingStartedHeader: Locator;
  readonly pomLink: Locator;
  readonly tocList: Locator;

  constructor(page: Page) {
    this.page = page;
    this.getStartedLink = page.locator('a', { hasText: 'Get started' });
    this.gettingStartedHeader = page.locator('h1', { hasText: 'Installation' });
    this.pomLink = page.locator('li', { hasText: 'Guides' })
      .locator('a', { hasText: 'Page Object Model' });
    this.tocList = page.locator('article div.markdown ul > li > a');
  }

  async goto() {
    await this.page.goto('https://playwright.dev');
  }

  async getStarted() {
    await this.getStartedLink.first().click();
    await expect(this.gettingStartedHeader).toBeVisible();
  }

  async pageObjectModel() {
    await this.getStarted();
    await this.pomLink.click();
  }
}

测试里怎么用

导入这个类,在测试里 new 一个出来,调用方法就行:

import { test, expect } from '@playwright/test';
import { PlaywrightDevPage } from './playwright-dev-page';

test('getting started should contain table of contents', async ({ page }) => {
  const playwrightDev = new PlaywrightDevPage(page);
  await playwrightDev.goto();
  await playwrightDev.getStarted();
  await expect(playwrightDev.tocList).toHaveText([
    `How to install Playwright`,
    `What's installed`,
    `How to run the example test`,
    `How to open the HTML test report`,
    `Write tests using web-first assertions, fixtures and locators`,
    `Run single or multiple tests; headed mode`,
    `Generate tests with Codegen`,
    `View a trace of your tests`,
  ]);
});

test('should show Page Object Model article', async ({ page }) => {
  const playwrightDev = new PlaywrightDevPage(page);
  await playwrightDev.goto();
  await playwrightDev.pageObjectModel();
  await expect(page.locator('article')).toContainText('Page Object Model is a common pattern');
});

看出好处了吗?测试读起来像在说业务:「去首页、点开始、看目录」。元素定位器全锁在类里,改页面只动那个文件。

Python 读者看这里

思路和上面完全一样,只是语法换成「类 + self」。完整的 Python 版 POM 实现和示例,请翻第 57 章(Python 版入门与差异),那里统一讲。

POM 和「别测实现细节」不冲突

前面讲过:测试要面向用户可见的行为,别依赖函数名、CSS 类这类实现细节。POM 正好帮你守这条线——类的公开方法应该描述「用户能做的事」,而不是暴露「点了第几个 div」。

Tip

类里选 Locator 时,照样优先 getByRolegetByText 这类抗变的写法,别用一长串 CSS 类。POM 收得了一处,但收不了一处烂定位器。

几个常踩的坑

  • 别把断言塞太多进页面类。页面类负责「操作」和「暴露元素」,断言留在测试里更清楚。
  • 别一个类管整个大站。按业务块拆:登录页一个类、商品列表一个类,比一个巨无霸类好维护。
  • 组件级也能用 POM 思路。组件测试里的 story 其实也是一种「页面对象」——把组件状态收拢,测试只管交互。

一句话:POM 把「页面在哪、怎么动」收进一个类,测试写得更像人话,页面一改只动一处。

小结

这章用页面对象模型把选择器和操作收敛到一个类里,测试只调用 login()search() 这类业务方法,不再满屏 locator。几个实操要点:断言留在测试里别塞进页面类,按业务块拆类别搞一个全站巨无霸,类内部选 Locator 照样优先 getByRole 这类抗变写法。POM 收得了一处定位器,收不了一处烂定位器。