Generics

Всем привет.

Работаю над автоматизацией тестирования веб-магазина(Java, WebDriver, PageObject) и столкнулся со следующей проблемой:

Периодически необходимо возвращать одним методом разные page object'ы. Например, клик по кнопке "Buy" должен возвращать page object страницы CheckOutPage в случае, если юзер авторизован или же страницу SignInPage, если он не авторизован.

Каким образом реализуются такие методы в Java? Я так понимаю необходимо использовать generics, но как-то ничего не получается.

Пока что довольствуюсь следующим:

  1. productPage.clickSignInButton();
  2. CheckOutPage checkOutPage = new CheckOutPage(webDriver);
​Но хотелось бы более красивого решения.
Кто-нибудь сталкивался с чем-то подобным? Как мне кажется, проблема достаточно распространённая. Может я вообще не в ту сторону копаю?

 

 

 

Page Object Pattern + Factory Pattern + Page Factory почитайте

На форуме есть все это

 

В таких случая я просто делал проверку и возвращал объект нужного типа ... или базовый/абстрактный класс для этих "страниц", а уже дальше во flow или тесте кастовал до нужного типа.

Не совсем понимаю как возвращать объекты разного типа одним методом. Чисто синтаксически. 

С кастованием до нужно типа идея интересная, но неужели нельзя сделать это красивее? Чтобы тело теста было предельно простым и тому, кто его пишет не приходилось задумываться о реализации. 

Или я всё-таки что-то не так понял? :)

Читал обо всём вышеперечисленном. Не совсем понимаю какое это имеет отношение к моему вопросу.

Про  Factory Pattern не расскажу, т.к. не работал с дженериками, а вот про "каст" могу пояснить:

 

Допустим у нас есть TabBar с несколькими табами tab1, tab2, tab3

 

1. Делаем абстрактный класс а в нем энумом перечисляем каждую табу, в этих табах нап понадобится локатор (что бы кликнуть и открыть таб)  ну и наверное самое название таба ... может и еще что нить :) . Каждая таба - будет отдельным классом и соотвественно на ней будет разная "начинка"

public abstract class TabBase {

 

public static enum Tabs {
TAB1("css=.tab1 > a", "Tab1Name"), TAB2("css=.tab2", "Tab2Name"), TAB3("css=.tab3", "Tab1Name");
 
private String tabLocator;
private String tabName;
 
   private Tabs(String tabLocator, String tabName) {
    this.tabLocator = tabLocator;
    this.tabName = tabName;
   }
 
    public String getTabLocator() {
    return tabLocator;
   }
    
   public String getTabName() {
    return tabName;
   }
    
 
};

 

 

public TabBase(){

//какое-то тело конструктора + проверки ... если надо

}

 

//тут какие нить общие методы для табов

 

abstract Tabs getCurrentTab();
 
 
public String getTabName(){
 
       return = getCurrentTab().getTabName();
 
}
 

// и т.д.

 

}

 

 

2. Дальше реализуем классы для каждой табы, все они "экстендят" абстрактный класс

 

 

public class Tab1 extends TabBase {
 
public OtherHotelsTab(){
     super();
}
 
@Override
Tabs getCurrentTab() {
       return Tabs.OTHER_HOTELS;
}
 
// ну и т.д. одним словом всё что надо =)
 
}

 

 

3. Т.к. у вас POM то в классе странице, на которой находятся эта tabBar реализуем метод аля

 

public AccommodationTabBase openTab(Tabs tab){
new Element(tab.getTabLocator()).click();
switch (tab) {
   case TAB1:
    currentTab = new Tab1();
   break;
 
   case TAB2:
    currentTab = new tab2();
  break;
 
  case TAB3:
    currentTab = new Tab3(); 
  break;
 
  default:
    throw new InvalidParameterException("Incorrect tab name.");
}
 
return currentTab;
 
}
 

т.е. какую бы мы табу не открыли , нам всегад вернутся базовый абстратный класс табы, далее этот класс кастуется до нужной табы.   (Tab3)openTab(TAB3).doЧТО_ТО()

 

За хардкод локаторов и констант - не ругайте - это всё для примера. И это не сакмая лучшая реализация, но то что смог набрасать на коленке :)

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

 

однако поскольку для каждого теста у меня свой java класс то я просто описываю в параметрах класса как private все используемые пейджобджект классы и далее делаю checkOutPage = new CheckOutPage(webDriver);

у меня много методов которые возвращают новый пейджобджект. как по мне ничего криминального в таком неизящном решении нет.

 

(обновлено)

поясню:

есть pageObjects: searchusersPage, editUsersPage.

 

на странице поиска юзеров  есть опция редактирования, нажимаем, ткрывается страница его редактирования.

 

код выглядит след. образом:

editUsersPage = searchUsersPage.editUser();

 

метод editUser выглядит примерно так:

public editUsersPage editUser(){

editUserButton.click();

return new editUsersPage(driver);

}

