Masquerade

Masquerade это библиотека для автоматизации тестирования пользовательского интерфейса в приложениях Jmix. Библиотека помогает использовать паттерн Page Object для всех частей пользовательского интерфейса Jmix, включая экраны, компоненты, диалоги, уведомления и композиты. Библиотека основана на Selenium WebDriver и Selenide.

Masquerade предназначена для приложений, созданных с использованием Jmix 2.6 или новее.

Установка Masquerade

Чтобы установить Masquerade, выполните следующие шаги:

  1. Убедитесь, что в проекте используется Jmix 2.6 или новее. Посетите раздел Апгрейд Проекта, чтобы узнать как обновить проект с помощью Studio.

  2. Укажите зависимость в файле build.gradle как testImplementation:

    build.gradle
    testImplementation 'io.jmix.masquerade:jmix-masquerade'

Использование Selenide

Masquerade основан на Selenide, что позволяет использовать любые его методы. Рассмотрим пример, типичный для автоматизации тестирования, в котором выполняется вход в приложение Jmix:

public class SelenideTest {

    @Test
    void selenideLogin() {
        Selenide.open("/");

        $(byId("vaadinLoginUsername")).shouldHave(value("admin"));
        $(byChained(byId("vaadinLoginUsername"), byTagName("input")))
                .setValue("")
                .setValue("admin");

        $(byId("vaadinLoginPassword")).shouldHave(value("admin"));
        $(byChained(byId("vaadinLoginPassword"), byTagName("input")))
                .setValue("")
                .setValue("admin");

        $(byCssSelector("[slot='submit']")).click();
    }
}

Стандартный метод Selenide.$ принимает селектор в качестве аргумента, например, byId. В результате будет возвращен объект SelenideElement, с которым можно выполнять дальнейшие действия.

Ознакомьтесь с Selenide API, чтобы узнать о других методах.

Создание Теста в Masquerade

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

Шаг 1. Создание Обертки Экрана

Базовый проект Jmix содержит экран логина по умолчанию. Для взаимодействия с этим экраном и его компонентами во время тестирования создадим для него обертку экрана:

  • В каталоге src/test/java, в пакете com.company.testproject, создайте пакет view. Внутри создайте Java класс с именем LoginView:

    TestProject/
    └── src/
        ├── main/
        └── test/
            └── java/
                └── com.company.testproject
                    └── test_support
                        └── view
                           └── LoginView.java
  • Добавьте обертки компонентов и методы get для каждого компонента, необходимого для теста:

    LoginView.java
    @TestView
    public class LoginView extends View<LoginView> {
    
        @FindBy(css = "[slot='submit']")
        private Button button;
    
        @FindBy(id = "vaadinLoginUsername")
        private TextField username;
    
        @FindBy(id = "vaadinLoginPassword")
        private PasswordField password;
    
        public Button getButton() {
            return button;
        }
    
        public TextField getUsernameField() {
            return username;
        }
    
        public PasswordField getPasswordField() {
            return password;
        }
    }

Шаг 2. Создание тестового класса

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

  • В каталоге src/test/java, в пакете com.company.testproject, создайте новый пакет ui-autotest. Внутри создайте Java класс с именем LoginUiTest:

    TestProject/
    └── src/
        ├── main/
        └── test/
            └── java/
                ├── com.company.testproject
                │   └── test_support
                │       └── view
                │          └── LoginView.java
                └────── ui_autotest
                        └── LoginUiTest.java
  • Определите последовательность действий для вашего тестового сценария:

    public class LoginUiTest {
    
        @Test
        public void loginAsAdmin() {
    
            Selenide.open("/"); (1)
    
            LoginView loginView = $j(LoginView.class); (2)
    
            loginView.getUsernameField()
                    .shouldHave(value("admin"))
                    .setValue("")
                    .setValue("admin");
    
            loginView.getPasswordField()
                    .shouldHave(value("admin"))
                    .setValue("")
                    .setValue("admin");
    
            loginView.getButton()
                    .shouldHave(text("Log in"))
                    .click();
        }
    
    }
    1 Переход на страницу логина выполняется с помощью стандартного метода Selenide.
    2 Используйте метод Masquerade.$j, чтобы выбрать класс обертки и вызывать его методы.

