Разделы портала

Онлайн-тренинги

.
Cypress – использование Page Objects
10.11.2020 00:00

Автор: Тоби Стид (Toby Steed)
Оригинал статьи
Перевод: Ольга Алифанова

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

Cypress, как практически все тест-инструменты и фреймворки, поддерживает разные паттерны дизайна. В этой статье, расширяя предыдущую, мы будем использовать Page Objects. Надеюсь, это поможет вам разобраться с парой моментов. Во-первых, вы увидите, как похож код с Page Objects в Cypress на Selenium (помимо синтаксиса), а во-вторых, как здорово Cypress позволяет быстро писать чистый и читабельный код.

Начнем там, где мы закончили в прошлый раз – надеюсь, вы сохранили свой тест и можете открыть его в Visual Studio Code. После открытия мы создадим новую папку в корневом каталоге Cypress – это можно сделать, кликнув по ней правой клавишей мыши, или через терминал:

mkdir cypress\pageobjects

Теперь у нас есть папка pageobjects, которая, что логично, будет содержать классы для всех страниц, которые мы хотим автоматизировать. Ряд инструкций и примеров в сети начинаются с создания базового класса по имени Page или чего-то не менее общего, который содержит абсолютно весь общий для разных страниц код. Однако лично мне кажется, что в Cypress это необязательно и повышает сложность, не принося никакой реальной пользы. Мы просто создадим по классу для каждой нужной нам страницы. Так как мы тестируем Google, начнем с создания файла HomePage.js в папке "pageobjects".

Очевидно, что домашняя страница Google не бьет рекорды по сложности, и нам не удастся продемонстрировать целую кучу типов элементов (вскоре выложу чит-лист Cypress специально для этого). Однако вы сможете разобраться, как организовать ваши классы page object.

export class HomePage {
 
searchTextBox= () => cy.get('.Search')
searchButton= () => cy.get('button').contains('Search')
feelingLuckyButton= () => cy.get('button').contains('Lucky')
 
navigate = () => {
cy.visit("https://www.google.co.uk/")
}
 
performSearch = (searchTerm) => {
searchTextBox().type(searchTerm)
searchButton().click()
}
}

Давайте разберемся с кодом выше. Прежде чем переходить к специфичному для Cypress коду, хотелось бы сказать пару слов о синтаксисе. Если ранее вы пользовались JavaScript, или хотя бы читали об этом или изучали руководства онлайн, то, возможно, заметили, что код иногда чуть-чуть отличается от того, что вам уже встречалось – например, то, как написаны функции.

Использование стрелочных функций в примере выше – это часть ES6. Это 6 версия языка ECMA Script, и он сильно улучшает JavaScript, добавляя в него множество классных штук. Ранее этот код мог бы выглядеть так:

searchTextBox() = cy.get('.Search')
searchButton() = cy.get('button').contains('Search')
feelingLuckyButton() = cy.get('button').contains('Lucky')
 
navigate() {
cy.visit("https://www.google.co.uk/")
}
 
performSearch(searchTerm) {
searchTextBox().type(searchTerm)
searchButton().click()
}

Повторюсь – так как наш код несложен, то различия тут минимальны и не очень очевидны, но о них все равно стоит упомянуть. А упомянуть о них стоит, потому что оба варианта верны, и вы можете пользоваться любым. Если вы учили JavaScript в эру до ES6 и хотите продолжать в том же духе – бога ради. Однако тем, кто раньше не работал с JavaScript, лучше будет сразу начинать с ES6. В других фреймворках и библиотеках ES6 надо настраивать при помощи Babel и пресетов – к счастью, Cypress уже сделал это за нас, и мы можем сосредоточиться на коде.

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

В нашей серии Selenium, создавая классы Page Objects, мы довольно стандартно настраивали класс, ряд свойств для отдельных элементов, а затем методы или действия, чтобы обернуть эти локаторы и что-нибудь с ними сделать. В примере выше просто перечислены методы, но мы используем их двумя различными способами. Один набор методов нужен для поиска и возвращения отдельных элементов – и, как и ранее, другой набор нужен для обертки и выполнения действий.

