Не находятся элементы внутри iframe при работе с PageFactory и в Selenide

page-factory
selenide
Теги: #<Tag:0x00007f3d400aca60> #<Tag:0x00007f3d400ac920>

#1

Добрый день. Столкнулся с небольшой проблемой при разработке тестов c PageFactory и Selenide.
Проблема состоит в том, что не находятся элементы(NoSuchException), расположенные внутри iframe - элементы внутри "fooFrame" из примера, находятся только элементы из Header.
Пример:

public class MainPage {

    @FindBy(id = "fooFrame") //фрейм
    private FooFrame fooFrame;

    @FindBy(id = "header")
    private Header header;

    public Header getHeader() {
        return header;
    }

    public FooFrame getFooFrame() {
        return fooFrame;
    }

public class FooFrame extends ElementsContainer {

    @FindBy(id = "otherFrame") //фрейм
    private OtherForm otherForm;

    @FindBy(id = "fooInput")
    private SelenideElement fooInput;

    @FindBy(id = "fooButton")
    private SelenideElement fooButton;

    public SelenideElement getFooInput() {
        return fooInput;
    }

    public SelenideElement getFooButton() {
        return fooButton;
    }

    public OtherForm getOtherForm() {
        return otherForm;
    }
}

Сам тест:

    @Test
    public void searchTest() {
        openBrowser();
        onMainPage().getFooFrame().getFooInput().sendKeys("123425");
        onMainPage().getFooFrame().getFooButton().click();
...
    }

    private MainPage onMainPage() {
        return onPage(MainPage.class);
    }

Страница инициализируется следующим образом:

public class DriverSteps {

    private final WebDriver driver;

    @Inject //через Guice
    public DriverSteps(WebDriver driver) {
        this.driver = driver;
    }

    private <T> T onPage(Class<T> pageClass) {
        WebDriver driver = getWebDriver();
        try {
            T page = ConstructorUtils.invokeConstructor(pageClass);
            SelenidePageFactory.initElements(new SelenideFieldDecorator(driver), page);
            return page;
        } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    private WebDriver getWebDriver() {
        return driver;
    }
}

Страница

<html>
<head>
<meta charset="UTF-8">
<title>Title here</title>
</head>
<body>
	<div id="header">
            ........................
	</div>
  	<iframe id = "fooFrame">
		<input id="fooInput" type="text" placeholder="--">
		<button id="fooButton" type="submit" >Искать</button>
			<iframe id="otherFrame">
			    .....................................
			</iframe>
	</iframe>
</body>
</html>

(Sergey Pirogov) #2

Ну дек перед поиском нужно делать switch to frame, проблема уже описана 100500 раз


#3

Я понимаю, но как это сделать в концепции с page-factory (@FindBy)?
Раньше у меня был класс-расширение FooBy, где элементы объявлялись так:

private final static FooBy FOO_INPUT = FooBy.id("fooInput").inFrame(FOO_FRAME); - указывался фрейм, в котором находится элемент.
private final static FooBy FOO_FRAME = ExBy.id("fooFrame");

Внутри класса FooBy:

    public FooBy inFrame(By frameId) {
        this.frame = frameId;
        return this;
    } 

переопределены методы findElement, findElements и в них вызывается метод 
Selenide.switchTo().frame($(frame));

При работе с page-factory ориентировался также на статью:
http://ru.selenide.org/2016/04/17/airtickets/
и код в репозитории:
https://github.com/selenide-examples/airtickets.ru


(Василь Головчак) #4

Мабуть ще один мінус в сторону підходу з PageFactory. Якщо просто інкапсулювати в методах пейджобджектів якісь "бізнес" дії допущені на сторінці і не виносити локатори\елементи в філди то й проблем таких не виникатиме.


(asolntsev) #5

В концепте page factory - никак. Поэтому это плохой концепт, не используйте его.


#6

Не будут ли в этом случае классы с pageobject излишне перегруженными, когда в одном классе и field, и бизнес методы. Основная идея использования pageFactory у нас была в том, чтобы переиспользовать блоки ElementsContainer на других Page.


#7

Да, я читал несколько комментариев в других темах, почему Selenide хоть и поддерживает PageFactory, но вы не рекомендуете его использовать, но пока альтернативного архитектурного решения у нас не наблюдается.


(Василь Головчак) #8

Справа в тому, що в більшості випадків філди-елементи і їхні гетери є зайвими. Краще просто "бізнес" дії, щоб в тесті було якось так:
FooFrame.SetFoo("12345").ClickFoo();
//next actions here or needed assertions


(asolntsev) #9

Привет!
Не понял, чего именно не наблюдается? Это же очень просто делается безо всяких "архитектурных решений" и фабрик. Например, так (схематично):

public class FooFrame {
    private By fooFrame = By.id("fooFrame");
    private By fooInput = By.id("fooInput");
    private By fooButton = By.id("fooButton");

    public doFooBusinessOperation(String businessParameter) {
      switchTo(frame(fooFrame));
      $(fooInput).val(businessParameter);
      $(fooButton).click();
      switchTo(defaultFrame());
    }
}

(Taras) #10

ответ есть - написать свой декоратор к Page Factory


(Roma Marinsky) #11

Хаха.. Не прикалываейся :slight_smile:

Не стоит даже думать о таком :slight_smile:


(Taras) #12

ну я в свое время сделал такое, и у меня теперь своя аннотация @Chunk, которая решает проблему вставки фрейма як части елемента страници либо даже другого куска страници как поле класа другого Page Object-a, ну и @FindBy тоже можно использовать в паралель


(Roma Marinsky) #13

Хаа)) Тонко)) Chuck


(Roma Marinsky) #14

Как раз таки в дополнение к тому, что Солнцев написал. И чтобы обращаться к таким объектам, надо написать просто new FooFrame(); И работай с полями своего объекта как хочешь) И не нужно писать аннотации, гетеры, особенно последнее не стоит писать


#15

FooFrame будет как отдельный объект, а что делать с MainPage, исключить? То есть в тестах будет:

 @Test
    public void searchTest() {
        openBrowser();
        onFooFrame().doFooBusinessOperation("businessParameter")
...
    }

private FooFrame onFooFrame(){
    return new FooFrame();
}

что не совсем понятно будет в плане поддержки кода и код ревью.


(Roma Marinsky) #16

Что у тебя в openBrowser()?
почему бы не:

class MainPage{
void doBusinessOpInFooFrame(){
      switchTo(frame(fooFrame));
      $(fooInput).val(businessParameter);
      $(fooButton).click();
      switchTo(defaultFrame());
}

class ClassWithTests{
    @Test 
    public void fooTest(){
        open("/");
        new MainPage().doBusinessOpInFooFrame();
   }
}

Если хочется, то сделай метод MainPage openMainPage() в MainPage, который вернёт this;


#17

openBrowser() - обычный метод инициализации браузера и открытия стартовой страницы. Да, с названием метода промахнулся.
На самом деле структура страницы немного посложнее и обычно состоит как минимум из 3 вложенных фреймов, т.е. по аналогии может быть:

void doBusinessOpInFooFrame2(){
      switchTo(frame(fooFrame));
      switchTo(frame(fooFrame1));
      switchTo(frame(fooFrame2));
      $(fooInput).val(businessParameter);
      $(fooButton).click();
      switchTo(defaultFrame());
}

Получается, что в MainPage лежат элементы из FooFrame, FooFrame1, FooFrame2


(Roma Marinsky) #18

У вас вложенные фреймы?


#19

Да, именно так.


(Roma Marinsky) #20

Если это полноценная бизнес манипуляция, то так и делай