<style>.lazy{display:none}</style>Основы работы с Cypress: Переменные

Основы работы с Cypress: Переменные

Если вы нашли эту статью через поиск Google, то, вероятно, задаетесь вопросом, почему подобный код не работает в Cypress:

it('stores value in variable', () => {

  let id

  cy.request('/api/boards')
    .then( res => {

      id = res.body[0].id
    })

  cy.visit('/board/' + id) // "id" is undefined?!

})

Если вы здесь только ради готового решения, прокрутите страницу вниз до раздела с названием “Возможные решения”. Но если вы хотите понять, что происходит, читайте дальше.

Почему же идентификатор (id) не определен? Если покопаться в документации, то можно даже запутаться. Можно изучить статью о том, что команды в Cypress асинхронны, потом можно немного почитать про, как следует обращаться с переменными, попробовать async/await, но потом обнаружить, что это все тоже не работает. Так что же происходит?

Давайте добавим в наш тест пару функций console.log() и посмотрим, как он себя поведет. Просто взглянув на код, сможете ли вы угадать, что будет напечатано в консоли браузера?

it('stores value in variable', () => {
  console.log('>>> first log')
  let id

  cy.request('/api/boards')
    .then( res => {
      console.log('>>> second log')

      id = res.body[0].id
    })
  console.log('>>> third log')

  cy.visit('/board/' + id)
})

Возможно, вы правильно догадались. Но, наверное, вам интересно, почему ответ будет именно таким:

>>> first log
>>> third log
>>> second log

Как уже было упомянуто выше, ответ находится в документации, но он может быть немного запутанным. По крайней мере, так кажется изначально. Рассмотрим еще один способ.

Подпишитесь на наш ТЕЛЕГРАМ КАНАЛ ПО АВТОМАТИЗАЦИИ ТЕСТИРОВАНИЯ

Цепочка Cypress против всего остального

Команды Cypress работают по цепочке. Каждое ее звено связано как с предыдущим, так и с последующим. Таким образом, Cypress гарантирует, что команды не будут друг друга опережать при выполнении, и будет автоматически ждать завершения предыдущей команды. Приведем пример.

cy
  .get('li')
  .should('have.length', 5) // wait until previous command finds elements
  .last() // wait until previous assertion passes
  .click() // wait until previous command finishes

Опять же, ни одна команда не будет выполняться до тех пор, пока не завершится предыдущая. Если какая-либо из них не завершится вовремя (обычно 4 секунды), тест будет не пройден.

Что же происходит с кодом, находящимся вне цепочки?  Поскольку он не является частью цепочки, то ничто не заставляет его ждать, и он выполняется немедленно.

Давайте теперь посмотрим на этот пример с другой точки зрения.

it('stores value in variable', () => {
  // outside of chain, run immediately
  console.log('>>> first log') 
  let id

  cy.request('/api/boards')
    .then( res => {
      // inside the chain, wait for .request to finish
      console.log('>>> second log') 
      id = res.body[0].id
    })

  // outside of chain, run immediately
  console.log('>>> third log') 

  cy.visit('/board/' + id)
})

Будем надеяться, что функции console.log() теперь имеют немного больше смысла. Но как быть с этой переменной id? Кажется, что она используется внутри цепочки.

На самом деле нет. Она передается в качестве аргумента, поэтому технически она не находится внутри командной цепочки, а передается “извне”. Мы объявили эту переменную в начале теста. В рамках нашего теста мы сообщаем Cypress, что хотим выполнить команду .visit() с каким бы то ни было значением '/board/' + id.

Это все станет немного понятнее, когда мы более внимательно рассмотрим наш принцип «внутренняя цепочка против внешней цепочки». Давайте еще раз посмотрим на код:

it('stores value in variable', () => {
  // not waiting to declare the variable
  let id

  cy.request('/api/boards')
    .then( res => {
      // waiting for .request to happen in test, then assign new value
      id = res.body[0].id
    })

  // not waiting to pass the variable
  cy.visit('/board/' + id)
})

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

Возможные решения

Решение 1. Переместить нужный код внутрь командной цепочки

Самое простое решение — убедиться в том, что все необходимое включено в нашу цепочку команд. Чтобы использовать новое значение, нам нужно вызвать нашу функцию .visit() внутри цепочки команд. Таким образом, id будет передан с новым значением. Конечно, несколько функций.then() могут привести к “пирамиде гибели“, поэтому данное решение лучше всего подходит для случаев, когда необходимо сразу передать одну переменную.

it('stores value in variable', () => {
let id // create variable

cy.request('/api/boards')
  .then( res => {
    id = res.body[0].id // assign value
    cy.visit('/board/' + id) // pass the newly assigned value
  })

})

Решение 2. Разделение логики на несколько тестов

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

let id // declare variable

it('assign new value', () => {
  cy.request('/api/boards')
    .then( res => {
      id = res.body[0].id 
    })
})

it('use variable', () => {
  cy.visit('/board/' + id) 
})

Решение 3. Использование перехватчиков

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

let id // declare variable

beforeEach( () => {
  cy.request('/api/boards')
    .then( res => {
    id = res.body[0].id 
  })
})

it('use variable', () => {
  cy.visit('/board/' + id) 
})

Решение 4. Использование псевдонимов

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

it('use alias', () => {

  cy.request('/api/boards')
    .as('board') // create alias
  
  // some more code
  // ...

  cy.get('@board') // use alias
    .its('body')
    .then( body => {
    
      cy.visit('/board/' + body[0].id)

    })

})

Решение 5. Использование псевдонимов и перехватчиков

Псевдонимы на самом деле являются частью Mocha – фреймворка, входящего в состав Cypress и используемого для выполнения тестов. При использовании команды .as() в контексте Mocha создается псевдоним, доступ к которому можно получить с помощью ключевого слова this, как показано в примере. Это будет общая переменная, так что вы сможете совместно использовать переменные между тестами в спецификации. Однако это ключевое слово нельзя использовать в функциях со стрелочным выражением () => {}, а нужно использовать традиционное функциональное выражение, function() {}. См. пример:

beforeEach( () => {
  cy.request('/api/boards')
    .as('board')
})

// using  it('use variable', () => { ... would not work 
it('use variable', function() {
  cy.visit('/board/' + this.board.body[0].id) 
})

Существует несколько примеров, которые могут помочь вам с хранением переменных в Cypress, мы рассмотрели лишь некоторые из них. Более подробные примеры вы можете посмотреть здесь.

Перевод статьи «Cypress basics: Variables».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *