Можно ли обойтись без page object паттерна

А в чём проблема-то? В том-то и дело, что я не вижу никакой проблемы с приведённым примером. Ну, можно вынести эти операции в методы пэдж обжекта, но он не станет более читабельным от этого. А кода будет вдвое больше.

4 лайка

Я писал автотесты на указанном стеке. Встречный вопрос - а почему не использовать? Задача - мы хотим инкапсулировать логику нахождения html элементов на странице. PageObject - не единственный возможный паттерн, но самый очевидный. На ruby есть готовые реализации, так что самому надо будет описывать только локаторы. Итак, в чем причина сомнений?

Но вообще да, встречался с ситуацией, в которой PageObject был бы неудачным решением. Проблема была с переиспользованием html объектов с динамическими локаторами, имеющиеся реализации PageObject на Java этого не позволяли.

1 лайк

Вот, в этом вся проблема!
Все используют пэдж обжекты, чтобы прятать локаторы. А на самом деле задача пэдж обжектов - инкапсулировать не нахождение элементов, а логику работы с этими элементами.

2 лайка

Ну это, мягко говоря, спорное утверждение. Есть ссылка на источник, как должно быть “на самом деле”? Давайте тогда начнем определения, что это вообще такое - “PageObject pattern” :smile:

PageObject’ы используют

  • и чтобы прятать локаторы: давать им красивые имена и нормальную поддержу рефакторинга
  • и чтобы инкапсулировать логику работы с этими элементами
  • а также чтобы упростить совместную работы команды

Вариантов как использовать PageObject’ы – множество. Нужно выбрать тот, который работает лучше всего для конкретного ЯП и проекта.

Причиной не использовать PageObject’ы может быть, как уже ополоснулось, проект на 10-30 тестов, который нужно за день накодить и забыть. Другая причина – это незнание основ ООП, которые в случае PageObject очень важны.

@asolntsev, вот вы, вроде бы, какие-то веб-приложения на Java разрабатываете. Неужели вы его тоже пишете в flat -стиле?:

10: if (User_Accaunt_isLoggedIn(httpContext, ACL, userName)) {
         int userFlagHandle = Project_Project_Settings_setLoginFlag(httpContext, ACL, User_Accaunt_getUserId(httpContext, userName), true);
       if (SQLBackend_getCurrentBackendName() == "Oracle")  {
          Oracle_SQL("INSERT INTO User VALUES" + (String)Project_Project_Settings_getLoginFlag(httpContext, ACL, User_Accaunt_getUserId(httpContext, userName));)
       }
      GOTO 20;
}
20: 

Конечно, можно сказать что это очень понятный flat-стиль код в контексте 10-ти строк… Но в масштабах большого проекта (приложения либо проект по автоматизации) – не использование классов и объектов (а PageObject не далеко стоит от обычного класса) мне кажется очень сомнительным, хотя и возможным.

2 лайка

Мне кажется тут все крутится вокруг идеологии того, что UI тестов должно быть мало.

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

Сейчас работаю над проектом, в котором пишутся и unit, и integration tests для бэкэенда, но UI полностью отделен и живет своей жизнью (JS frameworks) + unt тестов там нет. Помимо всего прочего, используются сервисы, написанные ранее другими командами. Ввиду масштабности и сложности продукта, есть множество сценариев, которые чисто технически очень сложно или вовсе невозможно покрыть на компонентном и интеграционном уровнях. К примеру, smart-cards, MFA, кастомные браузерные плагины / расширения, отдельные десктопные части, взаимодействующие с Web internally и т.п. Так вот в такой ситуации UI тесты - это связующее звено между frontend и backend. Потенциальные проблемы могут быть везде, а основные payment workflows зависят от внешних компонентов, к которым девелоперы никак не смогут получить доступ. И что они будут в итоге покрывать юнит тестами? Весь критический для бизнеса флоу останется непокрытым.

Итого, кол-во тестов у нас немалое + незабываем о кроссбраузерности (достаточно болезненный момент ввиду наличия независимого JS фронтэнда, в котором постоянно что-то меняется).

Вся эта лирика о том, что без page objects мой автомейшен проект давно превратился бы в такую кашу, на саппорт которой у меня не хватило бы сил, терпения и желания.

