PageObject + Инициализация PageFactory, driver и wait

Всем доброго времени суток. Я начинающий автоматизатор, поэтому прошу не судить строго и помочь разобраться в своем же коде :slight_smile: . Пишу свой pet automation project Selenium+Maven+TestNG+Intellij, использую PageObject и PageFactory. У меня есть пару вопросов.

Есть класс BrowserFactory с приватным конструктором и методом, возвращающим драйвер, - как альтернативу использованию синглтона (его пока не трогаю, оставляю как есть). В проекте использую properties-file. По поводу конструкции if-else – знаю, что смотрится коряво и лучше сделать через switch или enum – со временем переделаю.


public class BrowserFactory {

    private BrowserFactory() {
    }

    public static WebDriver getDriver(String browser){
        WebDriver defaultDriver = new FirefoxDriver();

        if ("CHROME".equalsIgnoreCase(browser)){            
            return new ChromeDriver();
        } else if ("IE".equalsIgnoreCase(browser)){
            return new InternetExplorerDriver();
        } else if ("OPERA".equalsIgnoreCase(browser)){
            return new OperaDriver();
        } else {
            return defaultDriver;
        }
    }
}

Есть класс BrowserHelper, в котором инициализируется необходимый браузер через системную переменную, содержащий собственно реализацию старта и зачистки драйвера.

public class BrowserHelper {

    private static WebDriver driver;
    private static WebDriverWait wait;

    public static void startBrowser(){        
        driver = BrowserFactory.getDriver("browser");
        wait = new WebDriverWait(driver, 30);
        driver.get("http://www.somesite.com");
        driver.manage().window().maximize();
    }
//other code
}

Есть базовый абстрактный класс Page, от которого экстендятся все остальные классы-страницы. В конструкторе этого базового класса получаем объект драйвера из BrowserFactory, делаем вейт и инициализируем PageFactory

public abstract class Page {

    WebDriver driver;
    WebDriverWait wait;

    public Page(){
        driver = BrowserFactory.getDriver(System.getProperty("browser"));
        wait = new WebDriverWait(driver, 30);//
        PageFactory.initElements(driver, this);//
    }

Вопросы:

  1. У меня дублируется wait. Где все-таки нужно его оставить, а где убрать?
  2. Нужно ли в каждом наследнике Page объявлять свой драйвер и вейт? И соответственно в Page поля драйвер и вейт должны быть публичными (если они таки наследуются в детях и в детях мы их не объявляем) или все-таки приватными (если в каждом наследнике мы должны объявлять и инициализировать их отдельно)? Понимаю что здесь лежит весь корень зла - так как тесты падают с NullPointerException.
  3. Касательно PageFactory: достаточно ли в наследниках Page просто вызова конструктора родителя через super() или все-таки нужно инициализировать PageFactory на каждой странице?
    public LoginPage() {
    super();
    }

на пейджах вообще не должно быть ни ожиданий ни инициализации драйвера, только вебэлементы, и работа с ними

Как по мне, класс Page и BrowserHelper нужно объединить в один, а инициализацию сделать отдельным методом с аннотацией в тестовом классе.

спасибо огромное Богдан за план фреймворка, но боюсь я такую конструкцию еще не осилю. С пейджами и тестами все понятно. В Controls выносятся все вебэлементы? а что в UIMap выносится?

так где все-таки лучше делать инициализацию драйвера - в хелпере или абстрактном Page?

public class LoginPage {

private class LoginPageUIMap {
	public final ITextField login;
	public final ITextField password;
	public final IButton signin;

