XPathRepository(?) vs Page Object - плюсы и минусы первой архитектуры

Добрый день!
В связи с не очень большим опытом тестирования используя selenide, прошу подсказки, какую “архитектуру” будет правильней выбрать для тестирования.

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

В первом случае используется так называемый XPathRepository, который, как я понимаю так же является и PageObject ом:
XPathRepository:

Код класса
public final class XPathRepository {

 public static final class Header {
        public static final AttributeEntry LOGO = createAttributeEntry("data-qa","header_logo");
        public static final AttributeEntry COMPLEXES = createAttributeEntry("data-qa","header_complexes");

public static final class Footer {
        public static final AttributeEntry EXPAND = createAttributeEntry("data-qa","footer_expand");
        public static final AttributeEntry COMPLEXES = createAttributeEntry("data-qa","footer_complexes");
        public static final AttributeEntry EXPERTS = createAttributeEntry("data-qa","footer_experts");
        public static final AttributeEntry KNOWS = createAttributeEntry("data-qa","footer_knows");

//Другие внутренние классы страниц

//Получение элемента/ов - код находится в этом же классе

 public static AttributeEntry createAttributeEntry(String attributeName, String attributeValue){
        return new AttributeEntry(attributeName,attributeValue);
    }

    public static CSSSelectorEntry createCSSSelectorEntry(String cssSelector){
        return new CSSSelectorEntry(cssSelector);
    }

    public static class AttributeEntry implements IEntry{
        private final String attributeName;
        private final String attributeValue;

        private AttributeEntry(String attributeName, String attributeValue) {
            this.attributeName = attributeName;
            this.attributeValue = attributeValue;
        }

        public String getAttributeName() {
            return attributeName;
        }

        public String getAttributeValue() {
            return attributeValue;
        }

        public ElementsCollection getElementsCollection() {
            return $$(byAttribute(attributeName,attributeValue));
        }

        public SelenideElement getSelenideElement() {
            return $(byAttribute(attributeName,attributeValue));
        }
    }

    public static class CSSSelectorEntry implements IEntry{
        private final String cssSelector;

        public String getCssSelector() {
            return cssSelector;
        }

        private CSSSelectorEntry(String cssSelector) {
            this.cssSelector = cssSelector;
        }

        public ElementsCollection getElementsCollection() {
            return $$(cssSelector);
        }

        public SelenideElement getSelenideElement() {
            return $(cssSelector);
        }
    }

    interface IEntry{
        ElementsCollection getElementsCollection();
        SelenideElement getSelenideElement();
    }
}

Обращение к элементам происходит так:

Код теста
 @Test
    public void ClickFooterElements(){
        Selenide.open("");
        XPathRepository.Footer.EXPAND.getSelenideElement().click();
        XPathRepository.Footer.COMPLEXES.getSelenideElement().click();
        XPathRepository.ComplexesFilter.WISHES.APARTMENT_BTN.getSelenideElement().click();
    }

Во втором случае имеется общий класс SiteFactory (не прикреплен), а также стандартная реализация паттерна PageObject, что можно заметить из тестов.

Код класса
public class FooterBlock {

    public SelenideElement getFooterExpand() {
        return $("[data-qa=footer_expand]");
    }
    public SelenideElement getFooterComplexes() {
        return $("[data-qa=footer_complexes]");
    }
    public SelenideElement getFooterExperts() {
        return $("[data-qa=footer_experts]");
    }

    public SelenideElement getFooterKnows() {
        return $("[data-qa=footer_knows]");
    }

    public SelenideElement getFooterJournal() {
        return $("[data-qa=footer_journal]");
    }
}
Код теста
 @Test(enabled = true)
    public void footerTestDataQa() {
        open(domain);
        siteFactory.footerBlock().getFooterExpand().scrollIntoView(true);
        siteFactory.footerBlock().getFooterExpand().click();
        siteFactory.footerBlock().getFooterKnows().click();
    }

Буду благодарен, если подскажете, какую структуру использовать наиболее правильно, и если возможно, уточнить, какие могут быть подводные камни в первом случае?

Я ни тем ни тем способом не пользуюсь, хотя можно сказать вторым под fluent page object. Первый способ выглядит как доп обертка на xpath, я бы не завязывал проект на это, как по мне лучше второй способ и если нужно можно просто расширение на by сделать

Если есть более универсальные методы, хотелось бы тоже посмотреть, так как поиск на гитхабе ничего интересного не выдает (везде page object)
Про fluent почитаю, спасибо

Просто page object устоявшаяся практика и все знают как с этим работать, плюс еще page object разбивают на блоки или компоненты и это правильно. Лично я не вижу в локаторах такой ценности чтобы вокруг них наворачивать архитектуру. Поляруш page object в ютубе гляньте, там как раз fluent page object

1 лайк

Вот тут например расширение на By только на C# NSelene/Selectors.cs at master · yashaka/NSelene · GitHub

1 лайк

Я бы вам рекомендовал Screen Play. Что избавит вас от большой боли в будущем если вы будете использовать Page Object, он нарушает SOLID принципы, и становится очень грамозким решением которое сложно поддерживать

Так в статье screenplay поверх page object). Мне кажется сравнивают несравниваеме. ScreenPlay можно поставить в ряд с BDD или например отсутствием организации бизнес логики на голом Page Object. Так что вы нарушаете SOLID получается в любом случае.

Можете описать конкретно, чем Page Object нарушает SOLID?

В статье да, мы на проекте используем Screen Play без Page Object, и нет это нельзя поставить в ряд с BDD. Вот например статья на хабре, и где то есть еще видео от команды Serenity по поводу PO и его проблем с SOLID.
По поводу “отсутствия организации бизнес логики” не согласен, мы используем этот паттерн наряду с DI и это очень сильно нам помогает решать проблемы, у нас очень сложное приложение с кучей бизнес логики + еще саппорт 10+ версий приложения.
Еще ссылки из статьи выше:

  1. Page Objects Refactored: SOLID Steps to the Screenplay/Journey Pattern
  2. Beyond Page Objects: Next Generation Test Automation with Serenity and the Screenplay Pattern
  3. Serenity BDD and the Screenplay Pattern
1 лайк

Первый вариант (XPathRepository) - это точно какая-то шляпа. Переусложнено, не вижу никаких преимуществ.

Второй вариант уже вполне похож на стандартный Page Object. Годится.

2 лайка

Спасибо за развернутый ответ)
Просмотрел паттерн, мне кажется, возможно я ошибаюсь

  1. Нарушен KISS, немного замудрено, много шаблонного кода, без фреймворков не обойдешься, не на всех языках думаю есть. Может зайти только на больших долгоиживущих командах и проектах. Без строгого контроля в команде можно наломать дров.
  2. Нет флюента, нужно постоянно догадываться что и куда вставить, Fluent Page Object мне кажется будет более скоростной в написании кода и позволит меньше напрягать мозг).
  3. Всеже выглядит очень похожим на BDD, но так как это чисто код, думаю будет более гибким
givenThat(james).wasAbleTo(Start.withAnEmptyTodoList());
when(james).attemptsTo(AddATodoItem.called("Buy some milk"));
then(james).should(seeThat(TheItems.displayed(), hasItem("Buy some milk")));
  1. Targets очень похожи на компонеты в PageObject, только там совсем нет бизнес логики, думаю в этом и фишка, но с BDD мне кажется будет практически тоже самое, если всю бизнес логику держать в Steps

5.Написано что в Page Object нарушены Single Responsibility Principle и Open Closed Principle. Если мы в PageObject или Page Component храним только логику для этого обьекта, то думаю Single Responsibility Principle не нарушен. Open Closed Principle - Вам таргеты тоже прийдется менять если UI изменится. Как по мне Page Object это чисто описание страниц и действий на них, там не должно быть бизнес логики, тогда по идее ничего особо не нарушается, это как UI на REACT, там тоже все организовано в страницы и компонеты, получается самый популярный подход для UI теперь неправильный?

получается самый популярный подход для UI теперь неправильный

Я где то говорил что PO неправильный? Я говорил что он нарушает SOLID. Никакой паттерн не является маст хев, нужно действовать по ситуации.

  1. Я бы мог сказать тоже самое про PO, где его встречал на проектах. Мы пишем ScreenPlay чисто без завязок на всякие фреймворки, там не так много кода получается в core что бы говорить что нужно обязательно завязываться на что либо. Про большие долгоживущие проекты согласен. Ну а про контроль он всегда должен, а то будут большие проблемы с кодом, что я встречал до этого на проектах с PO.
  2. Удобнее да, но так идет привязка к странице и к действиям которые ты можешь делать только на ней, если есть общее действие на всех страницах придется все равно вызывать другой класс.
  3. В этом та и плюс что у тебя каждый компонент только за одно (S из SOLID)

Что я хочу сказать по итогу, я рекомендую рассмотреть 2 варианта (PO & ScreenPlay), и понять какого рода автоматизация будет у вас на проекте. Если это 5-10 страниц на вебке и вы поддерживаете только актуальную версию продукта то лучше рекомендую смотреть в сторону PO (с разбивкой логики на разные компоненты, а не все писать внуть 1 класса страницы). Если это будет более сложная вебка и будете поддерживать несколько версий продукта + еще коллекцию из разныех браузеров + разных ОС то лучше посидеть и реализовать Screen Play в дальнейшем больше профита получите.
Еще, никакой паттерн не является “правильным” и никакой принцип не является мантрой которую нужно соблюдать, нужно всегда опираться на текущую ситуацию.

1 лайк

Давно хотел попробовать ScreenPlay, наверное испытаю на одном из проектов, посмотрю что выйдет