Разделы портала

Онлайн-тренинги

.
Cucumber в Cypress: пошаговое руководство
29.11.2023 12:39

Автор: Филип Рик (Filip Hric)
Оригинал статьи
Перевод: Ольга Алифанова

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

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

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

Что я думаю о применении Cucumber

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

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

При использовании черного ящика вся мощь Cypress испаряется, и это просто инструмент тест-автоматизации.

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

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

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

Установка

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

1  npm i @badeball/cypress-cucumber-preprocessor

Помимо установки препроцессора, документация плагина рекомендует установить упаковщик esbuild от Глеба Бахмутова, что значительно ускорит прогон.

1  npm i @bahmutov/cypress-esbuild-preprocessor

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

cypress.config.ts

1  import { defineConfig } from "cypress";
2  import createBundler from "@bahmutov/cypress-esbuild-preprocessor";
3  import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor";
4  import createEsbuildPlugin from "@badeball/cypress-cucumber-preprocessor/esbuild";
5
6  export default defineConfig({
7    e2e: {
8      specPattern: "**/*.feature",
9      async setupNodeEvents(
10        on: Cypress.PluginEvents,
11        config: Cypress.PluginConfigOptions
12      ): Promise<Cypress.PluginConfigOptions> {
13        await addCucumberPreprocessorPlugin(on, config);
14        on(
15          "file:preprocessor",
16          createBundler({
17            plugins: [createEsbuildPlugin(config)],
18          })
19        );
20        return config;
21      },
22    },
23  });

Тут много всякого, пройдемся пошагово.

Файл конфигурации написан на 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 и добавьте в него вот что:

cypress/e2e/board.feature

1  Feature: Board functionality
2
3    Scenario: Create a board
4      Given I am on empty home page
5      When I type and submit in the board name
6      Then I should be redirected to the board detail

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

cypress/e2e/board.ts

1  import { When, Then, Given } from "@badeball/cypress-cucumber-preprocessor";
2
3  Given("I am on empty home page", () => {
4    cy.visit("/");
5  });
6
7  When("I type and submit in the board name", () => {
8    cy.get("[data-cy=first-board]").type('new board{enter}');
9  });
10
11  Then("I should be redirected to the board detail", () => {
12    cy.location("pathname").should('match', /\/board\/\d/);
13  });

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

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

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

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

cypress/e2e/board.ts

1  import { When, Then, Given } from "@badeball/cypress-cucumber-preprocessor";
2
3  Given("I am on empty home page", () => {
4    cy.visit("/");
5  });
6
7  When("I type in {string} and submit", (boardName) => {
8    cy.get("[data-cy=first-board]").type(`${boardName}{enter}`);
9  });
10
11  Then("I should be redirected to the board detail", () => {
12    cy.location("pathname").should('match', /\/board\/\d/);
13  });

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

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

cypress/e2e/board.feature

1  Feature: Board functionality
2
3    Scenario: Create a board
4      Given I am on empty home page
5      When I type in "my board" and submit
6      Then I should be redirected to the board detail

Тестирование, управляемое через данные

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

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

cypress/e2e/board.feature

1  Feature: Board functionality
2
3    Scenario: Creating a <listName> list within a board
4      Given I am on empty home page
5      When I type in "<boardName>" and submit
6      And Create a list with the name "<listName>"
7      Then I should be redirected to the board detail
8
9    Examples:
10        | boardName | listName |
11        | Shopping list | Groceries |
12        | Rocket launch | Preflight checks |

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

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

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

cypress/e2e/board.feature

1  Feature: Creating cards functionality
2
3    Scenario: Create multiple cards
4      Given I am in board detail
5      When I create cards with names
6      | Milk | Bread | Butter | Jam |
7      Then 4 cards are visible

Шаг, однако, должен уметь работать с таблицей данных. Это делается так:

cypress/e2e/cards.ts

1  When("I create cards with names", (table: DataTable) => {
2    cy.get('[data-cy="new-card"]')
3      .click()
4
5    table.raw()[0].forEach(item => {
6
7      cy.get('[data-cy="new-card-input"]')
8        .type(`${item}{enter}`)
9
10    })
11  });

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

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

В дополнение к ключевым словам Given, When, Then, And есть и другие способы организовать множество тестов в едином файле .feature. Наш тест пока что создает новую доску и новый список – изменим его слегка, создав тест, который просто создает еще одну доску, и поставим его перед существующим тестом:

cypress/e2e/board.feature

1  Feature: Board functionality
2
3    Scenario: Opening a board
4      Given I am on empty home page
5      When I type in "<boardName>" and submit
6      Then I should be redirected to the board detail
7
8    Scenario: Creating a <listName> list within a board
9      Given I am on empty home page
10      When I type in "<boardName>" and submit
11      And Create a list with the name "<listName>"
12      Then I should be redirected to the board detail
13
14    Examples:
15      | boardName | listName |
16      | Shopping list | Groceries |
17      | Rocket launch | Preflight checks |

