<style>.lazy{display:none}</style>Cucumber в Cypress: Пошаговое руководство

Cucumber в Cypress: Пошаговое руководство

Перевод статьи «Cucumber in Cypress: A step by step guide».

Один из самых распространенных вопросов, которые встречаются на вебинарах и прямых трансляциях, звучит так:  Как использовать “X” в Cucumber? . Будь то тестирование API,  cy.session() или другая функциональность, Cucumber является обязательным требованием во многих командах.

Основным преимуществом использования Cucumber является возможность использования синтаксиса Gherkin для определения тестов. Все тесты пишутся как сценарии поведения, поэтому тесты не только выполняют роль проверки функциональности, но и служат своего рода документацией. Цель такого подхода – обеспечить большую наглядность того, что тестируется. Преимущество заключается в том, что, помимо инженеров, другие заинтересованные лица компании могут проверить, выполнены ли критерии приемки.

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

Содержание:

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

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

Нередко встречается критика синтаксического подхода Gherkin. Основное возражение против него заключается в том, что он использует тестирование методом “черного ящика”. Далеко не все считают такой подход эффективным – особенно в случае с Cypress.

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

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

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

В Cucumber используются определения, основанные на шагах, которые заключают каждую серию команд в отдельный файл. Хотя они и отличаются хорошей читаемостью, любое изменение, вносимое в приложение, может потребовать переопределения или добавления нескольких шагов. Это означает, что чем больше система, тем сложнее вводить новые изменения.

Это руководство создано для того, чтобы вы могли эффективно настроить Cypress с Cucumber, если это потребуется в вашей компании.

Установка

Для начала необходимо установить плагин cypress-cucumber-preprocessor. В настоящее время существует несколько различных версий, но эта – самая популярная, и она активно поддерживается. Установить его можно, выполнив следующую команду:

npm i @badeball/cypress-cucumber-preprocessor

Помимо установки препроцессора, в документации к плагинам рекомендуют установить бандлер esbuild, который значительно ускорит работу.

npm i @bahmutov/cypress-esbuild-preprocessor

После установки этих пакетов необходимо настроить Cypress на использование плагинов. Окончательная конфигурация будет выглядеть примерно так:

import { defineConfig } from "cypress";
import createBundler from "@bahmutov/cypress-esbuild-preprocessor";
import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor";
import createEsbuildPlugin from "@badeball/cypress-cucumber-preprocessor/esbuild";

export default defineConfig({
  e2e: {
    specPattern: "**/*.feature",
    async setupNodeEvents(
      on: Cypress.PluginEvents,
      config: Cypress.PluginConfigOptions
    ): Promise<Cypress.PluginConfigOptions> {
      await addCucumberPreprocessorPlugin(on, config);
      on(
        "file:preprocessor",
        createBundler({
          plugins: [createEsbuildPlugin(config)],
        })
      );
      return config;
    },
  },
});

Здесь есть, что распаковывать, поэтому рассмотрим все пошагово.

Конфигурационный файл написан на языке TypeScript. Файл на Javascript может быть немного проще, но по сути содержит все те же части. Мы импортируем различные пакеты и добавляем их в нашу функцию setupNodeEvents().

Атрибут specPattern сообщает Cypress, что мы хотим искать файлы .feature в нашей папке e2e. Это означает, что он будет игнорировать все другие форматы и использовать в качестве теста только эти файлы.

Функция addCucumberPreprocessorPlugin() займется обработкой файлов .feature и преобразованием их в Javascript. Поскольку Cypress работает в браузере, нам необходимо убедиться, что все, что мы запускаем (будь то файлы.ts , .jsx или другие форматы), в конечном итоге компилируется в обычный Javascript. Именно это и делают препроцессоры.

В части on("file:preprocessor") происходит объединение плагина esbuild с плагином cucumber, чтобы они хорошо работали вместе.

