当前位置: 首页 > 知识库问答 >
问题:

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

常经赋
2023-03-14

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

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

例如,在这个假设的登录场景中,提供管理员凭据将带您进入AdminWelcome页面,提供客户凭据将带您进入CustomerWelcome页面。

所以实现这一点最简单的方法是公开两个返回不同页面对象的方法。。。

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的单一log()方法。

我曾考虑过创建一个页面层次结构(或让它们实现一个接口),以便将其用作返回类型,但感觉很笨拙。我想到的是:

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

这是灵活的-您可以添加过期密码页面,而不必更改login()方法-只需添加另一个测试,并将相应的过期凭据和过期密码页面作为预期页面通过即可。

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

这样做的好处是,方法名称实际上意味着一些东西(你可以很容易地看到有3个可能的登录结果),PageObject的应用编程接口更容易使用(没有“预期页面”传递进来),但是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代码,并为数据/页面对象的每个排列公开许多不同的方法,这是常见的做法吗?

共有2个答案

穆鸿飞
2023-03-14

您正在用多种类型污染您的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);
   }
}

登录页面上的所有类型


请注意类型擦除的解决方法,即能够传递类的实例

范华清
2023-03-14

波西米亚人的答案不灵活——你不能有一个页面动作把你返回到同一个页面(比如输入一个坏密码),也不能有一个以上的页面动作导致不同的页面(想想如果登录页面有导致不同结果的另一个动作)。您最终还需要堆更多的PageObject来满足不同的结果。

在对此进行了一些测试(包括失败的登录场景)之后,我确定了以下几点:

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代码应该重构为细粒度方法。它们是细粒度公开的(这样测试类就可以选择相关的操作),还是作为一个单一的粗粒度方法组合并公开给测试类,这可能是个人偏好的问题。

 类似资料:
  • 问题内容: 我刚刚开始使用WebDriver,并且正在尝试学习最佳实践,尤其是使用PageObjects和PageFactory。 据我了解,PageObjects应该公开网页上的各种操作,并将WebDriver代码与测试类隔离。通常,根据所使用的数据,相同的操作可能导致导航到不同的页面。 例如,在这种假设的登录方案中,提供管理员凭据将带您进入AdminWelcome页面,提供客户凭据将带您进入C

  • 问题内容: 可以说我有一个叫做Guice服务的服务,这是它的构造函数 我的代码曾经使用枚举创建它 而且我必须在某处进行工厂实施。像这样 现在,我想使用Guice,我想我想使用FactoryModuleBuilder。 如果我对IPayment的了解不止一种,该怎么办? (例如,CardPayment,CashPayment) 适用于一个 .implement(IPayment.class, Cas

  • 问题内容: 重写的方法可以有不同的返回类型吗? 问题答案: Java支持*协变返回类型的重写方法。这意味着重写的方法可能具有更特定的返回类型。也就是说,只要新的返回类型可分配给你要覆盖的方法的返回类型,就可以使用。 例如: 这在Java语言规范的8.4.5节中指定: 如果返回类型是引用类型,则返回类型在彼此覆盖的方法之间可能会有所不同。返回类型可替换性的概念支持协变返回,即返回类型到子类型的特殊化

  • 重写的方法可以有不同的返回类型吗?

  • 我有一个接口 创建 有两种方法,一种是异步的,一种是同步的。我想为这两个方法中的每一个提供一个接口实现。 对于异步方法,其实现可能如下所示: 但是我应该如何实现使用同步方法创建 的类 呢? 我可以实现方法以同步运行: 然后,编译器将警告不要在方法签名中使用 : 此异步方法缺少'await'运算符,将同步运行。考虑使用'await'运算符等待非阻塞API调用,或'await task.run(...

  • 问题内容: 今天,我偶然发现了一些我什至没想到可以编译的Java代码。减少到最低限度,它看起来像这样: 乍一看,类型参数的的方法和期待,因为没有必要,不使用其他任何地方。无论如何,我发现这在允许冲突的返回值类型共存于同一实现中起着至关重要的作用:如果忽略了一个或两个,则代码不会编译。这里是非工作版本: 我不需要修复上面的代码片段,因为这些只是我用来解释我观点的示例。我只是想知道为什么编译器对它们的