如何实现可以返回不同PageObjects的WebDriver PageObject方法

Jam*_*ett 14 java selenium webdriver pageobjects

我刚刚开始使用WebDriver,我正在尝试学习最佳实践,特别是使用PageObjectsPageFactory.

我的理解是,PageObjects应该在网页上公开各种操作,并将WebDriver代码与测试类隔离开来.通常,相同的操作可能导致导航到不同的页面,具体取决于所使用的数据.

例如,在此假设的登录方案中,提供管理员凭据会将您带到AdminWelcome页面,并且提供客户凭据会将您带到CustomerWelcome页面.

所以最简单的方法是公开两个返回不同PageObjects的方法...

登录页面对象

package example;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class Login {

    @FindBy(id = "username")
    private WebElement username;

    @FindBy(id = "password")
    private WebElement password;

    @FindBy(id = "submitButton")
    private WebElement submitButton;

    private WebDriver driver;

    public Login(WebDriver driver){
        this.driver = driver;
    }

    public AdminWelcome loginAsAdmin(String user, String pw){
        username.sendKeys(user);
        password.sendKeys(pw);
        submitButton.click();
        return PageFactory.initElements(driver, AdminWelcome.class);
    }

    public CustomerWelcome loginAsCustomer(String user, String pw){
        username.sendKeys(user);
        password.sendKeys(pw);
        submitButton.click();
        return PageFactory.initElements(driver, CustomerWelcome.class);
    }

}
Run Code Online (Sandbox Code Playgroud)

并在测试类中执行以下操作:

Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome = loginPage.loginAsAdmin("admin", "admin");
Run Code Online (Sandbox Code Playgroud)

要么

Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome = loginPage.loginAsCustomer("joe", "smith");
Run Code Online (Sandbox Code Playgroud)

替代方法

我没有复制代码,而是希望有一种更简洁的方法来公开login()返回相关PageObject 的单个方法.

我考虑过创建一个页面层次结构(或者让它们实现一个接口),这样我就可以使用它作为返回类型,但它感觉很笨拙.我想出的是以下内容:

public <T> T login(String user, String pw, Class<T> expectedPage){
    username.sendKeys(user);
    password.sendKeys(pw);
    submitButton.click();
    return PageFactory.initElements(driver, expectedPage);
}
Run Code Online (Sandbox Code Playgroud)

这意味着您可以在测试类中执行以下操作:

Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome = 
    loginPage.login("admin", "admin", AdminWelcome.class);
Run Code Online (Sandbox Code Playgroud)

要么

Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome = 
    loginPage.login("joe", "smith", CustomerWelcome.class);
Run Code Online (Sandbox Code Playgroud)

这很灵活 - 您可以添加ExpiredPassword页面而不必更改login()方法 - 只需添加另一个测试并将适当的过期凭据和ExpiredPassword页面作为预期页面传递.

当然,您可以很容易地保留loginAsAdmin()loginAsCustomer()方法并通过调用泛型来替换它们的内容login()(然后将其设为私有).然后,新页面(例如,ExpiredPassword页面)将需要另一种方法(例如loginWithExpiredPassword()).

这样做的好处是方法名称实际意味着什么(您可以很容易地看到登录有3种可能的结果),PageObject的API更容易使用(没有"预期的页面"传入),但WebDriver代码仍在重用中.

进一步改进......

如果确实公开了单个login()方法,则可以通过向这些页面添加标记接口来更明显地通过登录来访问哪些页面(如果为每个方案公开方法,这可能不是必需的).

public interface LoginResult {}

public class AdminWelcome implements LoginResult {...}

public class CustomerWelcome implements LoginResult {...}
Run Code Online (Sandbox Code Playgroud)

并将登录方法更新为:

public <T extends LoginResult> T login(String user, String pw, 
    Class<T> expectedPage){
    username.sendKeys(user);
    password.sendKeys(pw);
    submitButton.click();
    return PageFactory.initElements(driver, expectedPage);
}
Run Code Online (Sandbox Code Playgroud)

这两种方法似乎都运行良好,但我不确定它如何扩展到更复杂的场景.我还没有看到任何类似的代码示例,所以我想知道当页面上的操作根据数据导致不同的结果时,其他人会怎么做?

或者通常的做法是复制WebDriver代码并为数据/ PageObjects的每个排列公开许多不同的方法?

Jam*_*ett 8

波希米亚人的答案不灵活 - 你不能让页面操作返回到同一页面(例如输入错误的密码),也不能有超过1页的操作导致不同的页面(想想你有什么样的混乱,如果登录页面有另一个操作导致不同的结果).你最终还会有更多的PageObjects来满足不同的结果.

在尝试了更多这些(包括失败的登录方案)之后,我已经解决了以下问题:

private <T> T login(String user, String pw, Class<T> expectedPage){
    username.sendKeys(user);
    password.sendKeys(pw);
    submitButton.click();
    return PageFactory.initElements(driver, expectedPage);
}

public AdminWelcome loginAsAdmin(String user, String pw){
    return login(user, pw, AdminWelcome.class);
}

public CustomerWelcome loginAsCustomer(String user, String pw){
    return login(user, pw, CustomerWelcome.class);
}

public Login loginWithBadCredentials(String user, String pw){
    return login(user, pw, Login.class);
}
Run Code Online (Sandbox Code Playgroud)

这意味着您可以重用登录逻辑,但是不需要测试类传入预期的页面,这意味着测试类非常易读:

Login login = PageFactory.initElements(driver, Login.class);
login = login.loginWithBadCredentials("bad", "credentials");
// TODO assert login failure message
CustomerWelcome customerWelcome = login.loginAsCustomer("joe", "smith");
// TODO do customer things
Run Code Online (Sandbox Code Playgroud)

为每个场景设置单独的方法也使得LoginPageObject的API非常清晰 - 并且很容易告诉所有登录的结果.我没有看到使用接口来限制该login()方法使用的页面的任何价值.

我同意Tom Anderson的说法,可重用的WebDriver代码应该重构为细粒度的方法.它们是否被细粒度暴露(因此测试类可以挑选和选择相关操作),或者作为单个粗粒度方法组合和暴露给测试类可能是个人偏好的问题.


Boh*_*ian 7

您使用多种类型污染API - 只需使用泛型和继承:

public abstract class Login<T> {

    @FindBy(id = "username")
    private WebElement username;

    @FindBy(id = "password")
    private WebElement password;

    @FindBy(id = "submitButton")
    private WebElement submitButton;

    private WebDriver driver;

    private Class<T> clazz;

    protected Login(WebDriver driver, Class<T> clazz) {
        this.driver = driver;
        this.clazz = clazz
    }

    public T login(String user, String pw){
        username.sendKeys(user);
        password.sendKeys(pw);
        submitButton.click();
        return PageFactory.initElements(driver, clazz);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后

public AdminLogin extends Login<AdminWelcome> {

   public AdminLogin(WebDriver driver) {
       super(driver, AdminWelcome.class);
   }
}

public CustomerLogin extends Login<CustomerWelcome> {

   public CustomerLogin(WebDriver driver) {
       super(driver, CustomerWelcome.class);
   }
}
Run Code Online (Sandbox Code Playgroud)

等等登录页面上的所有类型


注意变通为类型擦除的能够传递一个实例Class<T>PageFactory.initElements()方法中,通过使类的实例入构造,其被称为"类型令牌"图案.