Заключительное утверждение return config позволяет убедиться в том, что все, что мы настроили, действительно будет установлено в нашей конфигурации. Об этом шаге часто забывают, поэтому, если ваши плагины ведут себя так, как будто они вообще не установлены, проверьте наличие этого оператора возврата.

Поскольку компиляция в Javascript является важной частью работы с файлами .feature, обычно начальная настройка является самым большим препятствием, которое необходимо преодолеть. Существует наиболее простая для работы настройка из документации, но если вы работаете с другим бандлером, например Webpack или Browserify, вы можете найти примеры здесь.

Теперь, когда плагин установлен и настроен, давайте рассмотрим, как писать тесты.

Сценарии и этапы тестирования

Начнем с написания простого тестового сценария в синтаксисе Gherkin. Создайте новый файл cypress/e2e/board.feature и добавьте в него следующее содержимое:

Feature: Board functionality

  Scenario: Create a board
    Given I am on empty home page
    When I type and submit in the board name
    Then I should be redirected to the board detail

Теперь нам необходимо создать определения шагов для каждого этапа сценария. Самый простой способ определить шаги – создать новый файл board.ts в папке cypress/e2e, который может выглядеть примерно так:

import { When, Then, Given } from "@badeball/cypress-cucumber-preprocessor";

Given("I am on empty home page", () => {
  cy.visit("/");
});

When("I type and submit in the board name", () => {
  cy.get("[data-cy=first-board]").type('new board{enter}');
});

Then("I should be redirected to the board detail", () => {
  cy.location("pathname").should('match', /\/board\/\d/);
});

Вы можете поместить свой файл определений board.ts  в папку cypress/e2e либо выбрать другое имя и поместить его в папку cypress/e2e/board или в cypress/support/step_definitions, после чего препроцессор cucumber автоматически их подхватит. Для пользовательского пути вам необходимо явно указать это в конфигурации (к ней мы перейдем немного позже).

Для улучшения работы при написании тестов в VS Code рекомендуется установить это расширение. Оно обеспечит корректную подсветку в файлах .feature и легкий доступ к определениям шагов.

Добавление параметров к определениям шагов

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

import { When, Then, Given } from "@badeball/cypress-cucumber-preprocessor";

Given("I am on empty home page", () => {
  cy.visit("/");
});

When("I type in {string} and submit", (boardName) => {
  cy.get("[data-cy=first-board]").type(`${boardName}{enter}`);
});

Then("I should be redirected to the board detail", () => {
  cy.location("pathname").should('match', /\/board\/\d/);
});

Параметры автоматически передаются в соответствующие функции определения шага в качестве аргументов. Обратите внимание на {string} в определении шага. Это позволит проверить, правильный ли тип мы передаем в наш шаг.

Теперь создадим в нашем файле cypress/e2e/board.feature сценарий, принимающий в качестве параметра имя доски boardName. Выглядеть это будет примерно так:

Feature: Board functionality

  Scenario: Create a board
    Given I am on empty home page
    When I type in "my board" and submit
    Then I should be redirected to the board detail

Тестирование на основе данных

Еще одним важным понятием в Сucumber, которое необходимо знать, являются таблицы данных. DataTable в синтаксисе Gherkin позволяет передавать таблицу данных на шаг, что облегчает работу с несколькими наборами данных в тестовых сценариях. Это особенно полезно для тестирования на основе данных, когда необходимо проверить один и тот же сценарий с разными наборами входных данных.

Таблицы данных определяются в секции Examples вашего файла .feature. Продолжим работу с предыдущим файлом:

Feature: Board functionality

  Scenario: Creating a <listName> list within a board
    Given I am on empty home page
    When I type in "<boardName>" and submit
    And Create a list with the name "<listName>"
    Then I should be redirected to the board detail

  Examples:
      | boardName | listName |
      | Shopping list | Groceries |
      | Rocket launch | Preflight checks |

Определив шаги Examples, вы будете запускать свой тест несколько раз, передавая на каждом шаге разные данные. Обратите внимание, как мы создаем переменные boardName и listName, оборачиваем их в <> для передачи в качестве параметров в определениях шагов.

