Добрый день, коллеги. Один из самых известных и популярных фреймворков для написания автотестов - это Selenium. У этого фреймворка множество плюсов и возможностей, но в то же время есть некоторые неудобства в конфигурации, настройке и написании тестов. Поэтому появились фреймворки, которые расширяют Selenium.

Один из таких фреймворков - Geb Framework, он написан на Groovy и использует возможности groovy DSL на полную мощность.

Прежде чем приступать к изучению Geb и работе с ним, необходимо освежить в памяти темы, связанные с Groovy:

  • сравнение замыканий Groovy и лямбд Java;

  • использование возможностей метаклассов;

  • инструмент сборки Gradle;

  • фреймворк юнит-тестирования Spock;

В качестве фреймворка запуска и создания тестов в основе Geb лежит Spock.

Spock отличается от JUnit и предоставляет множество удобностей и “плюшек”.:

С Geb можно ознакомиться на официальном сайте: https://www.gebish.org/manual/current/#introduction

Geb основан на Selenium Web Driver.

Как с ним работать:

Выстраивается цепочка из тестовых классов:

*SpockSpec -> Browser.drive -> *Page -> *Module

SpockSpec - классы тестов, описанные с помощью фреймворка Spock.

Page и Module - доменные классы, могут взаимодействовать с Navigator.

Полезные ссылки:

Основной паттерн, который используется при работе с Selenium и также с Geb - это Page Object: https://github.com/SeleniumHQ/selenium/wiki/PageObjects.

В пользовательском интерфейсе вашего веб-приложения есть области, с которыми взаимодействуют ваши тесты. Объект страницы (Page Object) просто моделирует их как объекты в тестовом коде. Это уменьшает количество дублированного кода и означает, что при изменении пользовательского интерфейса исправление нужно применять только в одном месте.

Примеры выбора данных:

Прежде чем перейти к реализации паттерна Page Object, рассмотрим, как можно получать данные с веб-страницы.

Для выборки данных используются селекторы (привет, jquery).

$(«css selector», «index or range», «attribute / text matchers»)

$("div") 

Сопоставляет все "div" элементы на странице

$("div", 0) 

Сопоставляет первый  "div" элемент на странице

$("div", title: "section") 

Сопоставляет все "div"  на странице со значением атрибута tittle = "section".

$("div", 0, title: "section") 

Сопоставляет первый "div" элемент со значением атрибута title  "section".

$("div.main") 

Сопоставляет все "div" элементы, которые имеют класс  "main".

$("div.main", 0)

Сопоставляет первый "div"  с классом "main".

Также можно использовать By из WebDriver:

$(By.id("some-id"))
$(By.className("some-class"))
$(By.xpath('//p[@class="xpath"]'))

Существует два способа выборки данных:

  1. Inline (с помощью селекторов прямо в коде тестов)

  2. С использованием Page Object

Рассмотрим примеры:

Для начала посмотрим как делать выборку данных с помощью Inline:

import geb.Browser

   Browser.drive {
        go "http://gebish.org"

        assert title == "Geb - Very Groovy Browser Automation"

        $("div.menu a.manuals").click()
        waitFor { !$("#manuals-menu").hasClass("animating") }

        $("#manuals-menu a")[0].click()

        assert title.startsWith("The Book Of Geb")
    }

Более правильным будет воспользоваться паттерном Page Object:

import geb.Module
import geb.Page

class ManualsMenuModule extends Module {
    static content = {
        toggle { $("div.menu a.manuals") }
        linksContainer { $("#manuals-menu") }
        links { linksContainer.find("a") }
    }

    void open() {
        toggle.click()
        waitFor { !linksContainer.hasClass("animating") }
    }
}
import geb.Page
import modules.ManualsMenuModule

class GebHomePage extends Page {
    static url = "http://gebish.org"

    static at = { title == "Geb - Very Groovy Browser Automation" }

    static content = {
        manualsMenu { module(ManualsMenuModule) }
    }
}
class TheBookOfGebPage extends Page {
    static at = { title.startsWith("The Book Of Geb") }
}

Далее можно запустить тест с использованием Page Object-ов.

