cy.task() для работы с Node.js

Cypress обеспечивает возможность взаимодействия тестов с Node.js через функцию cy.task(). Этот механизм позволяет выполнять код вне браузера, например, обращаться к файловой системе, работать с базой данных или выполнять произвольные скрипты на серверной стороне. Отличие cy.task() от обычных команд Cypress заключается в том, что она запускается в Node-процессе, а не в браузере, что обеспечивает доступ к API, недоступным на фронтенде.


Регистрация задач в плагинах

Для использования cy.task() задачи нужно зарегистрировать в файле плагинов cypress.config.js (для Cypress 10+), либо plugins/index.js (для старых версий). Пример структуры:

const fs = require('fs');

module.exports = (on, config) => {
  on('task', {
    readFile(filePath) {
      return fs.readFileSync(filePath, 'utf8');
    },
    logMessage(message) {
      console.log('Log from cy.task:', message);
      return null; // обязательно возвращать значение
    },
    generateRandomNumber(max) {
      return Math.floor(Math.random() * max);
    }
  });
};

Ключевые моменты:

  • Каждая задача — это функция, которая возвращает значение. Cypress ожидает промис или синхронное значение.
  • Для задач, которые не возвращают результат (например, логирование), нужно явно вернуть null.
  • Аргументы передаются через вызов cy.task('название', аргумент).

Вызов задач в тестах

После регистрации задачи становятся доступны в тестах:

describe('Примеры использования cy.task', () => {
  it('Чтение файла через Node.js', () => {
    cy.task('readFile', 'data/sample.txt').then(content => {
      expect(content).to.include('Hello World');
    });
  });

  it('Логирование сообщений', () => {
    cy.task('logMessage', 'Тестовое сообщение');
  });

  it('Генерация случайного числа', () => {
    cy.task('generateRandomNumber', 100).then(number => {
      expect(number).to.be.lessThan(100);
    });
  });
});

Особенности вызова:

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

Асинхронные задачи

Функции, выполняющие асинхронные операции, можно реализовать с использованием промисов:

const axios = require('axios');

module.exports = (on, config) => {
  on('task', {
    fetchUserData(userId) {
      return axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`)
        .then(response => response.data)
        .catch(error => {
          throw new Error('Ошибка при получении данных: ' + error.message);
        });
    }
  });
};

В тесте:

it('Получение данных пользователя', () => {
  cy.task('fetchUserData', 1).then(user => {
    expect(user).to.have.property('id', 1);
    expect(user).to.have.property('username');
  });
});

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


Взаимодействие с базой данных

cy.task() часто используется для работы с базой данных при подготовке данных или очистке после теста:

const mysql = require('mysql2/promise');

module.exports = (on, config) => {
  const connection = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: 'password',
    database: 'test_db'
  });

  on('task', {
    queryDb(query) {
      return connection.query(query)
        .then(([rows]) => rows)
        .catch(err => { throw err; });
    }
  });
};

Использование в тесте:

it('Проверка записи в базу', () => {
  cy.task('queryDb', 'SELECT COUNT(*) AS count FROM users')
    .then(result => {
      expect(result[0].count).to.be.a('number');
    });
});

Особенности:

  • Любой код Node.js, который не может выполняться в браузере, можно поместить в задачу.
  • Рекомендуется использовать пул соединений для повторяющихся запросов.

Ограничения и лучшие практики

  1. Минимизировать время выполнения задач — длительные операции могут замедлить тесты.
  2. Не использовать задачи для действий, доступных в браузере — это лишняя усложняющая прослойка.
  3. Всегда возвращать значения или null — Cypress иначе выдаст ошибку.
  4. Отлов ошибок в асинхронных задачах — важно корректно выбрасывать ошибки, чтобы тесты не зависали.
  5. Секреты и конфиденциальные данные лучше хранить в Node.js, а не в тестах.

Примеры сложных задач

Множественные операции с файловой системой:

const path = require('path');

on('task', {
  readAndTransform(filePath) {
    const content = fs.readFileSync(filePath, 'utf8');
    const transformed = content.split('\n').map(line => line.toUpperCase());
    return transformed;
  },
  deleteFile(filePath) {
    fs.unlinkSync(filePath);
    return null;
  }
});

Интеграция с внешними API:

on('task', {
  fetchPosts() {
    return axios.get('https://jsonplaceholder.typicode.com/posts')
      .then(res => res.data)
      .catch(err => { throw err; });
  }
});

Эти подходы позволяют строить полноценные сценарии end-to-end, комбинируя действия в браузере и операции на серверной стороне через Node.js.


cy.task() является мощным инструментом для расширения возможностей Cypress, позволяя выполнять любые задачи Node.js, интегрировать тесты с внешними сервисами и эффективно управлять данными тестирования.