当您针对网页编写测试时,您需要参考该网页中的元素以单击链接并确定显示的内容。但是,如果您编写直接操作 HTML 元素的测试用例,则您的测试将无法应对 UI 中的频繁修改。PageObject对应于一个HTML网页、页面组件或者一个特定的API接口,PageObject可以直接操作特定的业务场景如:注册、登录等,取代之前直接对HTML标签进行操作的流程。
PageObject最基本的概念是它可以让软件客户端操作用户看到的或者用户可以操作的任何东西。它还应该提供易于操作的接口,并且对开发者来说隐藏内部逻辑。因此,要访问文本字段,您应该具有获取并返回字符串的访问器方法,复选框应使用布尔值,按钮应由面向操作的方法名称表示。
PageObject应该是封装用户感知到的界面、操作等机制。比较好的就是对外封装统一的接口定义,内部实现的改动不会影响到整体框架接口的改动。
尽管是将“Page”作为对象,但这些对象通常不应该为每个页面构建,而是为页面上的重要元素构建。因此,显示多个专辑的页面将有一个专辑列表的PageObject,其中包含多个专辑的PageObject。可能还有一个页眉的PageObject和一个页脚的PageObject。也就是说,复杂 UI 的某些层次结构只是为了构建 UI - 这种复合结构不应该由单一的PageObject表示。最好是对页面中会和用户有交互的结构建模。
同样,如果您导航到另一个页面,初始PageObject应该为新页面返回另一个PageObject。通常PageObject操作应该返回基本类型(字符串、日期)或其他PageObject。
对于PageObject是否应该包含断言本身,或者只是为测试脚本提供数据来执行断言,存在不同意见。在PageObject中包含断言的倡导者说,这有助于避免测试脚本中的断言重复,更容易提供更好的错误消息,并支持更多 TellDontAsk 风格的 API。无断言页面对象的拥护者说,包含断言将提供对PageObject的访问与断言逻辑的职责混合在一起,并导致PageObject膨胀。
我赞成在PageObject中没有断言。我认为您可以通过为常见断言提供断言库来避免重复,这也可以更容易地提供良好的诊断。
PageObject通常用于测试,但不应自己进行断言。他们的职责是提供对底层页面状态的访问。由测试客户端来执行断言逻辑。
我已经用 HTML 描述了这种模式,但同样的模式同样适用于任何 UI 技术。我已经看到这种模式被有效地用于隐藏 Java swing UI 的细节,而且我毫不怀疑它也被广泛用于几乎所有其他 UI 框架。
并发问题是PageObject可以封装的另一个主题。这可能涉及在异步操作中隐藏异步操作,而这些操作对用户来说并不显示为异步。它还可能涉及在 UI 框架中封装线程问题,您必须考虑在 UI 和工作线程之间如何分配
PageObject最常用于测试,但也可用于在应用程序之上提供脚本接口。通常最好将脚本界面放在 UI 下层,这通常不那么复杂且速度更快。然而,在有很多UI交互的应用程序,使用PageObject可能就不会成为最好的选择。 (但如果可以的话,请考虑移动该逻辑,这对于脚本编写和 UI 的长期健康都会更好。)
使用某种形式的 DomainSpecificLanguage 编写测试是很常见的,例如 Cucumber 或内部 DSL。如果您这样做,最好将测试 DSL 放在PageObject上,这样您就有一个解析器,可以将 DSL 语句转换为对PageObject的调用。
设计模式旨在将业务逻辑移出 UI 页面(例如表示模型、监督控制器和被动视图)这使得越来越少通过 UI 进行测试,并且减少了对PageObject的需求。
PageObject是封装的经典示例,它们对测试用例隐藏了操作UI的细节。在开发中试着去使用PageObject是很好的模式-问问自己“我怎样才能对软件的其余部分隐藏一些细节?” 与任何封装一样,这会产生两个好处。我已经强调过,通过将操作 UI 的逻辑限制在一个地方,您可以在那里修改它,而不会影响系统中的其他组件。一个相应的好处是它使客户端(测试)代码更容易理解,因为那里的逻辑是关于测试的意图,而不是被 UI 细节所干扰。
在您的 Web 应用程序的 UI 中,有一些与您的测试交互的区域。页面对象只是将这些建模为测试代码中的对象。这减少了重复代码的数量,意味着如果 UI 发生变化,则只需在一个地方应用修复。
PageObjects 可以被认为是同时面向两个方向。面对测试的开发人员,它们代表特定页面提供的服务。远离开发人员,他们应该是唯一对页面(或页面的一部分)的 HTML 结构有深入了解的人最简单的方法是将页面对象上的方法视为提供“服务”页面提供而不是暴露页面的细节和机制。例如,想想任何基于 Web 的电子邮件系统的收件箱。它提供的服务通常包括撰写新电子邮件、选择阅读单个电子邮件以及在收件箱中列出电子邮件的主题行的能力。如何实现这些对测试来说无关紧要。
因为我们鼓励测试的开发人员尝试考虑他们正在交互的服务而不是实现,所以 PageObjects 应该很少公开底层 WebDriver 实例。为方便起见,PageObject 上的方法应返回其他 PageObject。这意味着我们可以通过我们的应用程序有效地模拟用户的旅程。这也意味着如果页面相互关联的方式发生变化(例如,当登录页面要求用户在他们第一次登录服务时更改密码时,以前没有这样做)只需更改适当的方法的签名将导致测试无法编译。换一种方式,
这种方法的一个后果是,可能需要对成功和不成功的登录进行建模(例如),或者根据应用程序的状态,单击可能会产生不同的结果。发生这种情况时,通常在 PageObject 上有多个方法:
public class LoginPage { public HomePage loginAs(String username, String password) { // ... clever magic happens here } public LoginPage loginAsExpectingError(String username, String password) { // ... failed login here, maybe because one or both of the username and password are wrong } public String getErrorMessage() { // So we can verify that the correct error is shown } }
上面提供的代码显示了一个重要的点:测试,而不是 PageObjects,应该负责对页面的状态进行断言。例如:
public void testMessagesAreReadOrUnread() { Inbox inbox = new Inbox(driver); inbox.assertMessageWithSubjectIsUnread("I like cheese"); inbox.assertMessageWithSubjectIsNotUnread("I'm not fond of tofu"); }
可以改写为:
public void testMessagesAreReadOrUnread() { Inbox inbox = new Inbox(driver); assertTrue(inbox.isMessageWithSubjectIsUnread("I like cheese")); assertFalse(inbox.isMessageWithSubjectIsUnread("I'm not fond of tofu")); }
当然,与每个指南一样,也有例外,PageObjects 中常见的一个是在我们实例化 PageObject 时检查 WebDriver 是否在正确的页面上。这是在下面的示例中完成的。
最后,PageObject 不需要代表整个页面。它可能代表在站点或页面中多次出现的部分,例如站点导航。基本原则是您的测试套件中只有一个地方可以了解特定(页面的一部分)的 HTML 结构。