Использование объекта в тесте, Page object

Делаю свои первые попытки освоить автоматизацию. Написал следующие классы:
BasePage

public class BasePage {
    @FindBy(css = ".toast-success")
    private WebElement message;

    WebDriver driver;

    BasePage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    void login(){
        LoginPage loginTPage = new LoginPage(driver) ;
        loginTPage.goToLoginPage();
        loginTPage.enterName("IT");
        loginTPage.enterPassword("IT");
        loginTPage.clickLoginButton();
    }

    public String getTitlePage(){
        return driver.getTitle();
    }

    public boolean checkMessage() {
        Assert.assertTrue(message.isDisplayed());
        return true;
    }

    void contextClick(WebElement element){
        Actions actions = new Actions(driver);
        actions.contextClick(element).build().perform();
    }

    void doubleClick(WebElement element) {
        Actions actions = new Actions(driver);
        actions.doubleClick(element).build().perform();
    }
}

LoginPage

public class LoginPage extends BasePage {

    //*********Constructor*********
    public LoginPage(WebDriver driver) {
        super(driver);
    }

    //*********Page Methods*********
    public void goToLoginPage() {
        driver.get(baseURL);
    }

    public void enterName(String username) {
        this.username.clear();
        this.username.sendKeys(username);
    }

    public void enterPassword(String password) {
        this.password.clear();
        this.password.sendKeys(password);
    }

    public MainPage clickLoginButton() {
        loginButton.click();
        return new MainPage(driver);
    }

    public void verifyLoginUserNameError(String expectedText) {
        Assert.assertEquals(expectedText, errorMessagePassword.getText());
    }

    public void verifyLoginPasswordError(String expectedText) {
        Assert.assertEquals(expectedText, errorMessageUsername.getText());
    }
}

MainPage

public class MainPage extends BasePage {
    private LoginPage loginPage = new LoginPage(driver);
//*********Web Elements*********
    @FindBy(xpath = "//span[@class='username']")
    private WebElement username;
    @FindBy(id = "btn-mdl-int")
    private WebElement serviceButton;
    @FindBy(id = "btn-mdl-all")
    private WebElement moduleButton;
    @FindBy(id = "6-10")
    private WebElement enumModule;
    @FindBy(id = "18-12")
    private WebElement exportBDModule;
    @FindBy(id = "7-10")
    private WebElement calculationModule;
    @FindBy(id = "2-10")
    private WebElement userModule;
    @FindBy(id = "9-10")
    private WebElement groupUserModule;
    @FindBy(id = "8-10")
    private WebElement unitModule;
    @FindBy(id = "5-10")
    private WebElement referenceModule;
    @FindBy(id = "12-10")
    private WebElement interfaceModule;
    @FindBy(id = "3-10")
    private WebElement treeTypeModule;

    //*********Page Methods*********
    public MainPage(WebDriver driver) {
        super(driver);
    }

    public void openModule(WebElement module) {
        loginPage.login();
        moduleButton.click();
        module.click();
    }

    public String checkHeader() {
        return driver.findElement(By.xpath("//div[@class='page-content']//span[.='Главная']")).getText();
    }

    public String checkUsername() {
        return username.getText();
    }

    public String checkURL() {
        return driver.getCurrentUrl();
    }

    public String checkElementWall() {
        return driver.findElement(By.xpath("//div[@id='freewall']/legend[.='Оборудование']")).getText();
    }

    public boolean getFooter() {
        driver.findElement(By.xpath("//div[@class='page-footer-inner']")).getText();
        return true;
    }

    public boolean checkServiceButton() {
        serviceButton.isDisplayed();
        return true;
    }

    public boolean CheckModuleButton() {
        moduleButton.isDisplayed();
        return true;
    }
}

BaseTest

public class BaseTest {
    protected WebDriver driver;

    @BeforeClass
    public void setup() {
        System.setProperty("webdriver.chrome.driver", "src/test/resources/chromedriver.exe");
        driver = new ChromeDriver();
        driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS);
        driver.manage().window().maximize();
    }

    @AfterClass
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

И два теста этих страниц
LoginPageTest

public class LoginTest extends BaseTest {

