掌握Playwright: 使用页面对象模型实现Web自动化的最佳实践

2023-10-26   出处: RayRun  作/译者:Luc Gagan/Sally

关键字:页面对象模型;教程;先进的;端到端测试;Playwright测试

简介:学习使用POM创建可维护的、可靠的和可伸缩的测试脚本的最佳实践。

摘要:在Web自动化领域将Playwright与页面对象模型(Page Object Model, POM)整合可以增强您的测试策略。通过遵循POM最佳实践,您可以实现生成可维护的、可靠的和可伸缩的测试脚本。让我们深入讨论POM如何提升Playwright的能力并且为所有质量工程师(Quality Assurance, QA)提供帮助。

页面对象模型的分析和解释

一个页面对象(Page Object, PO)将某一特定网页(或网页中某一区域)中的所有页面元素和交互操作封装在一个类中。这种将页面元素和操作分离有三个主要的组成部分:

  • 元素选择器:是指向网页上的特定元素的定义,可以用来选择HTML文档中的元素
  • 方法:封装与页面元素的一个或多个交互操作的函数。
  • 属性:用于存储该页面的相关信息,例如页面的URL等。

POM的核心原则在于抽象化。您不仅仅是编写测试脚本,而是构建一个直观的界面来描述应用程序的用户接口(User Interface, UI)。

理解页面对象模型的意义

使用POM模式确保了测试脚本创的条理化。其核心思想是将UI交互和元素抽象为易于管理的对象。这种抽象化确保了在UI发生更改时只需要更新其中一个位置,从而使您的测试脚本能够适应频繁的应用程序更新。

例如,登录Web应用程序Learn Playwright。可以使用POM将与登录页面相关的操作封装在一个名为SignInPage的类中而不是在每个测试中都写原始的Playwright命令。

识别页面属性

假设您正在处理 https://ray.run/signin 上的登录表单。首先,确定页面的属性:

import { type Page } from '@playwright/test';

class SignInPage {
  readonly page: Page;
  readonly url: string = 'https://ray.run/signin';

  public constructor(page: Page) {
    this.page = page;
  }
}

识别元素定位器

然后确定页面元素:

import { type Page, type Locator } from '@playwright/test';

class SignInPage {
  readonly page: Page;
  readonly url: string = 'https://ray.run/signin';
  readonly emailInputLocator: Locator;
  readonly passwordInputLocator: Locator;
  readonly signinButtonLocator: Locator;

  public constructor(page: Page) {
    this.page = page;
    page.emailInputLocator = page.getByLabel('Email');
    page.passwordInputLocator = page.getByLabel('Password');
    page.signInButtonLocator = page.getByRole('button', { name: 'Sign In' })
  }
}

识别页面元素的行为

然后确定页面元素的行为:

import { type Page, type Locator } from '@playwright/test';

class SignInPage {
  readonly page: Page;
  readonly url: string = 'https://ray.run/signin';
  readonly emailInputLocator: Locator;
  readonly passwordInputLocator: Locator;
  readonly signinButtonLocator: Locator;

  public constructor(page: Page) {
    this.page = page;
    page.emailInputLocator = page.getByLabel('Email');
    page.passwordInputLocator = page.getByLabel('Password');
    page.signInButtonLocator = page.getByRole('button', { name: 'Sign In' })
  }

  async visit() {
    await this.page.goto(this.url);
  }

  async login(email: string, password: string) {
    await this.emailInputLocator.fill(email);
    await this.passwordInputLocator.fill(password);
    await this.signInButtonLocator.click();
  }
}

识别断言

然后再标识断言:

import { type Page, type Locator, expect } from '@playwright/test';

class SignInPage {
  readonly page: Page;
  readonly url: string = 'https://ray.run/signin';
  readonly emailInputLocator: Locator;
  readonly passwordInputLocator: Locator;
  readonly signinButtonLocator: Locator;

  public constructor(page: Page) {
    this.page = page;
    page.emailInputLocator = page.getByLabel('Email');
    page.passwordInputLocator = page.getByLabel('Password');
    page.signInButtonLocator = page.getByRole('button', { name: 'Sign In' })
  }

  async visit() {
    await this.page.goto(this.url);
  }

  async login(email: string, password: string) {
    await this.emailInputLocator.fill(email);
    await this.passwordInputLocator.fill(password);
    await this.signInButtonLocator.click();
  }

  async isSignedIn() {
    await expect(this.page.getByTestId('status')).toHaveText('Signed In');
  }
}

这种结构确保了任何UI的更改只需要我们在SignInPage类中进行更新,而不是在多个测试脚本中更新。

注意我们是如何一个一个的添加每个抽象层的。这与您在真实场景中采用POM的方式类似。此外抽象程度的选择完全取决于您的测试需求。

在测试中使用页面对象

使用页面对象后,我们现在可以编写利用封装的功能测试。以下是一个使用先前定义的页面对象的示例测试:

import { test, expect } from '@playwright/test';
import { SignInPage } from './SignInPage';

test('user signs in', async ({ page }) => {
  const signInPage = new SignInPage(page);
  await signInPage.visit();
  await signInPage.login('foo@ray.run', 'bar');
  await signInPage.isSignedIn();
});

这不很整洁吗?我们已经抽象出了与页面交互的底层细节,例如找到电子邮件和密码并单击提交按钮。这使得测试代码更易于阅读并专注于页面的高级行为。

使用夹具创建页面对象

Playwright测试框架的一个重要部分就是夹具(Fixture)的概念。夹具可用于设置测试环境,并提供对浏览器和页面对象的访问。它们还可用于创建可在多个测试中使用的页面对象。

让我们看看如何使用Fixture来创建页面对象。

