1. Введение

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

Я решил использовать связку из этих двух инструментов исходя из того что BDD тесты – это простой текст, на человеческом языке, написанный в форме истории (сценария), описывающей некоторое поведение, значит Cucumber прост для понимания как начинающему специалисту с небольшим опытом тестирования так и для лица технически не подкованным. При этом мощная сердцевина playwright дает большие возможности для написания авто тестов. Градация фреймворка на части позволит опытному специалисту создавать базовую часть функций (в данном примере на Playwright + TypeScript), а начинающему специалисту покрывать тестами приложение с использованием готовой базы и знаниями (Cucumber, X-path). PS возможно это станет более понятным в конце статьи.

Используемые фреймворки/инструменты:

  • Playwright

  • Cucumber

  • Typescript

  • NodeJS

2. Подготовительные работы

Для практики нам понадобиться простенькое web приложение / сайт. На пример это будет тестирование UI части некой signup/registration формы.
Оптимальный вариант запуск данного web приложения локально.
Вы можете как самостоятельно создать похожую страницу так и воспользоваться примером созданным мною. Желательно Чтобы сильно не загружать статью файлами, необходимый комплект файлов добавлен в git репозиторий -https://github.com/QaitkenQ/test_signup_form/tree/start_files

Шаги установки/настройки web приложения

  1. Установите Node JS.

  2. Создайте папку/каталог, например test_server.

  3. Распакуйте скачанные файлы в созданный каталог или загрузите файлы через консоль посредством git.
    Корневой каталог с web приложением будет выглядеть так: (*readme.md файл не обязателен)

    Web приложение
    Web приложение
  4. Откройте командную строку и перейдите в рабочий каталог или откройте каталог редактором кода (например VSC).

  5. Выполните следующие команды через терминал.

    • npm init ‑y

    • npm install express body-parser

  6. Запустите сервер командой

    • node server.js

  7. Убедитесь в работоспособности сервера открыв страницу http://localhost:3000 в браузере.

Сайт готов для выполнения тестирования.

3. Установка/настройка Playwright и Cucumber

  1. Создайте корневой каталог для фреймворка со следующей структурой (например Cucuwright):

    Структура фреймворка
    Структура фреймворка

    Папка feature - содержит файл сценариев Cucumber.
    Папка src - содержит 3 папки с файлами:
    1. step_defenitions - здесь находятся файлы c детальным описанием шагов cucumber.
    2. page_objects - здесь находятся файлы с описанием элементов страницы и действия выполняемые с ними.
    3. utils - здесь находится файл BaseFunctions содержащий базовые операции/действия, которые могут быть применимы для любой страницы.
    Файл cucumber.js - содержит некоторые настройки для фреймворка Cucumber.
    Файл tsconfig.json - содержит настройки TypeScript.

    Стартовый комплект файлов так же доступен в репозитории на другой ветке https://github.com/QaitkenQ/test_signup_form/tree/cucuwright
    В них уже имеются предварительно созданные данные для начала тестирования страницы с singup form, которая уже запущена на http://localhost:3000 (Например файл шагов уже содержит хуки BeforeAll и AfterAll для корректного открытия и закрытия страницы браузера, необходимые зависимости между файлами уже описаны).

  2. Откройте командную строку и перейдите в рабочий каталог или откройте каталог редактором кода (например VSC).

  3. Установите фреймворк Playwright выполнив команду npm init playwright@latest и выбрав необходимые опции (язык typescript, каталог для тестов и т.п.)

  4. Установите фреймворк Cucumber выполнив команду npm i @cucumber/cucumber .

  5. Установите дополнительные библиотеки при необходимости
    npm install typescript
    npm install ts-node

  6. Созданные каталоги tests и tests-examples можно удалить.

  7. Проверьте работоспособность запустив команду npx cucumber-js

В консоль должно вывести сообщение о том что для текущего сценария шаги не определены. Файл singup_page.feature имеет один сценарий с 2 шагами. Значит все инструменты корректно установлены и зависимости работают.

Результат в консоли после выполнения всех подготовительных операций
Результат в консоли после выполнения всех подготовительных операций
  1. По желанию можно установить несколько расширений Cucumber для VSC.
    Cucumber (Gherkin)
    Cucumber Reference Support

4. Написание авто теста

