Есть отличная удаленная работа для php+codeception+jenkins+allure+docker спецов. 100% remote! Присоединиться к проекту

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

page-factory
page-object
selenium
webdriver
testng
java
Теги: #<Tag:0x00007f7b6268a828> #<Tag:0x00007f7b6268a698> #<Tag:0x00007f7b6268a350> #<Tag:0x00007f7b6268a148> #<Tag:0x00007f7b62689ef0> #<Tag:0x00007f7b62689db0>

(Ksu) #1

Всем доброго времени суток. Я начинающий автоматизатор, поэтому прошу не судить строго и помочь разобраться в своем же коде :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();
    }


(Bohdan Harasym) #2

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


(Саня Дадижа) #3

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


(Bohdan Harasym) #4


(Ksu) #5

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


(Ksu) #6

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


(Bohdan Harasym) #7

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

}


(Bohdan Harasym) #8

есть очень неплохой ресурс по написанию фреймворка, пошагово с примером.
http://toolsqa.com/selenium-webdriver/automation-framework-introduction/
рекомендую, мне очень помогло


(asolntsev) #9

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


(Ksu) #10

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


(Roma Marinsky) #11

А теперь попробуй ипользовать Selenide :slight_smile:
http://ru.selenide.org/index.html
А потом присоединяйся к нам в чат: https://software-testers.herokuapp.com/


(Eugene Moskalenko) #12

после ввода почты, ошибочка - “Failed! token_revoked” - http://take.ms/ODnmb


(Eugene Moskalenko) #13

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

public abstract class BaseTest {

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

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


(Roma Marinsky) #14

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


(Ksu) #15

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


(Pavel Ponomaryov) #16

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


(Sergey Korol) #17

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

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


(Ksu) #18

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