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

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

.
Ожидания в Cypress, и как их избежать
13.04.2022 00:00

Автор: Филип Рик (Filip Hric)
Оригинал статьи
Перевод: Ольга Алифанова

Среди тестировщиков множество перфекционистов. Практически у всех, с кем я сталкивался, чешутся руки использовать команду .wait() в Cypress и остановить тест на пару секунд. Если вы из таких людей, то отлично знаете, что такое использование .wait() – не лучшее решение, и пробуете найти альтернативу. Тест просто ничего не делает несколько секунд. Их может быть достаточно, но иногда этого мало.

Когда мы используем .wait(), то хотим, чтобы приложение пршило в нужное состояние. Закрытие модального окна, получение ответа от сети, смена состояния кнопки… Cypress был создан с учетом повторных попыток – это означает, что как только команда сработает, Cypress перейдет к следующей. Если команда не срабатывает, Cypress будет пытаться выполнить ее повторно в течение нескольких секунд.

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

Просто используйте .wait, да и все

Знаю, знаю. В заголовке я обещал рассказать, как этого избежать, но дослушайте до конца. Иногда наилучшим решением для вас и вашей команды будет простое использование жестко закодированного ожидания. Не тратьте двое суток на поиск правильной комбинации контролей, ассертов, прерываний и чего бы то ни было, лишь бы избежать команды .wait(). Эти два дня, возможно, превысят общее время ожидания, созданное тестом. Что еще важнее, ваше время куда ценнее времени CI/CD. Вы могли бы заняться чем-то более полезным. Перфекционизм – это дорого. Просто добавьте ожидание и едем дальше, вернетесь к нему потом. Также хорошая практика – оставить комментарий "что нужно сделать", чтобы столкнувшийся с этим человек понял, почему в этом тесте ожидание.

// TODO: ыыы, пришлось использовать .wait(). Кнопка недостаточно быстро становится интерактивной.
cy.get('button')
    .wait(2000)
    .click()

Совет профессионала: можно использовать eslint-plugin-cypress, чтобы lint выдавал предупреждение каждый раз, когда в тесте используется .wait().

Используйте "defaultCommandTimeout" для изменения таймаута по умолчанию

Каждый запрашиваемый вами с использованием .get(), .contains() или иной команды элемент имеет время ожидания по умолчанию – 4 секунды. Cypress будет ждать появления элемента в DOM и попробует повторно обратиться к нему, как только сможет. Если четырех секунд недостаточно, время можно глобально задать для всего проекта в файле cypress.json, чтобы Cypress ждал дольше:

cypress.json
{
    "defaultCommandTimeout": 5000
}

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

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

it('slow test', { defaultCommandTimeout: 5000 },  () => {
 
    // 5-секундное ожидание появления элемента в dom
    cy.get('slowElement')
 
})

Этот объект конфигурации также работает для блоков describe:

describe('slow tests', { defaultCommandTimeout: 5000 },  () => {
 
    it('slow test #1', () => {
        // применит 5-секундный таймаут
    })
 
    it('slow test #2', () => {
        // и тут тоже
    })
 
})
 
it('not so slow test', () => {
    // но не тут
})

Используйте таймаут для команды

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

cy.get('#myElement', { timeout: 10000 })
    .should('be.visible')

Заметьте, что мы добавляем таймаут в команду .get(), а не .should(). Интуитивный подход – подождать, пока элемент будет соответствовать нашим ожиданиям. Но наше ожидание привязано к запросу элемента. Поэтому, если ожидаемый результат не достигнут, предыдущая команда также запустится повторно.

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

cy.get('#modal', { timeout: 10000 })
    .should('not.exist')
 
// продолжение теста

Заметка на полях: обратите внимание на разницу между not.exist и not.be.visible. Иногда вижу, что люди путают их, и на это есть причина. Интуитивно они кажутся одинаковыми. Однако not.exist проверяет отсутствие элемента в DOM, а not.be.visible пройдет только в том случае, когда элемент существует в DOM, но невидим.

Ожидайте загрузки страницы

Я уже писал про проверку ссылок и о том, почему клик по отдельным ссылкам – не всегда лучшее решение. Однако если редирект страницы – часть вашего теста, то,возможно, нужно подождать, чтобы тест продолжился. Вместо использования команды wait можно применить тот же принцип, что и в предыдущем примере.

cy.get('#redirectLink')
    .click()
 
cy.location('pathname', { timeout: 10000 })
    .should('eq', '/about')

Ожидайте ответа API

Cypress отлично работает с http-запросами. Если вы ожидаете загрузки определенных ресурсов, то можете перехватить запрос и создать для него алиас. Этот алиас будет использоваться с командой .wait(). Тесты продолжатся, как только эта команда завершится.

cy.intercept('/api/boards').as('boardList')
cy.visit('/')
cy.wait('@boardList')
// продолжение теста после получения ответа

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

Если нужно дождаться нескольких запросов, можно настроить несколько алиасов в одной команде:

cy.intercept('/api/boards').as('boardList')
cy.intercept('/api/cards').as('cardList')
cy.visit('/')
cy.wait(['@boardList', '@cardList'])
// продолжить тест после осуществления всех запросов

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

Ждите чего угодно

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

cy.contains('button', 'Sort alphabetically')
    .click()
 
// список переопределяется, это займет время
 
cy.get('.list')
    .should( (items) => {
 
        // нет проблем, мы будем ждать, пока функция сработает, или пока не кончится таймаут
        expect(items).to.have.length(2)
        expect(items[0]).to.have.text('Apples')
        expect(items[1]).to.have.text('Bananas')
 
    })
 
// все в порядке, продолжаем тест

Это можно применить к чему угодно – к примеру, тут мы проверяем, есть ли у поля ввода верное значение и класс:

cy.get('input')
    .should( (email) => {
 
        expect(email).to.have.value(' Этот e-mail адрес защищен от спам-ботов, для его просмотра у Вас должен быть включен Javascript ')
        expect(email).to.have.class('valid')
 
    })

Надеюсь, вам понравилось!

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