硒:设计模式

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

在测试 Web 应用程序时,Selenium WebDriver 被广泛用作首选框架。在这篇文章中,我想向您介绍两种设计模式,它们非常适合 selenium 测试。在这篇文章中,我们将深入研究页面对象模式并将其与页面工厂相结合。一如既往,我的 github 上提供了项目的完整代码。


页面对象模式

页面对象是一个概念,有助于减少重复代码的数量,并有助于测试维护,因为 Web 应用程序的结构和功能发生了变化。页面对象背后的主要思想是将与子页面功能相关的代码放在单独的类中。以一种非常简单的方式,如果您的 Web 应用程序包含页面:

  • 关于
  • 接触

我们应该创建三个独立的页面对象类:

  • 主页.java
  • 关于.java
  • 联系人.java

每个类应该只包含那些支持相应子页面功能的方法,并且只为这个子页面定义选择器。我们还应该记住,页面对象类中的公共方法只是那些代表用户工作流程的方法。

重要的一点是页面对象类中的每个公共方法都应该返回用户所在页面的对象。例如,如果页面上的按钮无法将您带到不同的子页面,则此方法应返回此值。此外,如果按钮是指向另一个页面的链接,则该方法应返回该子页面的页面对象类。下面是这种方法的代码片段,使用了上一篇文章中的示例,我们在其中编写了 Facebook 登录测试:


 public class LoginPage {
private static By userEmailLoginInput = By.id("email");
private static By userPasswordLoginInput = By.id("pass");
private static By loginSubmitBtn = By.id("u_0_n");

private WebDriver driver;

public LoginPage(WebDriver driver) {
    this.driver = driver;
    driver.get("http://facebook.com");
}

public LoginPage enterUserLogin(String login) {
    WebElement emailBox = driver.findElement(userEmailLoginInput);
    emailBox.click();
    emailBox.sendKeys(login);
    return this;
}

public LoginPage enterUserPassword(String password) {
    WebElement passwordBox = driver.findElement(userPasswordLoginInput);
    passwordBox.click();
    passwordBox.sendKeys(password);
    return this;
}

public HomePage submitLoginCredentials() {
    WebElement submitBtn = driver.findElement(loginSubmitBtn);
    submitBtn.click();
    return new HomePage(driver);
}

}

在上面的示例中,方法 enterUserLogin() 和 enterUserPassword() 不会将用户转移到另一个子页面,而是在登录页面上执行活动,因此返回类型是 LoginPage 类的对象 (this)。另一方面,submitLoginCredentials() 方法将用户移动到主页(或通知登录失败的页面),因此它返回主页类对象。在现实生活中的例子中,在 HomePage.class 中我们会有在主页上执行操作的方法,但由于它只是一个演示模式用法的示例代码,我们这里只有 checkIfLoginSucceed() 方法:


 public class LoginPage {
private static By userEmailLoginInput = By.id("email");
private static By userPasswordLoginInput = By.id("pass");
private static By loginSubmitBtn = By.id("u_0_n");

private WebDriver driver;

public LoginPage(WebDriver driver) {
    this.driver = driver;
    driver.get("http://facebook.com");
}

public LoginPage enterUserLogin(String login) {
    WebElement emailBox = driver.findElement(userEmailLoginInput);
    emailBox.click();
    emailBox.sendKeys(login);
    return this;
}

public LoginPage enterUserPassword(String password) {
    WebElement passwordBox = driver.findElement(userPasswordLoginInput);
    passwordBox.click();
    passwordBox.sendKeys(password);
    return this;
}

public HomePage submitLoginCredentials() {
    WebElement submitBtn = driver.findElement(loginSubmitBtn);
    submitBtn.click();
    return new HomePage(driver);
}

}

…测试如下所示:


 public class LoginPage {
private static By userEmailLoginInput = By.id("email");
private static By userPasswordLoginInput = By.id("pass");
private static By loginSubmitBtn = By.id("u_0_n");

private WebDriver driver;

public LoginPage(WebDriver driver) {
    this.driver = driver;
    driver.get("http://facebook.com");
}

public LoginPage enterUserLogin(String login) {
    WebElement emailBox = driver.findElement(userEmailLoginInput);
    emailBox.click();
    emailBox.sendKeys(login);
    return this;
}

public LoginPage enterUserPassword(String password) {
    WebElement passwordBox = driver.findElement(userPasswordLoginInput);
    passwordBox.click();
    passwordBox.sendKeys(password);
    return this;
}

public HomePage submitLoginCredentials() {
    WebElement submitBtn = driver.findElement(loginSubmitBtn);
    submitBtn.click();
    return new HomePage(driver);
}

}


页面工厂模式

这种模式背后的主要思想是支持页面对象类,并允许更好的页面选择器管理。 Page Factory 为我们提供了一组注解,它们与选择器配合得很好,增强了代码的可读性。为了理解这一点,让我们通过选择器看一个标准的初始化网络元素:


 public class LoginPage {
private static By userEmailLoginInput = By.id("email");
private static By userPasswordLoginInput = By.id("pass");
private static By loginSubmitBtn = By.id("u_0_n");

private WebDriver driver;

public LoginPage(WebDriver driver) {
    this.driver = driver;
    driver.get("http://facebook.com");
}

public LoginPage enterUserLogin(String login) {
    WebElement emailBox = driver.findElement(userEmailLoginInput);
    emailBox.click();
    emailBox.sendKeys(login);
    return this;
}

public LoginPage enterUserPassword(String password) {
    WebElement passwordBox = driver.findElement(userPasswordLoginInput);
    passwordBox.click();
    passwordBox.sendKeys(password);
    return this;
}

public HomePage submitLoginCredentials() {
    WebElement submitBtn = driver.findElement(loginSubmitBtn);
    submitBtn.click();
    return new HomePage(driver);
}

}

不幸的是,可读性还有很多不足之处。问题还在于带有选择器的变量和 Web 元素对象需要单独初始化。与 Page Factory 一起,这被大大简化了:


 public class LoginPage {
private static By userEmailLoginInput = By.id("email");
private static By userPasswordLoginInput = By.id("pass");
private static By loginSubmitBtn = By.id("u_0_n");

private WebDriver driver;

public LoginPage(WebDriver driver) {
    this.driver = driver;
    driver.get("http://facebook.com");
}

public LoginPage enterUserLogin(String login) {
    WebElement emailBox = driver.findElement(userEmailLoginInput);
    emailBox.click();
    emailBox.sendKeys(login);
    return this;
}

public LoginPage enterUserPassword(String password) {
    WebElement passwordBox = driver.findElement(userPasswordLoginInput);
    passwordBox.click();
    passwordBox.sendKeys(password);
    return this;
}

public HomePage submitLoginCredentials() {
    WebElement submitBtn = driver.findElement(loginSubmitBtn);
    submitBtn.click();
    return new HomePage(driver);
}

}

就这样! Page Factory 默认在页面源代码中搜索 id=”email” 的元素,并将其分配给声明的 webelement。当然,这种极简主义会带来一些混乱,因此我推荐使用 @FindBy 注释:


 public class LoginPage {
private static By userEmailLoginInput = By.id("email");
private static By userPasswordLoginInput = By.id("pass");
private static By loginSubmitBtn = By.id("u_0_n");

private WebDriver driver;

public LoginPage(WebDriver driver) {
    this.driver = driver;
    driver.get("http://facebook.com");
}

public LoginPage enterUserLogin(String login) {
    WebElement emailBox = driver.findElement(userEmailLoginInput);
    emailBox.click();
    emailBox.sendKeys(login);
    return this;
}

public LoginPage enterUserPassword(String password) {
    WebElement passwordBox = driver.findElement(userPasswordLoginInput);
    passwordBox.click();
    passwordBox.sendKeys(password);
    return this;
}

public HomePage submitLoginCredentials() {
    WebElement submitBtn = driver.findElement(loginSubmitBtn);
    submitBtn.click();
    return new HomePage(driver);
}

}