Рабочий массив данных

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

Feature: Creating cards functionality

  Scenario: Create multiple cards
    Given I am in board detail
    When I create cards with names
    | Milk | Bread | Butter | Jam |
    Then 4 cards are visible

Однако шаг должен уметь классифицировать таблицу данных. Вот как это можно сделать:

When("I create cards with names", (table: DataTable) => {
  cy.get('[data-cy="new-card"]')
    .click()

  table.raw()[0].forEach(item => {

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

  })
});

Функция table.raw()[0] вернет первую строку таблицы ([0]) в виде массива. Внутри определения шага мы перебираем этот массив для создания элементов в списке.

Группировка тестов

Помимо ключевых слов GivenWhenThen и And существует еще несколько способов организации нескольких тестов в одном файле .feature. До сих пор наш тест создавал новую доску и новый список, но давайте немного изменим его и создадим один тест, который просто создаст еще одну доску и поместит ее перед нашим существующим тестом:

Feature: Board functionality

  Scenario: Opening a board
    Given I am on empty home page
    When I type in "<boardName>" and submit
    Then I should be redirected to the board detail

  Scenario: Creating a <listName> list within a board
    Given I am on empty home page
    When I type in "<boardName>" and submit
    And Create a list with the name "<listName>"
    Then I should be redirected to the board detail

  Examples:
    | boardName | listName |
    | Shopping list | Groceries |
    | Rocket launch | Preflight checks |