Возможно, вы подумали "Почему бы не искать элементы в экшен-методе, в котором эти элементы понадобятся?", и это хороший вопрос. Это абсолютно правильный способ это сделать, но мне кажется хорошей практикой делать это так, как описано выше. Делается это для того, чтобы получить методы для утверждений или методы, которые можно использовать во множестве экшен-методов – вместо того, чтобы искать элементы во множестве методов. Следовательно, сокращается дупликация кода. Возможно, уместнее делать это иначе, если ваше приложение спроектировано определенным образом, но это ваш выбор, и оба варианта будут верными.

searchTextBox = () => cy.get('Search')

cy.get(‘Search’) –синтаксис, эквивалентный FindElement в Selenium. По умолчанию он берет селектор в качестве локатора – для Cypress существуют плагины, чтобы пользоваться чем-то вроде XPath, но из коробки используются селекторы, и это неплохо. Как и раньше, можно использовать инструменты разработчика для поиска нужного селектора, или воспользоваться средством запуска Cypress – вы найдете там интерактивную среду локаторов, где можно найти локатор (и код), который вам нужен. Однако не полагайтесь на них – это не самые гибкие и надежные локаторы, лучше возьмите свои собственные.

При правильном локаторе в cy.get вернется единственный элемент, и благодаря чистоте стрелочных методов ES6 результат можно не уточнять – по умолчанию однострочные стрелочные методы возвращают элемент или объект, поэтому мы получим одну чистенькую строчку кода.

В плане кнопок можно видеть, как я воспользовался возможностью использовать "contains" для поиска кнопок с определенными значениями или текстом. Вместо сложного локатора я просто говорю Cypress найти элемент типа "кнопка" с текстом Search или Lucky. Так как это проверка "contains", я могу использовать часть строки для поиска совпадений. Не лучшая практика для реального приложения, где элементы потенциально могут частично совпадать по названиям, но для нашего примера сойдет.

navigate = () => {
cy.visit("https://www.google.co.uk/")
}

Следующий метод – это метод "navigate", довольно стандартный метод для использования во всех классах страниц. Возможно, мы уже настроили базовый URL в настройках Cypress, но все равно было бы неплохо создать navigate-методы для других ссылок – вдруг нам понадобится перейти напрямую к ним, не полагаясь на UI. cy.visit переходит на страницу, которая передана в метод в форме строкового параметра.

performSearch = (searchTerm) => {
searchTextBox().type(searchTerm)
searchButton().click()
}

Второй метод – это метод поиска, в котором мы передаем поисковый запрос в текстовое поле и даем Google сделать свое дело. Можно видеть, как мы используем элементные методы для получения объекта, а также взаимодействуем с ним. Наше первое взаимодействие – это передача поискового запроса в текстовое поле. "type" симулирует нажатие каждой клавиши, а не передает всю строку в один присест, поэтому учтите, что это может быть не лучшим выбором для длинных строк (если вам важна скорость прогона теста).

click() – второе взаимодействие, клик по кнопке "Поиск" и разрешение для Google выполнить поиск с нашим поисковым запросом.

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

Вернемся к нашему тест-файлу и применим HomePage в наших тестах:

import { HomePage } from './pageobjects/HomePage'
 
describe('My First Tests', () => {
const homePage = new HomePage()
 
it('Can succesfully search using a search term', () => {
homePage.navigate()
homePage.search('test')
cy.title().should('contain', 'test');
})
})

Чтобы использовать наш класс HomePage, мы просто импортируем его (мы можем это сделать благодаря утверждению про экспорт в классе pageobjects). Это похоже на утверждение "using", которое применялось в цикле статей про Selenium и C#, но в этот раз мы не только используем класс, но и указываем ему на файл, расположение включительно.

Затем нам нужно просто создать его копию и назначить ее переменной, в этом случае – константе. Мы делаем это в блоке "describe", так как затем он позволит нам использовать класс в любых наших тестах или "it"-блоках. Если сделать это внутри it-блока, он будет существовать только внутри этого теста.

Внутри теста, как и в тестах Selenium Page Objects, мы просто используем объект для вызова методов, которые нужны нам для теста. В конце можно использовать утверждения Cypress для проверки, что тест проходит как ожидается. Если бы мы писали это текст без Page Objects, он занял бы около девяти строк кода и был бы куда менее читабелен. Я уже говорил о выгодах Page Objects в ряде своих статей, поэтому не считаю нужным прорабатывать развесистый класс для очередной демонстрации, как они хороши – их плюсы транслируются в любой инструмент и фреймворк. Ключевым тут было показать, как внедрять их в Cypress, и как легко это делается.

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

Обсудить в форуме