import { test as base } from '@playwright/test';
import { SignInPage } from './SignInPage';

export const test = base.extend<{ signInPage: SignInPage }>({
  signInPage: async ({ page }, use) => {
    const signInPage = new SignInPage(page);
    await use(signInPage);
  },
});

现在我们可以在测试中使用SignInPage 夹具来访问页面对象:

import { test, expect } from '@playwright/test';
import { SignInPage } from './SignInPage';

test('user signs in', async ({ signInPage }) => {
  await signInPage.visit();
  await signInPage.login('foo@ray.run', 'bar');
  await signInPage.isSignedIn();
});

页面对象模型的最佳实践

现在您已经很好地理解了页面对象模型以及如何在Playwright中实现它,那么让我们来看看创建页面对象的一些最佳实践。

为每个页面创建单独的页面对象

应用程序的每个页面都应该有自己的页面对象,这样可以确保代码保持组织良好且可维护,不同功能区域之间有明确的界限。

分离页面操作和断言

在页面对象中分离页面操作和断言是一种良好的做法。这使得测试的流程更容易理解,并确保页面对象可以在不同的测试中被重复使用。

例如,您可能想要将login方法写成这样:

public async login(email: string, password: string) {
  await this.emailInputLocator.fill(email);
  await this.passwordInputLocator.fill(password);
  await this.signInButtonLocator.click();
  await expect(this.page.getByTestId('status')).toHaveText('Signed In');
}

但是,将断言放在login方法中会降低代码的可重用性。

避免扩展页面对象

避免扩展页面对象是一种良好的做法。这可能导致页面对象过于臃肿,包含过多的方法和属性,从而难以维护和理解。

// ❌ 不要这样做
class AuthenticationPage {}
class SignInPage extends AuthenticationPage {}
class SignupPage extends AuthenticationPage {}

一般来说,如果您发现自己扩展了一个POM类,那就表明您需要重构您的代码。如果您发现自己重复使用POM之间的代码,那么考虑是否需要引入另一个抽象层。

保持页面对象的小型化

保持页面对象较小并专注于单个页面或页面的一小部分是一种良好的做法。这样可以确保代码保持组织良好且可维护,并在不同功能区域之间具有清晰的边界。

一般来说,避免添加不能在多个测试中重用的操作/断言。

不要在页面对象中使用状态

避免在页面对象中使用状态是一种很好的做法。这样可以确保页面对象可以在不同的测试中重复使用,并且更容易理解测试流程。

// ❌ 不要这样做
import { type Page, expect } from '@playwright/test';

class SignInPage {
  authenticated: boolean;

  // ...

  async isSignedIn() {
    await expect(this.page.getByTestId('status')).toHaveText('Signed In');

    this.authenticated = true;
  }
}

好的页面对象模型的原则

使用 Playwright 实现页面对象模型时,遵守以下原则至关重要:

  • 单一职责原则(Single Responsibility Principle, SRP):每个页面对象应该只负责一个页面或其中的一小部分。这样可以确保代码保持组织良好且可维护,并在不同功能区域之间具有清晰的边界。
  • 抽象化:页面对象应该抽象掉与页面交互的细节,例如定位器和操作元素的方法。抽象化通过提供更易读、更直观的接口减少了测试代码的脆弱性,提高了其可维护性。
  • 封装:页面对象应该封装页面的状态和行为,使人们更容易理解页面的当前状态以及可以执行的操作。这有助于保持明确的关注点分离并提高了测试的可读性。
  • 可重用性:页面对象应该被设计成为可以在不同的测试中重用,以减少代码重复,提高测试开发的效率。通过创建模块化和自包含的页面对象,您可以轻松地使用可重用的构建块来组建测试。
  • 易于理解:页面对象中方法和变量的命名应该是自解释的,使任何人都能轻松理解代码的目的和功能。清晰的和描述性的名称增强了测试的可读性和可维护性。
  • 关注点分离:测试代码应该关注页面的高级行为,而页面对象应该处理与页面交互的低级细节。这种分离允许更好的代码组织,并促进更易于维护和可伸缩的测试套件。

页面对象模型的优点

POM为QA工程师提供了许多明显的优势:

  • 可重用性:常见的页面交互被定义一次,并可在多个测试脚本中重复使用。
  • 可读性:测试变得不言自明。同事们无需深入了解UI细节就可以理解测试流程。
  • 可维护性:UI有变化?没问题。更新相应的页面对象,一切就绪!

关键的经验

  • 页面对象模型(POM)是一种抽象了页面特定属性、定位器、操作和断言的设计模式。
  • 在Playwright中实现POM包括为每个页面创建单独的类,为用户操作实现方法,并在测试中使用这些页面对象。
  • 避免常见的陷阱,例如扩展页面对象、混合操作和断言,以及创建臃肿的页面对象。

其他的资源

请记住,Playwright中页面对象模型的实现只是全面测试自动化策略的一部分。阅读Rayrun博客中的其他文章,继续探索和学习其他设计模式和最佳实践,以增强您的自动化测试工作技能。测试愉快!


声明:本文为本站编辑转载,文章版权归原作者所有。文章内容为作者个人观点,本站只提供转载参考(依行业惯例严格标明出处和作译者),目的在于传递更多专业信息,普惠测试相关从业者,开源分享,推动行业交流和进步。 如涉及作品内容、版权和其它问题,请原作者及时与本站联系(QQ:1017718740),我们将第一时间进行处理。本站拥有对此声明的最终解释权!欢迎大家通过新浪微博(@测试窝)或微信公众号(测试窝)关注我们,与我们的编辑和其他窝友交流。
213° /2138 人阅读/0 条评论 发表评论

登录 后发表评论