一尘不染

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

selenium

我刚刚开始使用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()loginAsCustomer()方法,并用对泛型的调用替换它们的内容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代码并为数据/ PageObjects的每个排列公开许多不同的方法,这是惯例吗?


阅读 383

收藏
2020-06-26

共1个答案

一尘不染

Bohemian的答案并不灵活-
您不能执行返回到同一页面的页面操作(例如输入错误的密码),也不能拥有多个页面操作导致不同的页面(如果您想弄乱了,登录页面还有另一项操作,导致结果不同)。最后,您还会堆满更多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代码重构为细粒度的方法。是否对它们进行细粒度的暴露(以便测试类可以选择相关的操作),或者将它们作为单个粗粒度的方法组合并暴露给测试类,可能是个人喜好问题。

2020-06-26