Открываем файл сценариев signup_page.feature По умолчанию в нем уже присутствует 1 сценарий с 2 шагами. При создании нового файла .feature создайте аналогичный сценарий.

singup_page.feature
singup_page.feature

Сценарий 1

Шаг 1 - открытие страницы

Откроем файл signup_page_steps.ts и создадим основное описание для первого шага Given SignupPage open page

singup_page_steps.ts
singup_page_steps.ts

Откроем файл signup_page.ts, добавим url нашей страницы и создадим функцию openPage() для выполнения операции открытия страницы.

singup_page.ts
singup_page.ts

Все стандартные операции действия которые возможно выполнять с различными элементами вынесены в отдельный файл BaseFunctions.ts в папке utils.
Функция openPage() включает в себя базовую функцию openUrl() и передает параметр url.

Откроем файл BaseFunctions.ts и создадим базовую функцию openUrl(), финальным действием является метод goto().

BaseFunctions.ts
BaseFunctions.ts

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

Проверим на работоспособность шаг 1 - запускаем тест npx cucumber-js

Результат в консоли - Шаг 1 выполнен успешно.
Результат в консоли - Шаг 1 выполнен успешно.

Шаг 2 - проверка заголовка

Заголовок Sign Up page
Заголовок Sign Up page

Перейдем в файл signup_page_steps.ts и создадим описание для второго шага

When SignupPage verify the header "Sign Up page"

Добавление шага 2 signup_page_steps.ts
Добавление шага 2 signup_page_steps.ts

В данном случаи мы передаем значение(название заголовка) как строку {string}. При любых изменениях текста потребуется только изменить значение в .feature файле.

В файле signup_page.ts, добавим локатор (X-path, или атрибут элемента) для заголовка и создадим функцию isHeadingVisible() для выполнения операции поиска элемента.

Добавление шага 2 singup_page.ts
Добавление шага 2 singup_page.ts

В базовую функцию findElement() мы передаем значение селектора уже с замененным текстом из первоначального шага When SignupPage verify the header "Sign Up page". Посредством .replace значение %s становится равным Sign Up page.

Добавление функции findElement() в файл BaseFunctions.ts

BaseFunctions.ts
BaseFunctions.ts

Для данной операции был выбран метод waitForSelector().

Запускаем наш тест еще раз npx cucumber-js

Результат в консоли - Сценарий 1, 2 шага выполнено успешно
Результат в консоли - Сценарий 1, 2 шага выполнено успешно

Проверим точно ли мы тест выполняется, действительно ли нужная страница загрузилась и тест проверяет название заголовка - изменим значение в шаге 1 "Sign Up page" -> "Sign Up page Fail"

Получаем в консоли ошибку при выполнении шага 2 - Error: function timed out...

Результат - ошибка на шаге 2.
Результат - ошибка на шаге 2.

Похоже на правду, но хотелось бы получить больше информации в данном случае.

Перепишем функцию findElement() в файле BaseFunctions.ts. Добавим дополнительную функцию erifyElementExists(), которая будет выводить в консоль более детализированное описание об ошибке. Так же можно добавить значение таймаута.

BaseFunction.ts
BaseFunction.ts

Перезапускаем тест npx cucumber-js

Получаем в консоли другой результат - Error: Can't find the element '//h2[text()='Sign Up page Fail']' within the specified timeout

Результат в консоли - ошибка с подробностями
Результат в консоли - ошибка с подробностями

Теперь можно сразу проверить верный ли locator для элемента. Легко проверить скопировав этот путь из лога и вставив его в devtools.

Совпадений 0.
Но если удалить лишний текст, который мы добавили ранее, то будет найдет необходимый элемент.

справляем значение - возвращаем изначальное в шаге 1.
Перезапускаем тест - 1 scenario (1 passed), 2 steps (2 passed). Получаем желаемый результат.

Сценарий 2 - Проверка ярлыков(labels)

Шаг 1 остается прежним открытие страницы.

Шаг 2 - проверка названия поля элемента

Создаем новый шаг в singup_page.feature файле. Так как в форме много однотипных элементов, то желательно создать шаг с использованием таблицы. Позже когда он будет выполнен успешно, мы добавим остальные ярлыки которые есть в форме. Для начала возьмем ярлык от первого поля First Name

В файле singup_page_steps.ts описываем шаг 2 с учетом передачи значений из таблицы.

