14

我刚刚开始使用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);
    }

}

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

Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome = loginPage.loginAsAdmin("admin", "admin");

或者

Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome = loginPage.loginAsCustomer("joe", "smith");

替代方法

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);
}

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

Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome = 
    loginPage.login("admin", "admin", AdminWelcome.class);

或者

Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome = 
    loginPage.login("joe", "smith", CustomerWelcome.class);

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

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

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

进一步改进...

如果您确实公开了单个login()方法,则可以通过向这些页面添加标记界面来更清楚地显示可以通过登录访问哪些页面(如果您为每个场景公开一个方法,这可能不是必需的)。

public interface LoginResult {}

public class AdminWelcome implements LoginResult {...}

public class CustomerWelcome implements LoginResult {...}

并将登录方法更新为:

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);
}

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

或者只是复制 WebDriver 代码并为每个数据/页面对象的排列公开许多不同的方法是常见的做法吗?

4

2 回答 2

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);
}

这意味着您可以重用登录逻辑,但防止测试类在预期页面中传递的需要,这意味着测试类非常可读:

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

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

我同意 Tom Anderson 的观点,即应将可重用的 WebDriver 代码重构为细粒度的方法。它们是细粒度地暴露(以便测试类可以选择相关操作),还是组合并作为单个粗粒度方法暴露给测试类可能是个人喜好问题。

于 2012-11-21T04:36:26.620 回答
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);
    }
}

接着

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);
   }
}

登录页面上的所有类型等


请注意通过将类的实例传递到构造函数中,能够将实例传递给方法的类型擦除的解决方法,这被称为“类型令牌”模式。Class<T>PageFactory.initElements()

于 2012-11-14T01:57:05.467 回答