Генерация Jmix Test IDs

Masquerade может помочь вам с генерацией специального атрибута j-test-id (Jmix test ID) для каждого компонента, созданного с помощью фабрики UiComponents. Эти идентификаторы упрощают поиск элементов на странице. Чтобы включить эту функцию, установите значение jmix.ui.ui-test-mode в true:

application.properties
jmix.ui.ui-test-mode = true
Генерация идентификаторов может повлиять на производительность. Рекомендуется использовать это свойство только для тестового профиля приложения.

Значение тестовых идентификаторов по умолчанию соответствует значение заданному в аттрибуте id для компонента:

masquerade j test id

Если id компонента не был задан, тестовый идентификатор j-test-id будет сгенерирован на основе привязки данных, атрибута action или text.

Чтобы работать с элементом напрямую по его j-test-id, используйте метод Masquerade.$j:

$j("myButton").click();

Обертки

Обертки это классы, которые представляют различные части пользовательского интерфейса Jmix инкапсулируя взаимодействия с ними. Они помогают отделить тестовые сценарии от деталей взаимодействия с пользовательским интерфейсом. Всего существует пять типов оберток.

Обертка Экрана

Обертка экрана инкапсулирует экран, создавая упрощенный интерфейс для взаимодействия с его компонентами во время теста. Добавлять все компоненты в обертку не обязательно — можно добавить только те, которые необходимы для тестов.

Рассмотрим простой пример обертки экрана:

@TestView(id = "MyView") (1)
public class MyView extends View<MyView> { (2)

    @TestComponent
    private EntityComboBox entityComboBox;

    @TestComponent(path = "myButton")
    private Button button;

    @FindBy(xpath = "//vaadin-text-area[@class='my-text-area']")
    private TextArea textArea;

    public EntityComboBox getEntityComboBox() {
        return entityComboBox;
    }

    public Button getButton() {
        return button;
    }
}
1 Обертка экрана должна содержать аннотацию io.jmix.masquerade.TestView. Значение id в аннотации должно соответствовать идентификатору представления, указанному через @ViewController:
@Route(value = "my-test-view", layout = MainView.class)
@ViewController(id = "MyView")
@ViewDescriptor(path = "my-test-view.xml")
public class MyView extends StandardView {

}

Если вы не укажете id, то в качестве идентификатора по умолчанию будет использован simple class name для этого экрана.

2 Класс обертки наследуется от io.jmix.masquerade.sys.View и передает собственный класс MyView в качестве параметра типа. Это необходимо для предоставления API для написания тестов.

Обертка Компонента

Обертки компонентов инкапсулируют компоненты внутри оберток экрана. Чтобы указать что поле класса является оберткой компонента, используйте аннотации @TestComponent или @FindBy. Это показано в следующем примере:

@TestComponent
private EntityComboBox entityComboBox;

@TestComponent(path = "myButton")
private Button button;

@FindBy(xpath = "//vaadin-text-area[@class='my-text-area']")
private TextArea textArea;
  • @TestComponent указывает, что поле является оберткой компонента. Если значение path не указано, предполагается, что по умолчанию используется значение атрибута j-test-id соответствующего веб-элемента.

  • @FindBy явно задает селектор, который будет использован при идентификации компонента.

Список всех доступных оберток компонентов доступен в пакете io.jmix.masquerade.component.

Обертки компонентов предлагают различные методы для взаимодействия с компонентами. Например, вы можете открыть оверлей или даже диалоговое окно для EntityCombobox:

@Test
public void testEntityComboBox() {
    EntityComboBox entityComboBox = openMyView().getEntityComboBox();

    entityComboBox.shouldHave(label("EntityComboBox"))
            .setValue("[admin]")
            .shouldHave(value("[admin]"))
            .clickItemsOverlay()
            .shouldHave(visibleItems("[admin]", "[test]", "[test1]"))
            .shouldHave(visibleItemsCount(3))
            .shouldHave(visibleItemsContains("[test]"));

    sleep(3000);

    entityComboBox.getItemsOverlay()
            .select("[test]");
    sleep(3000);

    entityComboBox.shouldHave(value("[test]"))
            .triggerActionWithView(UserListDialog.class, HasActions.LOOKUP)
            .selectAdmin();

    sleep(3000);
    entityComboBox.shouldHave(value("[admin]"));
}

Обертки компонентов также предлагают специфические условия проверки состояния компонентов. Вы можете найти список всех доступных условий в пакете io.jmix.masquerade.condition.

Обертка Диалоговых Окон

Обертки для диалоговых окон аналогичны оберткам экрана, но наследуются от класса io.jmix.masquerade.sys.DialogWindow. Этот класс предлагает специфические действия, такие как их закрытие окна и проверка заголовка. Для иллюстрации рассмотрим пример:

@TestView(id = "User.list")
public class UserListDialog extends DialogWindow<UserListDialog> {

    public UserListDialog selectAdmin() {
        $(By.xpath("//*[@id=\"usersDataGrid\"]/vaadin-grid-cell-content[22]"))
                .click();

        $(byChained(getBy(), byUiTestId("selectButton")))
                .shouldBe(VISIBLE)
                .shouldBe(ENABLED)
                .click();
        return this;
    }
}

Обертка Уведомлений

Обертка для уведомления представляет собой специальный тип обертки компонента. Рассмотрим пример работы с уведомлением:

@Test
public void notificationTest() {
    MainView mainView = loginAsAdmin();

    UserListView userListView = mainView.openItem(UserListView.class,
            "applicationListItem", "user.listListItem");

    userListView.showUsername();

    Notification notification = $j(Notification.class);
    notification
            .shouldBe(VISIBLE)
            .shouldHave(notificationPosition(Notification.Position.BOTTOM_END))
            .shouldHave(notificationTheme(Notification.Theme.SUCCESS))
            .shouldHave(notificationTitle("Username:"))
            .should(notificationTitleContains("name:"))
            .shouldHave(notificationMessage("test"))
            .should(notificationMessageContains("te"));

    sleep(3000);

    notification.shouldNotBe(EXIST);
}

Если на экране одновремнно открыто несколько уведомлений, вы можете выбрать нужное с помощью xpath:

$j(Notification.class, xpath("{xpath-to-notification}"));

Обертка Композита

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

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

@TestView
public class FragmentsView extends View<FragmentsView> {

    @TestComponent
    private TestFragment1 testFragment1Root;

    public TestFragment1 getTestFragment1() {
        return testFragment1Root;
    }
}

Обертки для упомянутых фрагментов выглядят следующим образом:

public class TestFragment1 extends Composite<TestFragment1> { (1)

    @TestComponent
    private TextField testFragment1TextField;

    public TextField getTestField() {
        return testFragment1TextField;
    }
}
@TestComponent(path = {"FragmentsView", "testFragment2Root"}) (2)
public class TestFragment2 extends Composite<TestFragment2> {

    @TestComponent
    private TextField testFragment2TextField;

    public TextField getTestField() {
        return testFragment2TextField;
    }
}
1 Обертка композита наследуется от io.jmix.masquerade.sys.Composite.
2 Обертки композита могут быть дополнительно аннотированы с помощью @TestComponent, чтобы предоставить значение пути для использования в Masquerade.$j.

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

FragmentsView fragmentsView = openFragmentsView();

fragmentsView.getTestFragment1()
        .getTestField()
        .shouldHave(value(""))
        .setValue("Fragment_1")
        .shouldHave(value("Fragment_1"));

В случае если фрагмент не добавлен в обертку экрана, получить к нему доступ можно передав его класс в метод Masquerade.$j:

FragmentsView fragmentsView = openFragmentsView();

$j(TestFragment2.class)
        .getTestField()
        .shouldHave(value(""))
        .setValue("Fragment_2")
        .shouldHave(value("Fragment_2"));