«1. Обзор

В этой статье мы кратко рассмотрим шаблон сценария в Serenity BDD. Мы предлагаем вам сначала прочитать основы Serenity BDD, прежде чем читать это. Также может быть интересна статья об интеграции Serenity BDD со Spring.

Сценарий, представленный в Serenity BDD, направлен на поощрение хороших привычек тестирования и хорошо разработанных наборов тестов, позволяя командам писать более надежные и надежные тесты. Он основан на Selenium WebDriver и модели Page Objects. Если вы читали наше введение в Selenium, то эти концепции вам покажутся довольно знакомыми.

2. Зависимость от Maven

Сначала добавим следующие зависимости в файл pom.xml:

<dependency>
    <groupId>net.serenity-bdd</groupId>
    <artifactId>serenity-junit</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>net.serenity-bdd</groupId>
    <artifactId>serenity-screenplay</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>net.serenity-bdd</groupId>
    <artifactId>serenity-screenplay-webdriver</artifactId>
    <version>1.4.0</version>
</dependency>

Последние версии serenity-screenplay и serenity-screenplay-webdriver можно загрузить с Maven Central. Репозиторий.

Нам также нужны веб-драйверы для выполнения сценариев — подойдет либо ChromeDriver, либо Mozilla-GeckoDriver. В этой статье мы будем использовать ChromeDriver.

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

<plugin>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.20</version>
    <configuration>
        <systemProperties>
            <webdriver.chrome.driver>chromedriver</webdriver.chrome.driver>
        </systemProperties>
    </configuration>
</plugin>

3. Поддержка WebDriver

Мы можем заставить Serenity управлять экземпляром WebDriver, пометив аннотацию @Managed в переменной WebDriver. Serenity открывает соответствующий драйвер в начале каждого теста и выключает его по завершении теста.

В следующем примере мы инициируем ChromeDriver и открываем Google для поиска «baeldung». Мы ожидаем, что имя Евгения будет присутствовать в результатах поиска:

@RunWith(SerenityRunner.class)
public class GoogleSearchLiveTest {

    @Managed(driver = "chrome") 
    private WebDriver browser;

    @Test
    public void whenGoogleBaeldungThenShouldSeeEugen() {
        browser.get("https://www.google.com/ncr");

        browser
          .findElement(By.name("q"))
          .sendKeys("baeldung", Keys.ENTER);

        new WebDriverWait(browser, 5)https://www.baeldung.com/serenity-screenplay
          .until(visibilityOfElementLocated(By.cssSelector("._ksh")));

        assertThat(browser
          .findElement(By.cssSelector("._ksh"))
          .getText(), containsString("Eugen (Baeldung)"));
    }
}

Если мы не укажем никаких параметров для @Managed, в этом случае Serenity BDD будет использовать Firefox. Весь список поддерживаемых драйверов по аннотации @Managed: firefox, chrome, iexplorer, htmlunit, phantomjs.

Если нам нужно протестировать в IExplorer или Edge, мы можем загрузить веб-драйверы отсюда (для IE) и отсюда (для Edge) соответственно. Safari WebDriver доступен только в MacOS в папке /usr/bin/safaridriver.

4. Объекты страницы

Объекты страницы Serenity представляют собой объект страницы WebDriver. PageObject скрывает детали WebDriver для повторного использования.

4.1. Пример рефакторинга с использованием PageObject

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

@DefaultUrl("https://www.google.com/ncr")
public class GoogleSearchPageObject extends PageObject {

    @FindBy(name = "q") 
    private WebElement search;

    @FindBy(css = "._ksh") 
    private WebElement result;

    public void searchFor(String keyword) {
        search.sendKeys(keyword, Keys.ENTER);
    }

    public void resultMatches(String expected) {
        assertThat(result.getText(), containsString(expected));
    }
}

WebElement представляет элемент HTML. Мы можем взаимодействовать с веб-страницами через API интерфейса. В приведенном выше примере мы использовали два способа поиска веб-элементов на странице: по имени элемента и по классам CSS элемента.

Существует больше подходов к применению при поиске веб-элементов, таких как поиск по имени тега, поиск по тексту ссылки и т. д. Подробную информацию см. в нашем руководстве по Selenium.

Мы также можем заменить WebElement на WebElementFacade, который предоставляет более удобные API для работы с веб-элементами.

Поскольку Serenity будет автоматически создавать экземпляры любых полей PageObject в тесте JUnit, предыдущий тест можно переписать в более чистом виде:

@RunWith(SerenityRunner.class)
public class GoogleSearchPageObjectLiveTest {

    @Managed(driver = "chrome") 
    private WebDriver browser;

    GoogleSearchPageObject googleSearch;