    @DataProvider
    public Object[][] invalidLoginData() {
        return new Object[][]{
                {" ", " "},
                {" ", "Test"},
        };
    }

    @DataProvider
    public Object[][] invalidPassData() {
        return new Object[][]{
                {"Test", " "},
                {"IT", "it"},
                {"it", "IT"}
        };
    }

    @Epic(value = "Проверка модуля логина в систему")
    @Test(dataProvider = "invalidLoginData")
    @Feature("Авторизацияв  системе")
    @Description(value = "Проверка логина в систему с некорректным именем пользователя")
    public void invalidLoginTest(String username, String password) {
        LoginPage loginPage = new LoginPage(driver);
        loginPage.goToLoginPage();

        loginPage.enterName(username);
        loginPage.enterPassword(password);
        loginPage.clickLoginButton();

        loginPage.verifyLoginUserNameError("Неверное имя пользователя или пароль");
        loginPage.verifyLoginPasswordError("Поле Имя пользователя обязательно для заполнения.");
    }

    @Epic(value = "Проверка модуля логина в систему")
    @Test(dataProvider = "invalidPassData")
    @Feature("Авторизацияв  системе")
    @Description(value = "Проверка логина в систему с некорректным паролем")
    public void invalidPassTest(String username, String password) {
        LoginPage loginPage = new LoginPage(driver);
        loginPage.goToLoginPage();

        loginPage.enterName(username);
        loginPage.enterPassword(password);
        loginPage.clickLoginButton();

        loginPage.verifyLoginUserNameError("Неверное имя пользователя или пароль");
    }

    @Epic(value = "Проверка модуля логина в систему")
    @Test
    @Feature("Авторизацияв  системе")
    @Description(value = "Проверка логина в систему с корректными данными")
    public void validLoginTest() {
        LoginPage loginPage = new LoginPage(driver);
        loginPage.goToLoginPage();

        loginPage.enterName(username);
        loginPage.enterPassword(password);
        loginPage.clickLoginButton();

        MainPage mainPage = new MainPage(driver);
        String string = " Главная ";
        Assert.assertEquals(mainPage.getTitlePage().trim(), string.trim(),"Invalid page title");
        Assert.assertEquals(mainPage.checkUsername(), USERNAME, "Wrong username");
        Assert.assertEquals(mainPage.checkURL(), HOME_URL, "Wrong URL mainpage");
    }
}

MainPageTest

public class MainPageTest extends BaseTest {

    private MainPage mainPage = new MainPage(driver);

    @Epic(value = "Проверка главной страницы")
    @Feature(value = "Проверка элементов страниц")
    @Test
    @Description(value = "Проверка имени пользователя")
    public void testCheckUsername() {
        Assert.assertEquals(mainPage.checkUsername(), USERNAME, "Wrong username");
    }

    @Epic(value = "Проверка главной страницы")
    @Feature(value = "Проверка элементов страниц")
    @Test
    @Description(value = "Проверка URL страницы")
    public void testCheckURL() {
        Assert.assertEquals(mainPage.checkURL(), HOME_URL, "Wrong URL mainpage");
    }

.............................

    @Epic(value = "Проверка главной страницы")
    @Feature(value = "Проверка элементов страниц")
    @Test
    @Description(value = "Проверка футера")
    public void testGetFooter() {
        Assert.assertTrue(mainPage.getFooter());
    }

    }
}

С прогоном теста страницы логина проблем нет, всё прекрасно. Проблемы начинаются с прогоном теста главной страницы. Сразу рагуется на java.lang.NullPointerException. Вроде понимаю суть проблемы, не использую объект, который создается при успешном логине в методе

public MainPage clickLoginButton() {
        loginButton.click();
        return new MainPage(driver);
    }

Но каким образом переписать MainPageTest пока понять не могу. Вариант с

loginPage.clickLoginButton().checkUsername();
или
loginPage.clickLoginButton().openModule(mainPage.getUnitModule());

мне кажется неправильным.
Подскажите пожалуйста возможные варианты.

https://github.com/A-root/Test

на какой строчке ругается? какой стек трейс?

Например, для метода testCheckUsername вот такое