	public LoginPageUIMap() {
		this.login = TextField.get().getById("selector");
		this.password = TextField.get().getById("selector");
		this.signin = Button.get().getByCssSelector("selector");
	}
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Elements
private LoginPageUIMap controls;

public LoginPage() {
	// super();
	controls = new LoginPageUIMap();
}

// PageObject - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Get Elements

public ITextField getLogin() {
	return this.controls.login;
}

public ITextField getPassword() {
	return this.controls.password;
}

public IButton getSignin() {
	return this.controls.signin;
}

public String getLoginText() {
	return getLogin().getText();
}

public String getPasswordText() {
	return getPassword().getText();
}

// Set Data

public void setLogin(String login) {
	getLogin().sendKeys(login);
}

public void setLoginClear(String login) {
	getLogin().sendKeysClear(login);
}

public void setPassword(String password) {
	getPassword().sendKeys(password);
}

public void setPasswordClear(String password) {
	getPassword().sendKeysClear(password);
}

public void clearLogin() {
	getLogin().clear();
}

public void clearPassword() {
	getPassword().clear();
}

public void clickLogin() {
	getLogin().click();
}

public void clickPassword() {
	getPassword().click();
}

public void clickSignin() {
	getSignin().click();
}

// business - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Functional

private void setLoginData(IUser user) {
	setLoginClear(user.getAccount().getLogin());
	setPasswordClear(user.getAccount().getPassword());
	clickSignin();
}

public AdminHomePage successAdminLogin(IUser admin) {
	setLoginData(admin);
	// Return a new page object representing the destination.
	return new AdminHomePage();
}

publicUserHomePage successUserLogin(IUser user) {
	setLoginData(user);
	// Return a new page object representing the destination.
	return new RegistratorHomePage();
}
//
// public LoginValidatorPage unsuccessfulLogin(IUser invalidUser) {
// setLoginData(invalidUser);
// return new LoginValidatorPage(); // return this;
// }

}

есть очень неплохой ресурс по написанию фреймворка, пошагово с примером.

рекомендую, мне очень помогло

Интересно, кто-нибудь всё-таки скажет, что это всё уже сделано и незачем городить свой велосипед?

2 лайка

спасибо за код, и за ссылку. буду изучать :slight_smile:

А теперь попробуй ипользовать Selenide :slight_smile:

А потом присоединяйся к нам в чат: https://software-testers.herokuapp.com/

2 лайка

после ввода почты, ошибочка - “Failed! token_revoked” - Monosnap

в абстрактном. Но правильнее будет сказать, что его не должно быть в хелперах, пейджах, тестах. Следовательно у каждых таких должен быть свой абстрактный класс: BaseHelper, BaseTest, BasePage, я когда-то делал именно так. Но сам драйвер и тип драйвера у меня был реализован в других классах. DriverSetup, DriverType. Затем можно сделать getDriver (синглтоном, к примеру) и уже в абстрактных классах подключать его как-то так:

public abstract class BaseTest {

@BeforeClass
public void setUp() {
    driver = DriverSetup.getInstance().getDriver();
}

тоже самое можно сделать в абстрактном классе Хелпера и унаследовавшись от него использовать драйвер… Делал когда-то так.

Интересно… Вот незадача

всем спасибо что откликнулись на мою просьбу :slight_smile:.
Меня очень интересует где мне все-таки делать инициализацию веб элементов с помощью PageFactory (я использую аанотацию @FindBy) - на каждой Page или достаточно в абстрактном родителе (как у меня сейчас и реализовано). NullPointerException может выдавать именно он

Насколько мне известно родитель будет инициализировать только свои элементы, а не элементы дочернего класса, поэтому у вас будет NPE. Два варианта - либо инициализировать в конструкторе каждого PageObject либо в методе init этого объекта, который будете вызывать вручную. Инициализация само собой ленивая, через PageFactory.

this в базовом классе всегда будет ссылаться на вызвавшего наследника. Инициализация в стандартной фабрике проходит итеративно, пробегаясь по всей цепи наследования.

Чтобы понять причину NPE, нужно для начала увидеть stacktrace, а не собирать вокруг себя толпу людей для гадания на кофейной гуще.

Спасибо, Роман. обязательно присоединюсь. Если на работе он будет нужен, то я никуда не денусь - буду изучать :slight_smile: