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

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

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

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

2 ответа

Решение

Богемский ответ не является гибким - вы не можете иметь действие на странице, возвращающее вас на одну и ту же страницу (например, ввод неверного пароля), и при этом вы не можете иметь действие на более чем 1 страницу, приводящее к различным страницам (подумайте, какой беспорядок у вас будет, если на странице входа было другое действие, приводящее к другим результатам). Вы также получите кучу других 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 API PageObject очень ясен - и очень легко узнать все результаты входа в систему. Я не видел никакого значения в использовании интерфейсов для ограничения страниц, используемых с login() метод.

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

Вы загрязняете свой 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);
   }
}

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


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

Другие вопросы по тегам