Большой гайд по Page Object Model

Содержание

Наберите в Google «page object model» или «объектная модель страницы», и вы получите более миллиона ссылок. К сожалению, подавляющее большинство материалов по этим ссылкам предоставляют только высокоуровневый обзор POM или дают пару простых примеров. Это хорошее введение, но материала совершенно для решения реальных задач, связанных с POM.

Ниже вы найдете двенадцать глубоких тем по Page Object Model, которые выходят за рамки того, что вы найдете в этих миллионах статей из Google. Следует ли вам использовать декларативные или императивные интерфейсы? Как и когда использовать агрегатор/акторный слой? Стоит ли вообще использовать наследование при проектировании классов объектов страниц? Это те вещи, которые вам придется выяснять в реальных реализациях автоматизации.

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

Паттерн Page Object

Page Object — это всего лишь шаблон проектирования, и многие, кажется, забывают об этом. Это не магия и не что-то космическое; это определенная организация кода, которая создает определенные преимущества.

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

Вот и все. Такова цель каждого паттерна проектирования, о котором вы когда-либо читали.

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

100% да, но все перечисленное выше — это результаты хорошего проектирования, эффекты правильного проектирования. Непосредственная цель проектирования по-прежнему такова: собрать в одном месте все то, что будет меняться. Поместите вещи, которые изменяются по другой причине в другое время, в другое место. То, что не меняется вообще, поместите в третье место.

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

Шаблон Page Object Model изолирует несколько типов изменений, самым значительным и очевидным из которых является интерфейс между кодом ваших тестов и DOM приложения. Тесты пользовательского интерфейса должны содержать информацию о том, как находить элементы на страницах. Эта информация имеет тенденцию меняться, и она имеет тенденцию меняться на разных страницах.

Таким образом, объекты страниц (Page Objects) собирают и инкапсулируют эти знания в одном месте. Они говорят: «Подождите все, не беспокойтесь о том, КАК найти элемент x на странице y, я владею всеми знаниями о поиске элементов на странице Y. Я ваш эксперт по поиску на Y!».

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

Еще одна вещь, которая часто меняется в UI-тестах — это шаги, необходимые для выполнения теста, например: «сначала нажмите на эту кнопку, перейдите на эту страницу, введите этот текст, нажмите на другую кнопку, затем ищите эти данные». Эти знания явно отличаются и изменяются иначе, чем знания о том, как найти элементы на конкретной странице. Таким образом, они НЕ является частью объектов страницы и не должны там существовать. Этими знаниями должен владеть ваш тест, и вы можете очень быстро определить плохую реализацию страничных объектов, найдя информацию о тесте в Page Object.

Это понимание паттернов проектирования не диктует каждую деталь ваших страничных объектов. Существует огромное количество субъективности в понятии «изолировать то, что изменяется вместе», и значительное количество личных предпочтений и свободы действий при разработке дизайна, основанного на языке, архитектуре, сложности автоматизации и т.д. Вот почему автоматизация тестирования требует критического и творческого мышления, свойственного разработке программного обеспечения, и не является шаблонной.

Императивные и декларативные интерфейсы страничных объектов

В декларативных интерфейсах страничных объектов вы говорите объекту, что делать. В императивных интерфейсах вы говорите объекту, как это сделать. Вот очень простой пример:

Declarative:
LoginPage.logInAs(“test user”, “test-password”);

Imperative: 
LoginPage.setUserName(“test user”);
LoginPage.setPassword(“test-password”)
LoginPage.clickSubmit();

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

Читатели, которые прочитали первый раздел могут сказать: «Но в примере декларативный интерфейс помещает знания, не связанные с поиском вещей, в объект страницы Login! Он знает, как войти в систему! Это нарушение хорошего дизайна!».

