Closures и контекст

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

Основы замыканий

Замыкание возникает, когда внутренняя функция «помнит» окружение внешней функции. Пример:

function createCounter() {
    let count = 0;
    return function() {
        count += 1;
        return count;
    };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2

Переменная count продолжает существовать в памяти благодаря замыканию. В Cypress подобная концепция проявляется при работе с переменными внутри cy.then() или колбэков в командах, где значение должно сохраняться между асинхронными вызовами.

Контекст выполнения в Cypress

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

  • Переменные, объявленные в глобальной области, доступны во всём тесте.
  • Переменные, объявленные внутри колбэка Cypress (cy.get().then()) — локальны для этого колбэка, но могут использоваться через замыкания.
  • this в тестах Mocha (используемых Cypress) ссылается на контекст теста, что позволяет хранить значения между хуками before, beforeEach, it и after.

Пример использования this и замыканий:

describe('Пример замыканий с Cypress', function() {
    before(function() {
        this.token = null;
    });

    it('Получение токена', function() {
        cy.request('POST', '/api/login', { username: 'user', password: 'pass' })
          .then((response) => {
              this.token = response.body.token; // Сохраняем в контексте
          });
    });

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

Здесь this.token хранит значение между тестами благодаря механизму контекста Mocha и замыканиям.

Практика с асинхронными цепочками

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

let value;
cy.get('#input').type('text').then(() => {
    value = 'текст';
});
console.log(value); // undefined

вернёт undefined, так как console.log выполняется раньше, чем Cypress завершит команду cy.get().type(). Решение — использовать then или замыкания:

cy.get('#input').type('text').then(() => {
    const value = 'текст';
    cy.log(value); // корректное использование внутри замыкания
});

Совмещение контекста и замыканий

Иногда возникает необходимость хранить значения из нескольких асинхронных вызовов для дальнейшей проверки. В Cypress это делается через cy.wrap() и then():

cy.get('.item').then(($items) => {
    const texts = [];
    $items.each((index, el) => texts.push(el.innerText));
    cy.wrap(texts).as('textsArray'); // сохраняем в алиас
});

cy.get('@textsArray').then((texts) => {
    expect(texts).to.include('Нужный элемент');
});

Использование алиасов и cy.wrap создаёт собственное замыкание, в котором данные безопасно передаются между асинхронными шагами.

Ошибки и подводные камни

  • Попытка использовать this внутри стрелочных функций в Mocha приведёт к потере контекста, так как стрелочные функции наследуют this из внешнего лексического окружения.
  • Прямое присваивание асинхронных значений глобальным переменным вне then() или замыканий не работает, из-за асинхронного характера Cypress.
  • Часто возникает желание использовать forEach с Cypress-командами. Это не работает корректно, лучше использовать cy.wrap(array).each() для соблюдения цепочек и замыканий.

Рекомендации по использованию замыканий в Cypress

  • Использовать then() для работы с результатами команд и локальных переменных.
  • Хранить значения между тестами через контекст Mocha (this).
  • Для массивов и коллекций DOM использовать cy.wrap().each(), чтобы сохранить асинхронность и избежать утечек переменных.
  • Не применять стрелочные функции, когда нужен доступ к this контекста теста.

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