{"id":29008294,"url":"https://github.com/jmix-framework/jmix-masquerade","last_synced_at":"2025-06-25T14:04:42.919Z","repository":{"id":37756202,"uuid":"291781620","full_name":"jmix-framework/jmix-masquerade","owner":"jmix-framework","description":"Jmix UI Testing Library","archived":false,"fork":false,"pushed_at":"2023-03-31T11:53:38.000Z","size":879,"stargazers_count":6,"open_issues_count":12,"forks_count":5,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-04-16T18:09:35.677Z","etag":null,"topics":["jmix"],"latest_commit_sha":null,"homepage":"https://www.jmix.io","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jmix-framework.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-31T17:33:27.000Z","updated_at":"2022-06-22T10:53:16.000Z","dependencies_parsed_at":"2023-01-31T11:30:30.243Z","dependency_job_id":null,"html_url":"https://github.com/jmix-framework/jmix-masquerade","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/jmix-framework/jmix-masquerade","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmix-framework%2Fjmix-masquerade","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmix-framework%2Fjmix-masquerade/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmix-framework%2Fjmix-masquerade/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmix-framework%2Fjmix-masquerade/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jmix-framework","download_url":"https://codeload.github.com/jmix-framework/jmix-masquerade/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jmix-framework%2Fjmix-masquerade/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261888103,"owners_count":23225137,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["jmix"],"created_at":"2025-06-25T14:04:24.590Z","updated_at":"2025-06-25T14:04:42.908Z","avatar_url":"https://github.com/jmix-framework.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"jmix-masquerade\n==========\n\nJmix UI testing library. \n\n\u003ca href=\"http://www.apache.org/licenses/LICENSE-2.0\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat\" alt=\"license\" title=\"\"\u003e\u003c/a\u003e\n\n# Overview\n\nWith the help of this library, you can create end-to-end tests for your Jmix application in Java or Kotlin and run them manually or in your CI environment.\n\nMasquerade allows you to separate test scenarios from the complexity of the UI elements under test. Firstly, you describe the UI screen content declaratively in a simple Java or Kotlin class with annotated fields, and then you use this class in test scenarios as a substitute of the real UI elements. \n \nMasquerade is based on Selenide and Selenium.\n\nYou can find examples of using the library in the following projects:\n- https://github.com/jmix-projects/jmix-sample-ui-test\n- https://github.com/Haulmont/jmix-ui-tests\n\n# Installation\n\nAdd the following dependencies to your `build.gradle` file:\n\n```groovy\nimplementation('org.jsoup:jsoup') {\n    version {\n        strictly '1.11.2'\n    }\n}\ntestImplementation 'com.codeborne:selenide:6.12.4'\ntestImplementation 'io.jmix.masquerade:jmix-masquerade:\u003clatest version\u003e'\n```\n\nGet the latest version from the [list of releases](https://github.com/Haulmont/jmix-masquerade/releases).\n\n## Table of compatibility\n| Masquerade | Selenide |\n|:----------:|:--------:|\n|   1.2.x    |  6.12.4  |\n|   1.1.x    |  6.6.1   |\n|   1.0.x    |  5.20.1  |\n\n# Creating tests\n\nLet's consider creating UI test on an example.\n\nIn the `src/test/java` folder, create a new package in `com.company.demo` and name it `screen`. \nCreate a new Java class in this package and name it `LoginScreen`. This class \nshould extend `Composite\u003cT\u003e` where `T` is the name of your \nclass under test. This class will be used as a helper class, usually it declares UI \ncomponents of an application screen / fragment / panel that is shown in a web page. \nAlso, all test methods can be declared here.\n \nAll class attributes should be marked with the ```@Wire``` annotation. \nThis annotation has optional `path` element which allows userService to define \nthe path to the component using the `j-test-id` parameter. If the component does \nnot have the `j-test-id` parameter, you can use the ```@FindBy``` annotation \ninstead. This annotation has a list of optional parameters, like `name`, \n`className`, `id` and so on, which helps to identify the component.\n\nThe type of the attribute in the class corresponds to the type of the screen \ncomponent. If the component has a type which is not defined in the library, use \nthe `Untyped` type. \n\nThe name of the attribute corresponds to the `j-test-id` attribute of a DOM \nelement that corresponds to the UI component. \n\n```java\npackage com.company.demo.screen;\n\nimport io.jmix.masquerade.Wire;\nimport io.jmix.masquerade.base.Composite;\nimport io.jmix.masquerade.component.*;\nimport org.openqa.selenium.support.FindBy;\n\n\npublic class LoginScreen extends Composite\u003cLoginScreen\u003e {\n\n    @Wire\n    private TextField usernameField;\n\n    @Wire\n    private PasswordField passwordField;\n\n    @Wire(path = {\"loginForm\", \"loginButton\"})\n    private Button loginButton;\n\n    @Wire\n    private ComboBox localesField;\n\n    @Wire\n    private Label welcomeLabel;\n\n    @FindBy(className = \"jmix-login-caption\")\n    private Label welcomeLabelTest;\n\n    public TextField getUsernameField() {\n        return usernameField;\n    }\n\n    public PasswordField getPasswordField() {\n        return passwordField;\n    }\n\n    public Button getLoginButton() {\n        return loginButton;\n    }\n\n    public ComboBox getLocalesField() {\n        return localesField;\n    }\n\n    public Label getWelcomeLabel() {\n        return welcomeLabel;\n    }\n\n    public Label getWelcomeLabelTest() {\n        return welcomeLabelTest;\n    }\n}\n``` \n\nCreate a Java class in the `com.company.demo` package in the `src/test/java` folder. Name it `LoginUiTest`. \n\nCreate a new method and add ```@Test``` JUnit 5 annotation to it. The ```@Test``` \nannotation tells JUnit 5 that the public void method can be run as a test case. \n \nYou can use all JUnit 5 annotations to improve the tests. Also it is possible to \nuse a set of assertion methods provided by JUnit 5.\n \n```java\npackage com.company.demo;\n\nimport com.company.demo.screen.LoginScreen;\nimport io.jmix.masquerade.component.Untyped;\nimport org.junit.jupiter.api.Test;\n\nimport static com.codeborne.selenide.Selenide.closeWindow;\nimport static com.codeborne.selenide.Selenide.open;\nimport static io.jmix.masquerade.Components.wire;\nimport static io.jmix.masquerade.Conditions.*;\nimport static io.jmix.masquerade.Selectors.$j;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\npublic class LoginUiTest {\n\n    @Test\n    public void login() {\n        // open URL of an application\n        open(\"http://localhost:8080\");\n\n        // obtain UI object\n        LoginScreen loginScreen = $j(LoginScreen.class);\n\n        assertNotNull(loginScreen.getUsernameField());\n        assertNotNull(loginScreen.getPasswordField());\n\n        // fluent asserts\n        loginScreen.getUsernameField()\n                .shouldBe(EDITABLE)\n                .shouldBe(ENABLED);\n\n        // setting values\n        loginScreen.getUsernameField().setValue(\"admin\");\n        loginScreen.getPasswordField().setValue(\"admin\");\n\n        // fluent asserts\n        loginScreen.getWelcomeLabelTest()\n                .shouldBe(VISIBLE);\n\n        // fluent asserts\n        loginScreen.getLoginButton()\n                .shouldBe(VISIBLE)\n                .shouldBe(ENABLED)\n                .shouldHave(caption(\"Submit\"));\n\n        Untyped loginForm = wire(Untyped.class, \"loginForm\");\n        loginForm.shouldBe(VISIBLE);\n\n        loginScreen.getLoginButton().click();\n\n        // close the browser tab\n        closeWindow();\n    }\n}\n``` \n\nThe `open()` method is a standard Selenide method. It opens a browser window \nwith the given URL. The second line creates an instance of the masquerade \nComponent and binds it to the UI component (LoginScreen) on the screen including \nall the annotated fields inside of the LoginScreen class. After that, you can \naccess the screen components as class attributes. You can check the attributes \nvisibility, get captions, set values, click the buttons and so on.\n\n# Running tests\n\nTo run the test, first of all, you need to set ```jmix.ui.testMode``` property to \ntrue in the `application.properties` file in your Jmix application. After that you \nshould start the application using Studio or Gradle tasks. To start application \nwith Gradle, run the following tasks in the terminal:\n\n    gradle bootRun\n\n## Webdriver containers\n\n[Testcontainers](https://www.testcontainers.org/modules/webdriver_containers/) can \nbe used to automatically instantiate and manage containers that include web browsers, \nsuch as Chrome or Firefox. No need to have specific web browsers, or even a desktop \nenvironment, installed on test servers. The only dependency is a working Docker \ninstallation and your Java JUnit test suite.\nCreation of browser containers is fast, so it's actually quite feasible to have a \ntotally fresh browser instance for every test.\n\nFirst of all, you need to add the testcontainer dependencies to the `build.gradle` file:\n```groovy\n// testcontainers\ntestImplementation 'org.testcontainers:selenium:1.17.6'\ntestImplementation 'org.testcontainers:junit-jupiter:1.17.6'\n```\n\nSecondly, you can create a JUnit 5 extension to run and configure a container with a browser.\n```java\npackage com.company.demo.extension;\n\nimport com.codeborne.selenide.Selenide;\nimport com.codeborne.selenide.WebDriverRunner;\nimport org.junit.jupiter.api.extension.AfterEachCallback;\nimport org.junit.jupiter.api.extension.BeforeEachCallback;\nimport org.junit.jupiter.api.extension.ExtensionContext;\nimport org.openqa.selenium.chrome.ChromeOptions;\nimport org.testcontainers.containers.BrowserWebDriverContainer;\n\npublic class ChromeExtension implements BeforeEachCallback, AfterEachCallback {\n\n    private BrowserWebDriverContainer browser;\n\n    @Override\n    public void beforeEach(ExtensionContext context) throws Exception {\n        browser = new BrowserWebDriverContainer()\n                .withCapabilities(new ChromeOptions());\n        browser.start();\n        WebDriverRunner.setWebDriver(browser.getWebDriver());\n    }\n\n    @Override\n    public void afterEach(ExtensionContext context) throws Exception {\n        WebDriverRunner.getWebDriver().manage().deleteAllCookies();\n        Selenide.closeWebDriver();\n        browser.stop();\n    }\n}\n```\n\nThirdly, declare the JUnit 5 extension in your test using `@ExtendWith` annotation\n```java\n@ExtendWith(ChromeExtension)\npublic class LoginUiTest {\n\n    @Test\n    public void login() {\n        open('http://localhost:8080')\n\n        LoginScreen loginScreen = $j(LoginScreen.class);\n        ...\n    }\n}\n```\n\n## Locally installed browser drivers\n\nPlease note that you need to download one of the latest versions of the web \ndriver depending on the browser you want to use to testing.\nFor Chrome browser this is [chromedriver](http://chromedriver.chromium.org/downloads), \nfor Firefox this is [geckodriver](https://github.com/mozilla/geckodriver/releases).\n\n### Chrome browser\n\nIf you run your tests in Chrome browser, you need to edit standard\ntest configuration for the test project in IntelliJ. To do so, click the \n*Select Run/Debug Configuration* button and select *Edit Configurations*  in the \ndrop-down list. In the VM options field, add the following:\n\n```\n-Dselenide.browser=chrome \n-Dwebdriver.chrome.driver=\u003cyour_path\u003e/chromedriver.exe \n```\n\nwhere `\u003cyour_path\u003e` is the path to the chrome driver on your computer.\n\n![Ui Chrome Test Configuration](images/chromeTestConfiguration.png)\n\nAfter that select the simple test or the test class you want to run, right \nclick on it and select *Debug* option.\n\nTo run the tests using Gradle, add the following task to the `build.gradle` file:\n```groovy\ntest {\n     systemProperty 'selenide.browser', System.getProperty('selenide.browser')\n     systemProperty 'webdriver.chrome.driver', System.getProperty('webdriver.chrome.driver')\n}\n```\nAfter that, run the following task in the terminal:\n```\ngradle test -Dselenide.browser=chrome -Dwebdriver.chrome.driver=\u003cyour_path\u003e/chromedriver.exe\n```\n    \nwhere `\u003cyour_path\u003e` is the path to the chrome driver on your computer.\n\n### Firefox browser\n\nIf you run your tests in Firefox browser, you need to edit standard\ntest configuration for the test project in IntelliJ. To do so, click the \n*Select Run/Debug Configuration* button and select *Edit Configurations*  in the \ndrop-down list. In the VM options field, add the following:\n\n```\n-Dselenide.browser=firefox\n-Dwebdriver.gecko.driver=\u003cyour_path\u003e/geckodriver.exe \n```\nwhere `\u003cyour_path\u003e` is the path to the firefox driver on your computer.\n\n![Ui Firefox Test Configuration](images/firefoxTestConfiguration.png)\n\nAfter that select the simple test or the test class you want to run, right \nclick on it and select *Debug* option.\n\nTo run the tests using Gradle, add the following task to the `build.gradle` file:\n```groovy\ntest {\n     systemProperty 'selenide.browser', System.getProperty('selenide.browser')\n     systemProperty 'webdriver.gecko.driver', System.getProperty('webdriver.gecko.driver')\n}\n```\nAfter that, run the following task in the terminal:\n```\ngradle test -Dselenide.browser=firefox -Dwebdriver.gecko.driver=\u003cyour_path\u003e/geckodriver.exe\n```\nwhere `\u003cyour_path\u003e` is the path to the firefox driver on your computer.\n\n# Tips \u0026 Tricks\n\nHere are some useful tips on how to work with the library.\n\n## How to work with elements\n\nThe library has a special method  ```$j``` to define any element on the screen. \nThis method has three implementations:\n\n* The first implementation gets the element by its class:\n\n    ```$j(Class\u003cT\u003e clazz)```\n* The second implementation gets the element by its class and the path:\n\n    ```$j(Class\u003cT\u003e clazz, String... path)```\n* The third implementation gets the element by its class and _by_ selector:\n\n    ```$j(Class\u003cT\u003e clazz, By by)```\n    \nFor example, we can click the button on the screen: \n\n```java\nimport static io.jmix.masquerade.Components.$j;\n\n$j(Button, 'logoutButton').click();\n```\n\n## How to check the state of an element\n\nSelenide allows you to check some conditions.\n\nTo check if the element is enabled, visible or checked, use the `shouldBe` \nelement. For example:\n\n```java\nloginButton\n   .shouldBe(VISIBLE)\n   .shouldBe(ENABLED);\n```\n\nTo check if the element has some properties, use the `shouldHave` element. For example:\n\n```java\nwelcomeLabel.shouldHave(Conditions.value('Welcome to Jmix application!'));\n```    \n\n## How to work with the Selenide elements\n    \nIf the component does not have the `j-test-id` parameter, you can use the \n```@FindBy``` annotation. This annotation has a list of optional parameters, \nlike `name`, `className`, `id` and so on, which helps to identify the component.\n\n```java\n@FindBy(className = \"jmix-login-caption\")\npublic Label welcomeLabelTest;\n```    \n\nAlso, using this annotation, you can define `SelenideElement` type for the attribute \ninstead of the types provides by masquerade. After that, you can use all test \nmethods provided by Selenide. The name of the attribute can be any.\n\n```java\nimport com.codeborne.selenide.SelenideElement;\n\n@FindBy(className = \"jmix-login-caption\")\npublic SelenideElement welcomeLabelTest;\n```   \n\nAnother way to define the `SelenideElement` type attribute is using the \n```@Wire``` annotation. You can write the `SelenideElement` type instead of \nmasquerade types, but the name of the attribute should correspond to the \n`j-test-id` attribute of a DOM element that corresponds to the UI component.\n\n```java\n@Wire\npublic SelenideElement loginField;\n```    \n\nThe third way to work with the Selenide elements is to use ```getDelegate()``` \nmethod. This method returns the `SelenideElement` type component. After that, you \ncan use all test methods provided by Selenide.\n\n```java\nloginScreen.getDelegate().exists();\n```    \n\n## Useful tips for the Groovy tests\n\nYou can use any JVM language with the library including Groovy / Scala / Kotlin. \nThere are some useful tips for those who use Groovy to write the tests. \n\n* .with() method.\n\nGroovy closures have a delegate associated with them. The delegate can respond \nto method calls which happen inside of the closure. It enables you to use \nmethods/properties within a `with {}` closure without having to repeat the \nobject name each time.\n\n```groovy\nloginScreen.with {\n    loginField.value = 'testUser'\n    passwordField.value = '1'\n    rememberMeCheckBox.checked = true\n\n    commit()\n}\n```\n* Ability to set the value of the element using \"property access\" syntax\n\nIn Groovy, getters and setters form what we call a \"property\", and offer a \nshortcut notation for accessing and setting such properties. So instead of the \nJava-way of calling getters / setters, you can use a field-like access notation: \n\n```groovy\nloginField.value = 'testUser'\n```\n\n* def\n\n```def``` means that the actual type of the value will be automatically inferred \nby the compiler. It eliminates the unnecessary boilerplate in variable \ndeclarations and makes your code shorter.\n\n```groovy\ndef loginScreen = $j(LoginScreen)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmix-framework%2Fjmix-masquerade","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjmix-framework%2Fjmix-masquerade","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjmix-framework%2Fjmix-masquerade/lists"}