Simple TestFX example fails
Asked Answered
H

2

7

Working with TestFX 4.0.14 in Eclipse photon and fx9 or fx11 (doesn't matter), the simple example test from the TestFX wiki fails in should_click_on_button() with

Expected: Labeled has text "clicked!"
         but: was "click me!"

When looking at the screen, the pane and its contained button is shown, but the mouse moves to someplace else: so the button is never clicked and consequently its text never changed.

Any idea what's wrong/how to fix?

The test code (all copied from the wiki for convenience):

import org.junit.Test;
import org.testfx.framework.junit.ApplicationTest;

import static org.testfx.api.FxAssert.*;
import static org.testfx.matcher.control.LabeledMatchers.*;

import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

/**
 * Simple testfx example from testfx wiki:
 * https://github.com/TestFX/TestFX/wiki/Getting-Started
 * 
 */
public class ClickApplicationTest extends ApplicationTest {
    @Override
    public void start(Stage stage) {
        Parent sceneRoot = new ClickApplication.ClickPane();
        Scene scene = new Scene(sceneRoot, 100, 100);
        stage.setScene(scene);
        stage.show();
    }

    @Test
    public void should_contain_button() {
        // expect:
        verifyThat(".button", hasText("click me!"));
    }

    @Test
    public void should_click_on_button() {
        // when:
        clickOn(".button");

        // then:
        verifyThat(".button", hasText("clicked!"));
    }


}

The application code:

import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

/**
 * Simple testfx example from testfx wiki:
 * https://github.com/TestFX/TestFX/wiki/Getting-Started
 * 
 */
public class ClickApplication extends Application {
    // application for acceptance tests.
    @Override public void start(Stage stage) {
        Parent sceneRoot = new ClickPane();
        Scene scene = new Scene(sceneRoot, 100, 100);
        stage.setScene(scene);
        stage.show();
    }

    // scene object for unit tests
    public static class ClickPane extends StackPane {
        public ClickPane() {
            super();
            Button button = new Button("click me!");
            button.setOnAction(actionEvent -> button.setText("clicked!"));
            getChildren().add(button);
        }
    }
}

Update:

Found an open issue in TestFX that might match. It mentions a core fx bug that might be the reason - but doesn't seem to be: it's fixed in fx11 (verified that the code in the core bug report passes), but the testfx issue prevails ..

Houle answered 2/10, 2018 at 9:8 Comment(0)
H
3

Turned out to be a problem with several reasons:

  • at the very base is a core bug - robot doesn't move correctly on win (10 only?) HiDPI screens, so not everybody experiences the failure
  • the core bug is fixed in fx11 (in native win code), so that the now public fx Robot behaves well
  • TestFX robot-related code in the latest release (4.0.14) is not yet updated to fx11, the repo version is
  • by default, TestFX uses the AWTRobot which still fails (at least in the context of TestFX, the example in the core bug report passes)
  • to make TestFX tests run reliable on win HiDPI screens, we need to force the usage of the fx robot, done by setting a system property testfx.robot to glass
  • one last quirk: when setting the property programmatically via System.getProperties().put("testfx.robot", "glass") in the testing code, it is mandatory to do this before the tests class starts, that is in a method annotated by @BeforeClass (not in @Before nor testApp.init())! Otherwise the very first test that directly or indirectly involves a mouseMove will fail, subsequent ones are fine (puzzled me a while because the failure feels spurious;).

Sample:

public class ClickTest extends ApplicationTest {

    @Test
    public void testClick() {
        verifyThat(".button", hasText("click me!"));
        clickOn(".button");
        verifyThat(".button", hasText("clicked!"));
    }

    /**
     * Use glass robot.
     * Note: this must happen once before the class is loaded. Otherwise,
     * the very first test run uses the awt robot
     */
    @BeforeClass
    public static void config() throws Exception {
        System.getProperties().put("testfx.robot", "glass");
    }

    @Override
    public void start(Stage stage) {
        Parent sceneRoot = new ClickPane();
        Scene scene = new Scene(sceneRoot, 100, 100);
        stage.setScene(scene);
        stage.show();
    }

    // scene object for unit tests
    public static class ClickPane extends StackPane {
        public ClickPane() {
            super();
            Button button = new Button("click me!");
            button.setOnAction(actionEvent -> button.setText("clicked!"));
            getChildren().add(button);
        }
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(ClickTest.class.getName());
}
Houle answered 5/10, 2018 at 12:37 Comment(1)
Thanks for your amazing knowledge and research which went into this. I find that this works if the ClickTest class is just run from within Eclipse, for example, but not when I try to incorporate it into a Gradle project and run as part of a build task. This may have something to do with the fact that I'm running Gradle from a Cygwin console in a Windows 10 OS. Who knows?Arkansas
D
3

It works for me.

Anyway, you may be checking the UI change before it happens since it's done in the FX Application Thread and not in the thread that is executing the test.

Use this line

WaitForAsyncUtils.waitForFxEvents()

between the clickOn and verifyThat calls.

Dineric answered 2/10, 2018 at 10:15 Comment(1)
thanks for the feedback, good to know that not everybody is affected :) Adding the waiting call doesn't help ..Houle
H
3

Turned out to be a problem with several reasons:

  • at the very base is a core bug - robot doesn't move correctly on win (10 only?) HiDPI screens, so not everybody experiences the failure
  • the core bug is fixed in fx11 (in native win code), so that the now public fx Robot behaves well
  • TestFX robot-related code in the latest release (4.0.14) is not yet updated to fx11, the repo version is
  • by default, TestFX uses the AWTRobot which still fails (at least in the context of TestFX, the example in the core bug report passes)
  • to make TestFX tests run reliable on win HiDPI screens, we need to force the usage of the fx robot, done by setting a system property testfx.robot to glass
  • one last quirk: when setting the property programmatically via System.getProperties().put("testfx.robot", "glass") in the testing code, it is mandatory to do this before the tests class starts, that is in a method annotated by @BeforeClass (not in @Before nor testApp.init())! Otherwise the very first test that directly or indirectly involves a mouseMove will fail, subsequent ones are fine (puzzled me a while because the failure feels spurious;).

Sample:

public class ClickTest extends ApplicationTest {

    @Test
    public void testClick() {
        verifyThat(".button", hasText("click me!"));
        clickOn(".button");
        verifyThat(".button", hasText("clicked!"));
    }

    /**
     * Use glass robot.
     * Note: this must happen once before the class is loaded. Otherwise,
     * the very first test run uses the awt robot
     */
    @BeforeClass
    public static void config() throws Exception {
        System.getProperties().put("testfx.robot", "glass");
    }

    @Override
    public void start(Stage stage) {
        Parent sceneRoot = new ClickPane();
        Scene scene = new Scene(sceneRoot, 100, 100);
        stage.setScene(scene);
        stage.show();
    }

    // scene object for unit tests
    public static class ClickPane extends StackPane {
        public ClickPane() {
            super();
            Button button = new Button("click me!");
            button.setOnAction(actionEvent -> button.setText("clicked!"));
            getChildren().add(button);
        }
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(ClickTest.class.getName());
}
Houle answered 5/10, 2018 at 12:37 Comment(1)
Thanks for your amazing knowledge and research which went into this. I find that this works if the ClickTest class is just run from within Eclipse, for example, but not when I try to incorporate it into a Gradle project and run as part of a build task. This may have something to do with the fact that I'm running Gradle from a Cygwin console in a Windows 10 OS. Who knows?Arkansas

© 2022 - 2024 — McMap. All rights reserved.