import geb.Browser

Browser.drive {
    to GebHomePage

    manualsMenu.open()

    manualsMenu.links[0].click()

    at TheBookOfGebPage
}

Как сделать тесты удобнее для проектирования и изучения? Интегрировать с Spock!

import geb.spock.GebSpec

class GebHomepageSpec extends GebSpec {

    def "can access The Book of Geb via homepage"() {
        given:
        to GebHomePage

        when:
        manualsMenu.open()
        manualsMenu.links[0].click()

        then:
        at TheBookOfGebPage
    }

}

Рассмотрим код более подробно.

Самая малая часть - селекторы и базовые классы фреймворка.

Browser: 

Browser.drive {
    go "signup"
    assert $("h1").text() == "Signup Page"
}

baseUrl - задается в конфигурации при создании объекта Browser.

Пример использования:

Browser.drive {
    go() 

    go "signup" 

    go "signup", param1: "value1", param2: "value2" 
}

Что происходит в данном фрагменте кода?

  • Переход на  base URL.

  • Переход на  относительный URL от  base URL

  • Переход на  URL с нужными параметрами: http://localhost/signup?param1=value1&param2=value2

Можно работать с разными окнами браузера:

Browser.drive {
    go()
    $("a").click()
    withWindow({ title == "Geb - Very Groovy Browser Automation" }) {
        assert $(".slogan").text().startsWith("Very Groovy browser automation.")
    }
}

Выборка данных (класс Navigator):

Рассмотрим имеющиеся в классе Navigator методы:

Case Sensitive

Case Insensitive

Description

startsWith

iStartsWith

Сопоставляет значения, которые начинаются с заданного значения

contains

iContains

Сопоставляет значения, которые содержат заданное значение в любом месте

endsWith

iEndsWith

Сопоставляет значения, которые заканчиваются на заданное значение

containsWord

iContainsWord

Сопоставляет значения, которые содержат заданное значение, окруженное либо пробелом, либо началом или концом значения.

notStartsWith

iNotStartsWith

Сопоставляет значения, которые НЕ начинаются с заданного значения

notContains

iNotContains

Сопоставляет значения, которые нигде НЕ содержат данное значение

notEndsWith

iNotEndsWith

Сопоставляет значения, которые НЕ заканчиваются заданным значением

notContainsWord

iNotContainsWord

Сопоставляет значения, которые НЕ содержат заданное значение, окруженное либо пробелом, либо началом или концом значения.

Можно комбинировать несколько Matcher:

  • allOf - работает, если все matcher-ы сработают

  • anyOf - работает, если хотя бы один matcher сработает

Можно отслеживать события:

Можно зарегистрировать слушатель, который будет получать уведомления каждый раз, когда происходят определенные события Navigator. Лучшим справочником по событиям, которые можно прослушать, является документация NavigatorEventListener интерфейс.Если вы хотите слушать только подмножество событий навигатора, тогда NavigatorEventListenerSupport может пригодиться, поскольку он поставляется со стандартными пустыми реализациями всех методов  NavigatorEventListener.

Пример:

import geb.Browser
import geb.navigator.Navigator
import geb.navigator.event.NavigatorEventListenerSupport

navigatorEventListener = new NavigatorEventListenerSupport() {
    void afterClick(Browser browser, Navigator navigator) {
        println "${navigator*.tag()} was clicked"
    }
}

Можно объединять несколько селекторов:

assert $($("p.a"), $("p.b"))*.text() == ["1", "2"]

или с помощью метода add():

assert $("p.a").add("p.b").add(By.className("c"))*.text() == ["1", "2", "3"]

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

Взаимодействие с элементами страницы

Нажатие на элемент (click()):

У класса Page есть несколько методов click():

click(Class) , click(Page) and click(List)

Пример:

$("a.login").click(LoginPage)

Фокусировка (focused()):

$(name: "description").click()

assert focused().attr("name") == "description"
assert $(name: "description").focused

Ввод текста (<<):

$("input") << "foo"
assert $("input").value() == "foo"

или нетекстовых данных:

import org.openqa.selenium.Keys

