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

Добрый день. Столкнулся с небольшой проблемой при разработке тестов 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>

Ну дек перед поиском нужно делать 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 ориентировался также на статью:

и код в репозитории:

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

1 лайк

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

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

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

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

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


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());
    }
}

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

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

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

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

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

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

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

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

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

Что у тебя в 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;

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

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

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

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

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

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