Другими словами
Page Object — это экземпляр класса, который абстрагирует (изолирует) пользовательский интерфейс от тестовой среды, представляет методы для взаимодействия с пользовательским интерфейсом и извлекает необходимую информацию.
Терминология
Термин Page Object слишком обобщенное понятие. По моему опыту Page Object включает в себя следующие 3 типа:
- Component Objects представляет определенные компоненты или виджеты в пользовательском интерфейсе. Например: таблицы, меню, статьи и прочие блоки содержащие в себе группу компонентов.
- Page Object описывает определенную область, или пользовательский интерфейс в веб приложении. Он может состоять из нескольких Component Objects и может содержать удобные методы для взаимодействия с абстракцией, которая содержится внутри данного объекта.
- Experience используется для группировки сложного функционала, тестирование которого требует выполнения нескольких шагов, или взаимодействия с несколькими страницами. По своему опыту я использовал эту концепцию для абстракции сложного поведения на странице (тестирование обучающих страниц, создание нового пользователя и т.д.)
Примеры
Рассмотрим простой тест RSpec Capybara, который создает блоги и не использует объекты страницы:
require 'feature_helper' feature 'Blog management', type: :feature do scenario 'Successfully creating a new blog' do visit '/' click_on 'Form Examples' expect(page).to have_content('Create Blog') fill_in 'blog_title', with: 'My Blog Title' fill_in 'blog_text', with: 'My new blog text' click_on 'Save Blog' expect(page).to have_selector('.blog--show') expect(page).to have_content('My Blog Title') expect(page).to have_content('My new blog text') end scenario 'Entering no data' do visit '/' click_on 'Form Examples' expect(page).to have_content('Create Blog') click_on 'Save Blog' expect(page).to have_content('4 errors stopped this form being submitted') expect(page).to have_content("Title can't be blank") expect(page).to have_content("Text can't be blank") expect(page).to have_content('Title is too short') expect(page).to have_content('Text is too short') end end
Рассмотрим код внимательнее, в нем есть несколько проблем. Здесь есть следующие действия: переход на соответствующую страницу, взаимодействие со страницей и проверка контента. Часть кода дублируется, но это можно исправить придерживаясь принципа DRY.
Важно понимать, что этот код трудно поддерживать, если есть изменения в тестируемом приложении. Например, классы элементов, имена и идентификаторы могут изменяться, что требует, регулярного потребует обновления кода теста.
Так же в этом коде нет ‘Семантического контекста’, трудно понять, какие строки кода логически сгруппированы.
Введение в Page Objects
Как обсуждалось в разделе терминологии, Page Objects могут использоваться для абстракций уровня представления.
Взяв предыдущий пример и применив Page Object для создания новых блогов и просмотра блогов, мы можем очистить код предыдущего примера.
Избавившись от конкретных сведений о технической реализации конечный результат (код) должны быть читабельным и не должен содержать конкретных сведений о пользовательском интерфейсе (id, css классы и т.д.).
require 'feature_helper' require_relative '../pages/new_blog' require_relative '../pages/view_blog' feature 'Blog management', type: :feature do let(:new_blog_page) { ::Pages::NewBlog.new } let(:view_blog_page) { ::Pages::ViewBlog.new } before :each do new_blog_page.visit_location end scenario 'Successfully creating a new blog' do new_blog_page.create title: 'My Blog Title', text: 'My new blog text' expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text' end scenario 'Entering no data' do new_blog_page.create title: '', text: '' expect(view_blog_page).to_not have_loaded expect(new_blog_page).to have_errors "Title can't be blank", "Text can't be blank", "Title is too short", "Text is too short" end end
Создание Page Objects
Первый шаг создания Page Objects это создание структуры basic page class:
module Pages class NewBlog include RSpec::Matchers include Capybara::DSL # ... end end
Подключение (включение) Capybara :: DSL позволить экземплярам Page Objects использовать методы доступные в Capybara
has_css? '.foo' has_content? 'hello world' find('.foo').click
Кроме того, я использовал
include RSpec :: Matchers
в приведенных выше примерах, чтобы использовать RSpec библиотеку expectation.
Не стоит нарушать соглашения, Page Objects не должен включать в себя expect (ожидания). Однако где уместно я предпочитаю этот подход, чтобы полагаться на встроенные механизмы Capybara для обработки условий.
Например, следующий код Capybara будет expect (ожидать), наличия ‘foo’ внутри Page Objects (в данном случае это self):
expect(self).to have_content 'foo'
Тем не менее, в следующем коде:
expect(page_object.content).to match 'foo'
Возможны непредвиденные ошибки (возможно возникновение плавающего теста), так как page_object.content сразу проверяется на соответствие условию, и возможно, еще не объявлен. Для большего количества примеров я бы порекомендовал прочитать thoughtbot’s написание надежных асинхронных интеграционных тестов с Capybara.
Создание методов
Мы может абстрагировать (описать) место (область), из которой мы хотим получить данных, в рамках одного метода:
def visit_location visit '/blogs/new' # It can be beneficial to assert something positive about the page # before progressing with your tests at this point # # This can be useful to ensures that the page has loaded successfully, and any # asynchronous JavaScript has been loaded and retrieved etc. # # This is required to avoid potential race conditions. expect(self).to have_loaded end def has_loaded? self.has_selector? 'h1', text: 'Create Blog' end
Важно выбрать семантически верные имена для методов для ваших Page Objects
def create(title:, text:) # ... end def has_errors?(*errors) # ... end def has_error?(error) # ... end
В целом, важно следовать принципу функционально объединенных методов и, где возможно, придерживаться принципа единой ответственности (Single Responsibility Principle).
Component Objects
В нашем примере мы используем класс NewBlog, но реализация для создания отсутствует.
Поскольку мы взаимодействуем с формой, мы могли бы дополнительно ввести класс для представления этого компонента:
# ... def create(title:, text:) blog_form.new.create title: title, text: text end # ... private def blog_form ::Components::BlogForm end
Где может быть спрятана реализация методов для BlogForm:
module Components class BlogForm include RSpec::Matchers include Capybara::DSL def create(title:, text:) within blog_form do fill_in 'blog_title', with: title fill_in 'blog_text', with: text click_on 'Save Blog' end end private def blog_form find('.blog--new') end end end
Все вместе
С помощью приведенных выше классов теперь можно запрашивать и создавать экземпляры объектов (Page Objects) вашей страницы в рамках описания объекта.
require 'feature_helper' require_relative '../pages/new_blog' require_relative '../pages/view_blog' feature 'Blog management', type: :feature do let(:new_blog_page) { ::Pages::NewBlog.new } let(:view_blog_page) { ::Pages::ViewBlog.new } # ... end
Примечание: Я намеренно создал объект страницы вручную в верхней части файла объектов. В некоторых RSpec тестах может быть удобно автоматически загружать все файлы поддержки и предоставлять доступ к ним в файлах объектов, однако это может привести к чрезмерным нагрузкам при использовании больших кусков кода. В частности, это приведет к медленному запуску и потенциальным непреднамеренным циклическим зависимостям.
Вызов Page Objects
Теперь в каждом сценарии у нас будет доступ к экземплярам new_blog_page и view_blog_page:
scenario 'Successfully creating a new blog' do new_blog_page.create title: 'My Blog Title', text: 'My new blog text' expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text' end
Naming Conventions / Predicate Methods
Как и в большинстве вещей в Rails / Ruby, существуют соглашения, которые могут показаться незначительными (не обязательными к исполнению) полностью с первого взгляда.
В наших тестах мы взаимодействовали с объектом страницы с помощью have_loaded и have_blog:
expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text'
Тем не менее, имена методов нашего объекта страницы на самом деле has_loaded? и has_blog?:
def has_loaded? # ... end def has_blog?(title:, text:) # ... end
Это тонкое различие, на которое нужно обратить внимание. Для получения более подробной информации об этом соглашении я бы рекомендовал к прочтению следующую ссылку predicate matchers.
ссылка на оригинал статьи https://habr.com/ru/post/466527/
Добавить комментарий