Один из вопросов, которые мне чаще всего задают на вебинарах, это: «Как мне использовать "X" в Cucumber?» Идет ли речь о тестировании API, cy.session() или другой функциональности, Cucumber, похоже, является обязательным требованием для многих команд.

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

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

Мои мысли об использовании Cucumber

В прошлом я был известен тем, что критиковал синтаксический подход Gherkin. Мое основное возражение против него заключается в том, что он использует подход «черного ящика» к тестированию. Я считаю этот подход не очень эффективным — особенно в Cypress.

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

Придерживаясь подхода «черного ящика», пользователь не применяет все возможности Cypress и использует его просто как инструмент автоматизации тестирования.

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

Cucumber использует определения на основе шагов, которые заключают каждую серию команд в отдельный файл. Хотя они также могут характеризоваться хорошей читабельностью, любое изменение, внесенное в приложение, может потребовать переопределения или добавления нескольких шагов. Это означает, что чем больше система, тем сложнее вводить новые изменения. Глеб Бахмутов доступно объясняет в ролике на YouTube, насколько непростым может быть введение изменений в тесты на основе 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. Это означает, что он будет игнорировать все другие форматы и использовать в качестве теста только файлы .feature.

Функция 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

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

Еще одна важная концепция в Cucumber, которую нужно знать, — это таблицы данных. 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]) таблицы в виде массива. Внутри определения шага мы перебираем этот массив для создания элементов в списке.

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

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

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() и служит группой верхнего уровня.

Внутри области 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

tests filtered by tag using cucumber

Это также можно проверить в режиме 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-отчет, который выглядит следующим образом:

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

В заключение

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


Приглашаем всех желающих на открытый урок, посвященный основам Cypress. Мы изучим основы фреймворка Cypress и рассмотрим, как его использовать для автоматизации тестирования веб-приложений. Рассмотрим основные принципы написания тестов с помощью этого фреймворка, а также поработаем с основными инструментами Cypress для создания и отладки тестов. Напишем несколько UI-тестов.

Комментарии (0)