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

我刚刚开始使用WebDriver ,我正在尝试学习最佳实践,特别是使用PageObjects和PageFactory 。

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

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

所以实现这个的最简单方法是公开两个返回不同PageObjects的方法……

登录PageObject

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

替代方法

我希望有一种更简洁的方式来公开一个返回相关PageObject的login()方法,而不是重复代码。

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

 public  T login(String user, String pw, Class 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()方法,并通过调用genericslogin() (然后将其设为私有)替换其内容。 然后,新页面(例如ExpiredPassword页面)将需要另一种方法(例如loginWithExpiredPassword() )。

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

进一步改进……

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

 public interface LoginResult {} public class AdminWelcome implements LoginResult {...} public class CustomerWelcome implements LoginResult {...} 

并将登录方法更新为:

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

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

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

波西米亚的答案不灵活 – 你不能让页面操作返回到同一页面(例如输入错误的密码),也不能有超过1页的操作导致不同的页面(想想你有什么混乱,如果登录页面有另一个操作导致不同的结果)。 为了迎合不同的结果,你最终还会有更多的PageObjects。

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

 private  T login(String user, String pw, Class 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 

为每个场景设置单独的方法也使Login PageObject的API非常清晰 – 并且很容易分辨登录的所有结果。我没有看到使用接口来限制login()方法使用的页面的任何价值。

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

您使用多种类型污染API – 只需使用generics和inheritance:

 public abstract class Login { @FindBy(id = "username") private WebElement username; @FindBy(id = "password") private WebElement password; @FindBy(id = "submitButton") private WebElement submitButton; private WebDriver driver; private Class clazz; protected Login(WebDriver driver, Class 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 { public AdminLogin(WebDriver driver) { super(driver, AdminWelcome.class); } } public CustomerLogin extends Login { public CustomerLogin(WebDriver driver) { super(driver, CustomerWelcome.class); } } 

等等,用于登录页面上的所有类型

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