Page Object Model 页面对象模型
本教程共 59 篇 · 第 52 篇 · 更新于 2026-08-04 · 约 8 分钟阅读
本节目标:学完你能用页面对象模型把「页面怎么操作、元素在哪」收拢到一个类,测试写起来短、改起来快。
测试一多,满屏都是 page.locator(...) 和点击逻辑。哪天设计师改了个按钮文案,几十个测试全要改。页面对象模型(Page Object Model,常缩写为 POM)就是来治这个的:把「页面长啥样、能干啥」包进一个类。
POM 解决什么
POM 把页面(或页面的一块)抽象成一个类。它做两件事:
- 简化编写:给你一套贴合自己业务的更高级 API,比如
login()、search()。 - 简化维护:把所有定位器集中在一处,改页面只改这一个类。
NotePOM 不是 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 时,照样优先
getByRole、getByText这类抗变的写法,别用一长串 CSS 类。POM 收得了一处,但收不了一处烂定位器。
几个常踩的坑
- 别把断言塞太多进页面类。页面类负责「操作」和「暴露元素」,断言留在测试里更清楚。
- 别一个类管整个大站。按业务块拆:登录页一个类、商品列表一个类,比一个巨无霸类好维护。
- 组件级也能用 POM 思路。组件测试里的 story 其实也是一种「页面对象」——把组件状态收拢,测试只管交互。
一句话:POM 把「页面在哪、怎么动」收进一个类,测试写得更像人话,页面一改只动一处。
小结
这章用页面对象模型把选择器和操作收敛到一个类里,测试只调用 login()、search() 这类业务方法,不再满屏 locator。几个实操要点:断言留在测试里别塞进页面类,按业务块拆类别搞一个全站巨无霸,类内部选 Locator 照样优先 getByRole 这类抗变写法。POM 收得了一处定位器,收不了一处烂定位器。