现在位置: 首页 > Playwright 教程 > 正文

Playwright 页面对象模型(POM)

本章介绍页面对象模型(Page Object Model)的设计思想和实现方式,帮助你组织大型测试套件。


什么是 POM

页面对象模型是一种测试设计模式,将页面的元素和操作封装到独立的类中。

每个页面对象代表 Web 应用中的一个页面或组件,包含该页面的定位器和操作方法。


POM 的优势

优势说明
简化测试编写测试代码使用高层 API(如 loginPage.login('user', 'pass'))而非原始的 Locator 操作
降低维护成本页面结构的变更只需要修改 Page Object 类,不需要修改每个测试
提高可读性测试代码更像业务语言,非技术人员也能理解
避免重复相同的定位器和操作只需定义一次

创建 Page Object 类

下面以 RUNOOB 网站为例,创建一个登录页面的 Page Object。

实例

// 文件路径:pages/LoginPage.ts
import { type Page, type Locator, expect } 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('用户名');
    this.passwordInput = page.getByLabel('密码');
    this.loginButton = page.getByRole('button', { name: '登录' });
    this.errorMessage = page.getByTestId('login-error');
  }

  // 导航到登录页
  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 expectLoginError(message: string) {
    await expect(this.errorMessage).toBeVisible();
    await expect(this.errorMessage).toContainText(message);
  }
}

在测试中使用 Page Object

实例

// 文件路径:tests/login.spec.ts
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

test.describe('登录功能', () => {
  test('正确用户名密码登录成功', async ({ page }) => {
    const loginPage = new LoginPage(page);

    // 使用 Page Object 的方法
    await loginPage.goto();
    await loginPage.login('runoob_user', 'correct_password');

    // 登录成功后验证跳转
    await expect(page).toHaveURL('/dashboard');
  });

  test('错误密码登录失败', async ({ page }) => {
    const loginPage = new LoginPage(page);

    await loginPage.goto();
    await loginPage.login('runoob_user', 'wrong_password');

    // 使用 Page Object 的断言方法
    await loginPage.expectLoginError('用户名或密码错误');
  });
});

多层页面对象

在真实项目中,可以创建多个 Page Object,它们之间可以相互关联。

实例

// 文件路径:pages/DashboardPage.ts
import { type Page, type Locator, expect } from '@playwright/test';

export class DashboardPage {
  readonly page: Page;
  readonly welcomeText: Locator;
  readonly logoutButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.welcomeText = page.getByText('欢迎回来');
    this.logoutButton = page.getByRole('button', { name: '退出' });
  }

  async expectWelcomeMessage(username: string) {
    await expect(this.welcomeText).toContainText(username);
  }

  async logout() {
    await this.logoutButton.click();
  }
}

页面对象的组合使用:

实例

test('完整的登录登出流程', async ({ page }) => {
  const loginPage = new LoginPage(page);
  const dashboardPage = new DashboardPage(page);

  // 登录
  await loginPage.goto();
  await loginPage.login('runoob_user', 'password');

  // 验证仪表盘
  await dashboardPage.expectWelcomeMessage('runoob_user');

  // 登出
  await dashboardPage.logout();
  await expect(page).toHaveURL('/login');
});

POM 最佳实践

实践说明
Locator 集中在构造函数所有定位器在构造函数中定义,不要在方法中动态创建
方法返回 Page Object操作方法可以返回新的 Page Object(如登录后返回 DashboardPage)
断言封装但不滥用常用的断言组合封装为方法,简单的断言直接在测试中写
不用继承用组合避免深层继承,使用组合模式关联多个 Page Object
一套对象覆盖一个页面/组件粒度适中,不要为每个小元素创建单独的类

POM 不是强制要求。

对于只有几个测试的小项目,直接在测试中写 Locator 也更清晰。当测试文件超过 5 个以上时,考虑引入 POM。