Работа с переменными в Cypress

Основные принципы работы с переменными

Cypress использует асинхронный подход к выполнению команд, что напрямую влияет на работу с переменными. В отличие от синхронного JavaScript, где значение переменной можно присвоить и сразу использовать, в Cypress прямое присваивание значений из команд, таких как cy.get() или cy.request(), часто приводит к неожиданным результатам.

Пример ошибки:

let text;
cy.get('h1').then($el => {
  text = $el.text();
});
cy.log(text); // Выведет undefined

Это происходит потому, что cy.get() и другие команды Cypress выполняются асинхронно в цепочке команд, а присвоение text = $el.text() происходит после того, как cy.log(text) уже был вызван.

Использование .then() для работы с переменными

Правильный способ сохранить и использовать данные — использовать цепочку .then(), которая обеспечивает доступ к значению после выполнения команды:

cy.get('h1').then($el => {
  const text = $el.text();
  cy.log(text); // Выведет корректное значение
});

В этом случае переменная text доступна только внутри функции .then(). Для дальнейшего использования данных в последующих командах можно передавать их через возвращаемое значение или использовать замыкания.

Алиасы и cy.wrap()

Для сохранения данных и их последующего использования в разных частях теста применяются алиасы через .as() и cy.get('@alias'):

cy.get('input[name="username"]').type('admin').as('usernameInput');
cy.get('@usernameInput').should('have.value', 'admin');

Алиасы особенно удобны для работы с объектами и элементами DOM, которые используются несколько раз в тесте.

Если требуется сохранить простое значение (например, текст или число), используется cy.wrap():

cy.get('h1').then($el => {
  cy.wrap($el.text()).as('headerText');
});
cy.get('@headerText').then(text => {
  cy.log('Текст заголовка: ' + text);
});

cy.wrap() позволяет превратить обычное значение в Cypress chainable, что обеспечивает корректное асинхронное выполнение.

Переменные в контексте тестов

Переменные можно определять в пределах describe или it. При этом важно помнить, что простое присваивание вне цепочки команд не синхронизируется с Cypress:

describe('Тест с переменными', () => {
  let token;

  it('Получение токена', () => {
    cy.request('POST', '/api/login', { user: 'admin', pass: '123' })
      .then(response => {
        token = response.body.token;
      });
  });

  it('Использование токена', () => {
    cy.request({
      method: 'GET',
      url: '/api/data',
      headers: { Authorization: `Bearer ${token}` }
    }).then(response => {
      expect(response.status).to.eq(200);
    });
  });
});

Такой код может привести к проблемам, если Cypress выполнит второй it до завершения первого. Для передачи данных между тестами рекомендуется использовать алиасы или ссылку на Cypress.env:

cy.request('POST', '/api/login', { user: 'admin', pass: '123' })
  .then(response => {
    Cypress.env('token', response.body.token);
  });

cy.request({
  method: 'GET',
  url: '/api/data',
  headers: { Authorization: `Bearer ${Cypress.env('token')}` }
});

Использование Cypress.env() гарантирует корректную доступность значения на протяжении всего тестового набора.

Локальные и глобальные переменные

  • Локальные переменные объявляются внутри it и доступны только в рамках этого блока.
  • Глобальные переменные объявляются в describe и могут использоваться в нескольких it. Но их присвоение должно быть аккуратным из-за асинхронного характера Cypress.

Сохранение и изменение состояния

Для динамических значений, получаемых в ходе теста (токены, ID, текст элементов), часто применяется комбинация cy.wrap() и алиасов, что позволяет безопасно хранить состояние между командами без риска гонок асинхронности.

cy.get('#item').invoke('text').then(text => {
  cy.wrap(text).as('itemText');
});
cy.get('@itemText').then(text => {
  cy.log('Сохранённый текст: ' + text);
});

Особенности работы с объектами и массивами

При работе с объектами и массивами важно помнить, что Cypress не клонирует объекты автоматически. Изменение объекта, сохранённого через алиас, повлияет на все последующие ссылки:

const data = { count: 0 };
cy.wrap(data).as('counter');
cy.get('@counter').then(obj => {
  obj.count += 1;
});
cy.get('@counter').then(obj => {
  cy.log(obj.count); // 1
});

Для безопасного копирования можно использовать Cypress._.cloneDeep() из встроенной библиотеки Lodash.

Итоговые рекомендации

  • Никогда не использовать простые переменные для хранения данных из асинхронных команд без .then() или cy.wrap().
  • Для повторного использования DOM-элементов или значений применять алиасы.
  • Для передачи данных между тестами безопаснее использовать Cypress.env().
  • При работе с объектами учитывать, что Cypress хранит ссылки, а не копии.

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