У меня так же. Один тест = один класс. Не совсем понял "я просто описываю в параметрах класса как private все используемые пейджобджект классы". Можно на этом месте поподробнее? :) 

И, останавливаясь, на приведённом коде: что если editUser() или любой другой метод, может открывать другую страницу?

Как вернуть им не editUsersPage а что-то другое?

Выглядит многообещающе :) Обязательно попробую такой подход, спасибо.

привет, если я правильно понял, что вы хотите сделать, то абстрактный код будет выглядеть вот так

метод который может возвращать разные типы PageObject

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

public class GoogleSearchPage extends Page {
public GoogleSearchPage(WebDriver driver) {
super(driver);
}

public <strong>&lt;T extends Page&gt;</strong><t extends="" page=""> T searchItem(String name, <strong>T page</strong>){
	WebElement element = driver.findElement(By.name("q"));
	element.clear();
	element.sendKeys(name);
	return <strong>page</strong>;
}

}

Тест, который использует данный код

import junit.framework.Assert;

import org.junit.Test;

public class MyFirstTest extends BaseTestCase{
@Test
public void myFirstTest(){
GoogleSearchPage search = new GoogleSearchPage(driver);
GoogleSearchPage newSearch = search.searchItem(“automated-testing.info”, new GoogleSearchPage(driver));
ResultPage r = newSearch.searchItem(“test automation”, new ResultPage(driver));
Assert.assertEquals(r.getFirstItem(), “123”);
}
}

Это то что вы хотите решить?

если надо могу прикрепить весь проект эклипса

Да, это как раз то самое. Использовал точно такое же решение.

Но в строчке "GoogleSearchPage newSearch = search.searchItem("automated-testing.info", new GoogleSearchPage(driver));" в первую очередь выполняется "new GoogleSearchPage(driver)" что делает данное решение не совсем красивым в случае, если в конструкторе класса GoogleSearchPage происходят некоторые проверки(например, что страница открылась).

Как это обходите? Или просто ничего не проверяете в конструкторе?

конечно было бы интересно :)

но все равно надо где-то указывать, какая страница будет возвращаться.

вы подразумеваете, что инициализация должна происходить в методе?

можно конечно еще в методе сделать условия, на основании которого я бы возвращал, какой-то объект

Да, инициализация в методе была бы тем, что нужно. Но как возвращать какой-то объект на основании условия не понимаю. Всё, что не пробую, не компилируется :)

На основании условия внутри метода наверное только вариант с кастами, приведенный товарищем devnull, подойдет. С дженериками нужно делать условие извне, на уровне - вот этот тест пойдет по такому пути, а этот тест по такому. Методу можно передавать класс, который будет инстанциироваться внутри метода. К примеру, измененный вариант polusok.

 

public <T extends Page> T searchItem(String name, Class<T> clazz){
        WebElement element = driver.findElement(By.name("q"));
        element.clear();
        element.sendKeys(name);
        return clazz.getConstructor(WebDriver.class).newInstance(driver);
}
 
GoogleSearchPage newSearch = search.searchItem("automated-testing.info", GoogleSearchPage.class);
ResultPage r = newSearch.searchItem("test automation", ResultPage.class);

 

А зачем вообще туда-сюда объекты гонять? Я обычно делаю PageObject примерно такими:

GoogleSearchPage.get().searchItem("info");

ResultPage.get().searchItem("other info");

 

Где get() - это реализация signleton стандартная.

Тесты и писать, и читать проще.

Могут возникнуть проблемы с распараллеливанием, но они решаемы внутри реализации get(), создавая экземпляр класса в рамках каждого потока.

Выглядит правильно, но не работает:

Валится на строчке return clazz.getConstructor(WebDriver.class).newInstance(driver); с таким эксепшеном:

java.lang.NoSuchMethodException: GoogleSearchPage.<init>()

Есть идеи?

Вопрос удобочитаемости весьма спорный :) Тут уже дело привычки наверное.

Значит нет такого конструктора, либо к нему нет доступа (не паблик к примеру)

Вот побольше пример, с хорошими конструкторами.

 

public class example {
    public static void main(String[] args) {
        GooglePage googlePage = getPage(null, GooglePage.class);
        YandexPage yandexPage = getPage(null, YandexPage.class);
        System.out.println(googlePage.getMessage());
        System.out.println(yandexPage.getMessage());
    }
 
    private static <T extends Page> T getPage(WebDriver driver, Class<T> clazz) {
        try {
            return clazz.getConstructor(WebDriver.class).newInstance(driver);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
 
class Page {
    protected WebDriver driver;
 
    public Page(WebDriver driver) {
        this.driver = driver;
    }
}
 
class GooglePage extends Page {
    public GooglePage(WebDriver driver) {
        super(driver);
    }
 
    public String getMessage() {
        return "I'm a google page";
    }
}
 
class YandexPage extends Page {
    public YandexPage(WebDriver driver) {
        super(driver);
    }
 
    public String getMessage() {
        return "I'm a yandex page";
    }
}