Это обоснованная критика, но есть убедительные аргументы с разных сторон. Является ли знание о том, как войти в систему, выполнив три отдельных действия, чем-то, выходящим за рамки ответственности объекта страницы? Нам кажется, что нет, но даже если вы не согласны, по крайней мере, мы ведем разговор в правильном русле. Слишком много автоматизаторов просто бросают методы в файлы или классы, где им удобно.

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

Actor / Orchestrator cлой

Одна из проблем со страничными объектами, особенно для больших тестов в стиле «user journey», требующих много шагов, заключается в том, что они могут стать довольно длинными. Интерфейс между объектом страницы и тестом на самом деле более детализирован, чем то, что действительно нужно тесту, и это создает тесты, перегруженные мелкими, дискретными шагами. Например:

LoginPage.setUserName(“test user”);
LoginPage.setPassword(“test-password”);
LoginPage.clickSubmit();

LandingPage.searchForItem(“test-item”);
LandingPage.selectCurrentItem();

DetailsPage.selectSize(‘XL’);
DetailsPage.selectQuantity(1);
DetailsPage.selectColor(“white”);
DetailsPage.selectReoccuringPurchase(false);
DetailsPage.addToCart();

Этот пример может выглядеть простым и легко читаемым, но это всего лишь несколько основных шагов. В реальном user journey тесте, которое использует объекты страницы, раскрывающие столь детальный интерфейс, могут быть сотни отдельных шагов.

Это не только делает тест трудночитаемым, но и нарушает принцип «Чеховского ружья», поскольку, хотя все шаги необходимы, многие из них, вероятно, не имеют значения. В приведенном выше примере мы задаем размер, цвет и количество, но эти параметры не важны для теста — нам просто нужен любой правильный выбор.

Как мы уже говорили в предыдущем разделе, переход от императивного к декларативному интерфейсу может значительно сократить количество лишнего «шума» в тесте. В примере выше мы перешли от такого варианта:

DetailsPage.selectSize(‘XL’);
DetailsPage.selectQuantity(1);
DetailsPage.selectColor(“white”);
DetailsPage.selectReoccuringPurchase(false);
DetailsPage.addToCart();

к такому:

DetailsPage.addValidItem();

Это сводит все шаги «как» к простому шагу «что» и значительно уменьшает шум при тестировании.

К сожалению, если у вас много user journey тестов и многие из них повторяют один и тот же базовый набор шагов (вход в систему, выбор валидного элемента и т.д.), то перехода от императивного к декларативному стилю интерфейса может быть недостаточно. На самом деле вам нужен еще более высокий уровень абстракции, который фактически охватывает объекты страницы.

Именно здесь на помощь приходит модель актора или агрегатора. Эти названия взаимозаменяемы, и я видел, как этот паттерн называли по-разному, но независимо от названия все они служат одной цели. Для упрощения я буду использовать «актор».

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

Page Object Model
Графика — Блейк Норриш

Эта схема добавления прослойки между объектами страницы и тестами на самом деле довольно распространена. Многие используют ее, не осознавая. Автоматизаторы видят кучу повторяющихся шагов в куче тестов, создают «помощника», который собирает эти шаги в одном месте, и используют этого помощника. Вуаля, паттерн актoра. (подробнее об этом в разделе DRY vs DAMP!).

В приведенном выше примере мы могли бы объединить весь набор шагов для всех трех объектов страницы в нечто подобное:

ShopperActor.logonAndSelectItem();

Этот метод может принимать аргументы, чтобы дать тесту немного больше контроля, но идея понятна. Важным замечанием является то, что акторы не должны использовать базовую библиотеку пользовательского интерфейса (selenium, playwright и т.д.), как это делают объекты страниц, они используют объекты страниц только для управления поведением пользовательского интерфейса. В противном случае они являются просто раздутыми страничными объектами.

Использование акторов для общих последовательностей действий и непосредственное использование page objects только для более тонкого контроля может сделать сложные user journey тесты значительно более читабельными.