Аналогично блокам describe()context() и it() в Mocha мы можем дополнительно организовать наши тесты и сгруппировать их в логические кластеры. Ключевое слово Feature выступает в роли блока describe(https://mochajs.org/) и служит группой верхнего уровня.

Внутри области видимости Feature можно добавить блок Rule, который еще больше разделит ваши сценарии на подгруппы.

По мере тестирования различных сценариев можно добавить шаг Background, который будет действовать подобно хуку beforeEach() в Mocha и запускать последовательность шагов перед каждым сценарием. Мы можем абстрагировать шаги Given и When от нашего текущего файла .feature и сделать наш тест немного чище.

В сочетании с ключевым словом Rule он может выглядеть примерно так:

Feature: Board functionality

  Rule: Happy paths

  Background: Empty board page
    Given I am on empty home page

  Scenario: Opening a board
    When I type in "new board" and submit
    Then I should be redirected to the board detail

  Scenario: Creating a <listName> list within a board
    When I type in "<boardName>" and submit
    And Create a list with the name "<listName>"
    Then I should be redirected to the board detail

  Examples:
    | boardName | listName |
    | Shopping list | Groceries |
    | Rocket launch | Preflight checks |

Использование хуков

Пока есть возможность добавить Background, мы все еще можем определить шаги Before и After, которые действуют как хуки beforeEach() и afterEach() в Mocha. Сбой в них не приведет к ошибкам в тестах, так как они фактически выполняются внутри тестов.

Шаги Before и After являются частью файла определения шагов, что означает, что их не требуется добавлять в файл .feature.

import { When, Then, Given, Before } from "@badeball/cypress-cucumber-preprocessor";

Before(() => {
  // reset application
  cy.request('POST', '/api/reset')
})

Given("I am on empty home page", () => {
  cy.visit("/");
});

When("I type in {string} and submit", (boardName) => {
  cy.get("[data-cy=first-board]").type(`${boardName}{enter}`);
});

When("Create a list with the name {string}", (listName) => {
  cy.get('[data-cy="add-list-input"]').type(`${listName}{enter}`);
});

Then("I should be redirected to the board detail", () => {
  cy.location("pathname").should('match', /\/board\/\d/);
});

Тегирование тестов

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

Чтобы добавить теги к сценариям, просто добавьте к сценарию или функции символ @, а затем имя тега. Например, добавим тег @regression к сценарию успешного входа в систему в файле cypress/e2e/board.feature:

Feature: Board functionality

  Rule: Happy paths

  Background: Empty board page
    Given I am on empty home page

  @smoke
  Scenario: Opening a board
    When I type in "new board" and submit
    Then I should be redirected to the board detail

  Scenario: Creating a <listName> list within a board
    When I type in "<boardName>" and submit
    And Create a list with the name "<listName>"
    Then I should be redirected to the board detail

  Examples:
    | boardName | listName |
    | Shopping list | Groceries |
    | Rocket launch | Preflight checks |

Чтобы запустить тесты с определенным тегом, используйте следующую команду:

npx cypress run --env tags="@smoke"

При этом будут пропущены тесты, не содержащие тег @smoke.

Вы также можете проверить это в режиме open, используя ту же команду, но с open вместо run.

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

npx cypress run --env tags="not @smoke"

Существует также способ запуска всех тестов, содержащих любой из этих тегов:

npx cypress run --env tags="@smoke or @regression"

Или тесты, содержащие и то, и другое одновременно:

npx cypress run --env tags="@smoke and @regression"

Для ускорения выполнения теста можно использовать опции filterSpecs и omitFiltered, которые работают аналогично тому, как работает плагин @cypress/grep. Вы можете включить эту функциональность, добавив следующие опции в файл cypress.config.ts:

import { defineConfig } from "cypress";
import createBundler from "@bahmutov/cypress-esbuild-preprocessor";
import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor";
import createEsbuildPlugin from "@badeball/cypress-cucumber-preprocessor/esbuild";

export default defineConfig({
  e2e: {
    specPattern: "**/*.feature",
    async setupNodeEvents(
      on: Cypress.PluginEvents,
      config: Cypress.PluginConfigOptions
    ): Promise<Cypress.PluginConfigOptions> {
      await addCucumberPreprocessorPlugin(on, config);
      on(
        "file:preprocessor",
        createBundler({
          plugins: [createEsbuildPlugin(config)],
        })
      );
      return config;
    },
    env: {
      omitFiltered: true,
      filterSpecs: true
    },
    fixturesFolder: false,
    baseUrl: 'http://localhost:3000'
  },
});

Конфигурация

Существует два способа изменения стандартной конфигурации препроцессора Cucumber. Можно создать конфигурационный файл .cypress-cucumber-preprocessorrc.json, который будет выглядеть следующим образом:

{
  "stepDefinitions": [
    "cypress/e2e/[filepath]/**/*.{js,ts}",
    "cypress/e2e/[filepath].{js,ts}",
    "cypress/support/step_definitions/**/*.{js,ts}",
  ]
}

Или установить все прямо в package.json, добавив эквивалент:

// rest of file skipped for brevity
"cypress-cucumber-preprocessor": {
  "stepDefinitions": [
    "cypress/e2e/[filepath]/**/*.{js,ts}",
    "cypress/e2e/[filepath].{js,ts}",
    "cypress/support/step_definitions/**/*.{js,ts}",
  ]
}

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

Отчетность

Плагин Cucumber для Cypress поставляется с различными вариантами настройки репортеров. Самый простой из них – HTML-репортер.

Практически все, что нужно сделать, – это настроить конфигурацию:

{
  "html": {
    "enabled": true
  }
}

После выполнения теста вы получите HTML-отчет с красивым форматированием, который выглядит следующим образом:

HTML-отчет с красивым форматированием, который формируется после выполнения теста

Если вам нужен более продвинутый вывод, который впоследствии вы захотите разобрать и передать в собственную систему отчетности, рекомендую обратить внимание на json-formatter от авторов Сucumber. Вам потребуется установить его отдельно и настроить на запуск в конфигурационном файле.

Заключение

Как уже упоминалось в самом начале, существуют и более эффективные способы использования Cypress. Выполнять все сквозные тесты с помощью UI – это небольшое излишество, а учитывая дизайн Cypress, можно раскрыть гораздо больше возможностей, если использовать его так, как он задумывался.

Тем не менее, хочется верить, что эта статья была полезна для вас, если вы собираетесь использовать Cucumber вместе с Cypress.

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

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