Логирование пользовательских команд в Cypress

Логирование пользовательских команд в Cypress

В одной из предыдущих статей мы говорили о пользовательских командах и о том, как использовать TypeScript для автозаполнения. Использование TypeScript, безусловно, целесообразно при работе над большим проектом с несколькими сотрудниками. В этой статье мы пойдем дальше и поговорим о том, как настроить пользовательское логирование, которое позволит оптимизировать работу с тест-раннером (test runner).

Если вас заинтересовала эта тема, рекомендуем посмотреть вебинар по паттернам и практикам, который провела команда Cypress DX. В нем много отличных советов, и они также рассказывают о пользовательском логировании.

Все примеры в статье описаны для приложения-клона Trello. Вы можете найти его на этой GitHub-странице и попрактиковаться самостоятельно.

Друзья, поддержите нас вступлением в наш телеграм канал QaRocks. Там много туториалов, задач по автоматизации и книг по QA.

Содержание

Создание пользовательской ошибки

Давайте создадим простую пользовательскую команду, которая будет взаимодействовать с пользовательским интерфейсом (UI) и создавать новую доску (board). Это будет выглядеть примерно так:

Cypress.Commands.add('addBoardUi', (name: string) => {

  cy
    .get('[data-cy="create-board"]')
    .click();

  cy
    .get('[data-cy="new-board-input"]')
    .type(`${name}`)
    .type('{enter}');

});

Затем мы используем её в нашем тесте следующим образом:

it('Creates a new board', () => {

  cy
    .visit('/')

  cy
    .addBoardUi() // forgot the board name!

});

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

Cypress выводит ошибку

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

Cypress.Commands.add('addBoardUi', (name: string) => {

  if (!name) throw new Error('You need to provide a board name');

  cy
    .get('[data-cy="create-board"]')
    .click();

  cy
    .get('[data-cy="new-board-input"]')
    .type(`${name}`)
    .type('{enter}');

});

Теперь при появлении ошибки мы получаем несколько более информативное сообщение:

Cypress показывает ошибку и также выводи текст: "You need to provide a board name".

Таким образом, при возникновении ошибки мы сразу поймем, в чем проблема и как её исправить.

Пользовательские сообщения

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

Для добавления логов можно просто использовать команду cy.log(), как и любую другую команду в тесте. Но можно сделать еще один шаг вперед, используя API Cypress.log(). Давайте добавим несколько пользовательских логов в нашу пользовательскую команду:

Cypress.Commands.add('addBoardUi', (name: string) => {

  Cypress.log({
    displayName: 'addBoardUi',
    message: name,
    name: 'Add new board'
  });

  cy
    .get('[data-cy="create-board"]')
    .click();

  cy
    .get('[data-cy="new-board-input"]')
    .type(`${name}`)
    .type('{enter}');

});

Теперь наша пользовательская команда будет отображаться в Cypress runner. Приятным дополнением к этому является параметр name, который выводится рядом с именем нашей команды:

Панель Cypress

Чтобы вывести на панель дополнительную информацию, мы можем добавить функцию consoleProps:

Cypress.log({
  consoleProps() {
    return {
      'board name': name
    }
  },
  displayName: 'addBoardUi',
  message: name,
  name: 'Add new board'
});

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

Cypress.Commands.add('addBoardUi', (name: string) => {

  let boardUrl;

  const log = Cypress.log({
    autoEnd: false,
    consoleProps() {
      return {
        'board name': name,
        'board url': boardUrl
      }
    },
    displayName: 'addBoardUi',
    message: name,
    name: 'Add new board'
  });

  cy
    .get('[data-cy="create-board"]')
    .click();

  cy
    .get('[data-cy="new-board-input"]')
    .type(name)
    .type('{enter}');

  cy
    .url()
    .then((url) => {
      boardUrl = url
      log.end()
    })

});

Сначала мы определяем переменную boardUrl. Она будет использоваться для присвоения нашего url позже, в 30-ой строке.

Другая вещь, которую мы делаем несколько иначе, заключается в том, что мы присваиваем Cypress.log() переменной. Это позволяет нам непрерывно передавать данные в наш лог. Это означает, что, хотя наш boardUrl сначала будет undefined, впоследствии мы сможем заполнить его информацией, которая появится в тест-раннере.

