Привет, меня зовут Иосип. В настоящее время я работаю над автоматизацией тестирования в компании Ars Futura. Кроме шуток, меня всегда интересовало автоматизированное тестирование с использованием Selenium. Но давайте обо всем по порядку. В старших классах я самостоятельно изучал Java. Мне всегда казалось, что это самый «логичный» язык программирования (интересно, кто тоже на это «клюнул», хе-хе).

Если серьезно, то когда я узнал про автоматизированные тесты, я сразу же начал их изучать. Написал первый тест — мне понравилось, выполнил его — тоже понравилось. Сначала для создания тестов я использовал только базовый Selenium с Java. Когда начал работать в Ars Futura, тимлид познакомил меня с Cucumber и Behavior Driven Development, которые действительно поднимают автоматизацию на новый уровень.

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

Selenium

Это бесплатное программное обеспечение с открытым исходным кодом, один из самых популярных инструментов автоматизации. Selenium используется для написания функциональных тестов без знания языка программирования тест-скрипта (при помощи расширения Selenium IDE). Он также предоставляет тестовый предметно ориентированный язык (DSL) для написания тестов на ряде популярных языков программирования, среди которых JavaScript (Node.js), C#, Groovy, Java, Perl, PHP, Python, Ruby и Scala. Selenium поддерживает браузеры Mozilla Firefox, Google Chrome, Safari и Internet Explorer. В каждом примере, приведенном в этой статьей, в качестве языка программирования используется Java.

Selenium WebDriver

Selenium WebDriver — это наиболее важный компонент, используемый для выполнения кроссбраузерных автоматизированных тестов. Selenium предоставляет драйверы, специфичные для каждого браузера. Не раскрывая внутренней логики работы браузера, драйвер браузера взаимодействует с соответствующим браузером путем установления безопасного соединения. Эти браузерные драйверы также зависят от языка, который используется для автоматизации тест-кейсов, например C#, Python или, как в данном случае, Java.

Давайте рассмотрим простой пример использования Selenium WebDriver. В этом примере драйвер откроет Google Chrome, увеличит размер окна, перейдет в Google и выполнит в поиске “YouTube”.

Первый шаг — создание экземпляра WebDriver, в данном случае для Google Chrome. Ниже показан простой пример создания ChromeDriver.

System.setProperty("chrome.driver", "path-to-driver");
WebDriver driver = new ChromeDriver();
driver.manage().window().maximize()

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

Второй шаг — перейти по URL-адресу Google:

driver.manage().timeouts().wait(3000);
driver.get("https://google.com");

Думаю, вы все обратили внимание на первую строку, в которой объявлено wait. Почему wait? Да потому что драйвер работает быстро, и он введет URL еще до того, как откроется Chrome, и тест будет завершен ошибкой. Поэтому создается неявное ожидание, которое в этом случае установлено на 3000 миллисекунд. В данный момент у нас открыт Chrome и открыт URL Google, как показано на рисунке выше.

Давайте пока поставим это на паузу и поговорим о локаторах. Локатор используется как способ идентификации элементов на нужной веб-странице или в приложении. Локатор — это основной аргумент, который передается методу поиска элементов. Вот некоторые из локаторов в Selenium: ID, CSS ClassName, атрибут name, xpath и т. д.

Теперь продолжим нашу автоматизацию. Итак, мы находимся на стартовой странице Google, и нам нужно сказать Google открыть YouTube. Как только мы изучим строку поиска Google (как показано на рисунке ниже):

Visual showing the inspection of the Google search bar
Visual showing the inspection of the Google search bar

В консоли мы увидим атрибуты элемента:

Visual showing console attributes
Визуальное отображение атрибутов консоли

С помощью локаторов Selenium мы можем использовать эти атрибуты, чтобы точно определить элемент, с которым хотим взаимодействовать. В этом сценарии мы будем использовать его имя, которое в данном случае равно "q".

driver.findElement(By.name("q")).sendKeys("Youtube");

Как видно выше, мы прописываем локатор (имя), чтобы драйвер знал, с каким элементом взаимодействовать. Кроме того, мы можем отправить нужную строку в той же команде с помощью функции sendKeys. В данный момент у нас есть значение "YouTube", которое находится в строке поиска Google. Логично, что теперь нужно нажать кнопку поиска. В примере кода ниже я предварительно сохранил кнопку поиска в качестве самостоятельного элемента, чтобы ее можно было использовать несколько раз:

WebElement searchIcon = driver.findElement(By.name("btnK"));
searchIcon.click();

Мы могли бы использовать .click так же, как мы использовали .sendKeys в предыдущем шаге, но сохранение элемента упрощает взаимодействие с ним.

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

Cucumber, Gherkin и BDD

Инструмент тестирования Cucumber поддерживает принцип поведенческой разработки (Behavior Driven Development, BDD). Он предлагает способ написания тестов, понятный всем, независимо от технических знаний. В BDD первое, что делают пользователи, — пишут сценарии или приемочные тесты, которые описывают поведение системы.

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

Scenario Login
 Given I'm on login page
 And I enter my email
 And I enter my password
 When I click login button
 Then I should be logged in

А теперь представьте, что кто-то в параллельной вселенной (я фанат Flash, не обессудьте) прислал вам полную автоматизацию для веб-приложения с кучей случайного кода в ней. И теперь вы задаетесь вопросом, какие функциональные возможности она тестирует? Поскольку вы читаете эту статью, вы можете просто открыть feature-файл, в котором находятся сценарии, и посмотреть, что именно тестируется и какие шаги выполняются.

Вы смотрите на шаги и задаетесь вопросом, что именно за ними происходит? Не волнуйтесь, я вас понял! Давайте разберемся немного подробнее. За каждым шагом стоит собственное определение шага. Определения шагов — это класс, который связан с файлом шагов и содержит методы, созданные в POM. POM (Page Object Model) — это способ структурировать проект таким образом, чтобы каждая страница приложения имела свой собственный класс. Например, кнопки входа и регистрации обычно находятся на начальной странице. Поэтому, как только мы найдем эти две кнопки и сохраним их в качестве веб-элементов, мы сохраним их в Java-классе, который называется, как вы уже догадались, LandingPO (Landing Page Object). 

A simple ivsual showing the graph structure
Простой визуальный пример структуры графа

Теперь давайте рассмотрим более сложный сценарий. В следующем примере сценария тестирования мы рассмотрим Selenium, Cucumber, Gherkin, BDD и POM одновременно. Так что пристегнитесь и поехали.

Расширенное использование на примере

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

  • Откроет Google Chrome

  • Найдет Stack Overflow

  • Зайдет на Stack Overflow

  • Откроет профиль пользователя

  • Откроет настройки профиля пользователя

  • Изменит отображаемое имя пользователя

  • Сохранит изменения

  • Проверит изменения

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

  • IntelliJ (или любая другая IDE)

  • установленный Gradle

  • установленная Java

  • аккаунт на Stack Overflow

Первый шаг — создание проекта. Откройте IDE и создайте проект. Выберите Gradle и свой Java JDK. После создания вы увидите структуру проекта слева (если она справа, то вы попали в точку):

A visual showing a project structure
Визуальное представление структуры проекта

Следующий шаг — открыть файл build.gradle и добавить все зависимости, которые будут использоваться. Зависимости можно найти, погуглив репозиторий Maven. На скриншоте мои зависимости:

dependencies {
   testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
   testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
   testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
   testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
   implementation group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '4.1.2'
   implementation group: 'org.seleniumhq.selenium', name: 'selenium-server', version: '3.141.59'
   implementation group: 'org.seleniumhq.selenium', name: 'selenium-support', version: '4.1.2'
   implementation group: 'org.seleniumhq.selenium', name: 'selenium-api', version: '4.1.2'
   implementation group: 'org.seleniumhq.selenium', name: 'selenium-chrome-driver', version: '4.1.2'
   implementation group: 'org.seleniumhq.selenium', name: 'selenium-firefox-driver', version: '4.1.2'
   testImplementation group: 'io.cucumber', name: 'cucumber-picocontainer', version: '7.2.3'
   testImplementation group: 'info.cukes', name: 'cucumber-junit', version: '1.2.6', ext: 'pom'
   implementation group: 'io.cucumber', name: 'cucumber-java', version: '7.2.3'
   implementation 'io.github.bonigarcia:webdrivermanager:5.1.1'
   testImplementation group: 'junit', name: 'junit', version: '4.13.2'
   implementation group: 'info.cukes', name: 'cucumber-jvm', version: '1.2.6', ext: 'pom'
   testImplementation 'com.codeborne:phantomjsdriver:1.5.0'
   implementation group: 'info.cukes', name: 'cucumber-core', version: '1.2.6'
   implementation group: 'info.cukes', name: 'cucumber-junit', version: '1.2.6'
}

