Masquerade
Masquerade is an end-to-end testing library designed to automate UI testing for Jmix applications. The library helps you use the Page Object pattern for all parts of Jmix UI, including views, components, dialogs, notifications, and composites. It is based on Selenium WebDriver and Selenide.
Masquerade is for applications that are built with Jmix 2.6 or newer. |
Install Masquerade
To install Masquerade, follow the steps below:
-
Ensure that your project uses Jmix 2.6 or newer. See the Upgrading Project section for how to upgrade using Studio.
-
Specify the dependency in the project
build.gradle
file astestImplementation
:build.gradletestImplementation 'io.jmix.masquerade:jmix-masquerade'
Using Selenide
Masquerade is based on Selenide, so you can use any of its methods. Consider an example, typical of test automation, that logs in to a Jmix application:
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();
}
}
The standard Selenide.$
method takes a selector as an argument, such as byId
. As a result, a SelenideElement
object will be returned, with which further actions can be performed.
Please see Selenide API to learn about more methods. |
Create Test with Masquerade
Masquerade emphasizes the use of wrappers for various Jmix components, allowing for better organization and management of tests. Let’s create a test that logs in to a Jmix application using Masquerade. This will involve two steps:
Step 1. Create a View Wrapper
A basic Jmix project includes a login view from the start. Create a view wrapper to represent this view and its components under test:
-
In the
src/test/java
directory, within thecom.company.testproject
package create a new package namedview
. Then, create a Java class namedLoginView
:TestProject/ └── src/ ├── main/ └── test/ └── java/ └── com.company.testproject └── test_support └── view └── LoginView.java
-
Add component wrappers along with get methods for each component needed for the test:
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; } }
Step 2. Create a Test Class
The test class will invoke methods from the wrapper to go through the login scenario. Follow the steps below to create it:
-
In the
src/test/java
directory, within thecom.company.testproject
package create a new package namedui-autotest
. Then create a Java class namedLoginUiTest
:TestProject/ └── src/ ├── main/ └── test/ └── java/ ├── com.company.testproject │ └── test_support │ └── view │ └── LoginView.java └────── ui_autotest └── LoginUiTest.java
-
Define a sequence of actions for your test scenario:
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 Open the login page using the standard Selenide method. 2 Use the Masquerade.$j
method to select the view wrapper and call its methods.
Jmix Test IDs
Masquerade may assist you in generating a special j-test-id
(Jmix test ID) attribute for each component created with the UiComponents
factory.
Such ids simplify identifying elements on the page. To enable this feature, set jmix.ui.ui-test-mode to true:
jmix.ui.ui-test-mode = true
ID generation can impact the performance. It is recommended to use this property only for a test application profile. |
The default test id value will match the id already given to a component:

If a component had no id, its test id will be generated based on its data binding, action
, or text
attribute.
To work with an element directly by its j-test-id
, use the Masquerade.$j
method:
$j("myButton").click();
Wrappers
Wrappers are classes that represent different parts of Jmix UI and encapsulate interactions with them. They help you separate test scenarios from the complexities of the UI under test. There are five types of wrappers.
View Wrapper
A view wrapper encapsulates a view, providing a simplified and consistent interface to interact with its components. A view wrapper does not have to include all components of the corresponding view – add just those that are needed for tests.
Consider a simple view wrapper example:
@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 | Annotate with io.jmix.masquerade.TestView . The id value must match the view ID specified through @ViewController :
If you don’t specify an ID, then the simple class name for this view will be used as id. |
2 | Extend the io.jmix.masquerade.sys.View class with typing on itself. This is necessary to provide fluent API for writing tests. |
Component Wrapper
Component wrappers encapsulate components within view wrappers. To designate a field as a component wrapper, use the @TestComponent
or @FindBy
annotations. This is illustrated in the following example:
@TestComponent
private EntityComboBox entityComboBox;
@TestComponent(path = "myButton")
private Button button;
@FindBy(xpath = "//vaadin-text-area[@class='my-text-area']")
private TextArea textArea;
-
@TestComponent
indicates that the current field is a component wrapper. If thepath
value is not specified, the default value is assumed to be the value of the j-test-id attribute for the corresponding web element. -
@FindBy
specifies a selector to be used for this field.
Find the list of all available component wrappers in the io.jmix.masquerade.component package.
|
Component wrappers offer various methods for interacting with components. For instance, you can open an overlay or even a dialog window for an 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]"));
}
Component wrappers also offer specific conditions for checking a component’s state. You can find the list of all available conditions in the io.jmix.masquerade.condition
package.
DialogWindows Wrapper
DialogWindow wrappers are similar to view wrappers, but extend io.jmix.masquerade.sys.DialogWindow
. This class offers specific actions for dialogs, such as closing them and checking the header. Consider the following example:
@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;
}
}
Notification Wrapper
Notification wrapper is a special type of component wrapper. Consider the following example working with a notification:
@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);
}
If you have multiple notifications open, you can select the one you want by xpath:
$j(Notification.class, xpath("{xpath-to-notification}"));
Composite Wrapper
Composite wrapper encapsulates a part of a view, also referred to as fragment. The way you access a fragment will vary depending on whether it is present in a view wrapper or not.
Assume we have a view composed of two fragments, but only one is added to the corresponding view wrapper:
@TestView
public class FragmentsView extends View<FragmentsView> {
@TestComponent
private TestFragment1 testFragment1Root;
public TestFragment1 getTestFragment1() {
return testFragment1Root;
}
}
Composite wrappers are as follows:
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 | Composite wrappers must extend io.jmix.masquerade.sys.Composite . |
2 | Composite wrappers can be optionally annotated with @TestComponent to provide a path value for use in Masquerade.$j . |
To access the fragment in the view wrapper, chain it as you would with components:
FragmentsView fragmentsView = openFragmentsView();
fragmentsView.getTestFragment1()
.getTestField()
.shouldHave(value(""))
.setValue("Fragment_1")
.shouldHave(value("Fragment_1"));
To test the second fragment, not present in the view wrapper, select it using Masquerade.$j
:
FragmentsView fragmentsView = openFragmentsView();
$j(TestFragment2.class)
.getTestField()
.shouldHave(value(""))
.setValue("Fragment_2")
.shouldHave(value("Fragment_2"));