Подтвердить что ты не робот

Как реализовать методы WebDriver PageObject, которые могут возвращать различные объекты PageObjects

Я только начал использовать 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");

Альтернативный подход

Вместо дублирования кода я надеялся, что существует более чистый способ разоблачения одного метода 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 возможных результата входа в систему), API 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?

4b9b3361

Ответ 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().

Я согласен с Томом Андерсоном в том, что многоразовый код WebDriver должен быть реорганизован в мелкомасштабные методы. Независимо от того, подвергаются ли они мелкозернистым (поэтому класс испытаний может выбирать и выбирать соответствующие операции), или объединены и подвержены испытательному классу, как один крупнозернистый метод, вероятно, являются вопросом личных предпочтений.

Ответ 2

Вы загрязняете свой API несколькими типами - просто используйте generics и inheritance:

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

и т.д. для всех типов на страницах входа


Обратите внимание на обход для типа erasure возможности передать экземпляр Class<T> в метод PageFactory.initElements(), передав экземпляр класс в конструктор, который известен как шаблон типа "токен".