После добавления зависимостей открываем терминал и собираем проект с помощью команды gradle:

./gradlew build

Отлично. Теперь, когда мы создали ядро проекта, настало время для реальных действий. Следуя Cucumber и POM, давайте создадим два пакета в src/test/java: PageObjects и StepMethods.

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

A visual showing hierarchy in the project structure
Визуальное отображение иерархии в структуре проекта

Теперь создадим тестовый сценарий с помощью Gherkin. Это можно сделать несколькими способами, вы можете попробовать свой, а я покажу свой:

Feature: Changing data on Stack Overflow profile
 Scenario Outline: Check if display name change works correctly
   Given I'm on Stack Overflow web page
   And I click login button
   And I login using my "<email>" and "<password>"
   And I open my profile
   And I click edit profile
   When I change my display name to "<display_name>"
   And I save changes
   Then Changes should be saved successfully

   Examples:
   | email                  | password           | display_name |
   | notrealemail@gmail.com | forsecurityreasons | Funky Monkey |

Это мой feature-файл для упомянутой автоматизации тестирования. Я говорил, что это будет немного более продвинутый вариант, поэтому я добавил таблицу примеров. По сути, таблица примеров принимает значения, определенные в шагах. Если мы добавим больше строк в таблицу примеров, наш тест будет выполняться несколько раз. Например, при наличии трех строк тест будет выполняться три раза, пока не будут использованы все заданные значения в таблице примеров. Здорово, да?

Но Иосип, почему мои шаги выделены? Не волнуйтесь, это потому, что эти шаги все еще не имеют определения. Если навести курсор на выделенные шаги и нажать кнопку Другие действия -> Создать все определения шагов (more actions -> create all step definitions), мы дадим им определение. Давайте сделаем это и назовем наш класс ChangeDisplayNameSteps.java. Также поместите этот класс в пакет StepMethods, если вы этого еще не сделали.

Теперь мы видим, как все собрано вместе — очень круто, на мой взгляд. В зависимости от вашей IDE и того, как настроен ваш локальный проект, импорт должен быть добавлен автоматически. Вот скриншот моего класса ChangeDisplayNameSteps.java:

package StepMethods;

import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;

public class ChangeDisplayNameSteps {
  
   @Given("I'm on Stack Overflow web page")
   public void iMOnStackOverflowWebPage() {
   }

   @And("I click login button")
   public void iClickLoginButton() {
   }

   @And("I login using my {string} and {string}")
   public void iLoginUsingMyAnd(String arg0, String arg1) {
   }

   @And("I open my profile")
   public void iOpenMyProfile() {
   }

   @And("I click edit profile")
   public void iClickEditProfile() {
   }

   @When("I change my display name to {string}")
   public void iChangeMyDisplayNameTo(String arg0) {
   }

   @And("I save changes")
   public void iSaveChanges() {
   }

   @Then("Changes should be saved successfully")
   public void changesShouldBeSavedSuccessfully() {
   }
}

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

@When("I change my display name to {string}")
public void iChangeMyDisplayNameTo(String display_name) {
}

Отлично, теперь давайте наконец-то начнем использовать наши навыки написания кода на Java. Автоматизация невозможна без Selenium WebDriver, поэтому нужно его создать. Лучше всего создать класс, который будет возвращать наш драйвер, чтобы его можно было использовать глобально в нашем проекте. Я начну с создания нового класса Java в StepMethods под названием Driver.

package StepMethods;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.time.Duration;

public class Driver {

   public void createChromeDriver(){
       System.setProperty("webdriver.chrome.driver", "StackDemo/chromedriver");
   }
   public static WebDriver driver = new ChromeDriver();
 public static WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));
}

Как мы видим, наш класс драйвера теперь создан. Полагаю, он не требует пояснений. Простой класс, из которого мы будем использовать наш драйвер и экземпляр wait. Wait создается с экземпляром драйвера и максимальным количеством секунд, в течение которых драйвер будет ожидать элемент. Учитывая, что мы будем использовать Google Chrome, мы указываем драйверу начать с ChromeDriver и задаем ему путь.

Теперь создадим объект Landing Page. Как только мы откроем сайт Stack Overflow, мы окажемся на странице Landing Page. Давайте заполним LandingPO элементами, которые мы собираемся использовать, но при этом воспользуемся возможностями метода FindBy от Selenium. Посмотрите:

public class LandingPO  {

   public LandingPO (){
       PageFactory.initElements(driver, this);
   }

   private String url = "https://stackoverflow.com/";

   @FindBy (xpath = "/html/body/header/div/ol[2]/li[3]/a")
   public WebElement loginBtn;

Во-первых, мы начнем с инициализации PageFactory для нашего LandingPO. Без PageFactory метод FindBy и ориентация POM не будут работать.

Как видим, URL сохраняется как приватная строка. Кнопка входа сохранена как публичный WebElement с помощью метода FindBy. Теперь можно использовать URL и кнопку входа в систему для создания методов, которые мы будем вызывать на шаге определения. Я использовал имя класса для поиска кнопки входа в систему, которое в данном случае было очень длинным и некрасивым, но это реальная ситуация, и иногда так бывает. Можно было бы также использовать xpath или другие локаторы, но в данной ситуации имя класса было наиболее стабильным. Конечно, если имя класса этого элемента изменится, наш тест завершится неудачей. Чтобы решить эту проблему, мы можем добавить data-testid к каждому элементу, который мы используем в автоматизации. Наличие атрибута data-testid у каждого элемента, который мы используем, сделает тесты устойчивыми к изменениям. Однако, поскольку Stack — это публичный сайт, не получится сделать это сейчас.

Давайте создадим Java-методы для вызова URL и нажатия кнопки входа.

public void openStackOverflowUrl(){
   driver.get(url);
   driver.manage().window().maximize();
}

public void clickLoginBtn(){
   wait.until(ExpectedConditions.visibilityOf(loginBtn)).click();
}

Этот код гораздо проще для чтения и написания по сравнению с тем, который был в начале статьи. По сути, мы открываем URL, ждем, пока появится кнопка входа, и, как только она появится, нажимаем ее. Удобно, правда?

Давайте соберем все это вместе. У нас есть методы, определения шагов и сами шаги. Сейчас определения шагов все еще пусты, поэтому давайте изменим это, вызвав методы, которые мы только что создали.

public class ChangeDisplayNameSteps {
   LandingPO landingPO;

   @Given("I'm on Stack Overflow web page")
   public void iMOnStackOverflowWebPage() {
       landingPO = new LandingPO();
       landingPO.openStackOverflowUrl();
   }

   @And("I click login button")
   public void iClickLoginButton() {
       landingPO.clickLoginBtn();
   }

Как видно из приведенного выше кода, за первым и вторым шагами стоят функциональные возможности. Функциональность, которую мы создали. Зная все это, мы создаем java-класс LoginPO, в котором будем хранить все элементы страницы входа.

public class LoginPO extends Driver {

   public LoginPO(){
       PageFactory.initElements(driver, this);
   }

   @FindBy (id="email")
   public WebElement emailInputFld;

   @FindBy (id="password")
   public WebElement passwordInputFld;

   @FindBy (id= "submit-button")
   public WebElement loginBtn;

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

public void enterEmail(String argEmail) {
   wait.until(ExpectedConditions.visibilityOf(emailInputFld)).sendKeys(argEmail);
}

public void enterPassword(String argPassword) {
   wait.until(ExpectedConditions.elementToBeClickable(passwordInputFld)).click();
   passwordInputFld.sendKeys(argPassword);
}

public void clickLoginBtn(){
   wait.until(ExpectedConditions.elementToBeClickable(loginBtn)).click();
}

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

@And("I login using my {string} and {string}")
public void iLoginUsingMyAnd(String email, String password) {
   loginPO = new LoginPO();
   loginPO.enterEmail(email);
   loginPO.enterPassword(password);
   loginPO.clickLoginBtn();
}

При этом мы берем значения из таблицы примеров, и у нас появляется множество возможностей. Не стоит забывать, что тесты будут выполняться столько раз, сколько строк в таблице примеров. Как по мне, это очень удобно для тестирования.

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

Вот как будет выглядеть проект в итоге:

LandingPO:

package PageObjects;

import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import static StepMethods.Driver.driver;
import static StepMethods.Driver.wait;

public class LandingPO  {

   public LandingPO (){
       PageFactory.initElements(driver, this);
   }

   private String url = "https://stackoverflow.com/";

   @FindBy (xpath = "/html/body/header/div/ol[2]/li[3]/a")
   public WebElement loginBtn;

