Using TestFX 4.0.1 in the SpreadsheetView

1,783 views
Skip to first unread message

samir.ha...@gmail.com

unread,
Apr 2, 2015, 10:08:13 AM4/2/15
to testfx-...@googlegroups.com
Hi guys,

I've seen that things were going pretty fast on TestFX and I wanted to see how it was. So I took TestFX 4.0.1-alpha in order to write some tests. I've seen that many things were changed compared to the previous releases so I would like to know where and how to start basically..

I dive into the source code and managed to write that :
import impl.org.controlsfx.spreadsheet.CellView;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import org.controlsfx.control.spreadsheet.SpreadsheetCell;
import org.controlsfx.control.spreadsheet.SpreadsheetView;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.testfx.api.FxRobot;
import org.testfx.api.FxToolkit;
import org.testfx.service.query.NodeQuery;

public class SpreadsheetViewTest {

   
static SpreadsheetView view;
   
public FxRobot fx = new FxRobot();

   
@BeforeClass
   
public static void setup() throws Exception {
       
FxToolkit.registerPrimaryStage();
       
FxToolkit.setupStage((stage) -> {
            view
= new SpreadsheetView();
            stage
.setScene(new Scene(view, 500, 500));
       
});
       
FxToolkit.showStage();
   
}

   
@AfterClass
   
public static void cleanup() throws Exception {
       
FxToolkit.hideStage();
   
}

   
@Test
   
public void selectCellAfterEnterIsPressed() {
       
//Find first cell
       
NodeQuery query = fx.lookup((Node t) -> {
           
if ((t instanceof CellView) && ((CellView) t).getItem() != null) {
               
SpreadsheetCell cell = ((CellView) t).getItem();
               
if (cell.getRow() == 0 && cell.getColumn() == 0) {
                   
return true;
               
}
           
}
           
return false;
       
});
       
Node cell = query.queryFirst();
       
Assert.assertNotNull(cell);

        fx
.doubleClickOn(cell);

       
TextField editor = fx.from(cell).lookup((Node t) -> t instanceof TextField).queryFirst();
       
Assert.assertNotNull(editor);
        fx
.clickOn(editor).write("test");
        fx
.type(KeyCode.ENTER);

        fx
.doubleClickOn(cell);

       
NodeQuery query2 = fx.lookup((Node t) -> {
           
if ((t instanceof CellView) && ((CellView) t).getItem() != null) {
               
SpreadsheetCell cell1 = ((CellView) t).getItem();
               
if (cell1.getRow() == 5 && cell1.getColumn() == 1) {
                   
return true;
               
}
           
}
           
return false;
       
});
       
Node cell2 = query2.queryFirst();
       
Assert.assertNotNull(cell2);

        fx
.clickOn(cell2);

       
Assert.assertEquals(5, view.getSelectionModel().getSelectedIndex());
   
}

}

The test is working, it basically tests a bug I have on the SpreadsheetView (of ControlsFX) where clicking after editing a cell brings the selection on the wrong cell.


But I really don't know if this is the right thing. Should I extend "ApplicationTest" instead? Should I extend FXRobot?

How do I use the FXToolkit? Right now, I've seen those cool methods (registerPrimaryStage and setupStage) so I used them before I initialize my class. But maybe there's a smarter way to do?

Is the use of the "lookup" method right? I've seen that you all put String in it? Does it refers to an ID? A CSS class?

Anyway, I'm a real beginner on TestFX and I intend to use it intensively so I will take any advice.

Best regards,

PS : I've seen a TableViewMatcher somewhere. Since the tableView and the SpreadsheetView are very lookalike, if I find myself using always the same methods, I can try to push them to your TableViewMatcherin order to improve your API. Let me know.

samir.ha...@gmail.com

unread,
Apr 2, 2015, 10:13:11 AM4/2/15
to testfx-...@googlegroups.com
Also, does this version use Monocle? Is the detection of the headless mode automatic or should something be specified?

Benjamin Gudehus

unread,
Apr 2, 2015, 11:52:31 AM4/2/15
to Sam', testfx-...@googlegroups.com
Hi Samir,

your SpreadsheetViewTest looks pretty good.


>Should I extend "ApplicationTest" instead? Should I extend FXRobot?

ApplicationTest is intended to act as a pendant or counterpart of Application to help developers who are already familiar with JavaFX to transfer concepts to the testing area.