И последнее: атрибут autoEnd, который будет указывать Cypress не завершать логирование до тех пор, пока мы явно не скажем об этом с помощью функции .end() в строке 31.

Панель Cypress после внесения изменений

Выделение элементов

Рассмотрим другой пример. В приложении есть несколько атрибутов данных, и нам нужно создать пользовательскую команду для их выбора. Назовем ее take. Это будет, по сути, сокращение для команды .get(), чтобы иметь возможность писать .take('create-board') вместо .get([data-cy='create-board']). Основная схема будет выглядеть следующим образом:

Cypress.Commands.add('take', (input: string) => {

  const log = Cypress.log({
    consoleProps() {
      return {
        selector: input
      };
    },
    displayName: 'take',
    name: 'Get by [data-cy] attribute'
  });

  cy
    .get(`[data-cy=${input}]`)

});

Но можно заметить, что новая команда не выделяет наш элемент. Давайте исправим это, а также избавимся от команды .get(), чтобы в тесте не было дублирования:

Cypress.Commands.add('take', (input: string) => {

  const log = Cypress.log({
    autoEnd: false,
    consoleProps() {
      return {
        selector: input,
      };
    },
    displayName: 'take',
    name: 'Get by [data-cy] attribute'
  });

  cy
    .get(`[data-cy=${input}]`, { log: false })
    .then(($el) => {
      log.set({ $el });
      log.snapshot()
      log.end();
    });

});

Теперь в нашем Cypress-раннере есть только одна команда. Более того, с помощью log.set({ $el }); можно выделять элемент, который находит наша команда .get().

Аналогично предыдущему примеру, мы используем autoEnd и функцию .end() для завершения логирования. Чтобы наше выделение работало, нам необходимо сделать хотя бы один снэпшот в нашей команде с помощью функции .snapshot().

Добавление дополнительных логов

По итогу вышло так, что мы потеряли пару консольных логов, которые предоставляла оригинальная команда .get(). Чтобы исправить это, мы снова создадим переменные-заместители и будем заполнять их информацией по мере выполнения наших действий:

Cypress.Commands.add('take', (input: string) => {

  let element: JQuery<HTMLElement> | HTMLElement[];
  let count: number;

  const log = Cypress.log({
    autoEnd: false,
    consoleProps() {
      return {
        selector: input,
        'Yielded': element,
        'Elements': count
      };
    },
    displayName: 'take',
    name: 'Get by [data-cy] attribute'
  });

  cy
    .get(`[data-cy=${input}]`, { log: false })
    .then(($el) => {
      element = Cypress.dom.getElements($el)
      count = $el.length;
      log.set({ $el });
      log.snapshot().end();
    });

});

Наша команда становится довольно аккуратной. Вывод в консоль выглядит точно так же, как и с оригинальной командой .get():

Консоль Cypress

Здесь есть один небольшой баг, который может быть заметен не сразу. Поздравляем, если вы его нашли.

Работа с ошибками

Наш атрибут autoEnd будет ждать, пока не будет вызвана функция.end(). Но если команда .get() не найдет элемент, то логирование не завершится. Тест все равно провалится, так что никакого вреда не будет. Просто команда take застрянет в состоянии загрузки. Чтобы исправить это, можно просто подключиться к событию fail, завершить логирование и выполнить throw err:

Cypress.Commands.add('take', (input: string) => {

  let element: JQuery<HTMLElement> | HTMLElement[];
  let count: number;

  const log = Cypress.log({
    autoEnd: false,
    consoleProps() {
      return {
        selector: input,
        'Yielded': element,
        'Elements': count
      };
    },
    displayName: 'take',
    name: 'Get by [data-cy] attribute'
  });

  cy
    .get(`[data-cy=${input}]`, { log: false })
    .then(($el) => {
      element = Cypress.dom.getElements($el)
      count = $el.length;
      log.set({ $el });
      log.snapshot().end();
    });

  cy
    .on('fail', (err) => {
      log.error(err)
      log.end()
      throw err
    })

});

Пользовательские команды можно использовать, например, для логирования информации из API-запросов, где делаются снимки состояния “до” и “после”. Команду, аналогичную нашей пользовательской команде .take(), можно использовать как двойную команду, то есть получить родительский элемент, а затем выбрать дочерний элемент только в контексте этого родительского элемента. В целом, с помощью этой команды можно сделать очень много интересного.

Перевод статьи «Improve your custom command logs in Cypress».

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

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