    @Test
    public void whenGoogleBaeldungThenShouldSeeEugen() {
        googleSearch.open();

        googleSearch.searchFor("baeldung");

        googleSearch.resultMatches("Eugen (Baeldung)");
    }
}

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

4.2. Асинхронная поддержка

В настоящее время многие веб-страницы обслуживаются или отображаются динамически. Чтобы иметь дело с такими случаями, PageObject также поддерживает множество богатых функций, которые позволяют нам проверять статусы элементов. Мы можем проверить, видны ли элементы, или подождать, пока они не станут видимыми, прежде чем продолжить.

Давайте усовершенствуем метод resultMatches, обеспечив видимость элемента, который мы хотим видеть:

public void resultMatches(String expected) {
    waitFor(result).waitUntilVisible();
    assertThat(result.getText(), containsString(expected));
}

Если мы не ожидаем слишком долгого ожидания, мы можем явно указать время ожидания для ожидающих действий: ~~ ~

public void resultMatches(String expected) {
    withTimeoutOf(5, SECONDS)
      .waitFor(result)
      .waitUntilVisible();
    assertThat(result.getText(), containsString(expected));
}

5. Шаблон сценария

Шаблон сценария применяет принципы проектирования SOLID к автоматизированному приемочному тестированию. Общее понимание паттерна сценария можно объяснить в контексте заданного_когда_тогда как:

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

«Теперь давайте поместим наш предыдущий тестовый сценарий в шаблон сценария: если у пользователя Китти, который может использовать Google, когда она ищет «baeldung» в Google, то Китти должна увидеть имя Евгения в результатах.

Сначала определите задачи, которые может выполнять Китти.

  1. Kitty can use Google:
    public class StartWith implements Task {
    
        public static StartWith googleSearchPage() {
            return instrumented(StartWith.class);
        }
    
        GoogleSearchPage googleSearchPage;
    
        @Step("{0} starts a google search")
        public <T extends Actor> void performAs(T t) {
            t.attemptsTo(Open
              .browserOn()
              .the(googleSearchPage));
        }
    }
  2. Kitty can make a search on Google:
    public class SearchForKeyword implements Task {
    
        @Step("{0} searches for '#keyword'")
        public <T extends Actor> void performAs(T actor) {
            actor.attemptsTo(Enter
              .theValue(keyword)
              .into(GoogleSearchPage.SEARCH_INPUT_BOX)
              .thenHit(Keys.RETURN));
        }
    
        private String keyword;
    
        public SearchForKeyword(String keyword) {
            this.keyword = keyword;
        }
    
        public static Task of(String keyword) {
            return Instrumented
              .instanceOf(SearchForKeyword.class)
              .withProperties(keyword);
        }
    }
  3. Kitty can see Google search results:
    public class GoogleSearchResults implements Question<List<String>> {
    
        public static Question<List<String>> displayed() {
            return new GoogleSearchResults();
        }
    
        public List<String> answeredBy(Actor actor) {
            return Text
              .of(GoogleSearchPage.SEARCH_RESULT_TITLES)
              .viewedBy(actor)
              .asList();
        }
    }

Кроме того, мы уже определили PageObject для поиска Google:

@DefaultUrl("https://www.google.com/ncr")
public class GoogleSearchPage extends PageObject {

    public static final Target SEARCH_RESULT_TITLES = Target
      .the("search results")
      .locatedBy("._ksh");

    public static final Target SEARCH_INPUT_BOX = Target
      .the("search input box")
      .locatedBy("#lst-ib");
}

Теперь наш основной тестовый класс будет выглядеть так:

@RunWith(SerenityRunner.class)
public class GoogleSearchScreenplayLiveTest {

    @Managed(driver = "chrome") 
    WebDriver browser;

    Actor kitty = Actor.named("kitty");

    @Before
    public void setup() {
        kitty.can(BrowseTheWeb.with(browser));
    }

    @Test
    public void whenGoogleBaeldungThenShouldSeeEugen() {
        givenThat(kitty).wasAbleTo(StartWith.googleSearchPage());

        when(kitty).attemptsTo(SearchForKeyword.of("baeldung"));

        then(kitty).should(seeThat(GoogleSearchResults.displayed(), 
          hasItem(containsString("Eugen (Baeldung)"))));
    }
}

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

6. Резюме

В этой статье мы рассказали, как использовать шаблон сценария с Serenity BDD. Кроме того, с помощью PageObject нам не нужно напрямую взаимодействовать с WebDrivers, что упрощает чтение, обслуживание и расширение наших тестов.

Дополнительные сведения о PageObject и шаблоне сценария в Serenity BDD см. в соответствующем разделе документации Serenity.

Как всегда, полный код примера можно найти на Github.