开始使用页面对象模式进行 Selenium 测试
介绍
与大多数软件模式一样,当您实际编码到需要帮助的情况时,它们才是最有意义的。提前学习模式可以防止您犯设计错误,但在大多数情况下,犯错是您学习的最佳机会。
然而,无论您已有一套功能性 Selenium 测试还是刚刚开始,页面对象模式都是非常有意义的。
这里的代码示例是用 Java 编写的,但这些概念适用于任何语言。你可以在 github 上找到完整的代码示例
问题
当您使用 Selenium 编写功能测试时,代码的主要部分将包括通过 WebDriver API 与您正在测试的 Web 界面的交互。获取元素后,您将通过各种断言验证元素的某些状态,然后继续获取下一个元素。
将此示例视为基本 Selenium 测试的一部分:
List<WebElement> zipCodes = driver.findElements(By.id("zipCodes"));
for (WebElement zipCode : zipCodes) {
if (zipCode.getText().equals("12345")){
zipCode.click();
break;
}
}
WebElement city = driver.findElement(By.id("city"));
assertEquals("MyCityName", city.getText());
这是一个简单的测试示例,它获取并迭代查找 12345 的邮政编码列表,单击它并获取城市元素,期望城市名称为 MyCityName。
即使是这么简单的测试,可读性也很差。有大量的 WebDriver 代码,模糊了测试的目的,导致测试速度缓慢且难以理解。
对于任何界面,尤其是 Web 界面,经常会对 UI 进行重大和微小的更改。这可能是新的设计,字段和按钮的重组,这可能会影响您的测试。因此您的测试失败,您需要更新选择器。
现在,如果您只对一个页面进行一次功能测试,并采用“快乐路径”,您可能不会认为这是什么大问题。但是,如果您有一整套回归测试,这绝对是一件大事。
因此,此类 Selenium 测试的一些典型问题如下:
- 测试用例难以阅读
- UI 的更改通常会在多个地方破坏多个测试
- 在测试内部和跨测试重复选择器 - 不可重用
解决方案
因此,页面对象模式引入了基本上是解耦层的内容,而不是让每个测试直接获取元素,并且对 UI 变化很敏感。
您创建一个对象来表示要测试的 UI,可以是整个页面,也可以是其中的重要部分。此对象的职责是包装 HTML 元素并封装与 UI 的交互,这意味着所有对 WebDriver 的调用都将在此进行。大多数 WebElement 都在这里。当 UI 发生变化时,这也是您唯一需要修改的地方。
下图说明了这一模式:
执行
开始实施这个实现实际上非常简单:
- 基本 Selenium 设置
- 分析正在测试的应用程序
- 创建页面对象
- 编写测试
1. 基本 Selenium 设置
当你启动 Selenium 测试套件时,我建议创建一个类来保存所有驱动程序生命周期管理代码。因此,从这样的类开始:
public class FunctionalTest {
protected static WebDriver driver;
@BeforeClass
public static void setUp(){
driver = new FirefoxDriver();
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
}
@After
public void cleanUp(){
driver.manage().deleteAllCookies();
}
@AfterClass
public static void tearDown(){
driver.close();
}
}
完整示例:https://github.com/kschiller/page-object-pattern-tutorial/blob/Initial/FunctionalTest.java
现在将其作为您创建的任何测试类的超类。
2. 分析被测应用程序
为了这个例子,我创建了一个包含姓名和地址字段的注册表单以及一个收据页面。这些例子可以在www.kimschiller.com/page-object-pattern-tutorial上看到。
在这个例子中,分析非常简单,我将为每个页面创建一个页面对象。但如上所述,完整的 HTML 页面和页面对象之间不一定存在一一对应的关系。页面对象应该代表页面中有意义的上下文部分。
3. 创建页面对象
现在我将创建两个页面对象,一个用于注册表单,一个用于收据。
public class SignUpPage extends PageObject {
@FindBy(id="firstname")
private WebElement firstName;
@FindBy(id="lastname")
private WebElement lastName;
@FindBy(id="address")
private WebElement address;
@FindBy(id="zipcode")
private WebElement zipCode;
@FindBy(id="signup")
private WebElement submitButton;
public SignUpPage(WebDriver driver) {
super(driver);
}
public void enterName(String firstName, String lastName){
this.firstName.clear();
this.firstName.sendKeys(firstName);
this.lastName.clear();
this.lastName.sendKeys(lastName);
}
public void enterAddress(String address, String zipCode){
this.address.clear();
this.address.sendKeys(address);
this.zipCode.clear();
this.zipCode.sendKeys(zipCode);
}
public ReceiptPage submit(){
submitButton.click();
return new ReceiptPage(driver);
}
}
public class ReceiptPage extends PageObject {
@FindBy(tagName = "h1")
private WebElement header;
public ReceiptPage(WebDriver driver) {
super(driver);
}
public String confirmationHeader(){
return header.getText();
}
}
完整示例:https://github.com/kschiller/page-object-pattern-tutorial/blob/Initial/ReceiptPage.java
现在,这实际上不起作用,直到我们初始化已注释的 WebElements。所以我将创建另一个超类来保存这小段但重要的代码。所以我的 PageObject 类如下所示:
public class PageObject {
protected WebDriver driver;
public PageObject(WebDriver driver){
this.driver = driver;
PageFactory.initElements(driver, this);
}
}
完整示例:https://github.com/kschiller/page-object-pattern-tutorial/blob/Initial/PageObject.java
PageFactory 处理所有带注释的 WebElement,并使用带注释的选择器在页面上定位元素。您可以在SeleniumHQ GitHub 页面上找到 PageFactory 的文档。
4. 编写测试
现在您已经准备好了所有东西,可以开始编写实际的测试用例了。以下是成功注册和确认的测试用例:
public class SignUpFormTest extends FunctionalTest {
@Test
public void signUp(){
driver.get("http://www.kimschiller.com/page-object-pattern-tutorial/index.html");
SignUpPage signUpPage = new SignUpPage(driver);
assertTrue(signUpPage.isInitialized());
signUpPage.enterName("First", "Last");
signUpPage.enterAddress("123 Street", "12345");
ReceiptPage receiptPage = signUpPage.submit();
assertTrue(receiptPage.isInitialized());
assertEquals("Thank you", receiptPage.confirmationHeader());
}
}
完整示例:https://github.com/kschiller/page-object-pattern-tutorial/blob/Initial/SignUpFormTest.java
现在看看测试用例,我认为我们可读性差的问题已经解决了。测试读起来很好,每个方法的意图都非常清晰。
由于我们已经从测试中删除了所有 WebDriver 调用,因此,如果 firstname 字段的 id 发生变化,我们实际上不必更改测试。只有页面对象需要更改。
最佳实践
使用页面对象时有一些最佳实践,您应该努力遵循。
- 页面对象不应该有任何断言
- 页面对象应该代表页面的有意义的元素,而不一定是完整的页面
- 当你导航时,你应该返回下一页的页面对象
提示和技巧
在与页面交互之前验证页面是否准备就绪是一种很好的做法,正如 SignUpFormTest 中所示,这正是我正在做的事情:
SignUpPage signUpPage = new SignUpPage(driver);
assertTrue(signUpPage.isInitialized());
您很可能在每次实例化页面对象时重复这种模式,因此我们可以在这里引入一个例外,以打破页面对象中没有断言的经验法则。
这里的想法是将此断言移到页面对象的构造函数中。这样,如果页面对象由于某种原因没有及时准备好,创建就会失败。
因此你的构造函数将如下所示:
public SignUpPage(WebDriver driver) {
super(driver);
assertTrue(firstName.isDisplayed());
}
简单又方便。
结论
因此,无论您是刚开始使用 Selenium 还是正在管理大量 Selenium 回归测试,将页面对象模式引入代码都很有可能对您有益。正如 WebDriver 的创建者 Simon Stewart - @shs96c直言不讳地说:“如果您的测试方法中有 WebDriver API,那么您就做错了”。就我个人而言,我一直做错了,也经历过这样做的挫折。
正如我们现在所看到的,页面对象模式通过引入一系列页面对象,为您提供了一种将测试脚本与您正在测试的 Web 界面分离的方法。页面对象负责与您正在测试的网页进行通信。通过 WebDriver API 触发的任何 DOM 查询都会经过页面对象,因为只有页面对象应该知道如何使用 Selenium 提供的众多定位器方法在页面上查找元素。
通过使用页面对象,您的测试变得更加简洁和可读。您的元素定位器集中化,使维护更加容易。用户界面的更改只会影响页面对象,而不会影响测试脚本。最后,这是良好而可靠的面向对象设计。
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~