Как лучше построить Page Object в случае если элементы на странице и их количество могут динамически изменятся?

Здравствуйте! Я сейчас автоматизирую одно веб приложение и здесь есть такая особенность, что в зависимости от введённых значений могут появляться новые контролы на той же странице - поля для ввода, чекбоксы и так далее, то есть например в зависимости от выбранного пункта дропдауна, может появится 3 новых поля для ввода и кнопка, выбираешь другой пункт - появляются пара чекбоксов и еще одно поле для ввода (при чём все локаторы у этих элементов будут разные). Как лучше построить page object в таком случае? Изначально я создавал nested class-ы и описывал эти контролы там, дошло до того, что этих вложенных классов развелось очень много и в них трудно ориентироваться, чувствую что я делаю что-то не так. Как лучше поступить в такой ситуации?

Как насчет наследования? 1 общая пейджа + N specific потомков. В отличие от nested classes, структурно вы точно не увязните в хаосе разросшегося класса. При этом, методы главной пейджи в зависимости от сценария будут возвращать по цепочке потомков.

public class CommonPage {
    private WebElement commonElement1;
    private WebElement commonElement2;

    public SpecificPage1 doSmthAndContinueWithSpecificPage1() {
        commonElement1.click();
        return new SpecificPage1();
    }

    public SpecificPage2 doSmthAndContinueWithSpecificPage2() {
        commonElement2.click();
        return new SpecificPage1();
    }
}

public class SpecificPage1 extends CommonPage {
    private WebElement specificElement;

    public SpecificPage1 doSmthVerySpecific() {
        specificElement.click();
        return this;
    }
}

public class SpecificPage2 extends CommonPage {
    private WebElement specificElement;

    public SpecificPage2 doSmthVerySpecific() {
        specificElement.click();
        return this;
    }
}

// call

commonPage
    .doSmthAndContinueWithSpecificPage1()
    .doSmthVerySpecific();

Как насчет наследования? 1 общая пейджа + N specific потомков. В отличие от nested classes, структурно вы точно не увязните в хаосе разросшегося класса. При этом, методы главной пейджи в зависимости от сценария будут возвращать по цепочке потомков.

В общем-то нечто подобное я и сейчас использую когда есть смысл такое делать (когда меняется не 1-2 поля, а, например, целыми блоками), но ради 1-2 полей создавать отдельные классы как-то избыточно на мой взгляд. Возможно ошибаюсь. Так же интересно как поступить, когда поля появляются в зависимости от выбранного значения дропдаун-филда, здесь как поступить? Написать отдельный метод на каждую опцию дропдауна и возвращать нужный класс? Либо написать какой-то обобщённый метод, который возвращает класс в зависимости от переданного значения в этот дропдаун (по enum или даже просто так, по тексту)?

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

Касательно последних вопросов. Если у вас будет 1 метод, вам придется в итоге приводить типы для получения доступа к методам той или иной пейджи. А множество геттеров приведет к перегруженности самого класса. Что именно будет лучше? Depends on… Это вы уже должны взвесить сами, ибо мне не видна общая картина.

Хм… Если количество элементов всё-таки фиксировано (кликнув на чекбокс, появятся 3 (всегда 3, не 2 и не 4) новых контрола) - я бы их все описал в родительском классе. По сути, нет разницы, появились они в момент загрузки на странице или после совершения какого-либо действия. Насчёт динамических айдишек, например, в ajax - найдите те части, которые у них остаются в любом случае (классы, текст, положение относительно статического родительского контрола и т.д.) и используйте их в локаторах.
Если количество абсолютно непредсказуемо (например, кликнув на кнопку, всегда получим 1 новый контрол, кликать можно сколько угодно раз) - для такого контрола я бы использовал список в родительском классе и findAll по субсекции для поиска.
Для большей информативности теста можно использовать nested classes, только я бы использовал их не для описания динамических контролов, а для описания субсекций на странице. Например, если на странице есть субформы и т.д., чтобы получить код вызова типа:

    myPage.mySubform.myControl.set ("myValue");

Хм… Если количество элементов всё-таки фиксировано (кликнув на чекбокс, появятся 3 (всегда 3, не 2 и не 4) новых контрола) - я бы их все описал в родительском классе.

Такой вариант не нравится тем, что во время написания теста будет доступно много лишнего api (гетеров и сетеров полей, которые в данный момент не доступны и всё такое прочее), я стараюсь такого избегать, т.к. страницы большие и контролов на них бывает очень много, из-за этого написание тестов усложняется - нужно смотреть в код и разбираться какой метод какому полю соответствует и так далее, поэтому сейчас использую нечто похожее на тот вариант, который вы описали - вложенные классы для различных блоков полей на странице, хоть по сути все они составляют единую страницу, но их зачастую можно разделить логически, например: блок полей ввода адреса, блок полей ввода информации о физическом лице, блок полей ввода информации об юридическом лице и так далее. Это я привёл абстрактный пример, но суть примерно такая.

Насчёт динамических айдишек, например, в ajax - найдите те части, которые у них остаются в любом случае (классы, текст, положение относительно статического родительского контрола и т.д.) и используйте их в локаторах.

С этим, в общем, проблем нет - я владею xpath :smile:

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

Ну, если Вы начинаете в них путаться - могу разве что посоветовать максимально их унифицировать, чтобы можно было их использовать, не вникая в структуру конкретного page object, т.е., называть контролы и, главное, блоки (nested classes) идентично. Т.е., поле имени в любом page object должно называться, например, nameField, блок информации о физлице - physicalPersonFieldGroup и т.д., тогда при работе с разными страницами у Вас не должно возникать проблем, достаточно будет посмотреть на страницу в браузере, и путь для обращения к конкретному объекту будет очевиден. Аналогично, обязательно использовать идентичную политику определения групп для всех page objects: если группа визуально выделена на странице, она должна быть создана в page object независимо от “да ладно, тут всего 2 контрола и 1 тестик, и без группы сойдёт”. Естессно, необходимо отрефачить все старые page objects, которые, вполне возможно, не соответствуют политике групп (у меня такое часто было, особенно когда автотесты только начинаются :grin: )