Masquerade
Masquerade это библиотека для автоматизации тестирования пользовательского интерфейса в приложениях Jmix. Библиотека помогает использовать паттерн Page Object для всех частей пользовательского интерфейса Jmix, включая экраны, компоненты, диалоги, уведомления и композиты. Библиотека основана на Selenium WebDriver и Selenide.
Masquerade предназначена для приложений, созданных с использованием Jmix 2.6 или новее. |
Установка Masquerade
Чтобы установить Masquerade, выполните следующие шаги:
-
Убедитесь, что в проекте используется Jmix 2.6 или новее. Посетите раздел Апгрейд Проекта, чтобы узнать как обновить проект с помощью Studio.
-
Укажите зависимость в файле
build.gradle
какtestImplementation
:build.gradletestImplementation '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:
jmix.ui.ui-test-mode = true
Генерация идентификаторов может повлиять на производительность. Рекомендуется использовать это свойство только для тестового профиля приложения. |
Значение тестовых идентификаторов по умолчанию соответствует значение заданному в аттрибуте 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 :
Если вы не укажете |
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"));