Это похоже на блоки describe(), context() и it() в Mocha – мы можем организовать наши тесты, сгруппировав их в логические кластеры. Ключевое слово Feature работает, как блок describe(), и служит группой верхнего уровня.

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

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

Совместно с ключевым словом Rule тест станет выглядеть так:

cypress/e2e/board.feature

1  Feature: Board functionality
2
3    Rule: Happy paths
4
5    Background: Empty board page
6      Given I am on empty home page
7
8    Scenario: Opening a board
9      When I type in "new board" and submit
10      Then I should be redirected to the board detail
11
12    Scenario: Creating a <listName> list within a board
13      When I type in "<boardName>" and submit
14      And Create a list with the name "<listName>"
15      Then I should be redirected to the board detail
16
17    Examples:
18      | boardName | listName |
19      | Shopping list | Groceries |
20      | Rocket launch | Preflight checks |

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

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

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

cypress/e2e/board.ts

1  import { When, Then, Given, Before } from "@badeball/cypress-cucumber-preprocessor";
2
3  Before(() => {
4    // reset application
5    cy.request('POST', '/api/reset')
6  })
7
8  Given("I am on empty home page", () => {
9    cy.visit("/");
10  });
11
12  When("I type in {string} and submit", (boardName) => {
13    cy.get("[data-cy=first-board]").type(`${boardName}{enter}`);
14  });
15
16  When("Create a list with the name {string}", (listName) => {
17    cy.get('[data-cy="add-list-input"]').type(`${listName}{enter}`);
18  });
19
20  Then("I should be redirected to the board detail", () => {
21    cy.location("pathname").should('match', /\/board\/\d/);
22  });

Теги тестов

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

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

cypress/e2e/board.feature

1  Feature: Board functionality
2
3    Rule: Happy paths
4
5    Background: Empty board page
6      Given I am on empty home page
7
8    @smoke
9    Scenario: Opening a board
10      When I type in "new board" and submit
11      Then I should be redirected to the board detail
12
13    Scenario: Creating a <listName> list within a board
14      When I type in "<boardName>" and submit
15      And Create a list with the name "<listName>"
16      Then I should be redirected to the board detail
17
18    Examples:
19      | boardName | listName |
20      | Shopping list | Groceries |
21      | Rocket launch | Preflight checks |

Для прогона тестов с конкретным тегом нужна команда:

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

Теперь тесты без тега @smoke будут пропущены.

 

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

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

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

Можно также прогнать все тесты, содержащие хотя бы один тег из списка:

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

Или тесты, содержащие оба тега:

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

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

1  import { defineConfig } from "cypress";
2  import createBundler from "@bahmutov/cypress-esbuild-preprocessor";
3  import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor";
4  import createEsbuildPlugin from "@badeball/cypress-cucumber-preprocessor/esbuild";
5
6  export default defineConfig({
7    e2e: {
8      specPattern: "**/*.feature",
9      async setupNodeEvents(
10        on: Cypress.PluginEvents,
11        config: Cypress.PluginConfigOptions
12      ): Promise<Cypress.PluginConfigOptions> {
13        await addCucumberPreprocessorPlugin(on, config);
14        on(
15          "file:preprocessor",
16          createBundler({
17            plugins: [createEsbuildPlugin(config)],
18          })
19        );
20        return config;
21      },
22      env: {
23        omitFiltered: true,
24        filterSpecs: true
25      },
26      fixturesFolder: false,
27      baseUrl: 'http://localhost:3000'
28    },
29  });

Настройка

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

.cypress-cucumber-preprocessorrc.json

1  {
2    "stepDefinitions": [
3      "cypress/e2e/[filepath]/**/*.{js,ts}",
4      "cypress/e2e/[filepath].{js,ts}",
5      "cypress/support/step_definitions/**/*.{js,ts}",
6    ]
7  }

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

package.json

1  // для краткости остаток файла пропущен
2  "cypress-cucumber-preprocessor": {
3    "stepDefinitions": [
4      "cypress/e2e/[filepath]/**/*.{js,ts}",
5      "cypress/e2e/[filepath].{js,ts}",
6      "cypress/support/step_definitions/**/*.{js,ts}",
7    ]
8  }

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

Отчетность

Плагин Cucumber для Cypress имеет множество опций для настройки отчетов. Покажу вам самую простую – отчет HTML.

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

1  {
2    "html": {
3      "enabled": true
4    }
5  }

После прогона теста вы получите красивый HTML-отчет:

 

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

Заключение

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

Однако я надеюсь, что статья будет вам полезной, если вам необходимо применять Cucumber в Cypress.

Обсудить в форуме