Статические методы и методы экземпляра — цепочка вызовов Page Objects

В примерах выше все взаимодействия с объектами страницы были реализованы как статические методы в классах объектов страницы. Обычно именно так начинаются Page Objects, и именно это чаще всего встречается у команд автоматизации. Другой подход заключается в реализации объектов страниц как методов экземпляра, которые возвращают экземпляр объекта страницы, соответствующий тому месту, где браузер должен находиться после этого взаимодействия. Это позволяет «выстроить цепочку» шагов и уменьшить визуальный беспорядок в тесте.

Это может показаться сложным, но на самом деле все довольно просто. Например, метод DetailsPage.selectSize() возвращает ссылку на ‘this’, так как после выбора размера мы все еще хотим оказаться на странице деталей. Если бы мы проделали это со всеми методами, это выглядело бы следующим образом:

new DetailsPage()
    .selectSize(‘XL’)
    .selectQuantity(1)
    .selectColor(“white”)
    .selectReoccuringPurchase(false)
    .addToCart();
    .checkOut();

Обратите внимание, что метод addToCart возвращает экземпляр другого объекта страницы (CartPage в примере выше), потому что после нажатия кнопки ‘addToCart’ мы ожидаем, что браузер окажется на странице корзины, а не на странице товара.

Реализация интерфейса объекта страницы для поддержки цепочки методов может быть выполнена даже в функциональных языках, таких как javascript. Независимо от того, как он реализован, это ценный инструмент для улучшения читаемости тестов при использовании Page Objects.

Дискуссия о расположении assertion-ов

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

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

Например:

New ItemDetailsPage()
    .addtoCart(item)
    .checkout()
    .assertCartIsEmpty();

В этом примере очевидно, что последний шаг является утверждением; он проверяет, что корзина пуста. Вы никогда не должны прятать функциональные assertion-ы внутри шагов таким образом, чтобы скрыть то, что они проверяют. Если через несколько месяцев после автоматизации вашего теста кто-то прочитает его и не сможет сразу и точно определить, что проверяет тест — вы плохой автоматизатор. Помните: код читают в 10 раз чаще, чем его пишут — делайте его максимально читаемым.

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

Например, при создании копии объектов страниц (new ItemDetailPage() ) вы можете сделать требованием, чтобы при создании копии объекта проверялось, что браузер находится на ожидаемой странице. Таким образом, конструктор ItemDetailPage будет искать и проверять некоторый элемент или заголовок страницы. Это позволяет тестам «быстро падать», когда что-то идет не так, даже без явного assertion-а на уровне теста.

Существует большая свобода действий при разработке assertion-ов в объектной модели страницы и тестах, и у каждого есть свое мнение. Будьте последовательны в своем подходе и убедитесь, что каждый человек, внедряющий автоматизацию, понимает и следует выбранному направлению.

Page Objects vs Page Components

Объекты страниц обычно соответствуют страницам. Однако большинство современных веб-приложений не строятся как набор уникальных страниц. Вместо этого страницы собираются из набора многократно используемых компонентов. Например, компонент заголовка может находиться в верхней части каждой страницы, а компонент корзины — в правой части большинства страниц, связанных с покупками.

Не имеет смысла дублировать знания о том, как найти элементы в компоненте заголовка, на все объекты страницы, включающие заголовок. Вместо этого создавайте Page Components (компоненты страницы), которые связаны с меньшими частями страниц (в нашем примере — с заголовком).

Что представляет собой компонент? Это во многом зависит от архитектуры и дизайна вашего конкретного веб-приложения. UI-фреймворки, такие как React, организованы вокруг многократно используемых компонентов, поэтому часто это отличная отправная точка.

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

компоненты страницы - Amazon

Еще один вариант дизайна тестов с компонентами страниц: некоторые предпочитают использовать компоненты страниц не иначе, чем объекты страниц, вызывая их непосредственно из тестов. Другой подход заключается в том, чтобы компоновать компоненты страниц внутри объектов страниц. Так, в приведенном выше примере вы бы написали что-то вроде:

SearchResultsPage.FilterComponent.setStarRating(5);

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

Page Objects и наследование

Должны ли классы объектов страницы наследоваться от общего класса страницы? Если да, то что входит в этот базовый класс и почему?

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

Одним из распространенных случаев использования наследования в иерархии классов объектов страницы, к сожалению, является предоставление всем объектам страницы доступа к «вспомогательным методам», которые оборачивают базовую библиотеку автоматизации пользовательского интерфейса (например, Selenium). Некоторые автоматизаторы считают, что интерфейс, предоставляемый этими библиотеками, либо слишком сложен, либо недостаточно мощный, и создают прослойку между API библиотеки и объектами страниц, которые их используют.

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

Еще один распространенный, но часто сомнительный пример наследования в классах объектов страниц — создание базовых классов для страниц, которые очень похожи, но имеют разные локаторы. Например, страница SearchResultsPage, которая была локализована на английский, французский и немецкий языки.

В этом примере страница FrenchSearchResultsPage будет наследоваться от BaseSearchResultsPage. BaseSearchResultsPage определяет интерфейс, используемый каждой SearchResultsPage, и реализует все методы, на которые не влияет локализация. Затем каждая страница для конкретного языка будет реализовывать специфические для нее методы.

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

Самое сомнительное использование наследования в страничных объектах — это просто собрать все возможные полезные вспомогательные классы в одном месте, для удобства автоматизатора. Например, объект BasePageObject будет либо объединять, либо компоновать хелперы для работы с базой данных, хелперы для работы с assertion-ами и все остальные хелперы. Таким образом, каждый объект страницы, производный от базового объекта страницы, сразу получает доступ ко всему и всем, что ему может понадобиться.

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

Page Object - наследование

Хотя такой подход может показаться полезным, это создает раздутые объекты страниц и превращает BasePageObject в God Object.

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

Повторное использование интерфейса — разделение «что» и «как»

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

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

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

LoginPage.logIn();

В приведенном выше методе LoginPage предполагает, что знает, под каким пользователем войти в систему. Это создает очень специфический кирпичик лего. Гораздо более универсальным был бы метод:

LoginPage.logInAs(“test user”, “test-password”); 

Конечно, это очевидный пример. При реализации сложного набора Page Objects существует множество случаев, когда возникает соблазн позволить страничным объектам принимать решения о том, какие данные и когда отправлять. Не поддавайтесь этому соблазну! Объекты страницы должны знать только, как перевести тест в действия браузера, они не должны принимать решения о том, что нажать или какие данные отправить. За эти знания полностью отвечает тест.

Более реалистичный, но не менее коварный пример — когда объекты страницы обращаются к какому-либо типу глобального состояния, чтобы получить тестовые данные (данные для входа в систему и т.д.). Объект страницы не решает напрямую, что делать, но он «звонит другу», чтобы получить эту информацию.

В случае входа в систему LoginPage может иметь ссылку на данные, созданные в методе BeforeSuite или BeforeEach и сохраненные в глобальном хранилище «текущей информации о пользователе» для последующего использования. Это может показаться выгодным, но все равно перегружает ответственность объекта страницы.

При широком использовании этот паттерн также делает тесты невероятно сложными для чтения. Он скрывает происходящее и заставляет читателя щелкать по множеству различных мест, чтобы понять ожидаемое поведение теста. Это пересекается с темой DRY vs DAMP, которую мы рассмотрим позже.

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

Централизованный класс локаторов VS локаторы в Page Objects

Каждый объект страницы будет содержать множество локаторов для соответствующих элементов на этой странице. Видя все локаторы на всех страницах, возникает соблазн собрать эти локаторы в некий централизованный класс Locators, и ссылается на него в каждом Page Object.