$("input") << Keys.chord(Keys.CONTROL, "c")

Выбор значений в списках:

$("form").artist = "1" 

Первая опция, выбранная по значению  ее атрибута.

$("form").artist = 2 

Второй вариант выбирается по его атрибуту value с приведением типа аргумента.

$("form").artist = "Alexander"

Третий вариант выбран по его тексту.

Для списков со множественным выбором:

$("form").genres = ["2", "3"] 

Второй и третий варианты выбираются по их атрибутам значения.

$("form").genres = [1, 4, 5] 

Первая, четвертая и пятая опции выбираются по их атрибутам значения с приведением аргумента.

$("form").genres = ["Alt folk", "Hair metal"] 

Первый и последний варианты выбираются по их тексту.

$("form").genres = []

Все опции на выбор.

Для чекбоксов:

$("form").pet = true

Множественный выбор:

$("form").pet = ["dog", "lizard"]
$("form").pet = ["Canis familiaris", "Lacerta"]

Загрузка файлов:

Переменная uploadedFile содержит экземпляр File, указывающий на файл, который вы хотите загрузить. Вот так вы можете загрузить файл:

$("form").csvFile = uploadedFile.absolutePath

Сложные взаимодействия (секция interact()):

Примеры:

interact {
    keyDown Keys.SHIFT
    click $("li.clicky")
    keyUp Keys.SHIFT
}

interact {
    clickAndHold($('#draggable'))
    moveByOffset(150, 200)
    release()
}

или

interact {
    dragAndDropBy($("#draggable"), 150, 200)
}

Content:

у каждого объекта Page можно настроить секцию Content - описание контента страницы.

Структура Content: «name» { «definition» }

Пример:

<div id="a">a</div>
class PageWithDiv extends Page {
    static content = {
        theDiv { $('div', id: 'a') }
    }
}

Как использовать?

  1. обращаться к свойствам объекта page

  2. напрямую к полям

Пример:

Browser.drive {
    to PageWithDiv

    // Following two lines are equivalent
    assert theDiv.text() == "a"
    assert theDiv().text() == "a"
}

Browser.drive {
    to PageWithDiv
    assert page.theDiv.text() == "a"
}

Шаблоны:

«name»(«options map») { «definition» }

Пример:

theDiv(cache: false, required: false) { $("div", id: "a") }

Какие есть опции?

  • required

  • max, min

  • cache

  • to - option allows the definition of which page the browser will be sent to if the content is clicked

  • wait - ожидание определенного времени, пока не произойдет какое-то событие (в целом аналог waitFor).

  • page - для фреймов

Примеры:

static content = {
    loginButton(to: [LoginSuccessfulPage, LoginFailedPage]) { $("input.loginButton") }
}

class DynamicPageWithWaiting extends Page {
    static content = {
        dynamicallyAdded(wait: true) { $("p.dynamic") }
    }
}
class PageWithFrame extends Page {
    static content = {
        myFrame(page: FrameDescribingPage) { $('#frame-id') }
    }
}

class FrameDescribingPage extends Page {
    static content = {
        frameContentsText { $('span').text() }
    }

Модули

Нужны для описания компонентов, которые могут переиспользоваться на разных страницах Page.

Пример:

class ParameterizedModule extends Module {
    static content = {
        button {
            $("form", id: formId).find("input", type: "button")
        }
    }
    String formId
}

Использование экземпляра модуля:

class ParameterizedModulePage extends Page {
    static content = {
        form { id -> module(new ParameterizedModule(formId: id)) }
    }
}
Browser.drive {
    to ParameterizedModulePage
    form("personal-data").button.click()
}

Таким образом, комбинируя различные Page (страницы), Content-ы и модули, вы можете писать очень гибко настраиваемые автотесты. Наибольшую гибкость и удобство использования обеспечивает использование языка groovy при написании тестов и внутри фреймворка Geb.

Статья подготовлена в преддверии старта курса Groovy Developer. Также приглашаем всех на бесплатный урок курса, где посмотрим на пайплайны в Jenkins: из каких шагов и блоков состоят, научимся писать groovy скрипты для создания пайплайнов, изучим их составные части.

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