В файле singup_page.ts создаем функцию verifyLabels() и добавляем селектор для элемента label.

Данная функция использует уже имеющуюся базовую функцию findElement() из BaseFunctions.ts.

Так как у нас теперь 2 сценария, то запускаем только второй сценарий по тегу @test2 npx cucumber-js --tags=@test2

Результат тест пройден.

Добавим в таблицу остальные ярлыки которые есть в форме

singup_page.feature
singup_page.feature

Перезапускаем сценарий 2 npx cucumber-js --tags=@test2

Результат - мы получили ошибку. Последний ярлык из таблицы "Accept learning the Cucuwright" - не может быть найден.

Результат в консоли - ошибка поиска элемента.
Результат в консоли - ошибка поиска элемента.

Выполнив небольшой анализ видно что ярлык для элемента checkbox имеет другую структуру в DOM дереве. Значит придется создать отдельный шаг для данного элемента.

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

singup_page.feature
singup_page.feature

В файле singup_page_steps.ts добавим описание шага

В файле singup_page.ts создаем функцию verifyCheckbox() и добавляем селектор для элемента checkbox_label. В селекторе мы использовали X-path для более детального уточнения элемента - элемент должен иметь тип checkbox с необходимым текстом.

Запускаем тест npx cucumber-js --tags=@test2
Результат - сценарий выполнен успешно.

Результат Сценарий 2.
Результат Сценарий 2.

Для перепроверки можно выполнить быстрый дебаг добавив ошибку в значение(название) ярлыка - Accept learning the Cucuwright -> Accept learning th Cucuwright
В результате мы получим ошибку что элемент checkbox с данным текстом не может быть найден.

Результат - ошибка выполнения последнего шага.
Результат - ошибка выполнения последнего шага.

Сценарий 3 - Проверка введенных значений в поле ввода

Новый сценарий и шаг в файле .feature

singup_page.feature
singup_page.feature

В файле singup_page_steps.ts добавим описание шага.

singup_page_steps.ts
singup_page_steps.ts

В файле singup_page.ts добавим локатор для поля First Name и создадим функцию enterUserName(). Данная функция будет передавать данные в виде string базовой функции enterText().

Создадим базовую функцию enterText() в файле BaseFunctions.

Проверим работоспособность теста npx cucumber-js --tags=@test3

Результат - тест выполнен успешно
Результат - тест выполнен успешно

Добавим следующий шаг к сценарию №3, проверка значения в поле ввода.
Новый шаг в фалйе .feature

singup_page.feature
singup_page.feature

В файле singup_page_steps.ts добавим описание шага.

Для примера сделаем этот шаг универсальным и применимым к другим полям ввода. К дополнению к основной функции verifyFieldValue() создадим дополнительную функцию determineField() определяющую поле ввода. Так же необходимо добавить локаторы для остальных полей из формы в singup_page.ts файле.

В зависимости от значения в шаге .feature файла будет определено поля для проверки и в случаи отсутствия его в списке для проверки будет выведена ошибка.

Далее в в файле BaseFunctions необходимо создать функцию verifyValue().

BaseFunctions.ts
BaseFunctions.ts

Запускаем тест еще раз npx cucumber-js --tags=@test3
Результат - тест выполнен успешно.

Результат
Результат

Небольшой дебаг - попробуем ввести ошибочное название поля First Name -> Fail Name

singup_page.feature
singup_page.feature

В результате мы получим ошибку с описанием, которое добавили немного ранее.
Unknown field 'Fail Name', please check the name

Результат
Результат

Сценарий 4 - проверка radio button элемента

Новый сценарий и шаг в файле .feature

singup_page.feature
singup_page.feature

Файл singup_page_steps.ts - описание шага.

Файл singup_page.ts - добавление локатора radio_btn и создание функции selectRadioButton().

singup_page.ts
singup_page.ts

Файл BaseFunctions - cоздание базовой функции selectItem().

BaseFunctions.ts
BaseFunctions.ts

Запуск теста npx cucumber-js --tags=@test4
Результат - тест выполнен успешно.

Результат
Результат

Добавим следующий шаг для проверки выбранного значения.

singup_page.feature
singup_page.feature

Файл singup_page_steps.ts - описание шага.

Файл singup_page.ts - создание функции isRadioChecked().