Например, на странице LoginPage вместо локатора мы увидим следующий код:

Locators.LoginPage.SubmitButtonLocator

Хотя сбор одинаковых вещей, которые изменяются вместе, является целью любого проектирования программного обеспечения, эта стратегия заходит слишком далеко. Хотя существуют языковые конструкции, которые могут помочь управлять организацией сотен или даже тысяч локаторов, использование чистой и последовательной реализации локаторов во всех объектах страницы должно позволить любому человеку быстро и легко находить и обновлять локаторы. Централизованные локаторы чаще всего создают больше сложностей, чем того стоят.

DRY vs DAMP — BeforeEach и BeforeAll

DRY — Don’t Repeat Yourself (Не повторяйся) — это общий принцип, используемый при разработке программного обеспечения. Новых программистов учат искать повторяющиеся участки кода или логики и перемещать их в какую-либо многократно используемую функцию, класс, библиотеку и т.д. В 99% случаев разработки программного обеспечения DRY очень полезен и уместен.

К сожалению, автоматизированные тесты попадают в этот другой 1%. Все верно, при написании автотестов часто лучше повторять себя. Почему так?

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

Например: в десятках тестов может потребоваться войти в систему, добавить товар в корзину, а затем отправиться дальше. Эти начальные общие шаги могут выглядеть как отличный кандидат на извлечение для устранения дублирования, но на самом деле их следует оставить прямо в тесте.

Этот принцип называется DAMP — Descriptive and Meaningful Phrases (понятные и осмысленные фразы). Он отдает приоритет многословию для удобочитаемости, а не дублированию, и должен стать вашим руководящим принципом в тестах.

«Так, стоп!» — скажете вы. «А как же шаблон actor??!!!» Разве использование чего-то вроде: ShopperActor.logonAndSelectItem(); не является именно тем DRY и анти-DAMP, которого вы говорите избегать?

Хороший вопрос, но не совсем. Да, в этом паттерне мы берем кучу шагов и объединяем их для повторного использования в тестах, но это скорее изменение уровня абстракции, который подходит для оптимальной читаемости тестов, чем удаление дублирования. DAMP улучшает читабельность, и мы использовали актор именно для улучшения читабельности. Акторы на самом деле очень совместимы с DAMP.

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

Вот почему акторы не нарушают DAMP. Строка ShopperActor.logonAndSelectItem(); говорит мне именно то, что мне нужно знать, чтобы понять, что делает тест.

Помещение шагов теста в BeforeEach и BeforeAll является особенно пагубным нарушением DAMP. Эти функции никогда не должны использоваться для выполнения обычных шагов теста. Это разрывает тест на части и помещает его фрагменты в разные места. Меня не волнует, что каждый тест должен сначала войти в систему. Никогда не входите в систему в BeforeEach.

Что должно входить в BeforeEach и BeforeAll? Любая настройка, которая не является функциональным шагом в тесте — то, что, если бы присутствовало в самом тесте, загромождало бы его и снижало читабельность. Забавно, что все всегда возвращается к читабельности, не так ли?

Хотя принцип DRY vs DAMP применим ко всей автоматизации тестирования, он имеет отношение к разговору о Page Object, поскольку пересекается с паттерном Actor. Тесты DAMP и акторы дают очень похожие преимущества: они ограничивают функцию тестирования только и только теми шагами, которые необходимы для того, чтобы сделать тест читабельным.

Как и во всем остальном, при попытке создать DRY или DAMP код все еще существует огромное количество гибкости и критического мышления — то, что является DRY для одного человека, может показаться быть «недостаточно DRY» для другого. Автоматизация тестирования — это не формула, и вам придется использовать свой мозг и свою интуицию.

Обсуждение DRY vs DAMP заслуживает больше места, чем мы можем выделить. Погуглите, и вы найдете много материалов по этой конкретной теме.

Связанность интерфейсов Page Object

