项目结构
Day 14 - Page Object Model
今日目标
- 掌握 POM 的基本思想。
- 学会把页面操作封装成类。
学习时间安排(60–120 分钟)
| 时间 | 模块 | 做什么 |
|---|---|---|
| 0-20 分钟 | 核心概念与词汇 | 读概念表,重点理解封装边界和命名语义。 |
| 20-45 分钟 | 官方文档阅读 | 读 Page object models 文档,对照官方示例。 |
| 45-75 分钟 | 实操练习 | 为 HomePage 和 LoginPage 创建 POM。 |
| 75-105 分钟 | 示例代码改写 | 把现有测试里的 locator 迁移进 Page Object。 |
| 105-120 分钟 | 复盘与作业 | 完成今日问题,检查是否过度封装。 |
核心概念与词汇
| English | 中文 | 场景用法 |
|---|---|---|
| Page Object Model (POM) | 页面对象模型 | Use it when you encapsulate a page's locators and actions into a class. |
| encapsulation | 封装 | Use it when you hide locators inside page classes so tests stay readable. |
| locator property | 定位器属性 | Use it when you declare readonly locators on a page class. |
| behavior method | 行为方法 | Use it when a method expresses a user action like login() or search(). |
| assertion method | 断言方法 | Use it when a method expresses a page state like expectLoaded(). |
goto() | 页面跳转方法 | Use it when the page object navigates to its own URL. |
| DRY | 不重复原则 | Use it when the same locator would otherwise appear in many tests. |
| maintainability | 可维护性 | Use it when UI changes should only touch one class, not many tests. |
| over-engineering | 过度封装 | Use it when a page class hides too much and becomes hard to understand. |
| reusable component | 可复用组件 | Use it when a header, footer, or widget is shared across pages. |
学习材料
- 必读:[Page object models](https://playwright.dev/docs/pom)
- 必读:[Best practices](https://playwright.dev/docs/best-practices) 中关于 POM 的说明
- 选读:[Playwright 官方 TodoMVC 示例项目](https://github.com/microsoft/playwright/tree/main/examples) 里的 POM 用法
重点理解
Page Object 示例:
import { expect, Page, Locator } from '@playwright/test';
export class HomePage {
readonly page: Page;
readonly getStartedLink: Locator;
constructor(page: Page) {
this.page = page;
this.getStartedLink = page.getByRole('link', { name: 'Get started' });
}
async goto() {
await this.page.goto('/');
}
async openIntro() {
await this.getStartedLink.click();
}
async expectLoaded() {
await expect(this.page).toHaveTitle(/Playwright/);
}
}
测试中使用:
test('open intro', async ({ page }) => {
const homePage = new HomePage(page);
await homePage.goto();
await homePage.expectLoaded();
await homePage.openIntro();
});
实操步骤
为一个页面创建 POM:
pages/HomePage.ts
pages/LoginPage.ts
要求:
- locator 放在 class 中。
- 行为方法表达业务动作。
- 断言方法表达页面状态。
- 测试用例不要出现大量底层 locator。
示例代码
// pages/LoginPage.ts
import { expect, Page, Locator } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly usernameInput: Locator;
readonly passwordInput: Locator;
readonly loginButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.usernameInput = page.getByLabel('Username');
this.passwordInput = page.getByLabel('Password');
this.loginButton = page.getByRole('button', { name: 'Login' });
this.errorMessage = page.getByText('Invalid username or password');
}
async goto() {
await this.page.goto('/login');
}
async login(username: string, password: string) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
async expectLoginFailed() {
await expect(this.errorMessage).toBeVisible();
}
}
常见坑
- 方法名叫
clickButton()而不是login(),调用方看不懂业务。 - locator 仍然散落在测试里,POM 形同虚设。
- 一个 Page 类几百行,什么组件都塞进去,没人敢改。
- 断言全塞进 POM,测试里看不到验证了什么。
- 为了“完全复用”强行继承/抽象,最后页面类之间耦合在一起。
今日产出
- 至少 1 个 Page Object。
- 至少 2 条使用 POM 的测试。
今日问题
- POM 解决什么维护问题?
- Locator 应该放在测试里还是 Page Object 里?
- Page Object 方法应该叫
clickButton()还是login()? - POM 会不会过度封装?
- 断言应该放在 POM 里还是测试里?
复盘要点
- POM 的验收标准:页面改版时,90% 的修改只发生在 page 类里。
- 方法名用业务语言(login、search、checkout),测试读起来像操作手册。
- 通用断言放 POM(expectLoaded),业务断言留测试(验证具体结果),保持平衡。
AI 时代扩展:AI 辅助 Playwright 测试
新增概念
| English | 中文 |
|---|---|
| POM generation | POM 生成 |
| component abstraction | 组件抽象 |
| naming convention | 命名规范 |
适用场景
让 AI 根据页面描述或测试代码反推 POM 骨架,或者审查现有 POM 的职责划分和命名。注意 AI 生成的 locator 必须经过真实页面验证。
可复用表达 / 提示词
我有一个登录页,包含用户名、密码、登录按钮和错误提示,请生成 LoginPage 类骨架,方法使用业务语义命名。
请审查我的 HomePage,指出过度封装、命名不当和职责越界的地方。
追问加练
- AI 生成的 POM 把所有断言都封装进去了,你怎么调整?
- 组件复用(页头、页脚)和页面类应该怎么组织?
- AI 怎么帮你判断一个方法该叫 clickButton 还是 login?
今日作业
- 完成 HomePage 和 LoginPage,把登录流程测试迁移到 POM 上跑通。
- 写一页笔记:POM 的 5 条设计原则(用今天的坑反推)。
- 让 AI 审查你的 POM 命名,统一方法命名风格。