   @FindBy (xpath = "/html/body/header/div/ol[2]/li[2]/a/div[1]/img")
   public WebElement myProfileBtn;

   public void openStackOverflowUrl(){
       driver.get(url);
       driver.manage().window().maximize();
   }

   public void clickLoginBtn(){
       wait.until(ExpectedConditions.visibilityOf(loginBtn)).click();
   }

   public void openMyProfile(){
       wait.until(ExpectedConditions.visibilityOf(myProfileBtn)).click();
   }
}

LoginPO:

package PageObjects;

import StepMethods.Driver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;

public class LoginPO extends Driver {

   public LoginPO(){
       PageFactory.initElements(driver, this);
   }

   @FindBy (id="email")
   public WebElement emailInputFld;

   @FindBy (id="password")
   public WebElement passwordInputFld;

   @FindBy (id= "submit-button")
   public WebElement loginBtn;

   public void enterEmail(String argEmail) {
       wait.until(ExpectedConditions.visibilityOf(emailInputFld)).sendKeys(argEmail);
   }

   public void enterPassword(String argPassword) {
       wait.until(ExpectedConditions.elementToBeClickable(passwordInputFld)).click();
       passwordInputFld.sendKeys(argPassword);
   }

   public void clickLoginBtn(){
       wait.until(ExpectedConditions.elementToBeClickable(loginBtn)).click();
   }

}

Сейчас я вкратце расскажу о методе enterPassword, который я создал здесь. Иногда элементы не реагируют на нажатия. Если отправка ключей у вас тоже не работает, сначала попробуйте щелкнуть элемент, а затем отправить ключи.

ProfilePO:

package PageObjects;

import StepMethods.Driver;
import org.junit.jupiter.api.Assertions;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;

public class ProfilePO extends Driver {

   public ProfilePO (){
       PageFactory.initElements(driver, this);
   }

   @FindBy(xpath = "//*[@id=\"mainbar-full\"]/div[1]/div[2]/a")
   public WebElement editProfileBtn;

   @FindBy (id = "displayName")
   public WebElement displayNameFld;

   @FindBy (xpath = "//*[@id=\"form-submit\"]/button")
   public WebElement saveChangesBtn;

   @FindBy (xpath = "//*[@id=\"mainbar-full\"]/div[1]/div[1]/div/div/div[1]")
   public WebElement displayNameElement;

   public void editMyProfile(){
       wait.until(ExpectedConditions.visibilityOf(editProfileBtn)).click();
   }

   public void changeDisplayName(String argDisplay_name){
       wait.until(ExpectedConditions.visibilityOf(displayNameFld));
       displayNameFld.clear();
       displayNameFld.sendKeys(argDisplay_name);
   }

   public void saveChanges(){
       wait.until(ExpectedConditions.elementToBeClickable(saveChangesBtn)).click();
   }

   public void checkNameChangeSuccess(){
      String actualDisplayName = wait.until(ExpectedConditions.visibilityOf(displayNameElement)).getText();
      String expectedDisplayName= "Funky Monkey";
       Assertions.assertEquals(expectedDisplayName, actualDisplayName);
       driver.quit();
   }
}

ProfilePO немного сложнее. Мы видим, что в этот класс вложено больше кода.

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

В заключительной части мы проверяем, успешно ли прошел наш тест. 

Основная функциональность, которую проверяла наша автоматизация, — работает ли изменение отображаемого имени. Соответственно, после сохранения изменений мы возьмем фактическое и ожидаемое отображаемое имя и сравним их. Как видите, с помощью метода FindBy я нашел элемент, в котором находится мое отображаемое имя, и сохранил его как displayNameElement. Затем я поместил этот же элемент display name в строку с помощью getText(). Это делается потому, что нам нужно сравнить две строки (в нашем случае реальное и ожидаемое имя), чтобы убедиться, что наш тест пройден. При сохранении элемента с помощью метода FindBy, как и сказано в его названии, отображаемое имя сохраняется как веб-элемент, а не как строка. Кроме того, я создал строку, содержащую наше ожидаемое отображаемое имя. Наконец, с помощью Assertions эти две строки сравниваются. Assertions — это функция из junit, которая сравнивает две строки (может быть int, float, byte, long и так далее). Если наши две строки абсолютно одинаковы, assertions возвращают true и тест (шаг) считается пройденным. В противном случае возвращается false, и тест завершается ошибкой.

Класс Steps:

package StepMethods;

import PageObjects.LandingPO;
import PageObjects.LoginPO;
import PageObjects.ProfilePO;
import io.cucumber.java.en.And;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;

public class ChangeDisplayNameSteps {
   LandingPO landingPO;
   LoginPO loginPO;
   ProfilePO profilePO;

