备受困扰的页面对象模型

1 天前   出处: medium  作/译者:James Lloyd/ 溜的一比

注: 我最初在2023年初撰写了这篇文章,那时我还不知道我的Xebia任务会将我带到哪里。这些任务与测试Web应用程序无关,但它们确实让我足够忙碌,以至于忘记发布这篇博客。它包含了一些关于如何架构Web测试的良好见解,因此我决定在这里发布。

当我第一次加入Xebia时,发现许多同事都参与了自动化Cypress测试,最近还使用了Playwright。我听到很多对页面对象模型(对于Java程序员来说,令人困惑地也称为POM)的批评。

我模糊记得,十年前POM是编写Selenium测试的一大进步。我大部分的浏览器自动化经验也同样分布在Cypress和TestCafe之间,后者对页面对象建模有明确的支持。发生了什么?为什么POM突然(对我来说)不再流行了?

当我向一位受人尊敬的同事提起这个问题时,他澄清了自己对POM应用的感受,主要集中在以下几点:

测试库选择器更清晰

以前的浏览器自动化框架需要使用深奥的CSS选择器来与页面交互。在POM中将这些选择器隐藏在可读的名称后面,可以使测试更清晰。

如今,测试库风格的选择器得到了广泛支持,并且更接近实际的用户体验和流程。直接在测试中使用它们时,本质上更清晰,因此降低了不熟悉整个代码库的人引入新测试的摩擦。

以下是一个更完整的示例,但现在请比较以下内容(CSS风格选择器),你可能会将其隐藏在POM的方法中:

cy.get("input[name='user']").type(username);

以及你可以直接在测试本身中编写的类似函数:

cy.findByRole("textbox", { name: "username" }).type("user");

虽然在初次阅读时它们可能相似,但第一个实际上需要了解页面的结构、用于密码字段的标签等。第二个使用的是测试库风格的选择器,可以在对实际页面了解甚少的情况下编写。

拥有“扁平”的、透明的测试,其中行为没有被抽象化——特别是在开发初期——也可能使测试更快地适应快速变化的被测试应用程序(AUT)。

测试库选择器更健壮

测试库选择器已经是对HTML和CSS实现细节的抽象。通过按“角色”、标签或其他可访问性特性进行选择,你的测试将默认更加健壮——因此,减少了将它们抽象到公共POM中的需求。

作为额外的好处,使用利用可访问性特性(如角色)的选择器,你的测试将帮助团队在整个代码库中编写更“可访问”的代码——不仅仅是测试!因此,你将能够在开发过程中从TA/QA/开发人员那里提供早期的UX/可访问性反馈,而无需额外的可访问性专家。这种类型的早期反馈往往被低估。

POM可能成为意外的资源消耗

许多测试工程师曾被诱惑过度设计他们的页面对象模型。这可能以多种形式出现,但实践中最常见的一种是管理实际屏幕与POM类之间的“状态”所带来的痛苦。随着你尝试与异步代码和行为集成,例如在Cypress中实际执行操作被延迟,这变得更加复杂。

再加上,现代应用程序中的页面概念比过去更加流动——你会发现自己更频繁地在各种“页面”之间移动行为。现代应用程序更容易围绕组件和流程的概念进行建模。

更完整的示例

POM方式:

// 不同的文件
class LoginPage {
  navigateTo() {
    cy.visit("/login");
  }

  enterUsername(username) {
    // 绑定到实际的input name属性;通过将'user'重构为'username'
    // 需要在没有任何功能性差异的情况下更新测试
    cy.get("input[name='user']").type(username);
  }

  enterPassword(password) {
    // 可能是最常用的方式;如果代码被重构并且
    // data-testid属性没有完全映射到相同的元素
    // 测试将再次失败
    cy.get("[data-testid='password']").type(password);
  }

  submit() {
    // 不明确的选择器;难以重构
    cy.get(".submit").click();
  }
}

// 测试
const LoginPage = require("./LoginPage");
describe("Login", () => {
  let loginPage;

  beforeEach(() => {
    loginPage = new LoginPage();
  });

  it("should allow a user to log in", () => {
    loginPage.navigateTo();
    loginPage.enterUsername("user1");
    loginPage.enterPassword("password1");
    loginPage.submit();
  });
});

测试库方式:

describe("Login", () => {
  it("should allow a user to log in", () => {
    cy.visit("/login");
    // 以下将使用label元素文本、aria-labelledby属性中引用的节点、aria-label属性等任何其他“可访问名称”
    // 参见:https://www.w3.org/TR/accname-1.1/
    cy.findByRole("textbox", { name: "username" }).type("user");
    // 对于表单页面来说可能更具可读性,下面的将使用标签文本查找输入;对标签的存在/可见性有隐含的断言,并且能够使用正则表达式
    // 可以使其在文本更改时更健壮
    cy.findByLabelText(/password/i).type("password");
    // 最后,我们还在点击之前断言存在包含“Submit”文本的按钮类元素
    cy.findByRole("button", { name: /Submit/i }).click();
  });
});

我的收获

通过这次讨论,我了解到,虽然页面对象模型本身并不一定不好,但它也不应该成为你的测试的默认架构模式。最好的情况下,它并没有比“扁平”测试提供更多的优势,最坏的情况下,你可能会花费更多时间让模型在异步环境中工作,而不是编写更有价值的东西——测试本身。

也就是说,将行为封装到可重复的动作中,如页面动作模型或自定义Cypress命令,始终有其位置。我的同事还提到,Cypress 12中有一个新功能,可以通过自定义查询来表达应用程序状态,这可能暗示了架构测试的其他方法。

感谢我的Xebia同事们对现代浏览器测试自动化架构和方法的见解——以及大部分本文材料!


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

登录 后发表评论
最新文章