Используйте cy.session() вместо login page object в Cypress |
17.10.2023 00:00 |
Автор: Филип Рик (Filip Hric). Авторизация – зачастую первое препятствие, с которым сталкиваются автоматизаторы, и преодолеть его бывает непросто. Наиболее распространенный способ решения вопроса авторизации – просто пройти ее так, как это делает обычный пользователь. Вот так это будет выглядеть в нашем приложении: 1 cy.visit('/login') Cypress пытается очищать данные браузера между тестами, поэтому авторизовываться надо перед каждым тестом. Это можно сделать при помощи хука beforeEach() или используя Cookies API, чтобы игнорировать удаление определенных куки из приложения. Абстрагирование логина в Page ObjectИмеет смысл абстрагировать последовательность авторизации в отдельную сущность. Готовое решение – это Page Object. Зачастую это будет выглядеть как-то так: 1 export class LoginPage { Таким образом мы можем легко авторизовываться перед каждым тестом и настроить его до перехода к действиям тест-сценария. Если вы хотите подойти к вопросу глобально, просто добавьте глобальный хук beforeEach() в ваш файл поддержки: support/e2e.ts Использование кастомной командыДля таких широко используемых функций, как авторизация, лично я предпочитаю использовать кастомные команды. Огромное преимущество кастомных команд в том, что они становятся частью вашей библиотеки Cypress. Следовательно, их легко найти, и они хорошо взаимодействуют с синтаксисом цепочек Cypress. Можно также создать снимки состояний DOM для дебага, и сделать многое другое. Я детальнее писал об этом в прошлой статье. Кастомная команда для логина выглядит примерно так: 1 declare global { Строки 1 – 15 добавляют нашу кастомную команду в библиотеку Cypress, расширяя определения TypeScript. Строки 17-24 – определение функции, содержащей последовательность логина. И, наконец, мы добавляем нашу функцию в строке 26. Схожим с Page Object образом мы можем добавить нашу свежесозданную команду cy.login() в глобальный хук beforeEach(), и тест будет авторизовываться перед каждым блоком it(). support/e2e.ts Программируемый логинОднако использование пользовательского интерфейса – не самый эффективный способ, и это уж точно повлияет на производительность. Второй вариант – авторизовываться программно. Конечно, проще сказать, чем сделать. В программном (как и любом другом) логине три части:
При авторизации сервер предоставляет данные (как правило, в форме токена), а фронтэнд затем сохраняет их в браузер (как правило, в форме куки). Чтобы иметь возможность авторизоваться программно, надо понимать, как ваше приложение передает данные на сервер, и как обращается с ответом. По сути вам нужно точно знать, что происходит, когда пользователь вводит данные и нажимает на кнопку «Войти». Иными словами, вы будете воссоздавать этот логин в своем тесте. Как только вы разберетесь с ним, все будет выполняться очень быстро, но это нелегко, особенно если приходится иметь дело со сторонним логином, потоками OAuth и другими продвинутыми методами авторизации. В документации Cypress много полезных руководств на этот счет. Использование cy.session()Однако на самом деле для эффективной работы нет нужды разбираться, что там происходит при авторизации. Команда cy.session() позволяет воспользоваться пользовательским интерфейсом всего лишь раз за весь набор тестов. Команда cy.session() вышла с версией 8.2.0. Этот релиз, на мой взгляд, получил меньше внимания, нежели заслуживал. Это один из самых эффективных способов справиться с авторизацией. Приведу простой пример использования, чтобы показать, как это работает. Мы будем использовать вышеупомянутую кастомную команду. 1 it('logs in the user', () => { В тесте мы обернули команду cy.login() в команду cy.session(). Это дает Cypress понять, что все, что происходит внутри обратного вызова cy.session() (иными словами, между строками 3 и 5), должно запоминаться как сессия. При повторном прогоне этого теста авторизации происходить не будет – вместо этого восстановится сессия. Обратите внимание на изображение, демонстрирующее первый прогон (первое изображение) и второй (второе изображение). Можно видеть, что второй прогон восстановит сессию, а первый ее создает. Заметьте также, что второй тест занимает лишь долю времени, потраченного на первый. Работает это вот как:
Восстановление сессии – это извлечение всех куки браузера, локального хранилища и хранилища сессии, присутствовавших в браузере после авторизации пользователя. Помните, мы говорили о программной авторизации и трех частях авторизационного потока? cy.session() берет на себя функцию браузера, и вам не нужно волноваться за фронтэнд и сервер. Применение cy.session() к проекту целикомТеперь мы можем применить сессию к проекту в целом, вставив последовательность cy.session() в файл поддержки: support/e2e.ts Обратите внимание на опцию cacheAcrossSpecs в строке 5. Она заставляет авторизацию прогоняться только один раз за прогон набора тестов. Если логин занимает 2 секунды, и у вас сто тестов с авторизацией, вы только что сэкономили более трех минут! Создание нескольких сессийДопустим, у вас несколько пользователей, под которыми вы хотите протестировать UI. Первый аргумент команды cy.session() – это по сути синоним для сессии. Это означает, что мы можем создать несколько сессий, дав им разные имена. Проще всего сделать это, перевернув порядок наших команд. Вместо того, чтобы оборачивать команду cy.login() в cy.session(), сделаем cy.session() частью команды cy.login(), вот так: 1 declare global { Обратите внимание, что на строке 17 мы даем сессии имя, совпадающее с электронной почтой, которую мы передаем функции cy.login(). Это означает, что каждый авторизующийся пользователь будет создавать свою собственную сессию. Иными словами, вне зависимости от количества авторизаций под разными пользователями каждый из них авторизуется лишь один раз. Почему кастомная команда, а не Page Object? Мое мнениеВозможно, вы уже сталкивались с моими возражениями против Page Object. Я критикую модель Page Object за то, как она применяется, но также говорю, что это не антипаттерн. Если в создании абстракций есть смысл, очень важен способ их реализации. Использование модели Page Object для создания абстракций и DRY-кода (Don’t Repeat Yourself) – это уже перебор. Я только за DRY-код, но мы, как тестировщики, должны также стремиться к DRY-выполнению тестов. Page Object часто используются для настройки состояния приложения. По сути, желая протестировать «Б», вы сначала добираетесь туда, делая «А». Если это «А» - авторизация, она может понадобиться во всех ваших тестах. cy.session() позволяет ограничить количество повторений А. Это необязательно авторизация – можно также выполнять настройку, вызывая ряд конечных точек API и осуществляя другие подготовительные действия. Использование кастомных команд или функций несет больше смысла, чем использование UI Page Object. Даже если вы хотите настраивать тесты через UI, то сбережете время, используя cy.session(). Использовать cy.session() с Page Object очень трудно. Это возможно, но по сути вам придется уместить всю авторизационную деятельность в одну функцию, в которой вы открываете, заполняете и отправляете форму авторизации. Такой Page Object будет выглядеть примерно так: 1 export class LoginPage { В этом случае Page Object содержит одну-единственную функцию, и добавлять больше нет смысла, так как это разорвет сессию. Поэтому лучше выбирать паттерн попроще – кастомную команду или отдельный функциональный модуль, импортируемый в тест. |