singup_page.ts
singup_page.ts

Файл BaseFunctions - cоздание базовой функции isChecked().

BaseFunctions.ts
BaseFunctions.ts

Повторный запуск теста npx cucumber-js --tags=@test4
Результат - тест выполнен успешно.

Результат
Результат

Для перепроверки добавим в шаг в табличное значение выбор следующего элемента radio button female.

singup_page.feature
singup_page.feature

В данном случае результатом будет ошибка сообщающая о том, что элемент radio button male не выбран.

Результат
Результат

Сценарий 5 - проверка dropdown поля

Новый сценарий и шаг в файле .feature

singup_page.feature
singup_page.feature

Файл singup_page_steps.ts - описание шага.

Файл singup_page.ts - создание функции selectDdlValue(). Локатор ddList уже был добавлен ранее.

singup_page.ts
singup_page.ts

Файл BaseFunctions - cоздание функции selectOption()

BaseFunctions.ts
BaseFunctions.ts

Запуск теста npx cucumber-js --tags=@test5
Результат - тест выполнен успешно.

Результат
Результат

Добавление следующего шага для проверки выбранного значения в выпадающем списке.

Новый шаг в файле .feature. Так как ранее был создан универсальный шаг, который применим к нашему выпадающему списку, создавать дополнительно ничего не требуется

singup_page.feature
singup_page.feature

Повторный запуск теста npx cucumber-js --tags=@test5
Результат - сценарий выполнен успешно.

Результат
Результат

Сценарий 6 - выбор checkbox'a

Новый сценарий и шаг в файле .feature

singup_page.feature
singup_page.feature

Файл singup_page_steps.ts - описание шага.

Файл singup_page.ts - создание локатора checkbox и функции selectCheckbox().

singup_page.ts
singup_page.ts

Базовая функция selectItem() уже была создана ранее.

Запуск теста npx cucumber-js --tags=@test6
Результат - тест выполнен успешно.

Результат
Результат

Добавление следующего шага для проверки был ли элемент выбран.

Новый сценарий и шаг в файле .feature

singup_page.feature
singup_page.feature

Файл singup_page_steps.ts - описание шага.

Файл singup_page.ts - создание локатора checkbox и функции selectCheckbox().

singup_page.ts
singup_page.ts

Базовая функция isChecked() уже была создана ранее.

Повторный запуск сценария npx cucumber-js --tags=@test6
Результат - сценарий выполнен успешно.

Результат
Результат

Сценарий 7 - проверка ошибки валидации.

Новый сценарий в файле .feature. Используем имеющиеся шаги и создаем новый шаг для нажатия кнопки Submit.

singup_page.feature
singup_page.feature

Файл singup_page_steps.ts - описание шага.

Файл singup_page.ts - создание локатора btn и функции clickButton().

singup_page.ts
singup_page.ts

Файл BaseFunctions - cоздание базовой функции clickBtn().

BaseFunctions.ts
BaseFunctions.ts

Запуск теста npx cucumber-js --tags=@test7
Результат - тест выполнен успешно.

Результат
Результат

Добавление новых шагов - проверка ошибки валидации.

singup_page.feature
singup_page.feature

Файл singup_page_steps.ts - описание шага.

Файл singup_page.ts - создание локатора valid_error и функции verifyError(). Так же как и в случае с проверкой значения в поле ввода в данной функции присутствует вспомогательная функция для распознавания пути - у какого элемента искать ошибку.

singup_page.ts
singup_page.ts

Файл BaseFunctions - cоздание базовой функции verifyText().

Запуск сценария npx cucumber-js --tags=@test7
Результат - тест выполнен успешно.

Результат
Результат

Добавим в данный сценарий проверку ошибки валидации для поля Last Name - "Required field"

singup_page.feature
singup_page.feature

Повторный запуск теста npx cucumber-js --tags=@test7
Результат - тест выполнен успешно.

Результат
Результат

Изменим текст ошибки для выполнения негативного сценария и перепроверки созданного теста

singup_page.feature
singup_page.feature

В результате получим ожидаемую ошибку. Фактический результат "Please use only alphabetical characters"

Результат
Результат

Заключение