Связанность — это принцип проектирования, который описывает степень зависимости или взаимосвязанности между частями системы. Если две части имеют значительную зависимость друг от друга, если изменения в одной части требуют значительных изменений в другой, мы говорим, что эти две части имеют высокую связанность. Низкая степень связанности считается преимуществом.

Одним из аспектов страничных объектов, который влияет на связанность между тестами и страницами, является тип и количество аргументов, передаваемых между тестами (или акторами) и Page Objects. Часто мы сталкиваемся с ситуациями, когда необходимо передать большое количество данных от теста к объекту страницы. Передаем ли мы их в виде списка из 10 переменных? Или мы создадим один объект и передадим его?

Допустим, мы создаем декларативный метод на странице создания аккаунта. Мы можем сделать это так:

AccountCreationPage.createAccount(“full name”,
    “test_username”,
    “test_password”,
    false, // isAdmin
    “867–5309”,
    “Test_city”,
    “Test_state”,
    …);

или так:

AccountCreationPage.createAccount(accountDescriptor);

Вторая реализация с использованием объекта accountDescriptor создает более высокую связанность между классом теста и классом объекта страницы. Использование примитивных типов — нет.

Что же выбрать? К сожалению, мы не можем вам сказать. Это зависит от характера вашего приложения и даже от языка, на котором реализована ваша автоматизация. В слабо типизированном языке, таком как javascript, где вы можете создавать объекты на лету с помощью объектной нотации, математика связанности значительно отличается от статически и сильно типизированных языков (C#, java и т.д.).

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

ScreenPlay и другие альтернативы Page Object

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

Например, шаблон ScreenPlay — это альтернативный вариант, который вращается вокруг акторов, задач, активностей и действий. Он пытается решить те же проблемы, что и шаблон объекта страницы, но другим способом. Cypress Blog выступает за AppActions, хотя мы не совсем согласны с их описанием «проблем страничных объектов», а их реализация полагается на возможности Cypress/DevTools, недоступные для фреймворков на базе WebDriver.

Шаблон Lean Page Object все еще представляет собой Page Object, но выступает за возврат локаторов из методов страничного объекта. Таким образом, потребитель объекта страницы должен сам решать, что делать с элементами, а объекты страницы становятся чрезвычайно тонким слоем для поиска вещей на странице.

Лучше ли паттерн ScreenPlay, чем паттерн Page Object? Мы не знаем, русский язык лучше английского? Американский английский лучше британского английского? Ценность языка можно измерить только относительно аудитории, с которой вы общаетесь… Если ваша команда действительно понимает Page Object Model, мы не видим никакой пользы от изменения подхода к автоматизации пользовательского интерфейса. Если вы начинаете новый проект с нуля, возможно, стоит все взвесить и выбрать наиболее подходящий вариант.

Что делать дальше?

Как мы уже говорили, набрав в Google «Page Object», вы получите миллион просмотров. Однако каждый уважающий себя автоматизатор тестов должен хотя бы прочитать статью Мартина Фаулера на эту тему.

После этого лучшей стратегией для глубокого понимания страничных объектов является не чтение об объектах страниц, а чтение и понимание принципов проектирования программного обеспечения и паттернов проектирования. Автоматизация тестирования — это разработка программного обеспечения, и все знания, относящиеся к программному обеспечению, относятся и к автоматизации тестирования. Читайте о SOLID, читайте больше о DRY vs DAMP, изучайте все другие паттерны проектирования и как они используются, изучайте функциональное программирование и как эти концепции применяются по-разному, список тем практически бесконечен.

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

Источник

***

Какой была ваша первая зарплата в QA и как вы искали первую работу?

Мега обсуждение в нашем телеграм-канале о поиске первой работы. Обмен опытом и мнения.

6 КОММЕНТАРИИ

Подписаться
Уведомить о
guest

6 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Dorian
Dorian
1 год назад

Спасибо за классный материал! Самый глубокий материал по Page Object который я видел.

Толя
Толя
1 год назад

Было бы неплохо оставить ссылку на оригинал

Ivan John
Ivan John
1 год назад
Ответить на  Толя

Да, Blake Norrish пишет крутые статьи, могли бы и указать, что просто перевели его статью 🙁

Алексей
Алексей
1 год назад

Хорошая статья, много нормального разбора ПО а не копирки статей «как написать логин пейдж», было интересно почитать. Но вот про Screenplay хотелось бы более широкий ответ, а не просто упоминание, причем без ссылок на упоминаемую статью и при чем сам паттерн и специфичные технологии? Очевидно что паттерн можно реализовать и с селениумом и с девтулзами и с playwright, и он не зависит от технологии. Этот паттерн как раз и решает много проблем пейдж обжекта, с его большими наследованиями, организацией кода, дублируемости, и читаемость тестов.
Посыл статьи хороший, что автоматизация это тоже разработка, да может у нас есть нюансы которые упрощают нам жизнь, но тем не менее это разработка, многие к сожалению как выучили классическую пейджу, пару библиотек, и пару паттернов так это и воспроизводят из проекта в проект, либо пишут посредственный код, ссылаясь на «мы же не разрабы, работает и хер с ним».

ien
ien
1 год назад

> Одним из распространенных случаев использования наследования в иерархии классов объектов страницы, к сожалению, является предоставление всем объектам страницы доступа к «вспомогательным методам», которые оборачивают базовую библиотеку автоматизации пользовательского интерфейса (например, Selenium). Некоторые автоматизаторы считают, что интерфейс, предоставляемый этими библиотеками, либо слишком сложен, либо недостаточно мощный, и создают прослойку между API библиотеки и объектами страниц, которые их используют.

Ну, я — автоматизатор-самоучка. Для UI-тестов (Java, Selenium -> Selenide) создал абстрактный класс AbstractPage с protected-методами, от которого наследуются все страницы.
Что это дает:
— централизованно обрабатывать эксепшены для каких-то экшенов (что актуально для Selenium, когда тебе нужно дождаться какого-либо изменения на странице и только потом идти дальше). Здесь же можно добавить логирование.
— создавать сложные методы вроде clearText(), в которых на какой-то элемент отправляется комбинация клавиш ака Ctrl+A -> BACK_SPACE, или returnInputText(), где начинается вообще оверинженеринг со StringSelection и Toolkit, берущим стрингу из буфера.
— использовать ограничение на тип класса в дженериках (например, abstract T isOpened(), который обязателен для всех страниц и позволяет убедиться в отображении и состоянии какого-то пулла элементов вместо их перечисления row by row)
— so on

Понятное дело, что нужно не впадать в крайность, но всё же)

Pavel Chachotkin
Pavel Chachotkin
11 месяцев назад

Лучшее что я читал по POM. Во всех пунктах нашел внутренний отзыв.
Спасибо!

Мы в Telegram

Наш официальный канал
Полезные материалы и тесты
Готовимся к собеседованию
Project- и Product-менеджмент

🔥 Популярное

💬 Telegram-обсуждения

Наши подписчики обсуждают, как искали первую работу в QA. Некоторые ищут ее прямо сейчас.
Наши подписчики рассказывают о том, как не бояться задавать тупые вопросы и чувствовать себя уверенно в новой команде.
Обсуждаем, куда лучше податься - в менеджмент или по технической ветке?
Говорим о конфликтных ситуациях в команде и о том, как их избежать
$1100*
медианная зарплата в QA в июне 2023

*по результатам опроса QA-инженеров в нашем телеграм-канале

Собеседование

19%*
IT-специалистов переехало или приняло решение о переезде из России по состоянию на конец марта 2022

*по результатам опроса в нашем телеграм-канале

live

Обсуждают сейчас