Тут прозвучало много различных определений и назначений page objects. Но давайте рассмотрим вопрос на более высоком уровне. Мы тестируем продукт определенной отрасли. Мы каждый день внутри команды общаемся посредством DSL (domain specific language). Мы создаем backend / UI код, называя методы / компоненты на языке домена… А UI тесты мы пишем на каком уровне? Не unit, не integration, а system (sys integration) / acceptance. Т.е. взаимодействуем с системой мы как? На уровне внешней вэб-оболочки. Внешняя оболочка представлена в виде набора из N страниц, логически объединенных между собой, и отражающих суть нашего бизнеса. Т.е. любая бизнес-операция может быть представлена в виде цепочки действий. Причем, каждое звено этой цепи логически ограничивает нас в путях возможного развития сценария. К примеру, мы не можем отправить платеж, без подтверждения заинтересованными лицами, т.е. функционал сайта не позволит нам осуществить send, минуя approve.

PageObject паттерн прежде всего призывает нас соблюдать бизнес модель нашего приложения: DSL именование, логическое компонентное разделение, строгое ограничение по контексту (невозможность использования логически неверных шагов) и т.п. Тем самым, мы защищаем себя от логических ошибок, посредством тестов общаемся с приложением на языке самого приложения, минимизируем объем рефакторинга в случае изменения бизнес логики / layout’а, соблюдаем UI структуру компонентов, снижаем порог вхождения для новых членов команды и т.п.

В общем, это совсем не призыв бездумного использования сего паттерна всегда и везде. Просто мне кажется, что на данный момент больший процент автоматизации задействован именно в аутсорсе, где идеальных процессов по факту почти нет. Плюс, не стоит забывать о множестве дополнительных факторов, которые так или иначе усложняют нам жизнь. Выбор архитектуры / инструментов / технологий / паттернов - достаточно сложный и кропотливый процесс, который, как говорится, depends on… Используйте то, что действительно обосновано в вашем конкретном случае, но при этом, не забывайте о сути и назначении паттернов. :wink:

7 лайков

Вы приводите какой-то страшный говнокод и почему-то считаете, что мой “flat” будет таким же страшным. Нет, мой “flat” код может быть вполне коротким и читабельным. Не надо ставить знак равенства между “flat” и “плохой”.

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

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

Пожалуйста, вот статья Мартина Фаулера: PageObject

The page object should encapsulate the mechanics required to find and manipulate the data in the gui control itself.
Прятать логику для поиска и манипуляции с элементами!

A good rule of thumb is to imagine changing the concrete control - in which case the page object interface shouldn’t change.

Золотые слова! Когда элемент меняется (скажем, с RADIO на SELECT), интерфейс пэдж обжекта не должен меняться.

@asolntsev Сколько у вас тестов на проекте?

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

PageObject - это шаблон для абстрагирования тестов от реализации интерфейса страницы. Он предоставляет нижележащему уровню (тестам, шагам) API для взаимодействия со страницей, скрывая особенности реализации. Таким образом интерфейс страницы может меняться не ломая работу тестов.

Абстрагировать можно и на других уровнях. Потому что страницы тоже не вечны и могут не только менять реализацию внутри API, но и сами со временем исчезать, сливаясь с другими страницами, при переработке интерфейса. Поэтому я, например, стараюсь выделять ещё такой уровень абстракции как бизнес-функционал (это типа DSL, но не обязательно на человеческом языке), который по своей сути практически не меняется. И именно его использую в тестах, таким образом сами тесты практически не меняются.

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

Андрей, ну вот и как ты предлагаешь решать такую проблему без Page Object? Работать в тесном контакте с разработчиками позволит только не получать такие проблемы неожиданно - ты сможешь менять тесты вместе с изменением интерфейса. Но менять тебе придется КАЖДЫЙ тест, которого коснется это изменение

@uslashka Примерно 3000 юнит-тестов и 300 UI-тестов.

@joemast Поддерживаю! Я об этом и говорю: пэдж обжекты надо использовать не как помойку локаторов, а для DSL из бизнес-операций.

@joemast Прежде всего, мы используем библиотеку Selenide, а она берёт на себя большУю часть этих проблем. Например, метод $("#fname").val("john") или $("#fname").shouldHave(value("john")) работает одинаково хорошо и с INPUT, и с SELECT.

