Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(refactor) Extract robot and service classes from GuiTest. #39

Merged
merged 67 commits into from Feb 11, 2014
Merged

(refactor) Extract robot and service classes from GuiTest. #39

merged 67 commits into from Feb 11, 2014

Conversation

hastebrot
Copy link
Member

This is my work-in-progress which extracts robot and service classes from org.loadui.testfx.GuiTest. It is an experiment to see if a modular approach is better than the current monolithic approach.

Framework:

  • Extract framework.FxRobotImpl that implements all robot interfaces (is-a KeyboardRobot, MouseRobot, ClickRobot, ...) and contains service classes (has-a NodeFinder, ScreenLocator, ScreenCapturer, ...).
  • Rename to framework.ScreenRobotImpl from FxScreenController.
  • Rename to framework.junit.StageRobotTest from GuiTest that inherits framework.FxRobot and implements service.SceneProvider.
  • Create framework.FxRobotConfig to configure timeouts and pauses.

Robots:

  • Extract robot.KeyboardRobot with press and release methods.
  • Extract robot.MouseRobot with press and release methods.
  • Extract robot.ClickRobot with click, rightClick and doubleClick methods.
  • Extract robot.DragRobot with drag, drop, dropTo and dropBy methods.
  • Extract robot.MoveRobot with moveTo and moveBy methods.
  • Extract robot.ScrollRobot with scroll, scrollUp and scrollDown methods.
  • Extract robot.SleepRobot with sleep method.
  • Extract robot.TypeRobot with type and erase methods.

Services:

  • Extract service.stage.StageApplication with TestFxApp functionality.
  • Extract service.stage.StageRetriever with setupStage and showNodeInStage methods.
  • Extract service.stage.SceneProvider with getRootNode methods.
  • Extract service.locator.PointLocator with pointFor and pointForBounds methods.
  • Extract service.locator.BoundsLocator with sceneBoundsToScreenBounds method.
  • Extract service.finder.NodeFinder with static find, findAll, findAllRecursively, exists, labelExists and selectorExists methods.
  • Extract service.finder.WindowFinder with getWindows, getWindowByIndex and findStageByTitle methods.
  • Extract service.capturer.ScreenCapturer with captureScreenshot method.

Miscellanous:

  • Cleanup static waitUntil() functionality.
  • Cleanup target(), and static targetWindow() and lastSeenWindow functionality.
  • Cleanup pos() and nodePosition functionality.
  • Replace static offset() and OffsetTarget functionality with PointQuery.

@hastebrot
Copy link
Member Author

I spend a lot of time and prototyped a refactored version. The pull description was updated accordingly. There will be robot and service classes. Dependency injection (for example with Google Guice) will be possible.

I also changed the interface of the drag and find functionality a bit.

Example drag functionality (the reference to the fx field is optional):

fx.drag("#nodeA").moveTo(nodeB).dropTo("NodeC")
fx.drag("#nodeA").moveBy(200, 50).sleep(2, TimeUnit.SECONDS).drop()
fx.drag("#nodeA").dropBy(200, 50)

Example find functionality (find was renamed to node):

Bounds nodeBounds = fx.locator.nodeBounds(fx.node("#nodeA"))

Rename ScreenController to framework.ScreenRobot.
Rename FXScreenController to framework.ScreenRobotImpl.
Refactor TestFxApp, setupStage() and showNodeInStage().
@hastebrot
Copy link
Member Author

I made some good progress on PointLocator and BoundsLocator. Here is the minimal class signature for them; the rest can then be infered for GuiTest#pointFor(Object target).

public class PointLocator {
    public PointQuery pointFor(Bounds bounds) {}
    public PointQuery pointFor(Point2D point) {}
    public PointQuery pointFor(Node node) {}
    public PointQuery pointFor(Scene scene) {}
    public PointQuery pointFor(Window window) {}
}

public class BoundsLocator {
    public Bounds screenBoundsFor(Node node) {}
    public Bounds screenBoundsFor(Bounds boundsInScene, Scene scene) {}
    public Bounds screenBoundsFor(Scene scene) {}
    public Bounds screenBoundsFor(Window window) {}
    public Bounds sceneBoundsFor(Node node) {}
    public Bounds sceneBoundsVisibleFor(Node node) {}
    private Bounds getSceneBounds(Scene scene) {}
    private Bounds intersectBounds(Bounds a, Bounds b) {}
}

@Philipp91
Copy link
Contributor

Will TestFX still be usable the same way, or do existing tests have to be modified?

@hastebrot
Copy link
Member Author

@Philipp91 There will be some minor changes to the tests necessary, you'll mostly need to rename methods. My refactorings currently didn't change the tests that are included in TestFX, and they pass for every single commit. The minimal feature set of TestFX will be the same.

@hastebrot
Copy link
Member Author

Changes to test classes.

Before:

public class MyTest extends GuiTest {
    @Override
    protected Parent getRootNode() { /*...*/ }
}

After:

public class MyTest extends StageRobotTest {
    public Scene setupScene(Parent sceneRootNode) {
        return SceneBuilder.create().width(600).height(400)
            .root(sceneRootNode).build();
    }

    public Parent setupSceneRoot() { /*...*/ }
}

Of course it is possible to write public abstract class GuiTest extends StageRobotTest {} that provides getRootNode().

@hastebrot
Copy link
Member Author

Changes to test methods.

Before:

click("#nodeA");
click(offset("#nodeA", 200, 50)); // offset() returns OffsetTarget.
pos(Pos.BOTTOM_RIGHT).click("#nodeB")
click("#nodeC", MouseButton.PRIMARY)

After:

click("#nodeA"); // no change.
click(offset("#nodeA", 200, 50)); // offset() returns Point2D and is typed.
pos(Pos.BOTTOM_RIGHT).click("#nodeB") // no change.
click("#nodeC", MouseButton.PRIMARY) // no change.

Before:

move(0, 0)
move("#nodeA")
move(predicate)
moveBy(200, 50)

After:

moveTo(0, 0)
moveTo("#nodeA") // moveTo() is internally typed now.
moveTo(predicate) // moveTo() is internally typed now.
moveBy(200, 50) // no change.

Before:

drag("#nodeA").via(nodeB).to("NodeC")
drag("#nodeA").by(200, 50).sleep(2, TimeUnit.SECONDS).drop()
drag("#nodeA").to(200, 50)

After:

drag("#nodeA").moveTo(nodeB).dropTo("NodeC")
drag("#nodeA").moveBy(200, 50).sleep(2, TimeUnit.SECONDS).drop()
drag("#nodeA").dropBy(200, 50)

Before:

find("#nodeA")
find("#nodeA", "#parentNode")
findAll(".classA")

After:

node("#nodeA")
node("#nodeA", "#parentNode") // WIP: parentObject should be typed.
nodes(".classA")

Before:

pointFor("#nodeA") // returns Point2D.
pointFor(new Point2D(50, 50))
pointFor(node)
pointFor(scene)
pointFor(window)
pointFor(query)
pointFor(matcher)
pointFor(predicate)

After:

pointFor("#nodeA") // returns PointQuery and is typed.
pointFor(new Point2D(50, 50))
pointFor(node)
pointFor(scene)
pointFor(window)
pointFor(query)
pointFor(matcher)
pointFor(predicate)

@hastebrot
Copy link
Member Author

By the way, I'm going to write a compatibility class. It would be great if you can run your test suites with these changes as soon this pull request is ready; no changes in test classes needed.

@Philipp91
Copy link
Contributor

Okay thank you.

#39 (comment)
Why is it called StageRobotTest? The name seems to come from the internal implementation of TestFX, but wouldn't a TestFX user expect the class that he looks for to be named "GuiTest" or something like that?

And is it really necessary to implement setupScene in every test class? And why does the new implementation use the deprecated SceneBuilder?

@hastebrot
Copy link
Member Author

Why is it called StageRobotTest? The name seems to come from the internal implementation of TestFX, but wouldn't a TestFX user expect the class that he looks for to be named "GuiTest" or something like that?

I had an abstract StageTest and StageRobotTest in my prototype. StageTest only provided a Stage without methods like click(), so I had to name the classes this way. This distinction was due to tests I wrote for the GuiTest methods. (Yes, I'm going to write tests for every single method in GuiTest in addition to the existing (integration?) tests like AssertionsTest, DragDropTest, ...)

And is it really necessary to implement setupScene in every test class? And why does the new implementation use the deprecated SceneBuilder?

Good point. I'll provide a default implementation for this. You can write your own abstract Test class, that implements a default setupScene method. I used the deprecated SceneBuilder because it was used in the previous implementation. Will change this later.

@Philipp91
Copy link
Contributor

Okay, that sounds good. For the application that I use TestFX for, it would also be pretty useful if one could customize the implementation of the javafx.application.Application (need to use a custom one here that connects to CDI). If you find a clever way to integrate that as well, it would be nice to have.

@hastebrot
Copy link
Member Author

customize the implementation of the javafx.application.Application

I already thought about this problem, but didn't find a solution so far. The modularization will be one step towards solving this problem; should be easier to experiment with solutions.

@minisu
Copy link
Member

minisu commented Jan 24, 2014

Hi! I've looked through the code so far and it looks solid. I'll focus on writing some tests.

@hastebrot
Copy link
Member Author

I was a bit unhappy with the flexibility of the the Application launch process. Here are some design decisions:

Notice: The launch process with GuiTest will stay the same; the abstract Parent getRootNode() method will be available to the user.

There is an Application called DefaultApplication that provides a primaryStage. Every test class that inherits ApplicationRobotTest ensures that DefaultApplication is launched and waits until primaryStage is provided. When multiple test classes are executed within a test suite, only the first test class will launch DefaultApplication.

Within every test class is an abstract method called setupStages. This method sets the Scene of primaryStage and optionally creates additional Stages.

// Runs in JavaFX Application Thread.
abstract public void setupStages(Stage primaryStage);

@hastebrot
Copy link
Member Author

So, I'm going to resolve all conflicts as merged now. Some of the commits (8d8bec7...72377b5) are integrated, some of them are not. I'll integrate them in the next couple of days.

Conflicts:
	pom.xml
	src/main/java/org/loadui/testfx/FXScreenController.java
	src/main/java/org/loadui/testfx/GuiTest.java
	src/main/java/org/loadui/testfx/ScreenController.java
	src/main/java/org/loadui/testfx/controls/TextInputControls.java
	src/main/java/org/loadui/testfx/controls/impl/HasLabelStringMatcher.java
	src/main/java/org/loadui/testfx/utils/FXTestUtils.java
	src/test/java/org/loadui/testfx/TextInputControlsTest.java
	src/test/java/org/loadui/testfx/VisibilityTest.java
hastebrot added a commit that referenced this pull request Feb 11, 2014
@hastebrot hastebrot merged commit 0e09c6f into TestFX:master Feb 11, 2014
@hastebrot hastebrot mentioned this pull request Feb 11, 2014
@hastebrot hastebrot changed the title WIP: Refactor GuiTest (refactor) Extract robot and service classes from GuiTest. Feb 25, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants