我刚开始使用WebDriver,我正在努力学习最佳做法,特别是使用PageObjects和PageFactory。
我的理解是,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");
而不是复制代码,我希望有一种更简洁的方法来公开返回相关PageObject的单个login()
方法。
我考虑过创建一个页面层次结构(或者让它们实现一个接口),这样我就可以使用它作为返回类型,但它感觉很笨拙。我想出的是以下内容:
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的排列公开许多不同的方法?
答案 0 :(得分:8)
波希米亚人的回答并不灵活 - 您不能让页面操作返回同一页面(例如输入错误的密码),也不能有超过1页的操作导致不同的页面(想想你有多乱)如果登录页面有另一个导致不同结果的操作,则d。为了迎合不同的结果,你最终还会有更多的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
为每个场景设置单独的方法也使得Login
PageObject的API非常清晰 - 并且很容易告诉所有登录的结果。我没有看到使用接口来限制页面的任何价值与login()
方法一起使用。
我同意Tom Anderson的说法,可重用的WebDriver代码应该重构为细粒度的方法。它们是否被细粒度暴露(因此测试类可以挑选和选择相关操作),或者作为单个粗粒度方法组合和暴露给测试类可能是个人偏好的问题。
答案 1 :(得分: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);
}
}
等登录页面上的所有类型
请注意type erasure能够将Class<T>
的实例传递给PageFactory.initElements()
方法的解决办法,方法是将类的实例传递给构造函数,这被称为“类型标记“模式。