@joemast Так я о том и твержу раз за разом! Этих тестов должно быть мало! Сотрудничество с разработчиками означает, что они сами пишут юнит-тесты и покрывают все возможные комбинации юнит-тестами. И поэтому UI-тестами не надо покрывать много сценариев. Не надо не потому, что лень, а потому, что это вредно! UI-тестов должно быть мало. Они должен покрывать только самые базовые сценарии.

Моё золотое правило:

Тестов должно быть как можно больше,
но
UI-тестов должно быть как можно меньше.

Мы реально так и работаем. В тех (очень редких) ситуациях, когда INPUT меняется на SELECT (и Selenide не поддерживает их одинаково), я меняю INPUT на SELECT в одном-двух UI-тестах. Это просто и быстро.

Если кто ещё не видел, вот мой доклад на Codefest 2015 про экономически эффективный процесс тестирования: Экономически эффективный процесс тестирования

2 лайка

@boring, обойтись можно конечно, но пожалейте людей, которые придут работать бок о бок с вами или же будут поддерживать ваш код.
POM - де-факто стандарт на данном витке автотестирования.

@asolntsev, я вас совсем не могу понять. У меня просто происходит разрыв шаблона: плавный, но очень целеустремленный.

Вы же начали спорить о том, что тесты в flat-стиле это вообще никакой не переходной этап перед использованием PageObject… Сейчас же вы яростно защищаете PageObject как средство создание DSL в проекте. Нет, ну сейчас то вы на нашей стороне, но почему-то продолжаете войну.

Selenide… не смотря на то, что Selenide поддерживает PageObject (секция Classic PageObject)
http://selenide.org/documentation/page-objects.html
В самой документации и тут на форуме, вы активно продвигаете анти-паттерн магических строк.

public class GoogleSearchPage {
  @FindBy(how = How.NAME, using = "q")
  private SelenideElement searchBox;

  public GoogleResultsPage search(String query) {
    searchBox.setValue(query).pressEnter();
    return page(GoogleResultsPage.class);
  }
}

В самом примере, используется обвертка SelenideElement… но, я уверен что и обычный PageObject из коробки будет работать с Selenide ( $ – поддерживает WebElement как параметр):

public class GoogleSearchPage {
  @FindBy(how = How.NAME, using = "q")
  private WebElement searchBox;

  public GoogleResultsPage search(String query) {
    $(searchBox).setValue(query).pressEnter(); // <--- 
    return page(GoogleResultsPage.class);
  }
}

Вопрос в том: почему же вы так негативно отзываетесь о Пейджобжектах в самой документации:

This style has some disadvantages, but if you want, Selenide allows it

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

public class GoogleSearchPage {
  public GoogleResultsPage search(String query) {
    $(By.name("q")).setValue(query).pressEnter();
    return page(GoogleResultsPage.class);
  }
}

Вы действительно считаете что вот это:

$(By.name("q")).setValue(query).pressEnter();

намного читабельнее вот этого?

$(searchBox).setValue(query).pressEnter();

Возвращаясь к цитате:

$("#fname").val("john")

Приводя примеры, вы как-то-то всегда обходите более сложные и длинные варианты локаторов. Я приводил примеры таковых выше, в начале темы. Но, тут как раз обратная ситуация: этот локатор слишком короткий.

Читая код, вот вопрос на миллион долларов:

Что означает #fname?

9 лайков

Поддержу в этом мега-трэде @asolntsev. Шаблон нужен, когда у вас одна и та же страница используется многократно.

Но если у вас одна и та же страница используется многократно, это значит, что вы в разных тестах делаете одно и то же. PageObject вам поможет избавиться от дублирования в коде, но не избавит вас от дублирования в выполнении тестов. От этого избавляет нормальная модульная архитектура приложения и более низкоуровневые тесты, ну и ли набившая всем уже оскомину “Пирамида”.

Сейчас работаю на проекте, где у нас 4000 юнит-тестов (некоторые из них слегка интеграционные), 100-200 интеграционных API тестов, пару сотен UI Unit Test’ов. Так вот необходимости в классических UI-тестах с Selenium’ом пока не возникло, так как бизнес-логика тестируется другими способами, быстрее чем это сделали бы через браузер. Вместо Selenium’a пока нам удобнее пройти руками, чтобы заодно протестировать вещи, которые автотесты не найдут. В общем, живём без Page Object’a.

5 лайков

Всё просто. Я не защищаю яростно PageObject. Я говорю, что если уж его использовать, то правильно его использовать как средство создание DSL. А все его обычно используют как помойку для локаторов.

Господь с вами, никаких антипаттернов я не продвигаю. Я как раз говорю, что сотрудничество с разработчиками означает (среди прочего), что разработчики будут делать читаемые и короткие локаторы. И тогда такой вариант: $("#firstName").val("john") будет ничем не хуже, чем такой: page.enterFirstName("john"). Если же локаторы нечитаемые и представляют из себя “магические строки”, то да, конечно, пэдж обжекты помогут скрыть эту проблему. Подчёркиваю: скрыть, но не решить!

Нет, конкретно это предложение говорит о том, что недостатки есть не у пэдж обжектов вообще, а конкретно у такого стиля пэдж обжектов, когда элементы объявляются как поля класса. Проблема, которую я имел в виду, вам наверняка хорошо известна: этот способ не поддерживает динамические локаторы, динамические элементы и всё такое. А этого добра становится всё больше.

Ну да, верно, для промоушина это звучит явно лучше. Спасибо. Надо будет ещё подредактировать.

Да, считаю, потом что в своих приложениях мы используем не “q”, а читаемый локатор типа “id=query”. И тогда действительно $("#query").setValue(query).pressEnter(); ничем не хуже.

Про fname отличный вброс, пять баллов! Если можно, я его буду цитировать в блогах и на конференциях.
На самом деле мы проповедуем понятие “чистый код” (clean code) и в коде, и в тестах. Если кто-то назовёт переменную “fname” или локатор “fname”, это будет одинаковой плохой говнокод. Конечно, мы назовём переменную “firstName”. Мой пример с “fname” был, конечно, неудачный. :smile:

Повторюсь: потому что мы сами придумываем локаторы. А те, кто сам не может, должен требовать этого от разработчиков. Для компании в целом будет гораздо эффективнее, если разработчики потратят немножко времени и напишут нормальные локаторы, чем если тестировщики будут городить конструкции, пэдж обжекты, фреймворки, микросервисы и парки серверов только лишь потому, что они не могут достучаться до разработчиков.

3 лайка

Это ограничение не PageObject'a или его вариаций. Это ограничение самого WebElement'a / PageFactory. Т.е. архитектурно эта связка не заточена для таких целей. Обычный technical debt, я бы сказал. Кому такой подход не нравился, или был недостаточен, уже давно изобрели свои кастомные обертки, поддерживающие динамические локаторы. Но концепция PageObject'а то от этого не поменялась.

1 лайк

Давайте подытожим:

Вариант 1. Вы и швец и жнец разработчик => вы можете писать тесты как угодно, ведь если что-нибудь “пойдет не так”, вы всегда сможете “подкрутить” приложение, что бы исправить ситуацию.

Вариант 2. Вы опытный автоматизатор => вы можете писать тесты как угодно, ведь если что-нибудь “пойдет не так”, вы всегда сможете предупредить эту ситуацию до точки “не возврата” – когда усилия, которые нужно будет затратить на рефакторинг, будут сопоставимы с усилиями на “переписать все с нуля”.

Вариант 3. Вы начинающий автоматизатор и каждый член команды перед каждым комитом ждет вашего апрува “Будет ли это ‘удобно’ тестировать?” => вы можете писать тесты как угодно, ведь если что-нибудь “пойдет не так”, вы всегда сможете спихнуть это на “коммитеров”.

Вариант юмористический. Вы начинающий автоматизатор и ваша команда - это нордические бородатые дядечки “перепиливающие” “довоенный” модуль с фортрана на плюсы, которым нету дела до ваших проблем, потому что у них “горят” сроки => вы можете писать flat тесты, ведь если что-нибудь “пойдет не так”, вы всегда сможете спихнуть это на начальство – объяснить ему, что у нас “неэффективный процесс разработки” и заставить всю команду на ежедневных стендап-митингах смотреть презентации тов. @asolntsev’a в профилактических целях.

Вариант реалистический. Вы начинающий автоматизатор и: не знакомы с анти-паттернами в программировании, не можете прогнозировать эволюцию своего кода, не знакомы с рефакторингом => пишите тесты используя общепринятые практики, что бы минимизировать возможные риски, ведь если что-нибудь “пойдет не так” – виноваты будете только вы.

11 лайков

Очень понравился ваш ответ, хоть я таки я раньше решил использовать эту практику и стараюсь внедрить site-prism, ваш ответ переубедил меня окончательно