   @Given("I'm on Stack Overflow web page")
   public void iMOnStackOverflowWebPage() {
       landingPO = new LandingPO();
       landingPO.openStackOverflowUrl();
   }

   @And("I click login button")
   public void iClickLoginButton() {
       landingPO.clickLoginBtn();
   }

   @And("I login using my {string} and {string}")
   public void iLoginUsingMyAnd(String email, String password) {
       loginPO = new LoginPO();
       loginPO.enterEmail(email);
       loginPO.enterPassword(password);
       loginPO.clickLoginBtn();
   }

   @And("I open my profile")
   public void iOpenMyProfile() {
       landingPO.openMyProfile();
   }

   @And("I click edit profile")
   public void iClickEditProfile() {
       profilePO = new ProfilePO();
       profilePO.editMyProfile();
   }

   @When("I change my display name to {string}")
   public void iChangeMyDisplayNameTo(String display_name) {
       profilePO.changeDisplayName(display_name);
   }

   @And("I save changes")
   public void iSaveChanges() {
       profilePO.saveChanges();
   }

   @Then("Changes should be saved successfully")
   public void changesShouldBeSavedSuccessfully() {
       landingPO.openMyProfile();
       profilePO.checkNameChangeSuccess();
   }
}

Мой класс шагов довольно понятен, поэтому нет необходимости вдаваться в подробности.

build.gradle:

configurations {
   cucumberRuntime {
       extendsFrom testImplementation
   }
}

task runTestsChrome () {
   dependsOn assemble, testClasses
   doLast {
       javaexec {
           main = "io.cucumber.core.cli.Main"
           classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
           args = [
                   '--plugin', 'pretty',
                   '--plugin', 'html:target/cucumber-report.html',
                   '--glue', 'StepMethods',
                   'src/test/resources']
       }
   }
}

В файле build.gradle есть одна конфигурация и одна задача. Чтобы запустить feature-файл через gradle, нам нужно создать задачу. Во-первых, мы добавляем среду выполнения cucumber в нашу конфигурацию gradle. Без него тесты не будут запускаться через gradle. Во-вторых, мы создаем задачу, которая, будучи запущенной, указывает gradle, что именно нужно сделать. Как вы видите, мы указываем gradle использовать Javaexec для запуска наших feature-файлов как Java-приложения с заданным classpath. Теперь можно просто запустить тест, открыв командную строку и выполнив команду

./gradlew runTestsChrome

Заключение

В этой статье мы разобрали простой и быстрый пример использования Cucumber BDD с Selenium и Java. 

Автоматизация тестирования — действительно интересная область в сфере разработки программного обеспечения. Нахождение багов (особенно трудно воспроизводимых) повышает качество приложений. Если автоматизированные тесты настроены правильно, они могут выполняться ежедневно или даже ежечасно без участия тестировщика. В этой статье мы только прошлись по верхам, а возможности автоматизации тестирования безграничны. Например, можно использовать любой инструмент непрерывной интеграции, чтобы тесты запускались по любому событию или устанавливались по любому расписанию. Конечно, для того чтобы добиться самоподдерживающихся и устойчивых к изменениям кода тестов, необходимо проделать большую работу. Стоит упомянуть и тестирование API. Оно играет важную роль в создании автоматизированных тестов, не требующих особого обслуживания.

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

Практическим навыкам и новейшим инструментам для ручного и автоматизированного тестированиям можно научиться у экспертов в IT на онлайн-курсах OTUS. В рамках курсов преподаватели ежедневно проводят открытые уроки на актуальные темы — приглашаем всех желающих QA-инженеров на ближайшие из них:

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


  1. Pardus_cx
    07.12.2023 06:18

    Спасибо за статью.

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

    Но пример приведён плохой. Проверка с хардкодом ожидаемого значения на уровне экшенов! Плохо....

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

    Использовать его в автоматизации постоянно (для регрессии) приведёт к костылям и самоограничению достаточно скоро.

    Кукумбер хорошо использовать для тестов в которых нет проверок с сохранением состояния. Плюс кукумбер становится полезным в случае декларативных степов. Если вы используете императивное написание - будет большая морока...