В итоге мы настроили запустили простенький сервер, установили и настроили фреймворк и написали несколько авто тестов. В начале статьи я писал, что обьясню как в моем понимании связка cucumber + playwright может помочь в написании авто тестов начинающему специалисту. При использовании данной структуры и работе в команде опытный специалист может создать основной костяк на основе небольшой части приложения и описав основные операции в файле BaseFunctions. В случаи большого веб приложения и схожих его частей и использовании шагов с универсальными способами, второму специалисту, с меньшим опытом написания авто тестов, будет не сложно покрыть тестами остальную часть приложения на основе имеющегося задела. Как результат это будут созданные фалы для других страниц с дублированием имеющихся операций и минимальными изменениями. Хотелось показать на примере создания тестов для другой страницы, но статья уже получилась не маленькая и я думаю это будет лишним. Так же плюсом будет простота понимания написанных сценариев в файле .feature для технически не подкованного человека.

Это моя первая статья, прошу отнестись с пониманием если имеются неточности или ошибки. Буду рад любой критике. Надеюсь кому-то статья будет полезной. В будущем планирую создать другую статью с практическими тестами для БД или API.

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


  1. amarkina17
    29.08.2024 15:21

    Для чего нужен cucumber, если page object передает весь смысл шага и теста: loginPage.click(signInButton) - довольно очевидно, что происходит. Складывается ощущение, что добавление cucumber тут просто оверхэд, который ещё и нужно будет сложнее поддерживать. Может быть тут кейсы не достаточно показательные, но так и не понятно зачем?


    1. Aitken Автор
      29.08.2024 15:21

      Приветствую amarkina17.

      На самом деле в ваших словах есть доля правды, cucumber уже давно не является популярным инструментом для тестирования. Но все же попробую донести мысль на следующем примере.

      Существует некое веб приложение. Оно имеет множество схожих страниц с различными данными. Существуют таблицы с различными колонками и одинаковыми типом данных Стоит задача покрыть тестами данную часть приложения используя некую методику и использовать множество вариаций тестовых данных.

      Пример веб приложения
      Пример веб приложения

      Достаточно создать основу с 1 тестом для 1 страницы одному опытному QA.

          @test1
      Scenario: Test 1 - Section 1 Page 1 - Verify table values and manufacturer filter 
          Given DB service insert data "Section_one_page_one.json"
          When PageOne open page
          Then PageOne verify table values
              | Model        | Type        | Color        |
              | Test model 1 | Test type 1 | Test color 1 |    
          When PageOne select  filter "Manufacturer" value
              | Test manufacturer 1  |    
          Then PageOne verify table values
              | Model        | Type        | Color        |
              | Test model 2 | Test type 2 | Test color 2 |  

      Далее задача покрыть все страницы тестами может быть передана менее опытному тестировщику. Который в последующем к примеру исходя из базы тест кейсов напишет тесты для имеющейся страницы используя определенный набор тестовых данных - просто копируя имеющиеся шаги в файле .feature и изменяя вводные данные

      Scenario: Test 2 
          ...        
          When PageOne select  filter "Manufacturer" value
              | Test manufacturer 2 |    
          Then PageOne verify table values
              | Model        | Type        | Color        |
              | Test model 3 | Test type 3 | Test color 3 |  
      
                
      Scenario: Test 20 
          ...        
          When PageOne select  filter "Date" value
              | 09-09-2024 |    
          Then PageOne verify table values
              | Model         | Type         | Color         |
              | Test model 50 | Test type 50 | Test color 50 |  

      Для тестирования следующих, практически идентичных по строению страниц, но с немного другими элементами, достаточно создать новые файлы в step_definitions и page_objects которые будут почти идентичны имеющимся созданным для первой страницы
      К примеру изменения будут выглядеть:


      let PageOne: Page_One -> let PageTwo: Page_Two
      await PageOne.openPage(); - > await PageTwo.openPage();

      Продолжить писать тесты по аналогии в .feature

      Scenario: Test 100 
          ...        
          When PageTwo select  filter "Side" value
              | Test side 10 |    
          Then PageTwo verify table values
              | Box        | Type        | Date       |
              | Test box 3 | Test type 3 | 01-01-2001 |  
      
                
      Scenario: Test 501 
          ...        
          When PageSix select  filter "Color" value
              | Red |    
          Then PageOne verify table values
              | User          | Date       | Work |
              | Test user 5   | 01-01-2001 | Done |  

      Надеюсь в данном примере мысль более детально раскрыта.