У роботі тестувальника автоматизації одним з ключових завдань є коректна робота з очікуваннями подій, що дозволяє виконувати підтвердження на вже підготовленому елементі. У жаргоні тестувальників ми зустрічаємо термін «очікування» від методу wait(), і в цій статті використовується саме така номенклатура.
У цьому пості я розповім про те, для чого потрібні очікування в автоматизованих тестах, представлю порівняння очікувань, доступних в Selenium і Cypress, а також приклад реалізації в Cypress.
Cypress значно виділяється серед інших доступних додатків для автоматизованого тестування завдяки одному специфічному очікуванню, яке також буде описано разом з методами cy.wait() та cy.intercept(). Останнім аспектом, який обговорюватиметься у цій статті, буде рекурсія, яку також можна використовувати у Cypress для очікування елемента в якості його власного користувацького підходу.
Очікування в автоматизованих тестах
Очікування – одна з основних і ключових частин автоматизованого тесту. Вона дозволяє нам виконати затримку тесту, щоб правильно завантажити веб-сторінку або будь-який елемент на ній. Така опція дозволяє перевірити, до чого це призведе – до позитивного або негативного завершення тесту.
Неправильна реалізація функції Wait може призвести до неправильного результату тесту, наприклад, в разі спроби взаємодії зі сторінкою, яка не повністю завантажилася. Інша популярна проблема – невідображення елемента, оскільки він очікував, наприклад, на відповідь сервера. Однак одна з найбільших труднощів, пов’язаних з неправильною реалізацією очікування, – це зайве очікування, яке збільшує тривалість тесту.
Очікування в Selenium
В одному з найпопулярніших інструментів для автоматизації тестування – Selenium – ми зустрічаємося з трьома типами очікувань, які я опишу нижче.
Неявне очікування
Воно полягає у спробі знайти елемент у DOM, який не вдається знайти відразу, за певний проміжок часу. За замовчуванням значення цього очікування дорівнює 0, і коли ми змінюємо це значення, неявне очікування триває до кінця сеансу.
Як показано в коді вище, реалізація цього рішення дуже швидка, але має деякі обмеження. Це очікування працює тільки з методом findElement(), що заважає нам дочекатися завантаження, наприклад, тексту в елементі.
Явне очікування
Завдяки йому ми маємо можливість зупинити тест, доки не буде виконано очікувану умову. Перевірка умови запускається з певною частотою, поки не закінчиться час очкування. Це означає, що якщо умова видасть хибне значення, Selenium зачекає і спробує ще раз.
У наведеному вище коді ми чекаємо, поки заголовок нашої сторінки набере очікуваного значення. Майте на увазі, що очікування тепер використовується тільки в момент спрацьовування тригера, а не як раніше – глобально. Тут ви знайдете перелік доступних прогнозованих явних умов очікування для Selenium.
Ми явно чекаємо певної ситуації протягом заданої кількості секунд – наприклад, наступний код протягом 10 секунд буде перевіряти, чи має наш сайт очікуваний заголовок, з частотою в одну секунду.
Очікування в Cypress
Cypress за замовчуванням використовує динамічне очікування елементів або дій, доступних на веб-сторінці, що використовується в нашому автоматизованому тесті. Можна виділити наступні способи обробки очікувань.
Час очікування за замовчуванням
У Cypress доступно 6 значень часу очікування за замовчуванням. Завдяки вбудованому механізму повторних спроб вони виконують перевірку через визначений нами або попередньо встановлений час, подібно до описаного раніше явного очікування:
- defaultCommandTimeout – час, за який Cypress намагається дістатися до елемента або дії під час виконання більшості команд на DOM.
- execTimeout – час очікування, який визначає, як довго потрібно чекати, поки система завершить операції під час виконання команди cy.exec().
- taskTimeout – максимальний час, протягом якого ми чекаємо на завершення завдання під час виконання команди cy.task().
- pageLoadTimeout – очікує на «подію переходу на іншу сторінку». Ця подія відбувається тоді, коли користувач відвідує або залишає певну веб-сторінку. Цей час очікування за замовчуванням також очікує на команди cy.visit(), cy.go(), cy.reload(), які виконують події завантаження. Вони спрацьовують, коли завантажується вся сторінка і весь її вміст, тобто таблиця стилів, скрипти, iframe і зображення.
- requestTimeout – максимальний час очікування виконання cy.wait().
- responseTimeout – очікує виконання методів cy.request(), cy.wait(), cy.fixture(), cy.getCookie(), cy.getCookies(), cy.setCookie(), cy.clearCookie(), cy.clearCookies() та cy.screenshot().
Якщо ми хочемо замінити значення часу очікування, ми робимо це у файлі cypress.json:
Ми також можемо змінити цей час для певного ланцюжка дій або самого методу, відправивши об’єкт з очікуваним часом:
У наведеному вище коді ми використали неявний суб’єкт для створення підтвердження за допомогою методу .should(). Спочатку Cypress намагається знайти елемент button протягом 20 секунд, визначених в об’єкті sent {timeout: 20000}, а потім виконує підтвердження для цієї кнопки, щоб перевірити, чи містить вона текст «Search in Google». Тут, знову ж таки, програма чекає до 20 секунд на появу тексту. Ми можемо отримати час очікування під час виконання методів cy.get() і should(), що зведе нанівець весь тест.
Для методів get(), find(), cointains(), виходячи зі свого досвіду, я рекомендую використовувати час очікування на ланцюжку дій на випадок, якщо очікування елемента або його властивостей затягнеться. При цьому час очікування, встановлений в cypress.json, повинен бути обмежений розумним мінімумом, щоб уникнути невиправданого збільшення тривалості тестів у випадку, якщо ми отримаємо незадовільний результат.
У Cypress є можливість виконати подібне затвердження з явним суб’єктом, використовуючи метод .then() і виконуючи затвердження на елементі JQuery:
У цьому випадку Cypress спочатку чекає до 20 секунд на метод .get(), а потім одразу ж виконує затвердження на елементі, пропускаючи визначене значення defaultCommandTimeout.
Вбудована функція очікування
Cypress має власну реалізацію методу wait, який приймає до двох аргументів:
- Одне з перших значень – це числове значення, подане в мілісекундах. Це значення часу, який минає між попередньою і наступною командою в ланцюжку дій Cypress.
- Наступне значення, яке ми можемо відправити замість часу – це псевдонім або таблиця псевдонімів. Псевдоніми – це величезна конструкція в Cypress, яка має багато застосувань, і про неї можна написати окрему статтю. Однак, для нас, в концепції очікувань, найважливіше зосередитися на можливості призначити псевдонім виклику API. Ми робимо це за допомогою cy.intercept() і методу .as(), де метод as зберігає псевдонім, до якого ми маємо доступ за допомогою префікса @.
- Останнє, необов’язкове, значення – це об’єкт option, який дозволяє нам перезаписати попередньо визначений час за замовчуванням для requestTimeout і responseTimeout. Крім того, це об’єкт {log: true/false}.
У цьому коді показано реалізацію методу очікування Cypress. Цей метод дозволяє нам чекати в певному місці коду протягом заданого періоду часу або поки не з’явиться відповідний псевдонім.
Створення псевдоніма
Щоб правильно створити очікування для псевдоніма, нам потрібен саме цей псевдонім. Одним з методів його створення є перегляд запитів, зроблених браузером на нашій тестовій сторінці. Потім ми вибираємо останній запит або кілька останніх запитів, які з високою ймовірністю покажуть нам, що сторінка завантажена.
Після того, як ми знайшли запит, який нас цікавить, все, що нам потрібно зробити, це використати метод cy.intercept(). Він дозволяє нам відстежувати запити і відповіді в мережі та керувати ними. Він дозволяє нам:
- Імітувати дані
- Перевіряти запити API
- А завдяки комбінації з cy.wait(), дочекатися закінчення заданого запиту API.
Потім, за допомогою методу .as(), ми можемо зберегти відстежений запит як псевдонім.
Реалізація перехоплення
Реалізація перехоплення, яку ми згодом використаємо у тесті, наведена вище. Її завдання полягає в тому, щоб:
- Відвідати сторінку google.pl.
- Прийняти файли cookie.
- Ввести в рядок пошуку «Sii» і натиснути кнопку Enter.
- Дочекатися псевдоніма @finishedGoogleSearch.
- Перевірити, чи на завантаженій сторінці видно текст «Około».
Я раджу уникати очікування протягом заданого часу і зосередитися на очікуванні псевдонімів. Це зробить наші тести коротшими, а код буде більш оптимізованим і в ньому буде значно менше неочікуваних винятків.
Одним з додаткових пакетів для ESLint є Cypress ESLint Plugin, який дозволяє визначити правило «cypress/no-unnecessary-waiting»: «error» і виявляти ці очікування під час створення автоматизованих тестів.
Реалізація користувацьких очікувань
Якщо нам потрібне спеціальне очікування, яке не відповідає описаним раніше параметрам cy.wait(), ми можемо використати рекурсивне очікування з перевіркою умови під час кожного звернення.
Рекурсія – це звернення функції або її самовизначення. Вона зустрічається в математиці (алгоритм Евкліда, послідовність Фібоначчі), програмуванні і навіть мистецтві (картина в картині або розміщення двох дзеркал навпроти один одного – саме тоді ми отримуємо відображення у відображенні).
Але повернімося до програмування і нашого прикладу. Ми хочемо написати функцію очікування, яка буде чекати, поки очікуваний елемент не з’явиться на сторінці.
Функція очікування виконує це завдання у наступному порядку:
- Отримання вмісту тіла методом .get().
- Потім перетворення цього об’єкта на об’єкт JQuery, виклик на ньому методу .find(locator) і, нарешті, .length, який видає значення 0, якщо елемента немає в тілі.
- Очікування, розподілене в часі – це час, через який ми хочемо провести ще одну перевірку, чи відповідає елемент заданій умові.
- Визначаємо totalTime і з кожним наступним рекурсивним викликом починаємо збільшувати його на заданий час перерви (split).
- Перевіряємо, чи totalTime менше або дорівнює часу очікування. Залежно від результату умови, рекурсивно викликається функція checkForElement(), або cy.get(resultText), щоб скасувати перевірку.
- Нарешті, у випадку невиконання умови body.find(elementID).length === 0 викликаємо cy.get(resultsText), який закриває рекурсію.
Це не ідеальне рішення описаної проблеми, і його слід використовувати лише в крайньому випадку, оскільки ми маємо справу з труднощами зчитування коду та його підтримки.
Наведений вище код дуже важко виправляти в Cypress і некомпетентне використання рекурсії або циклів може призвести до потрапляння в нескінченний цикл. Існує ймовірність його виникнення в разі змішування синхронного та асинхронного коду або некоректного контролю умов інкрементування. Зрештою, нескінченний цикл призведе до помилки тестування і браузер перестане реагувати.
Доступні зовнішні пакети очікування
У цьому розділі я розповім про два відомі мені пакети, які ми можемо використати у наших тестах.
- cypress-wait-until дозволяє чекати на все, чого не чекає метод cy.wait(). Метод cy.waitUntil() отримує два аргументи. Перший – це функція, яка виконує метод, методи або цілий ланцюжок дій Cypress. Наступний аргумент – об’єкт option, який дозволяє керувати часом очікування та інтервалом. Код намагається отримати доступ до поля браузера протягом трьох секунд з інтервалом в одну секунду. Редагуючи значення за замовчуванням, ми можемо вільно керувати значеннями часу очікування та інтервалу. Значення за замовчуванням для цього очікування: timeout=5000, interval=200.
cypress-recurse як пакет забезпечує рекурсивне очікування, поки очікуваний код не видасть значення True. У цьому випадку очікуваною умовою є expect(button).to.have.value(‘Szukaj w Google’)}. Метод відкине тест, якщо протягом 30 секунд ця умова не видасть значення True. Сама умова буде перевірятися з частотою 0,5 секунди. Значення за замовчуванням для методу: timeout=4000, limit=15, delay=800.
Порівняння очікування в програмах Seleniumта Cypress
Оцінімо схожість між очікуванням в програмі Selenium і очікуванням в програмі Cypress.
Почнемо з неявного очікування. Подібну поведінку ми можемо спостерігати в Cypress – керуючи «defaultCommandTimeout» або самим часом очікування, ми можемо чекати на появу елемента на сторінці за допомогою методу cy.get() у вказаний нами час.
Явне очікування в Cypress є досить суперечливою темою, оскільки воно передбачає перевірку умови. Метод Cypress .should(‘be.visible’) можна розглядати як еквівалент очікуваної умови elementIsVisible(element) в Selenium. Однак: чи є підтвердження очікуванням? Якщо припустити, що так, то ми можемо розглядати всі підтвердження .should() як явне очікування. Я впевнений, що думки щодо цієї теорії розділилися, тому залишаю шукати відповідь на це питання вам самим 🙂
Не забуваймо про cy.wait(@alias). Саме це очікування можна вважати повноцінним явним очікуванням. Представлені зовнішні пакети cypress-wait-until-until та cypress-recurse ближче до явного, або навіть вільного очікування, завдяки можливості керувати інтервалом, через який перевіряється умова.
Підсумок
У цій статті я представив доступні способи очікування елементів в програмах Selenium і Cypress. Ці очікування дозволяють вільно працювати з більшістю елементів. Я представив різницю в часі з підтвердженнями Cypress. Я підготував приклад коду з власним очікуванням і розглянув два зовнішні пакети, з якими ми можемо працювати в Cypress, залежно від наших потреб.
Якщо ви лише починаєте знайомство з Cypress, варто з самого початку дізнатися про область cy.wait(‘@Alias’). Це базовий функціонал інструменту, який підвищує якість коду в наших автоматизованих тестах і позбавляє від необхідності чекати, поки елемент завантажиться. Успіхів!