In general I recommend to use ApplicationTest. However ApplicationTest has some restrictions in regard of test setup and cleanup, e.g. it will reset the Stage for every @Test. You can use FxToolkit and extend from FxRobot if you need more flexibility.


>Is the use of the "lookup" method right? I've seen that you all put String in it? Does it refers to an ID? A CSS class?

Yes, it refers to the css class (".table-cell" in this case). You can use the string to lookup a Node by CSS ID with lookup("#submitButton"), by CSS class with lookup(".button"), or by its label with lookup("Submit").

You use the API methods for SpreadsheetView which is totally fine. It's the same which TableViews used in TestFX 3.x [1]. I liked the short CSS selector syntax for lookup of ListView cells, TableView row and cells, and especially ColorPicker colors very much, so I used it in the new matchers [2]. NodeQuery was introduced for exactly these use cases.


>Anyway, I'm a real beginner on TestFX and I intend to use it intensively so I will take any advice

Very nice. I've created the new matchers API also with support for ControlsFX in mind.


>I've seen a TableViewMatcher somewhere. Since the tableView and the SpreadsheetView are very lookalike, if I find myself using always the same methods, I can try to push them to your TableViewMatcherin order to improve your API. Let me know.

I think we could add TableViewMatcher.tableCell(tableView, rowIndex, cellIndex) and TableViewMatcher.tableCell(tableViewSelector, rowIndex, cellIndex) that both return a NodeQuery or Cell (I currently don't know which one is the best for this). I also think we should introduce a SpreadsheetViewMatcher class with methods for SpreadsheetView. But I'm not quite sure if this is the best idea in terms of code duplication.


>Also, does this version use Monocle? Is the detection of the headless mode automatic or should something be specified?

testfx-core uses the openjfx-monocle dependency for the compilation of tests only [3]. I guess you need to add the same dependency to you project in order to use monocle.

You need to explicitely specify that you want to use headless mode. The .travis.yml used headless mode for tests and lists the JVM arguments you need [4].

The args for desktop environments are: -Djava.awt.headless=true -Dtestfx.robot=glass -Dtestfx.headless=true -Dprism.order=sw -Dprism.text=t2k

--
You received this message because you are subscribed to the Google Groups "TestFX" group.
To unsubscribe from this group and stop receiving emails from it, send an email to testfx-discus...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

samir.ha...@gmail.com

unread,
Apr 2, 2015, 3:57:04 PM4/2/15
to testfx-...@googlegroups.com, samir.ha...@gmail.com
Thank you for your very detailed response Benjamin!

So basically, I could extend from FxRobot and use FxToolkit for my tests right? For example I could initialize a Stage before all the tests and destroy it after. And before each test, initialize a SpreadsheetView that I will put in the Scene somehow? That could be a good workflow?

Because I started to extend from ApplicationTest and I saw a "start" method so I wasn't very sure of how to write the tests. Since ApplicationTest do only one test within the start method? Should I write more test with "@Test" annotation? I wasn't sure so I decided to go with the other solution.

I've seen everybody talk about the "getRootNode()" method, this have been removed? Now with FxToolkit, I can easily place my Node into the Scene?

Does the lookup method faster with CSS ID rather than Predicate? I saw that it is better to use the "from(Node)" method before using lookup in order to narrow the search right?

I don't think a SpreadsheetViewMatcher will make sense.. At least it will do for me but it should be private to my tests. But I can improve the TableViewMatcher since I will be using a lot from that.

Also, in order to use the Glass robot, are we compelled to use the JVM option? Can't we specify that somehow  in the code?

I saw that you needed Javadoc and examples, I'll maybe submit some ideas in your Github if you're interested. Since I was new to TestFX I might be able to write the basic questions/answer in order to begin writting some simple tests.

Keep up the great work!

Benjamin Gudehus

unread,
Apr 5, 2015, 6:51:13 PM4/5/15
to Sam', testfx-...@googlegroups.com
On Thu, Apr 2, 2015 at 9:57 PM, <samir.ha...@gmail.com> wrote:
Thank you for your very detailed response Benjamin!

So basically, I could extend from FxRobot and use FxToolkit for my tests right? For example I could initialize a Stage before all the tests and destroy it after. And before each test, initialize a SpreadsheetView that I will put in the Scene somehow? That could be a good workflow?

Yes, you could just use FxRobot and FxToolkit. ApplicationTest is a convenience class to get rid of some cognitive load. It also uses the primary Stage, which is always initialized by the JavaFX toolkit, and spares you the manual destruction of Stages, because the tests will reuse the visible primary Stage for each @Test method and replace its Scene.

You can setup and cleanup a new scene manually. You can setup a SpreadsheetView in the @Before method (or start() when using ApplicationTest) or in the individual @Test method. If the setup of the SpreadsheetView doesn't differ between each @Test method, it is preferred to setup it in the @Before method.
 
Because I started to extend from ApplicationTest and I saw a "start" method so I wasn't very sure of how to write the tests. Since ApplicationTest do only one test within the start method? Should I write more test with "@Test" annotation? I wasn't sure so I decided to go with the other solution.

With an Application we will run through init(), start() and stop() one time. AppplicationTest will run through each of this methods for every @Test.
 
I've seen everybody talk about the "getRootNode()" method, this have been removed? Now with FxToolkit, I can easily place my Node into the Scene?

FxToolkit has setupSceneRoot() which is similar to getRootNode(). I banned GuiTest and getRootNode() into the testfx-legacy module. It turned out getRootNode() has a weird name (IMHO), and some restrictions.

I think we should circulate the use of ApplicationTest, because it is very similar to Application. The start() method is a counterpart to Application#start() and ApplicationTest.launch() could be a counterpart to Application.launch. Maybe we could use init() in the future, to allow more customization of the test process. There are a lot of internal test for TestFX that need to use FxToolkit, because of the inflexibility of ApplicationTest. Also note, that some folks prefer JUnit's @Rule instead of extending from a base testing class.
 
Does the lookup method faster with CSS ID rather than Predicate? I saw that it is better to use the "from(Node)" method before using lookup in order to narrow the search right?

The CSS and Predicate lookup use different implementations. JavaFX's Node class has a lookup() and lookupAll() method that supports querying of CSS selectors (not just CSS IDs, but whole CSS paths). The Predicate lookup uses a custom tree traversal algorithm. So the CSS query might be "faster".

I think the most important aspect of using from() is, that it narrows down the search space, which means you won't query the wrong Node by accident. I'm not sure if query speed matters at all, we would need some numbers to verify that. Also note that TestFX runs the query on all open Windows, to be more precise from all Windows' root Nodes.

If query speed really matters, we could implement NodeQuery with support for laziness. The class is actually designed to make it easier to implement laziness if we need it (queryFirst() and queryAll() deliver important information on the intention). NodeQuery once supported lazy queries to a certain degree, but I removed it in order to focus on fail-first and better error messages.
 
I don't think a SpreadsheetViewMatcher will make sense.. At least it will do for me but it should be private to my tests. But I can improve the TableViewMatcher since I will be using a lot from that.

Also, in order to use the Glass robot, are we compelled to use the JVM option? Can't we specify that somehow  in the code?

It is possible to set the properties manually within a static method, there is an example here [1]. Note that we have to set the properties before FxToolkit.registerPrimaryStage() was called, which initializes the JavaFX toolkit. This means, that we have to set the property in each Test class for test runs that run over all Test classes, because there is no explicit order in which the Test classes should be run. Using the static method bypasses this. Maybe we could add the ability to define which robot to be used in the META-INF package.
 
I saw that you needed Javadoc and examples, I'll maybe submit some ideas in your Github if you're interested. Since I was new to TestFX I might be able to write the basic questions/answer in order to begin writting some simple tests.

Javadocs and examples are very much appreciated. Very good idea.
 
Keep up the great work!

samir.ha...@gmail.com

unread,
Apr 7, 2015, 5:24:36 AM4/7/15
to testfx-...@googlegroups.com, samir.ha...@gmail.com
Allright thank you for all the answers.

I've managed to run in headless mode but I needed to set the properties directly using "System.setProperty" in my "@BeforeClass".. Because if I set the arguments through the JVM via "-DXXX=XX" it was not working.. But maybe my Netbeans configurations is wrong I don't know..

In order to use Monocle, I used the org.jfxtras Maven :
 <dependency>
           
<groupId>org.jfxtras</groupId>
           
<artifactId>openjfx-monocle</artifactId>
           
<version>1.8.0_20</version>
           
<scope>test</scope>
       
</dependency>

So the headless mode was working I guess (I wasn't seeing anything on my screen so that should be a clue no? ^^)

But one test is failing with Monocle, and it's not in normal mode so it's kind of weird. Here's what I do :
//Find first cell
       
NodeQuery query = findCell(view, 0, 0);
       
CellView cell = query.queryFirst();
       
Assert.assertNotNull(cell);

        doubleClickOn
(cell);

       
TextField editor = (TextField) cell.getGraphic();

       
Assert.assertNotNull(editor);

In fact it's failing here, the editor is null. So apparently, the double click is not bringing the editor in the cell. Do you have any clue on that?

I wanted to use the 8u40 version of Monocle (I'm using 8u20) but I did not succeed to find it or build it. I looked at the description in https://github.com/TestFX/Monocle . I downloaded the 8u40 openJFX source and replaced the file but
gradlew clean jar

failed. But I had several directories under "Monocle\src\main\java\com\sun\glass\ui\monocle" and there was not in "rt-e00e97499831\modules\graphics\src\main\java\com\sun\glass\ui\monocle".. So I guess the problem would come from here since I replaced some files but not all of them..

So I'll stick to the non headless mode right now but I hope to resolve that problem later. And by the way, I have a click and drag and drop test and it's apparently working with Monocle in Headless so good news huh?

Sam'

Benjamin Gudehus

unread,
Apr 7, 2015, 12:34:14 PM4/7/15
to Sam', testfx-...@googlegroups.com
I've managed to run in headless mode but I needed to set the properties directly using "System.setProperty" in my "@BeforeClass".. Because if I set the arguments through the JVM via "-DXXX=XX" it was not working.. But maybe my Netbeans configurations is wrong I don't know..

If Netbeans uses Maven you might use the environment variable `MAVEN_OPTS` to set the JVM properties.
 
So the headless mode was working I guess (I wasn't seeing anything on my screen so that should be a clue no? ^^)

You should see no window on you screen, but the tests in your IDE's test browser running and passing. :) I hope to improve the error handling of headless runs in the next week. Failed test screenshots should include the error text and show an red bounding box for the failed Node.
 
In fact it's failing here, the editor is null. So apparently, the double click is not bringing the editor in the cell. Do you have any clue on that?

Have you called show() of the Stage in order to run the layout pulse? This is the first thing which came in my mind.
 
So I'll stick to the non headless mode right now but I hope to resolve that problem later. And by the way, I have a click and drag and drop test and it's apparently working with Monocle in Headless so good news huh?

The DragAndDropBug occurs when the GlassRobot is used without Monocle. Automatic mouse movement will freeze.

--Benjamin

samir.ha...@gmail.com

unread,
Apr 7, 2015, 4:16:46 PM4/7/15
to testfx-...@googlegroups.com, samir.ha...@gmail.com
Well I did a "FxToolkit.registerPrimaryStage()" and "FxToolkit.showStage()" in the static method @BeforeClass.

Benjamin Gudehus

unread,
Apr 7, 2015, 5:21:48 PM4/7/15
to Sam', testfx-...@googlegroups.com
This works for me. The CSS-based query returns the wrong cell somehow (see comments behind the point-query in the @Test method).

~~~java
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;

import com.google.common.base.Predicate;
import impl.org.controlsfx.spreadsheet.CellView;
import org.controlsfx.control.spreadsheet.SpreadsheetCell;
import org.controlsfx.control.spreadsheet.SpreadsheetView;
import org.junit.Before;
import org.junit.Test;
import org.testfx.api.FxRobot;
import org.testfx.matcher.base.NodeMatchers;

import static org.hamcrest.Matchers.instanceOf;
import static org.testfx.api.FxAssert.verifyThat;
import static org.testfx.api.FxToolkit.registerPrimaryStage;
import static org.testfx.api.FxToolkit.setupStage;

public class SpreadsheetViewTest extends FxRobot {
    SpreadsheetView spreadsheetView;

    @Before
    public void setup() throws Exception {
        registerPrimaryStage();
        setupStage(stage -> {
            spreadsheetView = new SpreadsheetView();
            spreadsheetView.getGrid().setCellValue(0, 0, "foo");
            StackPane sceneRoot = new StackPane(spreadsheetView);
            stage.setScene(new Scene(sceneRoot, 400, 400));
            stage.setX(0);
            stage.setY(0);
            stage.show();
        });
    }

    @Test
    public void should_click_in_cell() throws Exception {
        // given:
        CellView cell = spreadsheetCell2(spreadsheetView, 0, 0);
        System.out.println(point(spreadsheetCell(spreadsheetView, 0, 0)).query()); // Point2D [x = 81.0, y = 392.5]
        System.out.println(point(spreadsheetCell2(spreadsheetView, 0, 0)).query()); // Point2D [x = 81.0, y = 37.0]

        // when:
        doubleClickOn(cell);

        // then:
        verifyThat(cell.getGraphic(), NodeMatchers.isNotNull());
        verifyThat(cell.getGraphic(), instanceOf(TextField.class));
    }

    private CellView spreadsheetCell(SpreadsheetView spreadsheetView,
                                     int rowIndex,
                                     int cellIndex) {
        return from(spreadsheetView)
            .lookup(".table-row-cell").selectAt(rowIndex)
            .lookup(".spreadsheet-cell").selectAt(cellIndex)
            .queryFirst();
    }

    private CellView spreadsheetCell2(SpreadsheetView spreadsheetView,
                                      int rowIndex,
                                      int cellIndex) {
        Predicate<Node> nodePredicate = (Node node) -> {
            if ((node instanceof CellView) && ((CellView) node).getItem() != null) {
                SpreadsheetCell cell = ((CellView) node).getItem();
                if (cell.getRow() == rowIndex && cell.getColumn() == cellIndex) {
                    return true;
                }
            }
            return false;
        };
        return from(spreadsheetView).lookup(nodePredicate).queryFirst();
    }
}
~~~

samir.ha...@gmail.com

unread,
Apr 8, 2015, 4:03:54 AM4/8/15
to testfx-...@googlegroups.com, samir.ha...@gmail.com
I tested your code and it's not working in headless mode on my computer..

In non-headless mode it's working and I do see the difference between the different cell retrieval methods.

Benjamin Gudehus

unread,
Apr 8, 2015, 12:11:34 PM4/8/15
to Sam', testfx-...@googlegroups.com
It works for me headless and non-headless.

Configuration: Windows 7, Java 1.8.0_20, TestFX 4.0.1-alpha, OpenJFX Monocle 1.8.0_20, ControlsFX 8.20.8.

When I change Java to 1.8.0_40 my code will throw an exception related to ControlsFX:

~~~
Caused by: java.lang.InstantiationError: com.sun.javafx.scene.control.SelectedCellsMap
    at impl.org.controlsfx.spreadsheet.SpreadsheetViewSelectionModel.<init>(SpreadsheetViewSelectionModel.java:198)
    at org.controlsfx.control.spreadsheet.SpreadsheetView.<init>(SpreadsheetView.java:395)
    at org.controlsfx.control.spreadsheet.SpreadsheetView.<init>(SpreadsheetView.java:356)
    at org.testfx.cases.sample.SpreadsheetViewTest.lambda$setup$0(SpreadsheetViewTest.java:36)
    ...
~~~

Benjamin Gudehus

unread,
Apr 8, 2015, 12:12:53 PM4/8/15
to Sam', testfx-...@googlegroups.com
Addendum: Here are the properties I set for headless:

~~~
static {
    System.setProperty("testfx.robot", "glass");
    System.setProperty("testfx.headless", "true");
    System.setProperty("prism.order", "sw");
    System.setProperty("prism.text", "t2k");
}
~~~

Note: It makes no diffence, if I set prism.text to t2k or not.

samir.ha...@gmail.com

unread,
Apr 8, 2015, 12:30:09 PM4/8/15
to testfx-...@googlegroups.com, samir.ha...@gmail.com
Can you use the latest snapshot of ControlsFX here with Java 8u40 and tell me if it's still working?

I used your configuration and things are not working on my side so I don't know if it's something I did on ControlsFX or Java 8u40.

On my side I have :
Configuration: Windows 7, Java 1.8.0_40, TestFX 4.0.1-alpha, OpenJFX Monocle 1.8.0_20, ControlsFX Latest snapshot

Benjamin Gudehus

unread,
Apr 9, 2015, 11:49:41 AM4/9/15
to Sam', testfx-...@googlegroups.com
Works without problems headless and non-headless.

~~~groovy
dependencies {
    // ...
    testCompile "org.controlsfx:controlsfx:8.20.9-SNAPSHOT"
}

repositories {
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
~~~

~~~java

@Test
public void should_click_in_cell() throws Exception {
    sleep(5, TimeUnit.SECONDS); // let us see the stage for a moment.


    // given:
    CellView cell = spreadsheetCell2(spreadsheetView, 0, 0);
    //System.out.println(point(spreadsheetCell(spreadsheetView, 0, 0)).query()); // Point2D [x = 81.0, y = 392.5]

    System.out.println(point(spreadsheetCell2(spreadsheetView, 0, 0)).query()); // Point2D [x = 81.0, y = 37.0]

    // when:
    doubleClickOn(cell);

    // then:
    verifyThat(cell.getGraphic(), NodeMatchers.isNotNull());
    verifyThat(cell.getGraphic(), instanceOf(TextField.class));
}
~~~

samir.ha...@gmail.com

unread,
Apr 10, 2015, 6:19:57 AM4/10/15
to testfx-...@googlegroups.com, samir.ha...@gmail.com
Hi Benjamin,

Well it worked! In fact I was doing that in the BeforeClass :
FxToolkit.registerPrimaryStage();
FxToolkit.showStage();
 and apparently it was not working. But I replaced it by the setupStage you used and then it was working. Is the "stage.show()" not called in FxToolkit.showStage(); ?

So now all tests are passing except for one, that is passing in non-headless but not in headless. If you have time, I would appreciate if you could take a look and tell me what is wrong. Basically, I set a Stage in the BeforeClass, and I want to keep it, and just instantiate a SpreadsheetView before each test. But I seem to be doing it wrong..

package com.nellarmonia.shuttlefx;

import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;

import com.sun.javafx.tk.Toolkit;
import impl.org.controlsfx.spreadsheet.CellView;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.input.KeyCode;

import org.controlsfx.control.spreadsheet.SpreadsheetCell;
import org.controlsfx.control.spreadsheet.SpreadsheetView;
import org.junit.Before;
import org.junit.Test;
import org.testfx.api.FxRobot;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.testfx.api.FxToolkit;

import static org.testfx.api.FxToolkit.registerPrimaryStage;
import static org.testfx.api.FxToolkit.setupStage;
import org.testfx.service.query.NodeQuery;

public class TestLol extends FxRobot {

   
static SpreadsheetView view;


   
static {
       
System.setProperty("testfx.robot", "glass");
       
System.setProperty("testfx.headless", "true");
       
System.setProperty("prism.order", "sw");
       
System.setProperty("prism.text", "t2k");
   
}


   
@BeforeClass
   
public static void beforeClass() throws Exception {
        registerPrimaryStage
();
        setupStage
(stage -> {
            view
= new SpreadsheetView();
           
StackPane sceneRoot = new StackPane(view);

            stage
.setScene(new Scene(sceneRoot, 400, 400));
            stage
.setX(0);
            stage
.setY(0);
            stage
.show();
       
});
   
}


   
@Before
   
public void before() throws Exception {
       
FxToolkit.setupSceneRoot(new Supplier<Parent>() {

           
@Override
           
public Parent get() {
                view
= new SpreadsheetView();
               
StackPane sceneRoot = new StackPane(view);
               
return sceneRoot;
           
}
       
});
   
}

   
@Test
   
public void testPressingTab() {
       
CellView cell = findCell(view, 0, 0).queryFirst();
       
Assert.assertNotNull(cell);

       
//Select first
        clickOn
(cell);
        type
(KeyCode.TAB);
        verifySelectedCell
(view, 0, 1);

       
TableView<ObservableList<SpreadsheetCell>> tableView = getTableView();

       
try {
           
//Select last on line
           
FxToolkit.setupFixture(() -> {
                tableView
.scrollToColumnIndex(view.getGrid().getColumnCount() - 1);
               
Toolkit.getToolkit().firePulse();
           
});
       
} catch (TimeoutException ex) {
           
Logger.getLogger(KeyTest.class.getName()).log(Level.SEVERE, null, ex);
       
}

        cell
= findCell(view, 0, view.getGrid().getColumnCount() - 1).queryFirst();
       
Assert.assertNotNull(cell);
        clickOn
(cell);
        type
(KeyCode.TAB);
        verifySelectedCell
(view, 1, 0);

       
try {
           
//Select last
           
FxToolkit.setupFixture(() -> {
                tableView
.scrollToColumnIndex(view.getGrid().getColumnCount() - 1);
                tableView
.scrollTo(view.getGrid().getRowCount() - 1);
               
Toolkit.getToolkit().firePulse();
           
});
       
} catch (TimeoutException ex) {
           
Logger.getLogger(KeyTest.class.getName()).log(Level.SEVERE, null, ex);
       
}

        cell
= findCell(view, view.getGrid().getRowCount() - 1, view.getGrid().getColumnCount() - 1).queryFirst();
       
Assert.assertNotNull(cell);
        clickOn
(cell);
        type
(KeyCode.TAB);
        verifySelectedCell
(view, view.getGrid().getRowCount() - 1, view.getGrid().getColumnCount() - 1);
   
}

   
protected TableView<ObservableList<SpreadsheetCell>> getTableView() {
       
return view.getSelectionModel().getTableView();
   
}

   
/**
     * Verify that only the specified row and column intersection is selected.
     *
     * @param view
     * @param row
     * @param column
     */

   
protected void verifySelectedCell(SpreadsheetView view, int row, int column) {
       
for (TablePosition pos : view.getSelectionModel().getSelectedCells()) {
           
SpreadsheetCell cell = view.getGrid().getRows().get(pos.getRow()).get(pos.getColumn());
           
Assert.assertEquals(row, cell.getRow());
           
Assert.assertEquals(column, cell.getColumn());
       
}
   
}

   
protected NodeQuery findCell(SpreadsheetView view, int row, int column) {
       
return from(view).lookup((Node t) -> {

           
if ((t instanceof CellView) && ((CellView) t).getItem() != null) {
               
SpreadsheetCell cell1 = ((CellView) t).getItem();

               
if (cell1.getRow() == row && cell1.getColumn() == column) {

                   
return true;
               
}
           
}
           
return false;
       
});
   
}
}

Benjamin Gudehus

unread,
Apr 10, 2015, 12:13:08 PM4/10/15
to Sam', testfx-...@googlegroups.com
Here the error message for reference:

~~~
java.lang.AssertionError:
Expected :1
Actual   :0
    at org.junit.Assert.failNotEquals(Assert.java:834)
    at org.testfx.cases.sample.TestLol.verifySelectedCell(TestLol.java:128)
    at org.testfx.cases.sample.TestLol.testPressingTab(TestLol.java:94)
~~~

The problem is, that Monocle provides a headless display with a certain screen size (com.sun.glass.ui.monocle.headless.HeadlessScreen uses 1280x800 with 32 bit per default). SpreadsheetView causes the Stage to have a width larger than 1280px (at least on my machine). If you don't set a size in the Scene explicitly, it will use the size of it's children. FxToolkit.setupSceneRoot() will create a Scene without explicit dimension.

--

Benjamin Gudehus

unread,
Apr 12, 2015, 11:49:21 AM4/12/15
to Sam', testfx-...@googlegroups.com
Here are some quick points on best-practices that (which I think) might improve the tests:

- introduce variables `lastColumnIndex` (and `lastRowIndex`) for `view.getGrid().getColumnCount() - 1`. Reason: tests should be very easy to understand. use speaking names to help developers understand the tests. this also helps getting rid of the one-line comments (e.g. select last on line.)

- use `FxRobot#interact()` instead of `FxToolkit.setupFixture()`. Reason: I suggest to use setupFixture() for the "arrange" step and interact() for the "act" step in traditional arrange-act-assert tests. interact() also waits for the next pulse.

- don't try-catch and log the errors. Let JUnit catch and report the exception as an error. Reason: test can pass, fail and error. If a test fails, we know that our assertion was not met, if a test errors, we know that something in the test is wrong.

- split testPressingTab() into three tests that test different states (tab from first cell; tab from last cell; tab from last cell and row). Reason: This helps us to faster find the failing test.

- don't use Assert.assertNotNull(cell). instead use nodeQuery.tryQueryFirst().get(). Reason: Finding the cell is part of the arrangement and not of the assertion. Let the Optional raise an exception (JUnit: error) and not Assert raise an AssertionException (JUnit: fail).

- structure you tests in given-when-then blocks (or arrange-act-assert). Reason: This helps developers to quickly comprehend the tests.

- don't use Assert.assertEquals() without context in verifySelectedCell(). Write tests that fail, improve the error message, and change them to pass. Reason: This will better guide developers. Maybe something like `verifyThat(selectedCells(view), hasItem(tablePosition(row, column))`.

- generally prefer FxAssert.verifyThat() over Assert.assert*(). Reason: verifyThat() writes a screenshot on failures (currently needs to be added back in TestFX 4).

--Benjamin

Benjamin Gudehus

unread,
Apr 12, 2015, 11:54:04 AM4/12/15
to Sam', testfx-...@googlegroups.com
Addendum:

- use underscores in test method names. Reason: this improves the readability. a suggestion from neal ford's book "the productive programmer".

samir.ha...@gmail.com

unread,
Apr 13, 2015, 6:06:33 AM4/13/15
to testfx-...@googlegroups.com, samir.ha...@gmail.com


Thank you very much for all the tips! I will modify my tests according to these right away!

samir.ha...@gmail.com

unread,
Apr 15, 2015, 11:41:36 AM4/15/15
to testfx-...@googlegroups.com, samir.ha...@gmail.com
Hi Benjamin,

Does the "press" method supposed to keep a KeyCode pressed?

Because I have a test where I want to press the shortcut key and do something. If I use KeyCode.SHORTCUT it does not work. If I use KeyCode.CONTROL, it is working..

import java.util.concurrent.TimeoutException;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.testfx.api.FxToolkit;
import static org.testfx.api.FxToolkit.setupStage;

public class LolTest extends SpreadsheetUtils {

   
protected Label label;

   
@BeforeClass
   
public static void setupClass() throws TimeoutException {
       
FxToolkit.registerPrimaryStage();

   
}

   
@Before
   
public void setup() throws Exception {

        setupStage
(localStage -> {
            label
= new Label();
            label
.setOnMouseClicked(new EventHandler<MouseEvent>() {

               
@Override
               
public void handle(MouseEvent event) {
                   
Assert.assertTrue(event.isShortcutDown());
               
}
           
});
           
StackPane sceneRoot = new StackPane(label);
            localStage
.setScene(new Scene(sceneRoot, 1000, 800));
            localStage
.setX(0);
            localStage
.setY(0);
            localStage
.show();
       
});
   
}

   
@After
   
public void after() throws TimeoutException {
       
FxToolkit.cleanupStages();
       
FxToolkit.hideStage();
   
}
   
   
@Test
   
public void test_shortcut(){
        press
(KeyCode.SHORTCUT);
        clickOn
(label);
   
}
}

samir.ha...@gmail.com

unread,
Apr 15, 2015, 11:54:36 AM4/15/15
to testfx-...@googlegroups.com, samir.ha...@gmail.com
Please ignore my precedent message.

I have a problem with the headless mode with the press method. Look at this test :
import java.util.concurrent.TimeoutException;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.testfx.api.FxRobot;

import org.testfx.api.FxToolkit;
import static org.testfx.api.FxToolkit.setupStage;

public class PressTest extends FxRobot{


   
static {
       
System.setProperty("testfx.robot", "glass");
//        System.setProperty("testfx.headless", "true");

       
System.setProperty("prism.order", "sw");
       
System.setProperty("prism.text", "t2k");
   
}

   
   
protected Label label;


   
@BeforeClass
   
public static void setupClass() throws TimeoutException {
       
FxToolkit.registerPrimaryStage();
   
}

   
@Before
   
public void setup() throws Exception {
        setupStage
(localStage -> {
            label
= new Label();
            label
.setOnMouseClicked(new EventHandler<MouseEvent>() {

               
@Override
               
public void handle(MouseEvent event) {

                   
Assert.assertTrue(event.isShiftDown());

               
}
           
});
           
StackPane sceneRoot = new StackPane(label);
            localStage
.setScene(new Scene(sceneRoot, 1000, 800));
            localStage
.setX(0);
            localStage
.setY(0);
            localStage
.show();
       
});
   
}

   
@After
   
public void after() throws TimeoutException {
       
FxToolkit.cleanupStages();
       
FxToolkit.hideStage();
   
}
   
   
@Test

   
public void test_shift(){
        press
(KeyCode.SHIFT);
        clickOn
(label);
        release
(KeyCode.SHIFT);
   
}
}

You will see it passing. Now un-comment the headless property and it will fail.. Any idea?

Also note that the same behavior with the SHortCut is failing in both headless and non headless....

import java.util.concurrent.TimeoutException;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.StackPane;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.testfx.api.FxRobot;

import org.testfx.api.FxToolkit;
import static org.testfx.api.FxToolkit.setupStage;

public class PressTest extends FxRobot{


   
static {
       
System.setProperty("testfx.robot", "glass");
//        System.setProperty("testfx.headless", "true");

       
System.setProperty("prism.order", "sw");
       
System.setProperty("prism.text", "t2k");
   
}

   
   
protected Label label;

        release
(KeyCode.SHORTCUT);
   
}
}

Benjamin Gudehus

unread,
Apr 17, 2015, 11:57:52 AM4/17/15
to Sam', testfx-...@googlegroups.com
Hmm, it seems we need a complete test story for the Robot functionality for headless and non-headless.

I'll check this out.

Reply all
Reply to author
Forward
0 new messages