我们还可以通过其他属性搜索选择器,例如 xpath、name、className 等。要使用此模式,我们要做的就是在页面对象类构造函数中初始化 PageFactory:


 public class LoginPage {
private static By userEmailLoginInput = By.id("email");
private static By userPasswordLoginInput = By.id("pass");
private static By loginSubmitBtn = By.id("u_0_n");

private WebDriver driver;

public LoginPage(WebDriver driver) {
    this.driver = driver;
    driver.get("http://facebook.com");
}

public LoginPage enterUserLogin(String login) {
    WebElement emailBox = driver.findElement(userEmailLoginInput);
    emailBox.click();
    emailBox.sendKeys(login);
    return this;
}

public LoginPage enterUserPassword(String password) {
    WebElement passwordBox = driver.findElement(userPasswordLoginInput);
    passwordBox.click();
    passwordBox.sendKeys(password);
    return this;
}

public HomePage submitLoginCredentials() {
    WebElement submitBtn = driver.findElement(loginSubmitBtn);
    submitBtn.click();
    return new HomePage(driver);
}

}

我们的带有页面工厂模式的 LoginPage.class 看起来像这样:


 public class LoginPage {
private static By userEmailLoginInput = By.id("email");
private static By userPasswordLoginInput = By.id("pass");
private static By loginSubmitBtn = By.id("u_0_n");

private WebDriver driver;

public LoginPage(WebDriver driver) {
    this.driver = driver;
    driver.get("http://facebook.com");
}

public LoginPage enterUserLogin(String login) {
    WebElement emailBox = driver.findElement(userEmailLoginInput);
    emailBox.click();
    emailBox.sendKeys(login);
    return this;
}

public LoginPage enterUserPassword(String password) {
    WebElement passwordBox = driver.findElement(userPasswordLoginInput);
    passwordBox.click();
    passwordBox.sendKeys(password);
    return this;
}

public HomePage submitLoginCredentials() {
    WebElement submitBtn = driver.findElement(loginSubmitBtn);
    submitBtn.click();
    return new HomePage(driver);
}

}

需要注意的重要一点是,每次我们在 web 元素上调用方法时,页面工厂都会通过页面源重新搜索我们的元素。如果页面不是基于 AJAX 的,我们可以使用页面工厂缓存仅通过页面工厂初始化来搜索元素,然后从缓存中检索它:


 public class LoginPage {
private static By userEmailLoginInput = By.id("email");
private static By userPasswordLoginInput = By.id("pass");
private static By loginSubmitBtn = By.id("u_0_n");

private WebDriver driver;

public LoginPage(WebDriver driver) {
    this.driver = driver;
    driver.get("http://facebook.com");
}

public LoginPage enterUserLogin(String login) {
    WebElement emailBox = driver.findElement(userEmailLoginInput);
    emailBox.click();
    emailBox.sendKeys(login);
    return this;
}

public LoginPage enterUserPassword(String password) {
    WebElement passwordBox = driver.findElement(userPasswordLoginInput);
    passwordBox.click();
    passwordBox.sendKeys(password);
    return this;
}

public HomePage submitLoginCredentials() {
    WebElement submitBtn = driver.findElement(loginSubmitBtn);
    submitBtn.click();
    return new HomePage(driver);
}

}


概括

与软件开发的各个方面一样,测试自动化中的设计模式帮助我们更快、更高效地开发测试。 Page Object 和 Page Factory 是两个简单的模式,它们显着提高了 selenium 测试的可维护性和可读性。有关本文中示例的完整来源,请访问我的 github 。如果您有任何疑问,请随时发表评论!