java.lang.NullPointerException
	at org.openqa.selenium.support.pagefactory.DefaultElementLocator.findElement(DefaultElementLocator.java:69)
	at org.openqa.selenium.support.pagefactory.internal.LocatingElementHandler.invoke(LocatingElementHandler.java:38)
	at com.sun.proxy.$Proxy13.getText(Unknown Source)
	at pages.MainPage.checkUsername(MainPage.java:90)
	at tests.MainPageTest.testCheckUsername(MainPageTest.java:21)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:124)
	at org.testng.internal.Invoker.invokeMethod(Invoker.java:583)
	at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:719)
	at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:989)
	at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
	at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:109)
	at org.testng.TestRunner.privateRun(TestRunner.java:648)
	at org.testng.TestRunner.run(TestRunner.java:505)
	at org.testng.SuiteRunner.runTest(SuiteRunner.java:455)
	at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:450)
	at org.testng.SuiteRunner.privateRun(SuiteRunner.java:415)
	at org.testng.SuiteRunner.run(SuiteRunner.java:364)
	at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
	at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:84)
	at org.testng.TestNG.runSuitesSequentially(TestNG.java:1208)
	at org.testng.TestNG.runSuitesLocally(TestNG.java:1137)
	at org.testng.TestNG.runSuites(TestNG.java:1049)
	at org.testng.TestNG.run(TestNG.java:1017)
	at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:72)
	at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:123)

90 стока в MainPage это

                    public String checkUsername() {
>>>>вот это>>>>>>        return username.getText();
                    }

При вызове каждого метода в тесте ругается на методы из MainPage

я в упор не вижу что бы мы искали вебелемент username на котором можно вызвать getText();

я просто вырезал вебэлементы из кода тут, чтобы сократить размер. Дополнил перввое сообщение

я подозреваю что проблема в инстансе драйвера. А MainPageTest и Login PageTest эти оба класса лежат в одном пакете?

да
image

и все пейджи тоже в одном пакете?

пейджи в папке Pages
image

Попробуйте инициализировать элементы в каждой странице. А еще лучше такой вариант использовать, чтобы он мог какое-то время подождать отрисовки элемента: PageFactory.initElements(new AjaxElementLocatorFactory(driver, 60), this); (60 секунд в данном случае)

я правильно понял? изменил метод в MainPage

public MainPage(WebDriver driver) {
        super(driver);
        PageFactory.initElements(driver, this);<<<<<<<<<<<<<<<<добавил вот это
    }

Не помогло

Может не стоит конструктор базового класса вызывать? У меня в конструкторах PageObject’ов по умолчанию такой набор:

	PageFactory.initElements(new AjaxElementLocatorFactory(driver, 90), this);
	this.driver = driver;
	wait = new WebDriverWait(driver, 30);

Никаких изменений

public MainPage(WebDriver driver) {
        PageFactory.initElements(new AjaxElementLocatorFactory(driver, 60), this);
        this.driver = driver;
    }

Очень сложно идентифицировать проблему по отрывкам кода.
Дайте ссылку на репозиторий, или архив с проектом.

2 лайка

del

Пофиксил здесь.
Бренча ‘fix’. Там есть пул реквест чтоб сравнить изменения.
Если кратко, то вы неправильно инилиализировали пейдж обжекты.
https://github.com/PavloBida/Test/tree/fix

Спасибо большое за помощь!
Есть небольшие вопросы по фиксу:

  1. Мне надо в BaseTest создать экземпляры всех своих страниц. К MainPage дописать все остальные?
  2. Когда запускается MainPageTest открывается страница логина (это логично и понятно). Как получить “залогиненый” экзампляр с теста логина? Не убивать после теста логина браузер? Потому что после проверки MainPage будут другие страницы и тесты для проверки. Как сказать After работать после запуска последнего теста, например описанного в .xml?

Изменил на BeforeSuite и AfterSuite. Теперь опять java.lang.NullPointerException

У Вас архитектура которая противоречит SOLID i GRASP, у Вас parent клас BasePage использует LoginPage и другие свои child-и - не делайте так -

Необходимо убрать из BasePage создание экземпляров страниц?

создание экземпляров child-ов BasePage-а