From 808086d3df8228830ba6fb1c8a6c3623718ccce4 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Thu, 24 Sep 2020 10:56:40 +0300 Subject: [PATCH 01/19] wrap runKeyword to asyncFx so that JavaFX operations are done FX thread --- pom.xml | 2 +- src/main/java/JavaFXLibrary.java | 3 +- .../keywords/Keywords/ClickRobot.java | 3 +- .../matchers/ExtendedNodeMatchers.java | 4 +- .../javafxlibrary/utils/HelperFunctions.java | 45 +++++++++---------- .../acceptance/0_ClickRobotTest.robot | 6 +-- 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/pom.xml b/pom.xml index cc67a5d..ae34b49 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ org.robotframework javafxlibrary jar - 0.5.5-SNAPSHOT + 0.6.1-SNAPSHOT UTF-8 1.44 diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index 8e9dd06..5842607 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -42,6 +42,7 @@ import javax.script.ScriptEngineManager; import javax.script.ScriptException; import static javafxlibrary.utils.HelperFunctions.*; +import static org.testfx.util.WaitForAsyncUtils.asyncFx; import static org.testfx.util.WaitForAsyncUtils.waitFor; import java.util.ResourceBundle; @@ -179,7 +180,7 @@ public Object runKeyword(String keywordName, List args) { waitFor(getWaitUntilTimeout(TimeUnit.MILLISECONDS) + 500, TimeUnit.MILLISECONDS, () -> { try { - retval.set(super.runKeyword(keywordName, finalArgs)); + retval.set(asyncFx(() -> super.runKeyword(keywordName, finalArgs)).get()); return true; } catch (JavaFXLibraryTimeoutException jfxte) { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java index 33f7dfe..0e846f9 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java @@ -49,12 +49,13 @@ public class ClickRobot extends TestFxAdapter { + "| Click On | .css-name | Motion=VERTICAL_FIRST | \n") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface clickOn(Object locator, String motion) { + try { Object target = checkClickTarget(locator); RobotLog.info("Clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "clickOn", target.getClass(), Motion.class, MouseButton.class); - try { + return (FxRobotInterface) method.invoke(robot, target, getMotion(motion), new MouseButton[]{MouseButton.PRIMARY}); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute click on using locator \"" + locator + "\" " + diff --git a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java index d2bd9c7..50f7996 100644 --- a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java +++ b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java @@ -31,8 +31,6 @@ import static javafxlibrary.utils.HelperFunctions.getHoveredNode; import static org.testfx.util.WaitForAsyncUtils.waitFor; -import static org.testfx.util.WaitForAsyncUtils.asyncFx; -import static org.testfx.util.WaitForAsyncUtils.waitForFxEvents; public class ExtendedNodeMatchers { @@ -58,7 +56,7 @@ public void describeMismatch(Object object, Description description) { private static boolean hoverable(Node node) { try { waitFor(HelperFunctions.getWaitUntilTimeout(), HelperFunctions.getTimeUnit("SECONDS"), () -> { - return asyncFx(() -> new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT") != null).get(); + return new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT") != null; }); return node.isHover(); } catch (JavaFXLibraryNonFatalException nfe) { diff --git a/src/main/java/javafxlibrary/utils/HelperFunctions.java b/src/main/java/javafxlibrary/utils/HelperFunctions.java index b932560..a43ee85 100644 --- a/src/main/java/javafxlibrary/utils/HelperFunctions.java +++ b/src/main/java/javafxlibrary/utils/HelperFunctions.java @@ -68,8 +68,6 @@ import static javafxlibrary.utils.TestFxAdapter.robot; import static org.testfx.matcher.base.NodeMatchers.*; import static org.testfx.util.WaitForAsyncUtils.waitFor; -import static org.testfx.util.WaitForAsyncUtils.asyncFx; -import static org.testfx.util.WaitForAsyncUtils.waitForFxEvents; public class HelperFunctions { @@ -85,10 +83,10 @@ public static Node waitUntilExists(String target, int timeout, String timeUnit) + timeout + ", timeUnit=" + timeUnit); try { - waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) != null).get()); - Node node = asyncFx(() -> createFinder().find(target)).get(); + waitFor(timeout, getTimeUnit(timeUnit), () -> createFinder().find(target) != null); + Node node = createFinder().find(target); // TODO: Add null checks for node.getScene() - waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> hasValidCoordinates(node)).get()); + waitFor(timeout, getTimeUnit(timeUnit), () -> hasValidCoordinates(node)); return node; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -106,7 +104,7 @@ public static void waitUntilDoesNotExists(String target, int timeout, String tim + timeout + ", timeUnit=" + timeUnit); try { - waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) == null).get()); + waitFor(timeout, getTimeUnit(timeUnit), () -> createFinder().find(target) == null); } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; } catch (TimeoutException te) { @@ -129,7 +127,7 @@ public static Node waitUntilVisible(Object target, int timeout) { RobotLog.trace("Waiting until target \"" + target + "\" becomes visible, timeout=" + timeout); try { - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isVisible()).matches(finalTarget)).get()); + waitFor(timeout, TimeUnit.SECONDS, () -> Matchers.is(isVisible()).matches(finalTarget)); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -151,7 +149,7 @@ public static Node waitUntilInvisible(Object target, int timeout) { RobotLog.trace("Waiting until target \"" + target + "\" becomes invisible, timeout=" + timeout); try { - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isInvisible()).matches(finalTarget)).get()); + waitFor(timeout, TimeUnit.SECONDS, () -> Matchers.is(isInvisible()).matches(finalTarget)); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -172,7 +170,7 @@ public static Node waitUntilEnabled(Object target, int timeout) { RobotLog.trace("Waiting until target \"" + target + "\" becomes enabled, timeout=" + timeout); try { - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> (Matchers.is(isEnabled()).matches(finalTarget))== true).get()); + waitFor(timeout, TimeUnit.SECONDS, () -> (Matchers.is(isEnabled()).matches(finalTarget))== true); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -193,7 +191,7 @@ public static Node waitUntilDisabled(Object target, int timeout) { RobotLog.trace("Waiting until target \"" + target + "\" becomes disabled, timeout=" + timeout); try { - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isDisabled()).matches(finalTarget) == true).get()); + waitFor(timeout, TimeUnit.SECONDS, () -> Matchers.is(isDisabled()).matches(finalTarget) == true); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -207,7 +205,7 @@ public static Node waitUntilDisabled(Object target, int timeout) { public static void waitForProgressBarToFinish(ProgressBar pb, int timeout) { try { - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(ProgressBarMatchers.isComplete()).matches(pb)).get()); + waitFor(timeout, TimeUnit.SECONDS, () -> Matchers.is(ProgressBarMatchers.isComplete()).matches(pb)); } catch (TimeoutException te) { throw new JavaFXLibraryNonFatalException("Given ProgressBar did not complete in " + timeout + " seconds!"); } @@ -494,20 +492,21 @@ public static void checkClickLocation(int x, int y) { } public static void checkClickLocation(Object object) { - - RobotLog.trace("Checking if target \"" + object.toString() + "\" is within active window"); - - if (safeClicking) { - - Point2D point = getCenterPoint(objectToBounds(object)); - - if (!visibleWindowsContain(robot.listWindows(), point)) { - throw new JavaFXLibraryNonFatalException("Can't click " + object.getClass().getSimpleName() + " at [" + point.getX() + - ", " + point.getY() + "]: out of window bounds. " + - "To enable clicking outside of visible window bounds use keyword SET SAFE CLICKING | OFF"); + try { + RobotLog.trace("Checking if target \"" + object.toString() + "\" is within active window"); + if (safeClicking) { + RobotLog.trace("object type is: " + object.getClass()); + Point2D point = getCenterPoint(objectToBounds(object)); + if (!visibleWindowsContain(robot.listWindows(), point)) { + throw new JavaFXLibraryNonFatalException("Can't click " + object.getClass().getSimpleName() + " at [" + point.getX() + + ", " + point.getY() + "]: out of window bounds. " + + "To enable clicking outside of visible window bounds use keyword SET SAFE CLICKING | OFF"); + } } + RobotLog.trace("Target location checks out OK, it is within active window"); + } catch (Exception e) { + throw new JavaFXLibraryNonFatalException("Click target location check failed: " + e.getMessage(), e); } - RobotLog.trace("Target location checks out OK, it is within active window"); } public static void verifyClickLocationOnFront(Object object) { diff --git a/src/test/robotframework/acceptance/0_ClickRobotTest.robot b/src/test/robotframework/acceptance/0_ClickRobotTest.robot index 7b46658..e1e0a2b 100644 --- a/src/test/robotframework/acceptance/0_ClickRobotTest.robot +++ b/src/test/robotframework/acceptance/0_ClickRobotTest.robot @@ -223,12 +223,12 @@ Click On Unreachable Point [Tags] smoke negative ${POINT} Create Point ${0} ${-20} ${MSG} Run Keyword And Expect Error * Click On ${POINT} - Should Start With ${MSG} Can't click Point2D at [0.0, -20.0]: out of window bounds. + Should Start With ${MSG} Click target location check failed: Can't click Point2D at [0.0, -20.0]: out of window bounds. Click On Unreachable Coordinates [Tags] smoke negative ${MSG} Run Keyword And Expect Error * Click On Coordinates ${0} ${-20} - Should Start With ${MSG} Can't click Point2D at [0.0, -20.0]: out of window bounds. + Should Start With ${MSG} Click target location check failed: Can't click Point2D at [0.0, -20.0]: out of window bounds. Click On Mouse Button That Does Not Exist [Tags] smoke negative @@ -253,7 +253,7 @@ Click On Unsupported Type ${IMAGE} Capture Image ${NODE} ${PIXELREADER} Call Object Method ${IMAGE} getPixelReader ${MSG} Run Keyword And Expect Error * Click On ${PIXELREADER} - Should Start With ${MSG} Unsupported parameter type: + Should Start With ${MSG} Click target location check failed: Unsupported parameter type: *** Keywords *** Setup all tests From cb6b84d3922ff86e0011318ad6a0d03fe68b3530 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Fri, 25 Sep 2020 17:01:39 +0300 Subject: [PATCH 02/19] more asyncfx fixes, went through whole code base --- src/main/java/JavaFXLibrary.java | 64 +------------- .../ConvenienceKeywords.java | 28 ++++--- .../AdditionalKeywords/RunOnFailure.java | 11 ++- .../keywords/Keywords/BoundsLocation.java | 13 ++- .../keywords/Keywords/ClickRobot.java | 4 +- .../keywords/Keywords/KeyboardRobot.java | 2 +- .../keywords/Keywords/MoveRobot.java | 11 +-- .../keywords/Keywords/NodeLookup.java | 5 +- .../keywords/Keywords/PointLocation.java | 6 +- .../keywords/Keywords/PointOffset.java | 7 +- .../keywords/Keywords/ScreenCapturing.java | 2 +- .../matchers/ExtendedNodeMatchers.java | 7 +- .../javafxlibrary/utils/HelperFunctions.java | 83 ++++++++++++------- .../CheckClickLocationTest.java | 2 +- .../acceptance/MoveRobotTest.robot | 3 +- .../acceptance/NodeLookupTest.robot | 2 +- 16 files changed, 105 insertions(+), 145 deletions(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index 333359b..bb196da 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -120,11 +120,11 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { try { retval.set(super.runKeyword(keywordName, finalArgs, finalKwargs)); return true; - } catch (JavaFXLibraryTimeoutException jfxte) { // timeout already expired, catch exception and jump out retExcep.set(jfxte); throw jfxte; + } catch (RuntimeException e) { // catch exception and continue trying retExcep.set(e); @@ -160,68 +160,6 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { return retval.get(); } - // overriding the run method to catch the control in case of failure, so that desired runOnFailureKeyword - // can be executed in controlled manner. - @Override - public Object runKeyword(String keywordName, List args) { - // TODO: Check if this is ever called anymore - RobotLog.info("runKeyword called with args ONLY"); - - List finalArgs; - // JavalibCore changes arguments of Call Method keywords to Strings after this check, so they need to handle their own objectMapping - if (!(keywordName.equals("callObjectMethod") || keywordName.equals("callObjectMethodInFxApplicationThread"))) { - finalArgs = HelperFunctions.useMappedObjects(args); - } else { - finalArgs = args; - } - - AtomicReference retval = new AtomicReference<>(); - AtomicReference retExcep = new AtomicReference<>(); - - try { - RobotLog.ignoreDuplicates(); - // timeout + 500 ms so that underlying timeout has a chance to expire first - waitFor(getWaitUntilTimeout(TimeUnit.MILLISECONDS) + 500, TimeUnit.MILLISECONDS, () -> { - - try { - retval.set(asyncFx(() -> super.runKeyword(keywordName, finalArgs)).get()); - return true; - - } catch (JavaFXLibraryTimeoutException jfxte) { - // timeout already expired, catch exception and jump out - retExcep.set(jfxte); - throw jfxte; - - } catch (RuntimeException e) { - // catch exception and continue trying - retExcep.set(e); - return false; - } - }); - } catch (TimeoutException te) { - RobotLog.reset(); - RuntimeException e = retExcep.get(); - runOnFailure.runOnFailure(); - - if (e.getCause() instanceof JavaFXLibraryFatalException) { - RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary FATAL exception: \n" + Throwables.getStackTraceAsString(e)); - throw e; - } else if (e.getCause() instanceof JavaFXLibraryNonFatalException) { - RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary NON-FATAL exception: \n" + Throwables.getStackTraceAsString(e)); - throw e; - } else { - RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary RUNTIME exception: \n" + Throwables.getStackTraceAsString(e)); - throw e; - } - } catch (JavaFXLibraryTimeoutException jfxte) { - RobotLog.reset(); - RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary TIMEOUT exception: \n" + Throwables.getStackTraceAsString(jfxte)); - throw jfxte; - } - RobotLog.reset(); - return retval.get(); - } - @Override public String getKeywordDocumentation(String keywordName) { if (keywordName.equals("__intro__")) { diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index e5e07c1..bf30bbe 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -22,7 +22,6 @@ import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.css.PseudoClass; -import javafx.geometry.BoundingBox; import javafx.geometry.Rectangle2D; import javafx.scene.Node; import javafx.scene.Parent; @@ -35,7 +34,6 @@ import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.keywords.Keywords.ClickRobot; import javafxlibrary.keywords.Keywords.KeyboardRobot; -import javafxlibrary.matchers.InstanceOfMatcher; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; @@ -48,7 +46,7 @@ import java.lang.reflect.Method; import java.util.*; import java.util.concurrent.Semaphore; -import java.util.stream.Collectors; + import static javafxlibrary.utils.HelperFunctions.*; @RobotKeywords @@ -181,32 +179,36 @@ public void logFXML(Object root) { @RobotKeyword("Enables/Disables clicking outside of visible JavaFX application windows. Safe clicking is on by" + " default, preventing clicks outside of the tested application.\n\n" + - "``value`` can be any of the following: ON, on, OFF, off.\n\n" + "``value`` can be any of the following: on, off.\n\n" + "Parameter _value_ specifies whether safety should be toggled on or off") @ArgumentNames({ "value" }) public void setSafeClicking(String value) { - switch (value) { - case "OFF": + switch (value.toLowerCase()) { case "off": RobotLog.info("Setting safe clicking mode to OFF"); HelperFunctions.setSafeClicking(false); break; - case "ON": case "on": RobotLog.info("Setting safe clicking mode to ON"); HelperFunctions.setSafeClicking(true); break; default: - throw new JavaFXLibraryNonFatalException("Unknown value: \"" + value + "\". Expected values are: on, ON, off and OFF."); + throw new JavaFXLibraryNonFatalException("Unknown value: \"" + value + "\". Expected values are `on` or `off`"); } } - @RobotKeyword("Sets the time waited for nodes to become available. Default value is 5 seconds." - + "``timeout`` is an Integer value for timeout.") - @ArgumentNames({ "timeout" }) - public void setTimeout(int timeout) { + @RobotKeyword("Sets the time waited for nodes to become available. Keyword returns old timeout value as return " + + "value. Default value is 5 seconds.\n\n" + + "``timeout`` is an Integer value for timeout in seconds.\n\n" + + "\nExample:\n" + + "| ${old_timeout}= | Set Timeout | 20 | \n" + + "| Click On | id=myidthatshallcomeavailable | | \n" + + "| [Teardown] | Set Timeout | ${old_timeout} | \n") + public Integer setTimeout(int timeout) { RobotLog.info("Setting timeout to " + timeout + "s"); + Integer oldTimeoutValue = getWaitUntilTimeout(); setWaitUntilTimeout(timeout); + return oldTimeoutValue; } /* @@ -385,7 +387,7 @@ public Object getPrimaryScreenBounds() { try { RobotLog.info("Getting the primary screen bounds"); Rectangle2D bounds = Screen.getPrimary().getVisualBounds(); - return mapObject(new BoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight())); + return mapObject(threadSafeBoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight())); } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Unable to get primary screen bounds.", e); } diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java index 6e08c68..5612e5b 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java @@ -24,8 +24,15 @@ import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; +import java.lang.reflect.Method; +import org.apache.commons.lang3.reflect.MethodUtils; import org.robotframework.javalib.annotation.RobotKeywords; +import org.testfx.api.FxRobotInterface; +import org.testfx.robot.Motion; + +import static javafxlibrary.utils.HelperFunctions.getMotion; +import static javafxlibrary.utils.HelperFunctions.threadSafeRectangle2D; @RobotKeywords public class RunOnFailure extends TestFxAdapter{ @@ -47,7 +54,7 @@ public class RunOnFailure extends TestFxAdapter{ public void runOnFailure() { // The keyword to run an failure - String runOnFailureKeyword = "Take Screenshot"; + String runOnFailureKeyword = "Capture Primary Screen"; RobotLog.debug("Executing cleanup functions by running: " + runOnFailureKeyword); RobotLog.debug("runningOnFailureRoutine: " + runningOnFailureRoutine); @@ -65,7 +72,7 @@ public void runOnFailure() { } else { GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); RobotLog.debug("Capturing screenshot from primary screen with resolution "+gd.getDisplayMode().getWidth()+"x"+gd.getDisplayMode().getHeight()+"."); - new ScreenCapturing().captureImage(new Rectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()),true); + new ScreenCapturing().captureImage(threadSafeRectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()),true); } } catch (Exception e) { RobotLog.error("Error when capturing screenshot. Actual error: "+e.getMessage()); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java index 8804c04..80b7151 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java @@ -17,8 +17,6 @@ package javafxlibrary.keywords.Keywords; -import javafx.geometry.BoundingBox; -import javafx.geometry.Rectangle2D; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.RobotLog; @@ -27,7 +25,6 @@ import org.robotframework.javalib.annotation.ArgumentNames; import org.robotframework.javalib.annotation.RobotKeyword; import org.robotframework.javalib.annotation.RobotKeywords; -import javafx.geometry.Point2D; import javafx.scene.Scene; import javafx.stage.Window; import org.testfx.service.query.BoundsQuery; @@ -51,7 +48,7 @@ public Object createBounds(double minX, double minY, double width, double height try { RobotLog.info("Creating bounds object with minX=\"" + minX + "\", minY=\"" + minY + "\", width=\"" + width + "\" and height=\"" + height + "\""); - return mapObject(robot.bounds(minX, minY, width, height).query()); + return mapObject(threadSafeBoundingBox(minX, minY, width, height)); } catch (Exception e) { if ( e instanceof JavaFXLibraryNonFatalException ) throw e; @@ -69,7 +66,7 @@ public Object createBounds(double minX, double minY, double width, double height public Object createPoint(double x, double y) { try { RobotLog.info("Creating point object with x=\"" + x + "\"" + " and y=\"" + y + "\""); - return mapObject(new Point2D(x, y)); + return mapObject(threadSafePoint2D(x, y)); } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) throw e; @@ -88,7 +85,7 @@ public Object createRectangle(double minX, double minY, double width, double hei try { RobotLog.info("Creating rectangle object with minX=\"" + minX + "\", minY=\"" + minY + "\", width=\"" + width + "\" and height=\"" + height + "\""); - return mapObject(new Rectangle2D(minX, minY, width, height)); + return mapObject(threadSafeRectangle2D(minX, minY, width, height)); } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) throw e; @@ -110,10 +107,10 @@ public Object getBounds(Object locator) { try { if (locator instanceof Window) { Window window = (Window) locator; - return mapObject(new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight())); + return mapObject(threadSafeBoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight())); } else if (locator instanceof Scene) { Scene scene = (Scene) locator; - return mapObject(new BoundingBox(scene.getX() + scene.getWindow().getX(), scene.getY() + + return mapObject(threadSafeBoundingBox(scene.getX() + scene.getWindow().getX(), scene.getY() + scene.getWindow().getY(), scene.getWidth(), scene.getHeight())); } diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java index 0e846f9..371a8d6 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java @@ -49,13 +49,11 @@ public class ClickRobot extends TestFxAdapter { + "| Click On | .css-name | Motion=VERTICAL_FIRST | \n") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface clickOn(Object locator, String motion) { - try { Object target = checkClickTarget(locator); RobotLog.info("Clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "clickOn", target.getClass(), Motion.class, MouseButton.class); - - + try { return (FxRobotInterface) method.invoke(robot, target, getMotion(motion), new MouseButton[]{MouseButton.PRIMARY}); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute click on using locator \"" + locator + "\" " + diff --git a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java index a19f80e..ef380b1 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java @@ -150,7 +150,7 @@ public FxRobotInterface closeCurrentWindow() { return robot.push(KeyCode.META, KeyCode.W).sleep(100); } else if (robot instanceof FxRobot) { RobotLog.info("Closing window via: ALT + F4"); - return ((FxRobot) robot).closeCurrentWindow(); + return robot.push(KeyCode.ALT, KeyCode.F4).sleep(100); } throw new JavaFXLibraryNonFatalException("No instance available for closing."); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java index 27ba604..3b8b28e 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java @@ -18,7 +18,6 @@ package javafxlibrary.keywords.Keywords; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; -import javafxlibrary.utils.finder.Finder; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; @@ -32,7 +31,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import static javafxlibrary.utils.HelperFunctions.getMotion; +import static javafxlibrary.utils.HelperFunctions.*; @RobotKeywords public class MoveRobot extends TestFxAdapter { @@ -51,13 +50,7 @@ public FxRobotInterface moveTo(Object locator, String motion) { try { RobotLog.info("Moving to target \"" + locator + "\" using motion: \"" + getMotion(motion) + "\""); if (locator instanceof String) { - String originalLocator = (String) locator; - locator = new Finder().find((String) locator); - if(locator==null) { - throw new JavaFXLibraryNonFatalException("Unable to move as locator \"" + originalLocator + "\" not found!"); - } else { - RobotLog.info("Locator at this point: " + locator); - } + locator = waitUntilExists((String) locator, getWaitUntilTimeout(), "SECONDS"); } Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "moveTo", locator.getClass(), Motion.class); return (FxRobotInterface) method.invoke(robot, locator, getMotion(motion)); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java index 9ef5c58..013d059 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java @@ -31,6 +31,9 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import static javafxlibrary.utils.HelperFunctions.getWaitUntilTimeout; +import static javafxlibrary.utils.HelperFunctions.waitUntilExists; + @RobotKeywords public class NodeLookup extends TestFxAdapter { @@ -52,7 +55,7 @@ public class NodeLookup extends TestFxAdapter { @ArgumentNames({"locator"}) public Object getRootNodeOf(Object locator) { if (locator instanceof String) { - Node node = new Finder().find((String) locator); + Node node = waitUntilExists((String) locator, getWaitUntilTimeout(), "SECONDS"); if( node != null ) return getRootNodeOf(node); throw new JavaFXLibraryNonFatalException("Unable to find any node with query: \"" + locator.toString() + "\""); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java b/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java index b867321..d8607c2 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java @@ -17,6 +17,7 @@ package javafxlibrary.keywords.Keywords; +import javafx.scene.Node; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.utils.finder.Finder; import javafxlibrary.utils.HelperFunctions; @@ -30,6 +31,9 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import static javafxlibrary.utils.HelperFunctions.getWaitUntilTimeout; +import static javafxlibrary.utils.HelperFunctions.waitUntilExists; + @RobotKeywords public class PointLocation extends TestFxAdapter { @@ -48,7 +52,7 @@ public Object pointTo(Object locator) { RobotLog.info("Creating a point query for target \"" + locator + "\""); if (locator instanceof String) - locator = new Finder().find((String) locator); + locator = waitUntilExists((String) locator, getWaitUntilTimeout(), "SECONDS"); Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "point", locator.getClass()); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java b/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java index 6101ec2..2b98189 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java @@ -30,6 +30,9 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import static javafxlibrary.utils.HelperFunctions.getWaitUntilTimeout; +import static javafxlibrary.utils.HelperFunctions.waitUntilExists; + @RobotKeywords public class PointOffset extends TestFxAdapter { @@ -43,10 +46,8 @@ public class PointOffset extends TestFxAdapter { @ArgumentNames({"locator", "offsetX", "offsetY"}) public Object pointToWithOffset(Object locator, double offsetX, double offsetY) { RobotLog.info("Creating a point query for target: \"" + locator + "\" with offset: [" + offsetX + ", " + offsetY + "]"); - if (locator instanceof String) - locator = new Finder().find((String) locator); - + locator = waitUntilExists((String) locator, getWaitUntilTimeout(), "SECONDS"); Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "offset", locator.getClass(), double.class, double.class); try { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java index ae2a27f..2f62fcc 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java @@ -68,7 +68,7 @@ else if (value.toLowerCase().equals("diskonly")) @RobotKeyword("Returns a screenshot from whole primary screen. Note that this shows also other applications that are open.") public Object capturePrimaryScreen() { GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - return this.captureImage(new Rectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()),true); + return this.captureImage(threadSafeRectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()),true); } @RobotKeyword("Returns a screenshot of the given locator, or if not given from whole active window.\n\n" diff --git a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java index 50f7996..00825a6 100644 --- a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java +++ b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java @@ -55,15 +55,10 @@ public void describeMismatch(Object object, Description description) { private static boolean hoverable(Node node) { try { - waitFor(HelperFunctions.getWaitUntilTimeout(), HelperFunctions.getTimeUnit("SECONDS"), () -> { - return new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT") != null; - }); + new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT"); return node.isHover(); } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; - } catch (TimeoutException te) { - throw new JavaFXLibraryTimeoutException("Given element \"" + node + "\" was not found within given timeout of " - + HelperFunctions.getWaitUntilTimeout() + " " + "SECONDS"); } catch (Exception e) { RobotLog.trace("Exception in hoverable matcher: " + e + "\n" + e.getCause().toString()); throw new JavaFXLibraryNonFatalException("hoverable matcher failed: ", e); diff --git a/src/main/java/javafxlibrary/utils/HelperFunctions.java b/src/main/java/javafxlibrary/utils/HelperFunctions.java index 5f9d9e7..4a38caa 100644 --- a/src/main/java/javafxlibrary/utils/HelperFunctions.java +++ b/src/main/java/javafxlibrary/utils/HelperFunctions.java @@ -32,7 +32,6 @@ import javafxlibrary.keywords.AdditionalKeywords.ConvenienceKeywords; import javafxlibrary.matchers.ProgressBarMatchers; import javafxlibrary.utils.finder.Finder; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.maven.model.Model; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; @@ -60,14 +59,13 @@ import java.util.concurrent.TimeoutException; import java.util.jar.JarEntry; import java.util.jar.JarFile; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import static javafxlibrary.matchers.ExtendedNodeMatchers.hasValidCoordinates; import static javafxlibrary.utils.TestFxAdapter.objectMap; import static javafxlibrary.utils.TestFxAdapter.robot; import static org.testfx.matcher.base.NodeMatchers.*; import static org.testfx.util.WaitForAsyncUtils.waitFor; +import static org.testfx.util.WaitForAsyncUtils.asyncFx; public class HelperFunctions { @@ -83,10 +81,10 @@ public static Node waitUntilExists(String target, int timeout, String timeUnit) + timeout + ", timeUnit=" + timeUnit); try { - waitFor(timeout, getTimeUnit(timeUnit), () -> createFinder().find(target) != null); - Node node = createFinder().find(target); + waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) != null).get()); + Node node = asyncFx(() -> createFinder().find(target)).get(); // TODO: Add null checks for node.getScene() - waitFor(timeout, getTimeUnit(timeUnit), () -> hasValidCoordinates(node)); + waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> hasValidCoordinates(node)).get()); return node; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -104,7 +102,7 @@ public static void waitUntilDoesNotExists(String target, int timeout, String tim + timeout + ", timeUnit=" + timeUnit); try { - waitFor(timeout, getTimeUnit(timeUnit), () -> createFinder().find(target) == null); + waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) == null).get()); } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; } catch (TimeoutException te) { @@ -127,7 +125,7 @@ public static Node waitUntilVisible(Object target, int timeout) { RobotLog.trace("Waiting until target \"" + target + "\" becomes visible, timeout=" + timeout); try { - waitFor(timeout, TimeUnit.SECONDS, () -> Matchers.is(isVisible()).matches(finalTarget)); + waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isVisible()).matches(finalTarget)).get()); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -149,7 +147,7 @@ public static Node waitUntilInvisible(Object target, int timeout) { RobotLog.trace("Waiting until target \"" + target + "\" becomes invisible, timeout=" + timeout); try { - waitFor(timeout, TimeUnit.SECONDS, () -> Matchers.is(isInvisible()).matches(finalTarget)); + waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isInvisible()).matches(finalTarget)).get()); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -170,7 +168,7 @@ public static Node waitUntilEnabled(Object target, int timeout) { RobotLog.trace("Waiting until target \"" + target + "\" becomes enabled, timeout=" + timeout); try { - waitFor(timeout, TimeUnit.SECONDS, () -> (Matchers.is(isEnabled()).matches(finalTarget))== true); + waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> (Matchers.is(isEnabled()).matches(finalTarget))).get()); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -191,7 +189,7 @@ public static Node waitUntilDisabled(Object target, int timeout) { RobotLog.trace("Waiting until target \"" + target + "\" becomes disabled, timeout=" + timeout); try { - waitFor(timeout, TimeUnit.SECONDS, () -> Matchers.is(isDisabled()).matches(finalTarget) == true); + waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isDisabled()).matches(finalTarget)).get()); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -205,7 +203,7 @@ public static Node waitUntilDisabled(Object target, int timeout) { public static void waitForProgressBarToFinish(ProgressBar pb, int timeout) { try { - waitFor(timeout, TimeUnit.SECONDS, () -> Matchers.is(ProgressBarMatchers.isComplete()).matches(pb)); + waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(ProgressBarMatchers.isComplete()).matches(pb)).get()); } catch (TimeoutException te) { throw new JavaFXLibraryNonFatalException("Given ProgressBar did not complete in " + timeout + " seconds!"); } @@ -488,32 +486,32 @@ public static long getWaitUntilTimeout(TimeUnit timeUnit) { } public static void checkClickLocation(int x, int y) { - checkClickLocation(new Point2D(x, y)); + checkClickLocation(threadSafePoint2D(x,y)); } + public static void checkClickLocation(Object object) { try { - RobotLog.trace("Checking if target \"" + object.toString() + "\" is within active window"); if (safeClicking) { - RobotLog.trace("object type is: " + object.getClass()); + RobotLog.trace("Checking if target \"" + object.toString() + "\" is within active window"); + Point2D point = getCenterPoint(objectToBounds(object)); if (!visibleWindowsContain(robot.listWindows(), point)) { - throw new JavaFXLibraryNonFatalException("Can't click " + object.getClass().getSimpleName() + " at [" + point.getX() + - ", " + point.getY() + "]: out of window bounds. " + - "To enable clicking outside of visible window bounds use keyword SET SAFE CLICKING | OFF"); + throw new JavaFXLibraryNonFatalException("Can't click " + object.getClass().getSimpleName() + " at [" + point.getX() + ", " + point.getY() + "]: out of window bounds. " + + "To enable clicking outside of visible window bounds use keyword `Set Safe Clicking` with argument `off`"); } + RobotLog.trace("Target location checks out OK, it is within active window"); } - RobotLog.trace("Target location checks out OK, it is within active window"); } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Click target location check failed: " + e.getMessage(), e); } } - public static void verifyClickLocationOnFront(Object object) { + public static void bringClickLocationToFront(Object object) { try { new ConvenienceKeywords().bringStageToFront((Stage) objectToNode(object).getScene().getWindow()); } catch (Exception e) { - RobotLog.trace("Node's window wasn't castable to Stage. Tried with object: "+object); + RobotLog.trace("Node's window wasn't castable to Stage. Tried with object: " + object); } } @@ -522,7 +520,7 @@ public static Object checkClickTarget(Object target) { if (target instanceof String || target instanceof Node) { target = waitUntilEnabled(waitUntilVisible(target, waitUntilTimeout), waitUntilTimeout); - verifyClickLocationOnFront(target); + bringClickLocationToFront(target); } checkClickLocation(target); return target; @@ -539,7 +537,7 @@ public static boolean visibleWindowsContain(List windows, Point2D point) boolean contains = false; for (Window window : windows) { if (window.isShowing()) { - Bounds windowBounds = new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); + Bounds windowBounds = threadSafeBoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); if (windowBounds.contains(point)) contains = true; } @@ -548,8 +546,7 @@ public static boolean visibleWindowsContain(List windows, Point2D point) } public static Point2D getCenterPoint(Bounds bounds) { - RobotLog.trace("Getting center point for " + bounds); - return new Point2D(bounds.getMinX() + (bounds.getWidth() / 2), bounds.getMinY() + (bounds.getHeight() / 2)); + return threadSafePoint2D(bounds.getMinX() + (bounds.getWidth() / 2), bounds.getMinY() + (bounds.getHeight() / 2)); } public static boolean isCompatible(Object o) { @@ -610,34 +607,58 @@ else if (target instanceof Node) { throw new JavaFXLibraryNonFatalException("Given target \"" + target.getClass().getName() + "\" is not an instance of Node or a query string for node!"); } - + //TODO: check robot.* usage in relation to threading public static Bounds objectToBounds(Object object) { + RobotLog.trace("object type is: " + object.getClass()); if (object instanceof Window) { Window window = (Window) object; - return new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); + return threadSafeBoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); } else if (object instanceof Scene) { Scene scene = (Scene) object; double x = scene.getX() + scene.getWindow().getX(); double y = scene.getY() + scene.getWindow().getY(); - return new BoundingBox(x, y, scene.getWidth(), scene.getHeight()); + return threadSafeBoundingBox(x, y, scene.getWidth(), scene.getHeight()); } else if (object instanceof Point2D) { return robot.bounds((Point2D) object).query(); } else if (object instanceof Node) { return robot.bounds((Node) object).query(); } else if (object instanceof String) { - Node node = waitUntilExists((String) object, waitUntilTimeout, "SECONDS"); - return robot.bounds(node).query(); + return robot.bounds(objectToNode(object)).query(); } else if (object instanceof Bounds) { return (Bounds) object; } else if (object instanceof PointQuery) { return robot.bounds(((PointQuery) object).query()).query(); } else if (object instanceof Rectangle2D) { Rectangle2D r2 = (Rectangle2D) object; - return new BoundingBox(r2.getMinX(), r2.getMinY(), r2.getWidth(), r2.getHeight()); + return threadSafeBoundingBox(r2.getMinX(), r2.getMinY(), r2.getWidth(), r2.getHeight()); } else throw new JavaFXLibraryNonFatalException("Unsupported parameter type: " + object.getClass().getName()); } + public static Bounds threadSafeBoundingBox(double x, double y, double width, double height) { + try { + return waitFor(1, TimeUnit.SECONDS, asyncFx(()->new BoundingBox(x, y, width, height))); + } catch (TimeoutException te) { + throw new JavaFXLibraryNonFatalException("threadSafeBoundingBox: Timeout in BoundingBox object creation!"); + } + } + + public static Point2D threadSafePoint2D(double x, double y) { + try { + return waitFor(1, TimeUnit.SECONDS, asyncFx(() -> new Point2D(x,y))); + } catch (Exception e) { + throw new JavaFXLibraryNonFatalException("threadSafePoint2D: Timeout in Point2D object creation!"); + } + } + + public static Rectangle2D threadSafeRectangle2D(double x, double y, double width, double height) { + try { + return waitFor(1, TimeUnit.SECONDS, asyncFx(() -> new Rectangle2D(x,y,width,height))); + } catch (Exception e) { + throw new JavaFXLibraryNonFatalException("threadSafeRectangle2D: Timeout in Rectangle2D object creation!"); + } + } + private static String remainingQueries(String query) { String[] queries = query.split(" ", 2); String currentQuery = queries[0]; diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickLocationTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickLocationTest.java index daa8897..893d9f6 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickLocationTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickLocationTest.java @@ -66,7 +66,7 @@ public void checkClickLocation_IsWithinVisibleWindow() { public void checkClickLocation_IsOutsideVisibleWindow() throws Exception { setBoundsQueryExpectations(30, 800); String target = "Can't click Point2D at [30.0, 800.0]: out of window bounds. To enable clicking outside " + - "of visible window bounds use keyword SET SAFE CLICKING | OFF"; + "of visible window bounds use keyword `Set Safe Clicking` with argument `off`"; thrown.expect(JavaFXLibraryNonFatalException.class); thrown.expectMessage(target); diff --git a/src/test/robotframework/acceptance/MoveRobotTest.robot b/src/test/robotframework/acceptance/MoveRobotTest.robot index e36aa02..50f97f2 100644 --- a/src/test/robotframework/acceptance/MoveRobotTest.robot +++ b/src/test/robotframework/acceptance/MoveRobotTest.robot @@ -87,7 +87,8 @@ Move To Window Move To Nonexistent Location [Tags] smoke - Run Keyword And Expect Error Unable to move as locator "css=\#rectangleNOTfound" not found! Move To css=\#rectangleNOTfound + Run Keyword And Expect Error Given element "css=\#rectangleNOTfound" was not found within given timeout of 0 SECONDS + ... Move To css=\#rectangleNOTfound *** Keywords *** Setup all tests diff --git a/src/test/robotframework/acceptance/NodeLookupTest.robot b/src/test/robotframework/acceptance/NodeLookupTest.robot index 6350eff..52d3e8b 100644 --- a/src/test/robotframework/acceptance/NodeLookupTest.robot +++ b/src/test/robotframework/acceptance/NodeLookupTest.robot @@ -47,7 +47,7 @@ Root Node Of XPath Query Root Node Of Node That Does Not Exist [Tags] smoke negative ${MSG} Run Keyword And Expect Error * Get Root Node Of id=non-existent-node-id - Should Contain ${MSG} Unable to find any node with query: "id=non-existent-node-id" + Should Contain ${MSG} Given element "id=non-existent-node-id" was not found within given timeout of 1 SECONDS *** Keywords *** Setup all tests From bdbe02e7e0bb7333707ef19d7f208623c4fab43c Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Wed, 30 Sep 2020 10:59:28 +0300 Subject: [PATCH 03/19] new wrapping to asyncfx thread --- pom.xml | 159 +++++++++--------- src/main/java/JavaFXLibrary.java | 91 +++++++++- .../ApplicationLauncher.java | 4 +- .../ConvenienceKeywords.java | 3 +- .../AdditionalKeywords/RunOnFailure.java | 3 +- .../keywords/Keywords/BoundsLocation.java | 13 +- .../keywords/Keywords/ClickRobot.java | 3 +- .../keywords/Keywords/ScreenCapturing.java | 2 +- .../javafxlibrary/utils/HelperFunctions.java | 48 ++---- .../GetMouseButtonsTest.java | 1 + .../ObjectToNodeTest.java | 2 +- 11 files changed, 204 insertions(+), 125 deletions(-) diff --git a/pom.xml b/pom.xml index ae34b49..ba87a78 100644 --- a/pom.xml +++ b/pom.xml @@ -269,12 +269,19 @@ run + smoke + not-ready + + Click On TextFX Query + + + monocle-issue @@ -285,84 +292,84 @@ - - documentation - package - - libdoc - - - - ${project.build.directory} - ${project.artifactId}.html - JavaFXLibrary - ${project.version} - - - - - xml - package - - libdoc - - - - ${project.build.directory} - ${project.artifactId}.xml - JavaFXLibrary - ${project.version} - - - - - - - org.apache.maven.plugins - maven-shade-plugin - 3.2.4 - - - package - - shade - - - - - JavaFXLibrary - - - - - com.google.common - shaded.com.google.common - - - org.apache.commons - shaded.org.apache.commons - - - - - *:* - - - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index bb196da..0a30ba1 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -21,8 +21,10 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.*; +import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import javafxlibrary.exceptions.JavaFXLibraryFatalException; @@ -52,6 +54,55 @@ public class JavaFXLibrary extends AnnotationLibrary { public static final String ROBOT_LIBRARY_VERSION = loadRobotLibraryVersion(); public static final TestListener ROBOT_LIBRARY_LISTENER = new TestListener(); + static List noWrappedAsyncFxKeywords = new ArrayList() {{ + add("callObjectMethodInFxApplicationThread"); + add("clearObjectMap"); + add("clickOn"); + add("clickOnCoordinates"); + add("doubleClickOn"); + add("doubleClickOnCoordinates"); + add("dragFrom"); + add("dropTo"); + add("getCurrentApplication"); + add("getLibraryVersion"); + add("getRootNodeOf"); + add("getScreenshot Directory"); + add("getScreenshotDirectory"); + add("getSystemProperty"); + add("isJavaAgent"); + add("launchJavafxApplication"); + add("launchSwingApplication"); + add("launchSwingApplicationInSeparateThread"); + add("logApplicationClasspath"); + add("logSystemProperties"); + add("moveTo"); + add("pointTo"); + add("pointToWithOffset"); + add("rightClickOn"); + add("rightClickOnCoordinates"); + add("setImageLogging"); + add("setSafeClicking"); + add("setScreenshotDirectory"); + add("setSystemProperty"); + add("setTimeout"); + add("setToClasspath"); + add("setWriteSpeed"); + add("waitForEventsInFxApplicationThread"); + add("waitUntilElementDoesNotExists"); + add("waitUntilElementExists"); + add("waitUntilNodeIsEnabled"); + add("waitUntilNodeIsNotEnabled"); + add("waitUntilNodeIsNotVisible"); + add("waitUntilNodeIsVisible"); + add("waitUntilProgressBarIsFinished"); + +// waitUntilInvisible +// waitUntilEnabled +// waitUntilDisabled +// objectToNode hanskattu + + }}; + static List includePatterns = new ArrayList() {{ add("javafxlibrary/keywords/AdditionalKeywords/*.class"); add("javafxlibrary/keywords/Keywords/*.class"); @@ -118,13 +169,27 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { waitFor(getWaitUntilTimeout(TimeUnit.MILLISECONDS) + 500, TimeUnit.MILLISECONDS, () -> { try { - retval.set(super.runKeyword(keywordName, finalArgs, finalKwargs)); + if (noWrappedAsyncFxKeywords.contains(keywordName)) { + retval.set(super.runKeyword(keywordName, finalArgs, finalKwargs)); + } else { + try { + retval.set(asyncFx(() -> { + try { + return super.runKeyword(keywordName, finalArgs, finalKwargs); + } catch (RuntimeException e) { + retExcep.set(e); + return null; + } + }).get()); + } catch (ExecutionException ee) { + throw (RuntimeException) ee.getCause(); + } + } return true; } catch (JavaFXLibraryTimeoutException jfxte) { // timeout already expired, catch exception and jump out retExcep.set(jfxte); throw jfxte; - } catch (RuntimeException e) { // catch exception and continue trying retExcep.set(e); @@ -156,6 +221,28 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary TIMEOUT exception: \n" + Throwables.getStackTraceAsString(jfxte)); throw jfxte; } + if(retExcep.get()!=null) { + //throw retExcep.get(); + RobotLog.reset(); + RuntimeException e = retExcep.get(); + runOnFailure.runOnFailure(); + + if (e.getCause() instanceof JavaFXLibraryFatalException) { + RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary FATAL exception: \n" + Throwables.getStackTraceAsString(e)); + throw e; + } else if (e.getCause() instanceof JavaFXLibraryNonFatalException) { + RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary NON-FATAL exception: \n" + Throwables.getStackTraceAsString(e)); + throw e; + } else if (e.getCause() instanceof IllegalArgumentException) { + RobotLog.trace("JavaFXLibrary: Caught IllegalArgumentException: \n" + Throwables.getStackTraceAsString(e)); + throw new JavaFXLibraryNonFatalException("Illegal arguments for keyword '" + keywordName + "':\n" + + " ARGS: " + Arrays.toString(args.toArray()) + "\n" + + " KWARGS: " + Arrays.toString(kwargs.entrySet().toArray())); + } else { + RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary RUNTIME exception: \n" + Throwables.getStackTraceAsString(e)); + throw e; + } + } RobotLog.reset(); return retval.get(); } diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java index 282ad1d..53d563a 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java @@ -68,8 +68,8 @@ public void launchJavafxApplication(String appName, String... appArgs) { @ArgumentNames({ "appName", "*args" }) public void launchSwingApplication(String appName, String... appArgs) { RobotLog.info("Starting application:" + appName); - Class c = getMainClass(appName); - Application app = createWrapperApplication(c, appArgs); + Class mainClass = getMainClass(appName); + Application app = createWrapperApplication(mainClass, appArgs); createNewSession(app); RobotLog.info("Application: " + appName + " started."); } diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index bf30bbe..0d91c99 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -22,6 +22,7 @@ import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.css.PseudoClass; +import javafx.geometry.BoundingBox; import javafx.geometry.Rectangle2D; import javafx.scene.Node; import javafx.scene.Parent; @@ -387,7 +388,7 @@ public Object getPrimaryScreenBounds() { try { RobotLog.info("Getting the primary screen bounds"); Rectangle2D bounds = Screen.getPrimary().getVisualBounds(); - return mapObject(threadSafeBoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight())); + return mapObject(new BoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight())); } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Unable to get primary screen bounds.", e); } diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java index 5612e5b..ec0d97c 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java @@ -32,7 +32,6 @@ import org.testfx.robot.Motion; import static javafxlibrary.utils.HelperFunctions.getMotion; -import static javafxlibrary.utils.HelperFunctions.threadSafeRectangle2D; @RobotKeywords public class RunOnFailure extends TestFxAdapter{ @@ -72,7 +71,7 @@ public void runOnFailure() { } else { GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); RobotLog.debug("Capturing screenshot from primary screen with resolution "+gd.getDisplayMode().getWidth()+"x"+gd.getDisplayMode().getHeight()+"."); - new ScreenCapturing().captureImage(threadSafeRectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()),true); + new ScreenCapturing().captureImage(new Rectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()),true); } } catch (Exception e) { RobotLog.error("Error when capturing screenshot. Actual error: "+e.getMessage()); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java index 80b7151..8bd8810 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java @@ -17,6 +17,9 @@ package javafxlibrary.keywords.Keywords; +import javafx.geometry.BoundingBox; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.RobotLog; @@ -48,7 +51,7 @@ public Object createBounds(double minX, double minY, double width, double height try { RobotLog.info("Creating bounds object with minX=\"" + minX + "\", minY=\"" + minY + "\", width=\"" + width + "\" and height=\"" + height + "\""); - return mapObject(threadSafeBoundingBox(minX, minY, width, height)); + return mapObject(new BoundingBox(minX, minY, width, height)); } catch (Exception e) { if ( e instanceof JavaFXLibraryNonFatalException ) throw e; @@ -66,7 +69,7 @@ public Object createBounds(double minX, double minY, double width, double height public Object createPoint(double x, double y) { try { RobotLog.info("Creating point object with x=\"" + x + "\"" + " and y=\"" + y + "\""); - return mapObject(threadSafePoint2D(x, y)); + return mapObject(new Point2D(x, y)); } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) throw e; @@ -85,7 +88,7 @@ public Object createRectangle(double minX, double minY, double width, double hei try { RobotLog.info("Creating rectangle object with minX=\"" + minX + "\", minY=\"" + minY + "\", width=\"" + width + "\" and height=\"" + height + "\""); - return mapObject(threadSafeRectangle2D(minX, minY, width, height)); + return mapObject(new Rectangle2D(minX, minY, width, height)); } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) throw e; @@ -107,10 +110,10 @@ public Object getBounds(Object locator) { try { if (locator instanceof Window) { Window window = (Window) locator; - return mapObject(threadSafeBoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight())); + return mapObject(new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight())); } else if (locator instanceof Scene) { Scene scene = (Scene) locator; - return mapObject(threadSafeBoundingBox(scene.getX() + scene.getWindow().getX(), scene.getY() + + return mapObject(new BoundingBox(scene.getX() + scene.getWindow().getX(), scene.getY() + scene.getWindow().getY(), scene.getWidth(), scene.getHeight())); } diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java index 371a8d6..0c5d10a 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java @@ -106,10 +106,11 @@ public FxRobotInterface ClickOnMouseButton(String... buttons) { RobotLog.info("Clicking mouse buttons \"" + Arrays.toString(buttons) + "\""); return robot.clickOn(getMouseButtons(buttons)); } catch (Exception e) { + RobotLog.trace("exception at this point: " + e.getCause()); if (e instanceof JavaFXLibraryNonFatalException) { throw e; } - throw new JavaFXLibraryNonFatalException("Unable to click mouse button.", e); + throw new JavaFXLibraryNonFatalException("Unable to click mouse button.", e.getCause()); } } diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java index 2f62fcc..ae2a27f 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java @@ -68,7 +68,7 @@ else if (value.toLowerCase().equals("diskonly")) @RobotKeyword("Returns a screenshot from whole primary screen. Note that this shows also other applications that are open.") public Object capturePrimaryScreen() { GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - return this.captureImage(threadSafeRectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()),true); + return this.captureImage(new Rectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()),true); } @RobotKeyword("Returns a screenshot of the given locator, or if not given from whole active window.\n\n" diff --git a/src/main/java/javafxlibrary/utils/HelperFunctions.java b/src/main/java/javafxlibrary/utils/HelperFunctions.java index 4a38caa..136d957 100644 --- a/src/main/java/javafxlibrary/utils/HelperFunctions.java +++ b/src/main/java/javafxlibrary/utils/HelperFunctions.java @@ -329,7 +329,8 @@ public static MouseButton[] getMouseButtons(String[] slist) { try { array[i] = MouseButton.valueOf(slist[i]); } catch (IllegalArgumentException e) { - throw new JavaFXLibraryNonFatalException("\"" + slist[i] + "\" is not a valid MouseButton. Accepted values are: " + throw new JavaFXLibraryNonFatalException("\"" + slist[i] + "\" is not a valid MouseButton. Accepted values are: " +// throw new IllegalArgumentException("\"" + slist[i] + "\" is not a valid MouseButton. Accepted values are: " + Arrays.asList(MouseButton.values())); } } @@ -486,7 +487,7 @@ public static long getWaitUntilTimeout(TimeUnit timeUnit) { } public static void checkClickLocation(int x, int y) { - checkClickLocation(threadSafePoint2D(x,y)); + checkClickLocation(new Point2D(x,y)); } @@ -537,7 +538,7 @@ public static boolean visibleWindowsContain(List windows, Point2D point) boolean contains = false; for (Window window : windows) { if (window.isShowing()) { - Bounds windowBounds = threadSafeBoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); + Bounds windowBounds = new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); if (windowBounds.contains(point)) contains = true; } @@ -546,7 +547,7 @@ public static boolean visibleWindowsContain(List windows, Point2D point) } public static Point2D getCenterPoint(Bounds bounds) { - return threadSafePoint2D(bounds.getMinX() + (bounds.getWidth() / 2), bounds.getMinY() + (bounds.getHeight() / 2)); + return new Point2D(bounds.getMinX() + (bounds.getWidth() / 2), bounds.getMinY() + (bounds.getHeight() / 2)); } public static boolean isCompatible(Object o) { @@ -597,12 +598,15 @@ public static String getVersion() { public static Node objectToNode(Object target) { - if (target instanceof String) - return waitUntilExists((String) target, waitUntilTimeout, "SECONDS"); + if (target instanceof String) { + Node node = createFinder().find((String) target); + return node; + //return waitUntilExists((String) target, waitUntilTimeout, "SECONDS"); + } else if (target instanceof Node) { return (Node) target; } else if (target == null) { - throw new JavaFXLibraryNonFatalException("Target object was null"); + throw new JavaFXLibraryNonFatalException("Target object was empty (null)"); } else throw new JavaFXLibraryNonFatalException("Given target \"" + target.getClass().getName() + "\" is not an instance of Node or a query string for node!"); @@ -612,12 +616,12 @@ public static Bounds objectToBounds(Object object) { RobotLog.trace("object type is: " + object.getClass()); if (object instanceof Window) { Window window = (Window) object; - return threadSafeBoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); + return new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); } else if (object instanceof Scene) { Scene scene = (Scene) object; double x = scene.getX() + scene.getWindow().getX(); double y = scene.getY() + scene.getWindow().getY(); - return threadSafeBoundingBox(x, y, scene.getWidth(), scene.getHeight()); + return new BoundingBox(x, y, scene.getWidth(), scene.getHeight()); } else if (object instanceof Point2D) { return robot.bounds((Point2D) object).query(); } else if (object instanceof Node) { @@ -630,35 +634,11 @@ public static Bounds objectToBounds(Object object) { return robot.bounds(((PointQuery) object).query()).query(); } else if (object instanceof Rectangle2D) { Rectangle2D r2 = (Rectangle2D) object; - return threadSafeBoundingBox(r2.getMinX(), r2.getMinY(), r2.getWidth(), r2.getHeight()); + return new BoundingBox(r2.getMinX(), r2.getMinY(), r2.getWidth(), r2.getHeight()); } else throw new JavaFXLibraryNonFatalException("Unsupported parameter type: " + object.getClass().getName()); } - public static Bounds threadSafeBoundingBox(double x, double y, double width, double height) { - try { - return waitFor(1, TimeUnit.SECONDS, asyncFx(()->new BoundingBox(x, y, width, height))); - } catch (TimeoutException te) { - throw new JavaFXLibraryNonFatalException("threadSafeBoundingBox: Timeout in BoundingBox object creation!"); - } - } - - public static Point2D threadSafePoint2D(double x, double y) { - try { - return waitFor(1, TimeUnit.SECONDS, asyncFx(() -> new Point2D(x,y))); - } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("threadSafePoint2D: Timeout in Point2D object creation!"); - } - } - - public static Rectangle2D threadSafeRectangle2D(double x, double y, double width, double height) { - try { - return waitFor(1, TimeUnit.SECONDS, asyncFx(() -> new Rectangle2D(x,y,width,height))); - } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("threadSafeRectangle2D: Timeout in Rectangle2D object creation!"); - } - } - private static String remainingQueries(String query) { String[] queries = query.split(" ", 2); String currentQuery = queries[0]; diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetMouseButtonsTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetMouseButtonsTest.java index d6a1719..abc4f76 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetMouseButtonsTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetMouseButtonsTest.java @@ -49,6 +49,7 @@ public void getMouseButtons_MultipleValues() { @Test public void getMouseButtons_InvalidValue() { thrown.expect(JavaFXLibraryNonFatalException.class); + // thrown.expect(IllegalArgumentException.class); thrown.expectMessage("\"HUGE_RED_ONE\" is not a valid MouseButton. Accepted values are: [NONE, PRIMARY, MIDDLE, SECONDARY]"); HelperFunctions.getMouseButtons(new String[]{"HUGE_RED_ONE"}); } diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java index ce0d3d1..66b0a09 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java @@ -61,7 +61,7 @@ public void objectToNode_InvalidType() { @Test public void objectToNode_NullObject() { thrown.expect(JavaFXLibraryNonFatalException.class); - thrown.expectMessage("Target object was null"); + thrown.expectMessage("Target object was empty (null)"); HelperFunctions.objectToNode(null); } } From 743028e65a04ea8448f6de05c1dd7d345ae0fcd7 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Fri, 2 Oct 2020 15:41:05 +0300 Subject: [PATCH 04/19] additional rewrite to have all needed kw's wrapped in FX thread, output and error handling improvements --- pom.xml | 159 +++++++++--------- src/main/java/JavaFXLibrary.java | 98 +++-------- .../ApplicationLauncher.java | 59 ++++++- .../ConvenienceKeywords.java | 41 +---- .../AdditionalKeywords/RunOnFailure.java | 7 - .../AdditionalKeywords/Verifiers.java | 4 +- .../keywords/Keywords/ClickRobot.java | 43 +++-- .../keywords/Keywords/DragRobot.java | 25 +-- .../keywords/Keywords/KeyboardRobot.java | 18 +- .../keywords/Keywords/MouseRobot.java | 1 - .../keywords/Keywords/MoveRobot.java | 2 +- .../keywords/Keywords/NodeLookup.java | 19 +-- .../keywords/Keywords/PointLocation.java | 16 +- .../keywords/Keywords/PointOffset.java | 14 +- .../keywords/Keywords/PointPosition.java | 1 - .../keywords/Keywords/ScreenCapturing.java | 16 +- .../keywords/Keywords/ScrollRobot.java | 1 - .../keywords/Keywords/WindowLookup.java | 4 +- .../keywords/Keywords/WindowTargeting.java | 5 +- .../matchers/ExtendedNodeMatchers.java | 6 - .../javafxlibrary/utils/HelperFunctions.java | 121 +++++++------ ...aitForEventsInFxApplicationThreadTest.java | 6 +- .../acceptance/0_ClickRobotTest.robot | 12 +- .../acceptance/MenuAppTest.robot | 38 +++-- .../acceptance/MoveRobotTest.robot | 2 +- .../acceptance/NodeLookupTest.robot | 2 +- .../acceptance/ScrollRobotTest.robot | 8 +- .../SwingApplicationWrapperTest.robot | 3 + 28 files changed, 327 insertions(+), 404 deletions(-) diff --git a/pom.xml b/pom.xml index ba87a78..ae34b49 100644 --- a/pom.xml +++ b/pom.xml @@ -269,19 +269,12 @@ run - smoke - not-ready - - Click On TextFX Query - - - monocle-issue @@ -292,84 +285,84 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + documentation + package + + libdoc + + + + ${project.build.directory} + ${project.artifactId}.html + JavaFXLibrary + ${project.version} + + + + + xml + package + + libdoc + + + + ${project.build.directory} + ${project.artifactId}.xml + JavaFXLibrary + ${project.version} + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + JavaFXLibrary + + + + + com.google.common + shaded.com.google.common + + + org.apache.commons + shaded.org.apache.commons + + + + + *:* + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index 0a30ba1..95e5d44 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -21,7 +21,6 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.*; -import java.util.concurrent.CancellationException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.ExecutionException; @@ -43,9 +42,8 @@ import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; -import static javafxlibrary.utils.HelperFunctions.*; -import static org.testfx.util.WaitForAsyncUtils.asyncFx; -import static org.testfx.util.WaitForAsyncUtils.waitFor; +import static org.testfx.util.WaitForAsyncUtils.*; + import java.util.ResourceBundle; public class JavaFXLibrary extends AnnotationLibrary { @@ -57,15 +55,9 @@ public class JavaFXLibrary extends AnnotationLibrary { static List noWrappedAsyncFxKeywords = new ArrayList() {{ add("callObjectMethodInFxApplicationThread"); add("clearObjectMap"); - add("clickOn"); - add("clickOnCoordinates"); - add("doubleClickOn"); - add("doubleClickOnCoordinates"); - add("dragFrom"); add("dropTo"); add("getCurrentApplication"); add("getLibraryVersion"); - add("getRootNodeOf"); add("getScreenshot Directory"); add("getScreenshotDirectory"); add("getSystemProperty"); @@ -75,11 +67,6 @@ public class JavaFXLibrary extends AnnotationLibrary { add("launchSwingApplicationInSeparateThread"); add("logApplicationClasspath"); add("logSystemProperties"); - add("moveTo"); - add("pointTo"); - add("pointToWithOffset"); - add("rightClickOn"); - add("rightClickOnCoordinates"); add("setImageLogging"); add("setSafeClicking"); add("setScreenshotDirectory"); @@ -95,12 +82,7 @@ public class JavaFXLibrary extends AnnotationLibrary { add("waitUntilNodeIsNotVisible"); add("waitUntilNodeIsVisible"); add("waitUntilProgressBarIsFinished"); - -// waitUntilInvisible -// waitUntilEnabled -// waitUntilDisabled -// objectToNode hanskattu - + add("writeTo"); }}; static List includePatterns = new ArrayList() {{ @@ -163,66 +145,36 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { AtomicReference retval = new AtomicReference<>(); AtomicReference retExcep = new AtomicReference<>(); + RobotLog.ignoreDuplicates(); try { - RobotLog.ignoreDuplicates(); - // timeout + 500 ms so that underlying timeout has a chance to expire first - waitFor(getWaitUntilTimeout(TimeUnit.MILLISECONDS) + 500, TimeUnit.MILLISECONDS, () -> { - - try { - if (noWrappedAsyncFxKeywords.contains(keywordName)) { - retval.set(super.runKeyword(keywordName, finalArgs, finalKwargs)); - } else { + if (noWrappedAsyncFxKeywords.contains(keywordName)) { + retval.set(super.runKeyword(keywordName, finalArgs, finalKwargs)); + } else { + try { + retval.set(asyncFx(() -> { try { - retval.set(asyncFx(() -> { - try { - return super.runKeyword(keywordName, finalArgs, finalKwargs); - } catch (RuntimeException e) { - retExcep.set(e); - return null; - } - }).get()); - } catch (ExecutionException ee) { - throw (RuntimeException) ee.getCause(); + return super.runKeyword(keywordName, finalArgs, finalKwargs); + } catch (RuntimeException rte) { + retExcep.set(rte); + return null; } + }).get()); + waitForFxEvents( 3); + } catch (InterruptedException | ExecutionException iee) { + throw (RuntimeException) iee.getCause(); } - return true; - } catch (JavaFXLibraryTimeoutException jfxte) { - // timeout already expired, catch exception and jump out - retExcep.set(jfxte); - throw jfxte; - } catch (RuntimeException e) { - // catch exception and continue trying - retExcep.set(e); - return false; - } - }); - } catch (TimeoutException te) { - RobotLog.reset(); - RuntimeException e = retExcep.get(); - runOnFailure.runOnFailure(); - - if (e.getCause() instanceof JavaFXLibraryFatalException) { - RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary FATAL exception: \n" + Throwables.getStackTraceAsString(e)); - throw e; - } else if (e.getCause() instanceof JavaFXLibraryNonFatalException) { - RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary NON-FATAL exception: \n" + Throwables.getStackTraceAsString(e)); - throw e; - } else if (e.getCause() instanceof IllegalArgumentException) { - RobotLog.trace("JavaFXLibrary: Caught IllegalArgumentException: \n" + Throwables.getStackTraceAsString(e)); - throw new JavaFXLibraryNonFatalException("Illegal arguments for keyword '" + keywordName + "':\n" + - " ARGS: " + Arrays.toString(args.toArray()) + "\n" + - " KWARGS: " + Arrays.toString(kwargs.entrySet().toArray())); - } else { - RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary RUNTIME exception: \n" + Throwables.getStackTraceAsString(e)); - throw e; } +// return true; } catch (JavaFXLibraryTimeoutException jfxte) { - RobotLog.reset(); - RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary TIMEOUT exception: \n" + Throwables.getStackTraceAsString(jfxte)); - throw jfxte; + // timeout already expired, catch exception and jump out + retExcep.set(jfxte); +// throw jfxte; + } catch (RuntimeException rte) { + // catch exception and continue trying + retExcep.set(rte); +// return false; } if(retExcep.get()!=null) { - //throw retExcep.get(); RobotLog.reset(); RuntimeException e = retExcep.get(); runOnFailure.runOnFailure(); diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java index 53d563a..13cfc7f 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java @@ -18,6 +18,7 @@ package javafxlibrary.keywords.AdditionalKeywords; import javafx.application.Application; +import javafx.application.Platform; import javafxlibrary.exceptions.JavaFXLibraryFatalException; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.utils.RobotLog; @@ -25,16 +26,18 @@ import org.robotframework.javalib.annotation.ArgumentNames; import org.robotframework.javalib.annotation.RobotKeyword; import org.robotframework.javalib.annotation.RobotKeywords; + import java.io.File; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.FileSystems; -import java.util.*; +import java.util.Enumeration; +import java.util.Objects; +import java.util.Properties; +import java.util.concurrent.Semaphore; -import static javafxlibrary.utils.HelperFunctions.createThreadedWrapperApplication; -import static javafxlibrary.utils.HelperFunctions.createWrapperApplication; -import static javafxlibrary.utils.HelperFunctions.getMainClassFromJarFile; +import static javafxlibrary.utils.HelperFunctions.*; @RobotKeywords public class ApplicationLauncher extends TestFxAdapter { @@ -50,6 +53,7 @@ public void launchJavafxApplication(String appName, String... appArgs) { try { RobotLog.info("Starting application:" + appName); createNewSession(appName, appArgs); + waitForEventsInFxApplicationThread(getWaitUntilTimeout()); RobotLog.info("Application: " + appName + " started."); } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Unable to launch application: " + appName, e); @@ -71,6 +75,7 @@ public void launchSwingApplication(String appName, String... appArgs) { Class mainClass = getMainClass(appName); Application app = createWrapperApplication(mainClass, appArgs); createNewSession(app); + waitForEventsInFxApplicationThread(getWaitUntilTimeout()); RobotLog.info("Application: " + appName + " started."); } @@ -90,6 +95,7 @@ public void launchSwingApplicationInSeparateThread(String appName, String... app Class c = getMainClass(appName); Application app = createThreadedWrapperApplication(c, appArgs); createNewSession(app); + waitForEventsInFxApplicationThread(getWaitUntilTimeout()); RobotLog.info("Application: " + appName + " started."); } @@ -105,7 +111,7 @@ private Class getMainClass(String appName) { } } - private void _addPathToClassPath(String path) { + private void addPathToClassPath(String path) { URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); RobotLog.info("Setting following path to classpath: " + path); @@ -143,7 +149,7 @@ public void setToClasspath(String path, boolean failIfNotFound) { for (File file : Objects.requireNonNull(fileList)) { if (file.getName().endsWith(".jar")) { jarsFound = true; - _addPathToClassPath(file.getAbsolutePath()); + addPathToClassPath(file.getAbsolutePath()); } } if (!jarsFound) { @@ -163,7 +169,7 @@ public void setToClasspath(String path, boolean failIfNotFound) { } } } else { - _addPathToClassPath(path); + addPathToClassPath(path); } } @@ -263,6 +269,45 @@ public String getCurrentApplication() { } } + @RobotKeyword("Waits for current events in Fx Application Thread event queue to finish before continuing.\n\n" + + "``timeout`` is the maximum time in seconds that the events will be waited for. If the timeout is " + + "exceeded the keyword will fail. Default timeout is 5 seconds.\n\n") + @ArgumentNames({ "timeout=5" }) + public void waitForEventsInFxApplicationThread(int timeout) { + + final Throwable[] threadException = new JavaFXLibraryNonFatalException[1]; + try { + Semaphore semaphore = new Semaphore(0); + Platform.runLater(semaphore::release); + Thread t = new Thread(() -> { + int passed = 0; + try { + while (passed <= timeout) { + Thread.sleep(1000); + passed++; + } + + if (semaphore.hasQueuedThreads()) + throw new JavaFXLibraryNonFatalException("Events did not finish within the given timeout of " + + timeout + " seconds."); + } catch (InterruptedException e) { + throw new JavaFXLibraryNonFatalException("Timeout was interrupted in Wait For Wait For Events in " + + "Fx Application Thread: " + e.getMessage()); + } + }); + t.setUncaughtExceptionHandler((thread, e) -> threadException[0] = e); + t.start(); + semaphore.acquire(); + + if (threadException[0] != null) + throw new JavaFXLibraryNonFatalException(threadException[0].getMessage()); + + } catch (InterruptedException e) { + throw new JavaFXLibraryNonFatalException("Wait For Events in Fx Application Thread was interrupted: " + + e.getMessage()); + } + } + @RobotKeyword("Returns if JavaFXLibrary is started as java agent.") public boolean isJavaAgent() { return TestFxAdapter.isAgent; diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index 0d91c99..b91464a 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -49,6 +49,7 @@ import java.util.concurrent.Semaphore; import static javafxlibrary.utils.HelperFunctions.*; +import static org.testfx.util.WaitForAsyncUtils.waitForFxEvents; @RobotKeywords public class ConvenienceKeywords extends TestFxAdapter { @@ -103,6 +104,7 @@ public void callObjectMethodInFxApplicationThread(Object object, String method, Object[] tempArgs = HelperFunctions.checkMethodArguments(arguments); Object[] finalArgs = HelperFunctions.useMappedObjects(tempArgs); callMethod(object, method, finalArgs, true); + waitForFxEvents(1); } @RobotKeyword("Lists methods available for given node.\n" @@ -881,43 +883,4 @@ public Object getProgressBarValue(Object locator) { throw new JavaFXLibraryNonFatalException("Unable to handle given locator as ProgressBar!"); } } - - @RobotKeyword("Waits for current events in Fx Application Thread event queue to finish before continuing.\n\n" - + "``timeout`` is the maximum time in seconds that the events will be waited for. If the timeout is " - + "exceeded the keyword will fail. Default timeout is 5 seconds.\n\n") - @ArgumentNames({ "timeout=5" }) - public void waitForEventsInFxApplicationThread(int timeout) { - - final Throwable[] threadException = new JavaFXLibraryNonFatalException[1]; - try { - Semaphore semaphore = new Semaphore(0); - Platform.runLater(semaphore::release); - Thread t = new Thread(() -> { - int passed = 0; - try { - while (passed <= timeout) { - Thread.sleep(1000); - passed++; - } - - if (semaphore.hasQueuedThreads()) - throw new JavaFXLibraryNonFatalException("Events did not finish within the given timeout of " - + timeout + " seconds."); - } catch (InterruptedException e) { - throw new JavaFXLibraryNonFatalException("Timeout was interrupted in Wait For Wait For Events in " + - "Fx Application Thread: " + e.getMessage()); - } - }); - t.setUncaughtExceptionHandler((thread, e) -> threadException[0] = e); - t.start(); - semaphore.acquire(); - - if (threadException[0] != null) - throw new JavaFXLibraryNonFatalException(threadException[0].getMessage()); - - } catch (InterruptedException e) { - throw new JavaFXLibraryNonFatalException("Wait For Events in Fx Application Thread was interrupted: " - + e.getMessage()); - } - } } \ No newline at end of file diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java index ec0d97c..48f9fde 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java @@ -24,14 +24,7 @@ import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; -import java.lang.reflect.Method; - -import org.apache.commons.lang3.reflect.MethodUtils; import org.robotframework.javalib.annotation.RobotKeywords; -import org.testfx.api.FxRobotInterface; -import org.testfx.robot.Motion; - -import static javafxlibrary.utils.HelperFunctions.getMotion; @RobotKeywords public class RunOnFailure extends TestFxAdapter{ diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index f6181d3..9533d4d 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -218,9 +218,9 @@ public static void nodeShouldNotBeHoverable(Object locator) { try { RobotLog.info("Checking that locator node is not hoverable: \"" + locator + "\"."); verifyThat(objectToNode(locator), ExtendedNodeMatchers.isHoverable()); - throw new JavaFXLibraryNonFatalException("Expected \"" + locator + "\" to be not hoverable - failed!"); + throw new JavaFXLibraryNonFatalException("Expected that \"" + locator + "\" is not hoverable - failed!"); } catch (AssertionError ae){ - // Was not hoverable, keyword should pass + RobotLog.info("Was not hoverable."); } } diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java index 0c5d10a..1ed4555 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java @@ -49,15 +49,14 @@ public class ClickRobot extends TestFxAdapter { + "| Click On | .css-name | Motion=VERTICAL_FIRST | \n") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface clickOn(Object locator, String motion) { - Object target = checkClickTarget(locator); - RobotLog.info("Clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "clickOn", - target.getClass(), Motion.class, MouseButton.class); try { + Object target = checkClickTarget(locator); + RobotLog.info("Clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "clickOn", + target.getClass(), Motion.class, MouseButton.class); return (FxRobotInterface) method.invoke(robot, target, getMotion(motion), new MouseButton[]{MouseButton.PRIMARY}); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new JavaFXLibraryNonFatalException("Could not execute click on using locator \"" + locator + "\" " + - "and motion " + motion + ": " + e.getCause().getMessage(), e); + } catch (IllegalAccessException | InvocationTargetException | JavaFXLibraryNonFatalException e) { + throw new JavaFXLibraryNonFatalException("Click On failed: " + e.getMessage(), e); } } @@ -68,14 +67,13 @@ public FxRobotInterface clickOn(Object locator, String motion) { + "is usually HORIZONTAL_FIRST.\n\n") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface rightClickOn(Object locator, String motion) { - Object target = checkClickTarget(locator); - RobotLog.info("Right clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "rightClickOn", target.getClass(), Motion.class); try { + Object target = checkClickTarget(locator); + RobotLog.info("Right clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "rightClickOn", target.getClass(), Motion.class); return (FxRobotInterface) method.invoke(robot, target, getMotion(motion)); } catch (IllegalAccessException | InvocationTargetException e) { - throw new JavaFXLibraryNonFatalException("Could not execute right click on using locator \"" + locator + "\" " + - "and motion " + motion + ": " + e.getCause().getMessage(), e); + throw new JavaFXLibraryNonFatalException("Right Click On failed: " + e.getCause().getMessage(), e); } } @@ -85,16 +83,14 @@ public FxRobotInterface rightClickOn(Object locator, String motion) { + "``motion`` defines the path for mouse to move to a target location. Default value is _DIRECT_.") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface doubleClickOn(Object locator, String motion) { - Object target = checkClickTarget(locator); - RobotLog.info("Double clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "doubleClickOn", - target.getClass(), Motion.class, MouseButton.class); - try { + Object target = checkClickTarget(locator); + RobotLog.info("Double clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "doubleClickOn", + target.getClass(), Motion.class, MouseButton.class); return (FxRobotInterface) method.invoke(robot, target, getMotion(motion), new MouseButton[]{MouseButton.PRIMARY}); } catch (IllegalAccessException | InvocationTargetException e) { - throw new JavaFXLibraryNonFatalException("Could not execute double click on using locator \"" + locator + "\" " + - "and motion " + motion + ": " + e.getCause().getMessage(), e); + throw new JavaFXLibraryNonFatalException("Double Click On failed: " + e.getCause().getMessage(), e); } } @@ -146,10 +142,9 @@ public FxRobotInterface rightClickOnMouseButton() { + "Optional argument ``motion`` defines how mouse pointer is moved to target. Defaults to _DIRECT_.") @ArgumentNames({ "x", "y", "motion=DIRECT" }) public FxRobotInterface clickOnCoordinates(int x, int y, String motion) { - RobotLog.info("Clicking on coordinates x=\"" + x + "\"" + ", y=\"" + y + "\"" + " and motion=\"" + motion + "\""); - checkClickLocation(x, y); - try { + RobotLog.info("Clicking on coordinates x=\"" + x + "\"" + ", y=\"" + y + "\"" + " and motion=\"" + motion + "\""); + checkClickLocation(x, y); return robot.clickOn(x, y, getMotion(motion), MouseButton.PRIMARY); } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) { @@ -164,8 +159,8 @@ public FxRobotInterface clickOnCoordinates(int x, int y, String motion) { + "Optional argument ``motion`` defines how mouse pointer is moved to target. Defaults to _DIRECT_.") @ArgumentNames({ "x", "y", "motion=DIRECT" }) public FxRobotInterface doubleClickOnCoordinates(int x, int y, String motion) { - checkClickLocation(x, y); try { + checkClickLocation(x, y); RobotLog.info("Double clicking on coordinates x=\"" + x + "\"" + ", y=\"" + y + "\"" + " and motion=\"" + motion + "\""); return robot.doubleClickOn(x, y, getMotion(motion), MouseButton.PRIMARY); } catch (Exception e) { @@ -181,8 +176,8 @@ public FxRobotInterface doubleClickOnCoordinates(int x, int y, String motion) { + "Optional argument ``motion`` defines how mouse pointer is moved to target. Defaults to _DIRECT_.") @ArgumentNames({ "x", "y", "motion=DIRECT" }) public FxRobotInterface rightClickOnCoordinates(int x, int y, String motion) { - checkClickLocation(x, y); try { + checkClickLocation(x, y); RobotLog.info("Right clicking on coordinates x=\"" + x + "\"" + ", y=\"" + y + "\"" + " and motion=\"" + motion + "\""); return robot.rightClickOn(x, y, getMotion(motion)); } catch (Exception e) { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java index f4d66e3..dd18b05 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java @@ -31,7 +31,10 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.concurrent.ExecutionException; + import static javafxlibrary.utils.HelperFunctions.*; +import static org.testfx.util.WaitForAsyncUtils.asyncFx; @RobotKeywords public class DragRobot extends TestFxAdapter { @@ -45,10 +48,10 @@ public class DragRobot extends TestFxAdapter { + "| Drag From | ${node} | SECONDARY | \n") @ArgumentNames({ "locator", "button=PRIMARY" }) public FxRobotInterface dragFrom(Object locator, String button) { - Object target = checkClickTarget(locator); - RobotLog.info("Dragging from \"" + target + "\"" + " with button=\"" + button + "\""); - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "drag", target.getClass(), MouseButton.class); try { + Object target = checkClickTarget(locator); + RobotLog.info("Dragging from \"" + target + "\"" + " with button=\"" + button + "\""); + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "drag", target.getClass(), MouseButton.class); return (FxRobotInterface) method.invoke(robot, target, new MouseButton[]{MouseButton.valueOf(button)}); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute drag from using locator \"" + locator + "\" " + @@ -63,14 +66,17 @@ public FxRobotInterface dragFrom(Object locator, String button) { + "| Drop To | \\#some-node-id | \n") @ArgumentNames({ "locator" }) public FxRobotInterface dropTo(Object locator) { - Object target = checkClickTarget(locator); - RobotLog.info("Dropping to \"" + target + "\""); - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "dropTo", target.getClass()); - try { + Object target = asyncFx(() -> checkClickTarget(locator)).get(); + RobotLog.info("Dropping to \"" + target + "\""); + // TODO: Below needs to be put to asyncFx thread instead of below + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "dropTo", target.getClass()); return (FxRobotInterface) method.invoke(robot, target); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new JavaFXLibraryNonFatalException("Could not execute drop to using locator \"" + locator + "\" " + + } catch (InterruptedException | ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("Drop To target check failed for locator \"" + locator + "\" " + + ": " + iee.getCause().getMessage(), iee); + } catch (Exception e) { + throw new JavaFXLibraryNonFatalException("Could not execute drop to using locator \"" + locator + "\" " + ": " + e.getCause().getMessage(), e); } } @@ -110,7 +116,6 @@ public FxRobotInterface drop() { + "| Drop By | -300 | 0 | \n") @ArgumentNames({ "x", "y" }) public FxRobotInterface dropBy(int x, int y) { - try { RobotLog.info("Dropping by x=\"" + x + "\" and y=\"" + y + "\""); return robot.dropBy(x, y); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java index ef380b1..8a97ca4 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java @@ -31,7 +31,11 @@ import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.util.Arrays; +import java.util.concurrent.ExecutionException; + import static javafxlibrary.utils.HelperFunctions.*; +import static org.testfx.util.WaitForAsyncUtils.asyncFx; +import static org.testfx.util.WaitForAsyncUtils.waitForFxEvents; @RobotKeywords public class KeyboardRobot extends TestFxAdapter { @@ -162,8 +166,6 @@ public FxRobotInterface closeCurrentWindow() { } } - // ----------------------------------------------------------------------------------------------- - // Write uses JavaFX events @RobotKeyword("Writes a given text characters one after the other.\n\n" + "``text`` is the text characters to write\n" + "\nExample: \n" @@ -213,11 +215,15 @@ public void writeFast(String text) { + "\nExample: \n" + "| Write To | .css-name | Robot Framework | \n") @ArgumentNames({ "locator", "text" }) - public FxRobotInterface writeTo(Object locator, String text) { - RobotLog.info("Writing \"" + text + "\" to " + locator); + public void writeTo(Object locator, String text) { try { - clickRobot.clickOn(locator,"DIRECT"); - return robot.write(text, sleepMillis); + RobotLog.info("Writing \"" + text + "\" to " + locator); + asyncFx(() -> clickRobot.clickOn(locator,"DIRECT")).get(); + asyncFx(() -> write(text)).get(); + waitForFxEvents(3); + } catch (InterruptedException | ExecutionException iee) { + RobotLog.trace("failed"); + throw new JavaFXLibraryNonFatalException("Unable to write to: " + locator, iee); } catch (Exception e) { if(e instanceof JavaFXLibraryNonFatalException) throw e; diff --git a/src/main/java/javafxlibrary/keywords/Keywords/MouseRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/MouseRobot.java index 0802bd1..a8fc6f4 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/MouseRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/MouseRobot.java @@ -37,7 +37,6 @@ public class MouseRobot extends TestFxAdapter { + "| Press Mouse Button | PRIMARY | \n") @ArgumentNames({ "*buttons" }) public FxRobotInterface pressMouseButton(String... buttons) { - try { RobotLog.info("Pressing mouse buttons: \"" + Arrays.asList(buttons) + "\""); return robot.press(HelperFunctions.getMouseButtons(buttons)); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java index 3b8b28e..b238b8b 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java @@ -50,7 +50,7 @@ public FxRobotInterface moveTo(Object locator, String motion) { try { RobotLog.info("Moving to target \"" + locator + "\" using motion: \"" + getMotion(motion) + "\""); if (locator instanceof String) { - locator = waitUntilExists((String) locator, getWaitUntilTimeout(), "SECONDS"); + locator = objectToNode(locator); } Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "moveTo", locator.getClass(), Motion.class); return (FxRobotInterface) method.invoke(robot, locator, getMotion(motion)); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java index 013d059..35d45e1 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java @@ -19,7 +19,6 @@ import javafx.scene.Node; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; -import javafxlibrary.utils.finder.Finder; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; @@ -31,8 +30,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import static javafxlibrary.utils.HelperFunctions.getWaitUntilTimeout; -import static javafxlibrary.utils.HelperFunctions.waitUntilExists; +import static javafxlibrary.utils.HelperFunctions.*; @RobotKeywords public class NodeLookup extends TestFxAdapter { @@ -54,16 +52,13 @@ public class NodeLookup extends TestFxAdapter { + "| ${root} | Get Root Node Of | \\#some-node-id | \n" ) @ArgumentNames({"locator"}) public Object getRootNodeOf(Object locator) { - if (locator instanceof String) { - Node node = waitUntilExists((String) locator, getWaitUntilTimeout(), "SECONDS"); - if( node != null ) - return getRootNodeOf(node); - throw new JavaFXLibraryNonFatalException("Unable to find any node with query: \"" + locator.toString() + "\""); - } - - RobotLog.info("Getting root node of target \"" + locator + "\""); - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "rootNode", locator.getClass()); try { + if (locator instanceof String) { + Node node = objectToNode(locator); + return getRootNodeOf(node); + } + RobotLog.info("Getting root node of target \"" + locator + "\""); + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "rootNode", locator.getClass()); return HelperFunctions.mapObject(method.invoke(robot, locator)); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute get root node of using locator \"" + locator diff --git a/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java b/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java index d8607c2..875dbd2 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java @@ -17,9 +17,7 @@ package javafxlibrary.keywords.Keywords; -import javafx.scene.Node; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; -import javafxlibrary.utils.finder.Finder; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; @@ -31,8 +29,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import static javafxlibrary.utils.HelperFunctions.getWaitUntilTimeout; -import static javafxlibrary.utils.HelperFunctions.waitUntilExists; +import static javafxlibrary.utils.HelperFunctions.*; @RobotKeywords public class PointLocation extends TestFxAdapter { @@ -49,14 +46,11 @@ public class PointLocation extends TestFxAdapter { + "| Move To | ${point query} | | | # moves to bottom right corner of a node that was stored in PointQuery object. |\n") @ArgumentNames({"locator"}) public Object pointTo(Object locator) { - RobotLog.info("Creating a point query for target \"" + locator + "\""); - - if (locator instanceof String) - locator = waitUntilExists((String) locator, getWaitUntilTimeout(), "SECONDS"); - - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "point", locator.getClass()); - try { + RobotLog.info("Creating a point query for target \"" + locator + "\""); + if (locator instanceof String) + locator = objectToNode(locator); + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "point", locator.getClass()); return HelperFunctions.mapObject(method.invoke(robot, locator)); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute point to using locator \"" + locator diff --git a/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java b/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java index 2b98189..5783a65 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java @@ -18,7 +18,6 @@ package javafxlibrary.keywords.Keywords; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; -import javafxlibrary.utils.finder.Finder; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; @@ -30,8 +29,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import static javafxlibrary.utils.HelperFunctions.getWaitUntilTimeout; -import static javafxlibrary.utils.HelperFunctions.waitUntilExists; +import static javafxlibrary.utils.HelperFunctions.*; @RobotKeywords public class PointOffset extends TestFxAdapter { @@ -45,12 +43,12 @@ public class PointOffset extends TestFxAdapter { + "| ${point query offset}= | Call Method | ${point query} | getOffset | \n") @ArgumentNames({"locator", "offsetX", "offsetY"}) public Object pointToWithOffset(Object locator, double offsetX, double offsetY) { - RobotLog.info("Creating a point query for target: \"" + locator + "\" with offset: [" + offsetX + ", " + offsetY + "]"); - if (locator instanceof String) - locator = waitUntilExists((String) locator, getWaitUntilTimeout(), "SECONDS"); - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "offset", - locator.getClass(), double.class, double.class); try { + RobotLog.info("Creating a point query for target: \"" + locator + "\" with offset: [" + offsetX + ", " + offsetY + "]"); + if (locator instanceof String) + locator = objectToNode(locator); + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "offset", + locator.getClass(), double.class, double.class); return HelperFunctions.mapObject(method.invoke(robot, locator, offsetX, offsetY)); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute 'point to with offset' using locator \"" + locator diff --git a/src/main/java/javafxlibrary/keywords/Keywords/PointPosition.java b/src/main/java/javafxlibrary/keywords/Keywords/PointPosition.java index 104b1e9..26058c6 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/PointPosition.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/PointPosition.java @@ -36,7 +36,6 @@ public class PointPosition extends TestFxAdapter { + "| Set Target Position | TOP_LEFT | \n") @ArgumentNames({ "pointPosition" }) public FxRobotInterface setTargetPosition(String pointPosition) { - try { RobotLog.info("Setting new target position as: \"" + pointPosition + "\""); return robot.targetPos(HelperFunctions.getPosition(pointPosition)); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java index ae2a27f..a5c0791 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java @@ -26,7 +26,6 @@ import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.robotframework.javalib.annotation.ArgumentNames; import org.robotframework.javalib.annotation.RobotKeyword; import org.robotframework.javalib.annotation.RobotKeywords; @@ -85,15 +84,15 @@ public Object capturePrimaryScreen() { + "| ${capture}= | Capture Image | \\#id | logImage=False |\n" ) @ArgumentNames({"locator=target window", "logImage=True"}) public Object captureImage(Object locator, boolean logImage){ - if (locator == null) - throw new JavaFXLibraryNonFatalException("Unable to capture image, given locator was null!"); + try { + if (locator == null) + throw new JavaFXLibraryNonFatalException("Unable to capture image, given locator was null!"); - RobotLog.info("Capturing screenshot from locator: \"" + locator + "\""); - Image image; - Bounds targetBounds = objectToBounds(locator); - String logPath; + RobotLog.info("Capturing screenshot from locator: \"" + locator + "\""); + Image image; + Bounds targetBounds = objectToBounds(locator); + String logPath; - try { image = robot.capture(targetBounds).getImage(); Path path = createNewImageFileNameWithPath(); robotContext().getCaptureSupport().saveImage(image, path); @@ -132,7 +131,6 @@ public Object captureImage(Object locator, boolean logImage){ } } return mapObject(image); - } catch (IOException e) { throw new JavaFXLibraryNonFatalException("Unable to take capture : \"" + locator + "\"", e); } catch (Exception e) { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScrollRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/ScrollRobot.java index 0f6febc..80e09c1 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScrollRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScrollRobot.java @@ -60,7 +60,6 @@ public void scrollVertically(String direction, int amount) { + "| Scroll Horizontally | RIGHT | \n") @ArgumentNames({ "direction", "amount=1" }) public void scrollHorizontally(String direction, int amount) { - try { RobotLog.info("Scrolling \"" + direction + "\" by \"" + amount + "\" ticks."); robot.press(KeyCode.SHIFT); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java b/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java index 63f65ce..7cccdab 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java @@ -78,9 +78,8 @@ public List listTargetWindows() { ) @ArgumentNames({"locator"}) public Object getWindow(Object locator) { - RobotLog.info("Getting window using locator \"" + locator + "\""); - try { + RobotLog.info("Getting window using locator \"" + locator + "\""); if (locator instanceof String) { if (((String) locator).startsWith("pattern=")) { locator = ((String) locator).replace("pattern=",""); @@ -93,7 +92,6 @@ public Object getWindow(Object locator) { return HelperFunctions.mapObject(robot.window((String) locator)); } } - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "window", locator.getClass()); return HelperFunctions.mapObject(method.invoke(robot, locator)); } catch (IllegalAccessException | InvocationTargetException e) { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java b/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java index 67f2d46..49dd46f 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java @@ -66,9 +66,8 @@ public Object getTargetWindow() { ) @ArgumentNames("locator") public void setTargetWindow(Object locator) { - RobotLog.info("Setting target window according to locator \"" + locator + "\""); - try { + RobotLog.info("Setting target window according to locator \"" + locator + "\""); if (locator instanceof String) { if (((String) locator).startsWith("pattern=")){ locator = ((String) locator).replace("pattern=",""); @@ -86,9 +85,7 @@ public void setTargetWindow(Object locator) { Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "targetWindow", locator.getClass()); method.invoke(robot, locator); } - Platform.runLater((robot.targetWindow())::requestFocus); - } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute set target window using locator \"" + locator + "\""); } catch (Exception e) { diff --git a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java index 00825a6..8dd3c88 100644 --- a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java +++ b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java @@ -20,17 +20,12 @@ import javafx.geometry.Bounds; import javafx.scene.Node; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; -import javafxlibrary.exceptions.JavaFXLibraryTimeoutException; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.RobotLog; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; - -import java.util.concurrent.TimeoutException; - import static javafxlibrary.utils.HelperFunctions.getHoveredNode; -import static org.testfx.util.WaitForAsyncUtils.waitFor; public class ExtendedNodeMatchers { @@ -48,7 +43,6 @@ public void describeTo(Description description) { public void describeMismatch(Object object, Description description) { description.appendText("Given target node is not hoverable, it seems to be hidden under this node: \""). appendValue(getHoveredNode()).appendText("\""); - } }; } diff --git a/src/main/java/javafxlibrary/utils/HelperFunctions.java b/src/main/java/javafxlibrary/utils/HelperFunctions.java index 136d957..b7a8438 100644 --- a/src/main/java/javafxlibrary/utils/HelperFunctions.java +++ b/src/main/java/javafxlibrary/utils/HelperFunctions.java @@ -77,10 +77,9 @@ public static Node waitUntilExists(String target) { } public static Node waitUntilExists(String target, int timeout, String timeUnit) { - RobotLog.trace("Waiting until target \"" + target + "\" becomes existent, timeout=" - + timeout + ", timeUnit=" + timeUnit); - try { + RobotLog.trace("Waiting until target \"" + target + "\" becomes existent, timeout=" + + timeout + ", timeUnit=" + timeUnit); waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) != null).get()); Node node = asyncFx(() -> createFinder().find(target)).get(); // TODO: Add null checks for node.getScene() @@ -98,10 +97,9 @@ public static Node waitUntilExists(String target, int timeout, String timeUnit) } public static void waitUntilDoesNotExists(String target, int timeout, String timeUnit) { - RobotLog.trace("Waiting until target \"" + target + "\" becomes non existent, timeout=" - + timeout + ", timeUnit=" + timeUnit); - try { + RobotLog.trace("Waiting until target \"" + target + "\" becomes non existent, timeout=" + + timeout + ", timeUnit=" + timeUnit); waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) == null).get()); } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -116,15 +114,12 @@ public static void waitUntilDoesNotExists(String target, int timeout, String tim // TODO: Take same parameters as waitUntilExists in all waitUntil methods public static Node waitUntilVisible(Object target, int timeout) { - - // if target is a query string, let's try to find the relevant node - if (target instanceof String) - target = waitUntilExists((String) target, timeout, "SECONDS"); - - final Object finalTarget = target; - RobotLog.trace("Waiting until target \"" + target + "\" becomes visible, timeout=" + timeout); - try { + // if target is a query string, let's try to find the relevant node + if (target instanceof String) + target = waitUntilExists((String) target, timeout, "SECONDS"); + final Object finalTarget = target; + RobotLog.trace("Waiting until target \"" + target + "\" becomes visible, timeout=" + timeout); waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isVisible()).matches(finalTarget)).get()); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { @@ -138,15 +133,12 @@ public static Node waitUntilVisible(Object target, int timeout) { } public static Node waitUntilInvisible(Object target, int timeout) { - - // if target is a query string, let's try to find the relevant node - if (target instanceof String) - target = waitUntilExists((String) target, timeout, "SECONDS"); - - final Object finalTarget = target; - RobotLog.trace("Waiting until target \"" + target + "\" becomes invisible, timeout=" + timeout); - try { + // if target is a query string, let's try to find the relevant node + if (target instanceof String) + target = waitUntilExists((String) target, timeout, "SECONDS"); + final Object finalTarget = target; + RobotLog.trace("Waiting until target \"" + target + "\" becomes invisible, timeout=" + timeout); waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isInvisible()).matches(finalTarget)).get()); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { @@ -160,14 +152,12 @@ public static Node waitUntilInvisible(Object target, int timeout) { } public static Node waitUntilEnabled(Object target, int timeout) { - - if (target instanceof String) - target = waitUntilExists((String) target, timeout, "SECONDS"); - - final Object finalTarget = target; - RobotLog.trace("Waiting until target \"" + target + "\" becomes enabled, timeout=" + timeout); - try { + // if target is a query string, let's try to find the relevant node + if (target instanceof String) + target = waitUntilExists((String) target, timeout, "SECONDS"); + final Object finalTarget = target; + RobotLog.trace("Waiting until target \"" + target + "\" becomes enabled, timeout=" + timeout); waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> (Matchers.is(isEnabled()).matches(finalTarget))).get()); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { @@ -181,14 +171,12 @@ public static Node waitUntilEnabled(Object target, int timeout) { } public static Node waitUntilDisabled(Object target, int timeout) { - - if (target instanceof String) - target = waitUntilExists((String) target, timeout, "SECONDS"); - - final Object finalTarget = target; - RobotLog.trace("Waiting until target \"" + target + "\" becomes disabled, timeout=" + timeout); - try { + // if target is a query string, let's try to find the relevant node + if (target instanceof String) + target = waitUntilExists((String) target, timeout, "SECONDS"); + final Object finalTarget = target; + RobotLog.trace("Waiting until target \"" + target + "\" becomes disabled, timeout=" + timeout); waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isDisabled()).matches(finalTarget)).get()); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { @@ -520,7 +508,8 @@ public static Object checkClickTarget(Object target) { try { if (target instanceof String || target instanceof Node) { - target = waitUntilEnabled(waitUntilVisible(target, waitUntilTimeout), waitUntilTimeout); + target = objectToBounds(target); + //target = waitUntilEnabled(waitUntilVisible(target, waitUntilTimeout), waitUntilTimeout); bringClickLocationToFront(target); } checkClickLocation(target); @@ -599,9 +588,12 @@ public static String getVersion() { public static Node objectToNode(Object target) { if (target instanceof String) { - Node node = createFinder().find((String) target); - return node; - //return waitUntilExists((String) target, waitUntilTimeout, "SECONDS"); + Node node = createFinder().find((String) target); + if (node == null) { + throw new JavaFXLibraryNonFatalException("Unable to find node for query \"" + target + "\""); + } + return node; + //return waitUntilExists((String) target, waitUntilTimeout, "SECONDS"); } else if (target instanceof Node) { return (Node) target; @@ -611,32 +603,33 @@ else if (target instanceof Node) { throw new JavaFXLibraryNonFatalException("Given target \"" + target.getClass().getName() + "\" is not an instance of Node or a query string for node!"); } + //TODO: check robot.* usage in relation to threading public static Bounds objectToBounds(Object object) { - RobotLog.trace("object type is: " + object.getClass()); - if (object instanceof Window) { - Window window = (Window) object; - return new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); - } else if (object instanceof Scene) { - Scene scene = (Scene) object; - double x = scene.getX() + scene.getWindow().getX(); - double y = scene.getY() + scene.getWindow().getY(); - return new BoundingBox(x, y, scene.getWidth(), scene.getHeight()); - } else if (object instanceof Point2D) { - return robot.bounds((Point2D) object).query(); - } else if (object instanceof Node) { - return robot.bounds((Node) object).query(); - } else if (object instanceof String) { - return robot.bounds(objectToNode(object)).query(); - } else if (object instanceof Bounds) { - return (Bounds) object; - } else if (object instanceof PointQuery) { - return robot.bounds(((PointQuery) object).query()).query(); - } else if (object instanceof Rectangle2D) { - Rectangle2D r2 = (Rectangle2D) object; - return new BoundingBox(r2.getMinX(), r2.getMinY(), r2.getWidth(), r2.getHeight()); - } else - throw new JavaFXLibraryNonFatalException("Unsupported parameter type: " + object.getClass().getName()); + RobotLog.trace("object type is: " + object.getClass()); + if (object instanceof Window) { + Window window = (Window) object; + return new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); + } else if (object instanceof Scene) { + Scene scene = (Scene) object; + double x = scene.getX() + scene.getWindow().getX(); + double y = scene.getY() + scene.getWindow().getY(); + return new BoundingBox(x, y, scene.getWidth(), scene.getHeight()); + } else if (object instanceof Point2D) { + return robot.bounds((Point2D) object).query(); + } else if (object instanceof Node) { + return robot.bounds((Node) object).query(); + } else if (object instanceof String) { + return robot.bounds(objectToNode(object)).query(); + } else if (object instanceof Bounds) { + return (Bounds) object; + } else if (object instanceof PointQuery) { + return robot.bounds(((PointQuery) object).query()).query(); + } else if (object instanceof Rectangle2D) { + Rectangle2D r2 = (Rectangle2D) object; + return new BoundingBox(r2.getMinX(), r2.getMinY(), r2.getWidth(), r2.getHeight()); + } else + throw new JavaFXLibraryNonFatalException("Unsupported parameter type: " + object.getClass().getName()); } private static String remainingQueries(String query) { diff --git a/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/WaitForEventsInFxApplicationThreadTest.java b/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/WaitForEventsInFxApplicationThreadTest.java index 7844cce..440fa03 100644 --- a/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/WaitForEventsInFxApplicationThreadTest.java +++ b/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/WaitForEventsInFxApplicationThreadTest.java @@ -21,7 +21,7 @@ import javafx.scene.control.Button; import javafxlibrary.TestFxAdapterTest; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; -import javafxlibrary.keywords.AdditionalKeywords.ConvenienceKeywords; +import javafxlibrary.keywords.AdditionalKeywords.ApplicationLauncher; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Rule; @@ -33,11 +33,11 @@ public class WaitForEventsInFxApplicationThreadTest extends TestFxAdapterTest { @Rule public ExpectedException thrown = ExpectedException.none(); - private static ConvenienceKeywords keywords; + private static ApplicationLauncher keywords; @BeforeClass public static void setupKeywords() { - keywords = new ConvenienceKeywords(); + keywords = new ApplicationLauncher(); } @Test diff --git a/src/test/robotframework/acceptance/0_ClickRobotTest.robot b/src/test/robotframework/acceptance/0_ClickRobotTest.robot index e1e0a2b..20291be 100644 --- a/src/test/robotframework/acceptance/0_ClickRobotTest.robot +++ b/src/test/robotframework/acceptance/0_ClickRobotTest.robot @@ -5,7 +5,7 @@ Suite Setup Setup all tests Suite Teardown Teardown all tests Test Setup Setup test case Test Teardown Enable Image Logging -Force tags set-clickrobot +Force Tags set-clickrobot *** Variables *** ${TEST_APPLICATION} javafxlibrary.testapps.TestClickRobot @@ -217,13 +217,13 @@ Right Click On Coordinates Click On ID That Does Not Exist [Tags] smoke negative ${MSG} Run Keyword And Expect Error * Click On id=idThatDoesNotExist - Should Be Equal ${MSG} Given element "id=idThatDoesNotExist" was not found within given timeout of 1 SECONDS + Should Be Equal ${MSG} Click On failed: Unable to find node for query "id=idThatDoesNotExist" Click On Unreachable Point [Tags] smoke negative ${POINT} Create Point ${0} ${-20} ${MSG} Run Keyword And Expect Error * Click On ${POINT} - Should Start With ${MSG} Click target location check failed: Can't click Point2D at [0.0, -20.0]: out of window bounds. + Should Start With ${MSG} Click On failed: Click target location check failed: Can't click Point2D at [0.0, -20.0]: out of window bounds. Click On Unreachable Coordinates [Tags] smoke negative @@ -238,14 +238,14 @@ Click On Mouse Button That Does Not Exist Click On Using Motion That Does Not Exist [Tags] smoke negative ${MSG} Run Keyword And Expect Error * Click On id=button ZIGZAG - Should Start With ${MSG} "ZIGZAG" is not a valid Motion. Accepted values are: + Should Start With ${MSG} Click On failed: "ZIGZAG" is not a valid Motion. Accepted values are: Click On ID That Does Not Exist With Safe Clicking Off [Tags] smoke negative Set Safe Clicking OFF ${MSG} Run Keyword And Expect Error * Click On id=idThatDoesNotExist Set Safe Clicking ON - Should Be Equal ${MSG} Given element "id=idThatDoesNotExist" was not found within given timeout of 1 SECONDS + Should Be Equal ${MSG} Click On failed: Unable to find node for query "id=idThatDoesNotExist" Click On Unsupported Type [Tags] smoke negative @@ -253,7 +253,7 @@ Click On Unsupported Type ${IMAGE} Capture Image ${NODE} ${PIXELREADER} Call Object Method ${IMAGE} getPixelReader ${MSG} Run Keyword And Expect Error * Click On ${PIXELREADER} - Should Start With ${MSG} Click target location check failed: Unsupported parameter type: + Should Start With ${MSG} Click On failed: Click target location check failed: Unsupported parameter type: *** Keywords *** Setup all tests diff --git a/src/test/robotframework/acceptance/MenuAppTest.robot b/src/test/robotframework/acceptance/MenuAppTest.robot index f617b53..5a0e86c 100644 --- a/src/test/robotframework/acceptance/MenuAppTest.robot +++ b/src/test/robotframework/acceptance/MenuAppTest.robot @@ -55,26 +55,30 @@ Menus - Change Theme Should Contain ${STYLESHEET}[0] Javastyle.css Menus - Change Font Size - [Tags] smoke demo-set - Click On text="Settings" - Move To text="Font size" - Click On text="26px" HORIZONTAL_FIRST - ${LABEL} Find css=.textLabel - ${STYLE} Call Object Method ${LABEL} getStyle - Should Contain ${STYLE} -fx-font-size: 26px + [Tags] smoke demo-set + Click On text="Settings" + Wait Until Node Is Visible text="Font size" + Move To text="Font size" + Wait Until Node Is Visible text="26px" + Click On text="26px" HORIZONTAL_FIRST + ${LABEL} Find css=.textLabel + ${STYLE} Call Object Method ${LABEL} getStyle + Should Contain ${STYLE} -fx-font-size: 26px Combined - [Tags] smoke demo-set - Click On text="Settings" - Move To text="Theme" + [Tags] smoke demo-set + Click On text="Settings" + Wait Until Node Is Visible text="Theme" + Move To text="Theme" # Horizontal first is required because submenu closes if the cursor moves outside of menu bounds - Click On text="Gradient" HORIZONTAL_FIRST - ${SCENE} Get Scene css=.textLabel - @{STYLESHEET} Call Object Method ${SCENE} getStylesheets - Should Contain ${STYLESHEET}[0] Gradientstyle.css - Click On text="Services" - Click On text="Analyze" - Verify String css=.textLabel Analyze + Wait Until Node is Visible text="Gradient" + Click On text="Gradient" HORIZONTAL_FIRST + ${SCENE} Get Scene css=.textLabel + @{STYLESHEET} Call Object Method ${SCENE} getStylesheets + Should Contain ${STYLESHEET}[0] Gradientstyle.css + Click On text="Services" + Click On text="Analyze" + Verify String css=.textLabel Analyze # Using Find All instead of text-value based css-selector here to avoid dependencies with the second test case @{COMBOBOXES} Find All css=.combo-box diff --git a/src/test/robotframework/acceptance/MoveRobotTest.robot b/src/test/robotframework/acceptance/MoveRobotTest.robot index 50f97f2..9a53a06 100644 --- a/src/test/robotframework/acceptance/MoveRobotTest.robot +++ b/src/test/robotframework/acceptance/MoveRobotTest.robot @@ -87,7 +87,7 @@ Move To Window Move To Nonexistent Location [Tags] smoke - Run Keyword And Expect Error Given element "css=\#rectangleNOTfound" was not found within given timeout of 0 SECONDS + Run Keyword And Expect Error Unable to find node for query "css=\#rectangleNOTfound" ... Move To css=\#rectangleNOTfound *** Keywords *** diff --git a/src/test/robotframework/acceptance/NodeLookupTest.robot b/src/test/robotframework/acceptance/NodeLookupTest.robot index 52d3e8b..1c7b053 100644 --- a/src/test/robotframework/acceptance/NodeLookupTest.robot +++ b/src/test/robotframework/acceptance/NodeLookupTest.robot @@ -47,7 +47,7 @@ Root Node Of XPath Query Root Node Of Node That Does Not Exist [Tags] smoke negative ${MSG} Run Keyword And Expect Error * Get Root Node Of id=non-existent-node-id - Should Contain ${MSG} Given element "id=non-existent-node-id" was not found within given timeout of 1 SECONDS + Should Contain ${MSG} Unable to find node for query "id=non-existent-node-id" *** Keywords *** Setup all tests diff --git a/src/test/robotframework/acceptance/ScrollRobotTest.robot b/src/test/robotframework/acceptance/ScrollRobotTest.robot index e081994..cc114ed 100644 --- a/src/test/robotframework/acceptance/ScrollRobotTest.robot +++ b/src/test/robotframework/acceptance/ScrollRobotTest.robot @@ -24,7 +24,7 @@ Scroll down Scroll Vertically DOWN 25 Verify String ${VERTICAL_TOTAL} ${TARGET_DISTANCE} Verify String ${VERTICAL_ACTUAL} -${TARGET_DISTANCE} - Verify String ${VERTICAL_EVENTS} 25 + Verify String ${VERTICAL_EVENTS} 1 Scroll up [Tags] smoke @@ -34,7 +34,7 @@ Scroll up Scroll Vertically UP 25 Verify String ${VERTICAL_TOTAL} ${TARGET_DISTANCE} Verify String ${VERTICAL_ACTUAL} ${TARGET_DISTANCE} - Verify String ${VERTICAL_EVENTS} 25 + Verify String ${VERTICAL_EVENTS} 1 Scroll Once Vertically [Tags] smoke @@ -54,7 +54,7 @@ Scroll Left Scroll Horizontally LEFT 25 Verify String ${HORIZONTAL_TOTAL} ${TARGET_DISTANCE} Verify String ${HORIZONTAL_ACTUAL} ${TARGET_DISTANCE} - Verify String ${HORIZONTAL_EVENTS} 25 + Verify String ${HORIZONTAL_EVENTS} 1 Scroll Right [Tags] smoke @@ -65,7 +65,7 @@ Scroll Right Scroll Horizontally RIGHT 10 Verify String ${HORIZONTAL_TOTAL} ${TARGET_DISTANCE} Verify String ${HORIZONTAL_ACTUAL} -${TARGET_DISTANCE} - Verify String ${HORIZONTAL_EVENTS} 10 + Verify String ${HORIZONTAL_EVENTS} 1 *** Keywords *** Setup all tests diff --git a/src/test/robotframework/acceptance/SwingApplicationWrapperTest.robot b/src/test/robotframework/acceptance/SwingApplicationWrapperTest.robot index 456545f..5c2ad99 100644 --- a/src/test/robotframework/acceptance/SwingApplicationWrapperTest.robot +++ b/src/test/robotframework/acceptance/SwingApplicationWrapperTest.robot @@ -24,7 +24,10 @@ Swing Embedded JavaFX Type Test Run Keyword If ${headless} Set Tags monocle-issue Launch Swing Application javafxlibrary.testapps.SwingApplication Wait Until Keyword Succeeds 15 sec 250ms Find id=textField ${True} + #Click On id=textField Write To id=textField JavaFXLibrary + #Click On id=textField + #Write JavaFXLibrary Wait Until Keyword Succeeds 3 sec 250ms Text Value Should Be JavaFXLibrary Launch Swing Application Using External Wrapper Class From 12cf74051dc8bac06b1a15d88a56aa95ff95a9c7 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Thu, 8 Oct 2020 16:05:38 +0300 Subject: [PATCH 05/19] handle null object properlty, timeout to be generic kw timeout, handle separately wait until kws, kw output improvements --- src/main/java/JavaFXLibrary.java | 55 ++++-- .../ApplicationLauncher.java | 6 +- .../ConvenienceKeywords.java | 159 +++++++++--------- .../keywords/AdditionalKeywords/Find.java | 4 +- .../AdditionalKeywords/Verifiers.java | 39 ++++- .../keywords/Keywords/BoundsLocation.java | 10 +- .../keywords/Keywords/ClickRobot.java | 11 +- .../keywords/Keywords/DragRobot.java | 2 + .../keywords/Keywords/KeyboardRobot.java | 9 +- .../keywords/Keywords/MoveRobot.java | 1 + .../keywords/Keywords/NodeLookup.java | 3 +- .../keywords/Keywords/PointLocation.java | 1 + .../keywords/Keywords/PointOffset.java | 1 + .../keywords/Keywords/ScreenCapturing.java | 6 +- .../keywords/Keywords/WindowLookup.java | 13 +- .../keywords/Keywords/WindowTargeting.java | 5 +- .../javafxlibrary/utils/HelperFunctions.java | 66 ++++---- .../CheckClickLocationTest.java | 4 +- .../CheckClickTargetTest.java | 4 +- .../HelperFunctionsTest.java | 4 +- .../ObjectToBoundsTest.java | 2 +- .../ObjectToNodeTest.java | 4 +- .../acceptance/0_ClickRobotTest.robot | 11 +- .../acceptance/BoundsLocationTest.robot | 3 +- .../acceptance/DatePickerTest.robot | 1 - .../acceptance/DragRobotTest.robot | 1 - .../robotframework/acceptance/FindTest.robot | 1 - .../acceptance/KeyboardRobotTest.robot | 1 - .../robotframework/acceptance/MiscTests.robot | 27 ++- .../acceptance/MoveRobotTest.robot | 5 +- .../acceptance/NodeLookupTest.robot | 3 +- .../acceptance/PointLocationTest.robot | 7 +- .../acceptance/ScreenCapturingTest.robot | 1 - 33 files changed, 279 insertions(+), 191 deletions(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index 95e5d44..7121a1b 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -23,7 +23,6 @@ import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; import javafxlibrary.exceptions.JavaFXLibraryFatalException; @@ -43,6 +42,7 @@ import javax.script.ScriptEngineManager; import javax.script.ScriptException; import static org.testfx.util.WaitForAsyncUtils.*; +import static javafxlibrary.utils.HelperFunctions.*; import java.util.ResourceBundle; @@ -52,9 +52,27 @@ public class JavaFXLibrary extends AnnotationLibrary { public static final String ROBOT_LIBRARY_VERSION = loadRobotLibraryVersion(); public static final TestListener ROBOT_LIBRARY_LISTENER = new TestListener(); + static List noLibraryKeywordTimeoutKeywords = new ArrayList() {{ + add("launchJavafxApplication"); + add("launchSwingApplication"); + add("launchSwingApplicationInSeparateThread"); + add("closeJavafxApplication"); + add("closeSwingApplication"); + add("waitForEventsInFxApplicationThread"); + add("waitUntilElementDoesNotExists"); + add("waitUntilElementExists"); + add("waitUntilNodeIsEnabled"); + add("waitUntilNodeIsNotEnabled"); + add("waitUntilNodeIsNotVisible"); + add("waitUntilNodeIsVisible"); + add("waitUntilProgressBarIsFinished"); + }}; + static List noWrappedAsyncFxKeywords = new ArrayList() {{ add("callObjectMethodInFxApplicationThread"); add("clearObjectMap"); + add("closeJavafxApplication"); + add("closeSwingApplication"); add("dropTo"); add("getCurrentApplication"); add("getLibraryVersion"); @@ -146,37 +164,50 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { AtomicReference retExcep = new AtomicReference<>(); RobotLog.ignoreDuplicates(); +// try { +// RobotLog.ignoreDuplicates(); +// // timeout + 500 ms so that underlying timeout has a chance to expire first +// waitFor(getWaitUntilTimeout(TimeUnit.MILLISECONDS) + 500, TimeUnit.MILLISECONDS, () -> { try { if (noWrappedAsyncFxKeywords.contains(keywordName)) { - retval.set(super.runKeyword(keywordName, finalArgs, finalKwargs)); + if (noLibraryKeywordTimeoutKeywords.contains(keywordName)) { + retval.set(super.runKeyword(keywordName, finalArgs, finalKwargs)); + } else { + retval.set(waitForAsync(getLibraryKeywordTimeout(TimeUnit.MILLISECONDS) + 500, () -> { + try { + //super.runKeyword(keywordName, finalArgs, finalKwargs); + return super.runKeyword(keywordName, finalArgs, finalKwargs); + } catch (RuntimeException rte) { + retExcep.set(rte); + return null; + } + })); + } } else { - try { - retval.set(asyncFx(() -> { + retval.set(waitForAsyncFx(getLibraryKeywordTimeout(TimeUnit.MILLISECONDS) + 500, () -> { try { return super.runKeyword(keywordName, finalArgs, finalKwargs); } catch (RuntimeException rte) { retExcep.set(rte); return null; } - }).get()); + })); waitForFxEvents( 3); - } catch (InterruptedException | ExecutionException iee) { - throw (RuntimeException) iee.getCause(); - } } -// return true; + //return true; } catch (JavaFXLibraryTimeoutException jfxte) { // timeout already expired, catch exception and jump out retExcep.set(jfxte); -// throw jfxte; + //throw jfxte; } catch (RuntimeException rte) { // catch exception and continue trying retExcep.set(rte); -// return false; + //return false; } if(retExcep.get()!=null) { RobotLog.reset(); RuntimeException e = retExcep.get(); + // TODO: to asyncFx thread runOnFailure.runOnFailure(); if (e.getCause() instanceof JavaFXLibraryFatalException) { @@ -185,6 +216,8 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { } else if (e.getCause() instanceof JavaFXLibraryNonFatalException) { RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary NON-FATAL exception: \n" + Throwables.getStackTraceAsString(e)); throw e; + } else if (e.getCause() instanceof TimeoutException) { + throw new JavaFXLibraryNonFatalException("Library keyword timeout (" + getLibraryKeywordTimeout() + "s) for keyword: " + keywordName); } else if (e.getCause() instanceof IllegalArgumentException) { RobotLog.trace("JavaFXLibrary: Caught IllegalArgumentException: \n" + Throwables.getStackTraceAsString(e)); throw new JavaFXLibraryNonFatalException("Illegal arguments for keyword '" + keywordName + "':\n" + diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java index 13cfc7f..811b499 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java @@ -53,7 +53,7 @@ public void launchJavafxApplication(String appName, String... appArgs) { try { RobotLog.info("Starting application:" + appName); createNewSession(appName, appArgs); - waitForEventsInFxApplicationThread(getWaitUntilTimeout()); + waitForEventsInFxApplicationThread(getLibraryKeywordTimeout()); RobotLog.info("Application: " + appName + " started."); } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Unable to launch application: " + appName, e); @@ -75,7 +75,7 @@ public void launchSwingApplication(String appName, String... appArgs) { Class mainClass = getMainClass(appName); Application app = createWrapperApplication(mainClass, appArgs); createNewSession(app); - waitForEventsInFxApplicationThread(getWaitUntilTimeout()); + waitForEventsInFxApplicationThread(getLibraryKeywordTimeout()); RobotLog.info("Application: " + appName + " started."); } @@ -95,7 +95,7 @@ public void launchSwingApplicationInSeparateThread(String appName, String... app Class c = getMainClass(appName); Application app = createThreadedWrapperApplication(c, appArgs); createNewSession(app); - waitForEventsInFxApplicationThread(getWaitUntilTimeout()); + waitForEventsInFxApplicationThread(getLibraryKeywordTimeout()); RobotLog.info("Application: " + appName + " started."); } diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index b91464a..90103bd 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -35,6 +35,7 @@ import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.keywords.Keywords.ClickRobot; import javafxlibrary.keywords.Keywords.KeyboardRobot; +import static javafxlibrary.utils.HelperFunctions.*; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; @@ -46,9 +47,7 @@ import java.lang.reflect.Method; import java.util.*; -import java.util.concurrent.Semaphore; -import static javafxlibrary.utils.HelperFunctions.*; import static org.testfx.util.WaitForAsyncUtils.waitForFxEvents; @RobotKeywords @@ -81,14 +80,12 @@ public void bringStageToFront(Stage stage) { public Object callObjectMethod(Object object, String method, Object... arguments) { /* Javalib Core changes all parameters to Strings after runKeywords automatic argument replacement, so arguments are replaced with objects from objectMap here instead. */ - object = HelperFunctions.useMappedObject(object); - Object[] tempArgs = HelperFunctions.checkMethodArguments(arguments); - Object[] finalArgs = HelperFunctions.useMappedObjects(tempArgs); + object = useMappedObject(object); + Object[] tempArgs = checkMethodArguments(arguments); + Object[] finalArgs = useMappedObjects(tempArgs); Object result = callMethod(object, method, finalArgs, false); - if (result != null) return mapObject(result); - return null; } @@ -100,11 +97,11 @@ public Object callObjectMethod(Object object, String method, Object... arguments @ArgumentNames({ "object", "method", "*arguments=" }) public void callObjectMethodInFxApplicationThread(Object object, String method, Object... arguments) { // Check callObjectMethod for info about argument replacing. - object = HelperFunctions.useMappedObject(object); - Object[] tempArgs = HelperFunctions.checkMethodArguments(arguments); - Object[] finalArgs = HelperFunctions.useMappedObjects(tempArgs); + object = useMappedObject(object); + Object[] tempArgs = checkMethodArguments(arguments); + Object[] finalArgs = useMappedObjects(tempArgs); callMethod(object, method, finalArgs, true); - waitForFxEvents(1); + waitForFxEvents(3); } @RobotKeyword("Lists methods available for given node.\n" @@ -115,21 +112,21 @@ public void callObjectMethodInFxApplicationThread(Object object, String method, + "| List Component Methods | ${my node} |\n") @ArgumentNames({ "node" }) public String[] listNodeMethods(Node node) { - RobotLog.info("Listing all available methods for node: \"" + node + "\""); try { - Class klass = node.getClass(); + RobotLog.info("Listing all available methods for node: \"" + node + "\""); + Class nodeClass = node.getClass(); ArrayList list = new ArrayList<>(); System.out.println("*INFO*"); - while (klass != null) { - String name = String.format("\n*%s*\n", klass.getName()); + while (nodeClass != null) { + String name = String.format("\n*%s*\n", nodeClass.getName()); System.out.println(name); list.add(name); - for (Method m : klass.getDeclaredMethods()) { + for (Method m : nodeClass.getDeclaredMethods()) { String entry = getMethodDescription(m); System.out.println(entry); list.add(entry); } - klass = klass.getSuperclass(); + nodeClass = nodeClass.getSuperclass(); } return list.toArray(new String[0]); } catch (Exception e) { @@ -175,6 +172,7 @@ public void printChildNodes(Object root) { + "| Log FXML | ${my node} | \n") @ArgumentNames({"root="}) public void logFXML(Object root) { + RobotLog.info("Logging FXML of root \"" + root + "\"."); XPathFinder logger = new XPathFinder(); logger.setNodeLogging(false); RobotLog.info(logger.getFxml((Parent) objectToNode(root))); @@ -200,8 +198,8 @@ public void setSafeClicking(String value) { } } - @RobotKeyword("Sets the time waited for nodes to become available. Keyword returns old timeout value as return " - + "value. Default value is 5 seconds.\n\n" + @RobotKeyword("Sets the maximum time library waits for keyword to finish. Keyword returns old timeout value as return " + + "value. Default value is 10 seconds.\n\n" + "``timeout`` is an Integer value for timeout in seconds.\n\n" + "\nExample:\n" + "| ${old_timeout}= | Set Timeout | 20 | \n" @@ -209,8 +207,8 @@ public void setSafeClicking(String value) { + "| [Teardown] | Set Timeout | ${old_timeout} | \n") public Integer setTimeout(int timeout) { RobotLog.info("Setting timeout to " + timeout + "s"); - Integer oldTimeoutValue = getWaitUntilTimeout(); - setWaitUntilTimeout(timeout); + Integer oldTimeoutValue = getLibraryKeywordTimeout(); + setLibraryKeywordTimeout(timeout); return oldTimeoutValue; } @@ -223,9 +221,8 @@ public Integer setTimeout(int timeout) { + "``switchAmount`` is an Integer value and specifies how many switches will be made in total") @ArgumentNames({ "switchAmount" }) public void switchWindow(int switchAmount) { - RobotLog.info("Switching window for: \"" + switchAmount + "\" times."); - try { + RobotLog.info("Switching window for: \"" + switchAmount + "\" times."); if (isMac()) { robot.press(KeyCode.META); } else { @@ -251,13 +248,13 @@ public void switchWindow(int switchAmount) { + "| Log List | ${states} | \n") @ArgumentNames({ "node" }) public Set getPseudoClassStates(Object locator) { - Node node = objectToNode(locator); - + checkObjectArgumentNotNull(locator); try { - RobotLog.info("Getting pseudoclass states for node: \"" + node + "\""); + RobotLog.info("Getting pseudoclass states for node: \"" + locator + "\""); + Node node = objectToNode(locator); return node.getPseudoClassStates(); } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("Unable to get pseudoClassStates for node: " + node.toString()); + throw new JavaFXLibraryNonFatalException("Unable to get pseudoClassStates for locator: " + locator); } } @@ -266,10 +263,11 @@ public Set getPseudoClassStates(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public String getNodeText(Object locator) { + checkObjectArgumentNotNull(locator); Node node = objectToNode(locator); - RobotLog.info("Getting text value for node: \"" + node + "\""); - Class c = node.getClass(); try { + RobotLog.info("Getting text value for node: \"" + node + "\""); + Class c = node.getClass(); return (String) c.getMethod("getText").invoke(node); } catch (NoSuchMethodException e) { throw new JavaFXLibraryNonFatalException("Get node text failed for node: " + node + ": Node has no getText method"); @@ -285,9 +283,10 @@ public String getNodeText(Object locator) { + "Note, impl_getUrl -method is deprecated! Support for this method will be removed from Java in the future.") @ArgumentNames({ "node" }) public String getNodeImageUrl(Object locator) { + checkObjectArgumentNotNull(locator); Node node = objectToNode(locator); - RobotLog.info("Getting image url from node: \"" + node + "\""); try { + RobotLog.info("Getting image url from node: \"" + node + "\""); Method[] methods = node.getClass().getMethods(); for (Method m : methods) { if (m.getName().equals("getImage") && m.getParameterCount() == 0) { @@ -316,8 +315,8 @@ public String getNodeImageUrl(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "node" }) public Object getNodeParent(Object locator) { + checkObjectArgumentNotNull(locator); Node node = objectToNode(locator); - try { RobotLog.info("Getting node parent object for: \"" + node + "\""); return mapObject(node.getParent()); @@ -333,8 +332,8 @@ public Object getNodeParent(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public String getObjectClassName(Object locator) { + checkObjectArgumentNotNull(locator); Node node = objectToNode(locator); - try { RobotLog.info("Getting class name for object: \"" + node + "\""); return node.getClass().getSimpleName(); @@ -347,6 +346,7 @@ public String getObjectClassName(Object locator) { + "``locator`` is either a _query_, a _Node_ or a _Window_, see `3.2 Using locators as keyword arguments`\n\n") @ArgumentNames({ "locator" }) public Object getScene(Object locator) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Getting a Scene object for: \"" + locator + "\""); if (locator instanceof Node){ @@ -357,9 +357,7 @@ public Object getScene(Object locator) { } else if (locator instanceof Window) { return mapObject(((Window) locator).getScene()); } - throw new JavaFXLibraryNonFatalException("Unsupported locator type. Locator must be an instance of Node, String or Window!"); - } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) throw e; @@ -372,9 +370,9 @@ public Object getScene(Object locator) { + "`3.2 Using locators as keyword arguments`. This keyword can be coupled with e.g. `List Windows` -keyword.\n\n") @ArgumentNames({ "window" }) public String getWindowTitle(Object object) { - RobotLog.info("Getting the window title for: \"" + object + "\""); - + checkObjectArgumentNotNull(object); try { + RobotLog.info("Getting the window title for: \"" + object + "\""); Method m = object.getClass().getMethod("getTitle"); return (String) m.invoke(object, (Object[]) null); } catch (NoSuchMethodException e) { @@ -396,9 +394,9 @@ public Object getPrimaryScreenBounds() { } } - @RobotKeyword("Returns the library version from POM file") + @RobotKeyword("Returns the JavaFXLibrary version.") public String getLibraryVersion() { - return HelperFunctions.getVersion(); + return getVersion(); } @RobotKeyword("Returns the value of cell in the given location\n\n" @@ -408,12 +406,14 @@ public String getLibraryVersion() { + "``column`` Integer value for the column") @ArgumentNames({ "table", "row", "column" }) public Object getTableCellValue(Object locator, int row, int column) { + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting table \"" + locator + "\" cell value from row \"" + row + "\" and column \"" + column + "\"."); TableView table = (TableView) objectToNode(locator); Object item = table.getItems().get(row); TableColumn col = (TableColumn) table.getColumns().get(column); Object value = col.getCellObservableValue(item).getValue(); - return HelperFunctions.mapObject(value); + return mapObject(value); } catch (ClassCastException cce) { throw new JavaFXLibraryNonFatalException("Unable to handle argument as TableView!"); } catch (IndexOutOfBoundsException e) { @@ -430,10 +430,11 @@ public Object getTableCellValue(Object locator, int row, int column) { + "``column`` Integer value for the column") @ArgumentNames({ "table", "row", "column" }) public Object getTableCell(Object locator, int row, int column) { + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting table \"" + locator + "\" cell from row \"" + row + "\" and column \"" + column + "\"."); TableView table = (TableView) objectToNode(locator); return mapObject(getTableRowCell(table, row, column)); - } catch (ClassCastException cce) { throw new JavaFXLibraryNonFatalException("Unable to handle argument as TableView!"); } @@ -445,24 +446,22 @@ public Object getTableCell(Object locator, int row, int column) { + "``column`` Integer value for the column") @ArgumentNames({ "table", "column" }) public List getTableColumnValues(Object locator, int column) { + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting table \"" + locator + "\" values from column \"" + column + "\"."); TableView table = (TableView) objectToNode(locator); ObservableList items = table.getItems(); List values = new ArrayList<>(); TableColumn tableColumn = (TableColumn) table.getColumns().get(column); - if (tableColumn.getText() != null) RobotLog.info("Getting values from column " + tableColumn.getText()); else RobotLog.info("Getting values from column using index " + column); - for(Object item : items) { Object value = tableColumn.getCellObservableValue(item).getValue(); values.add(mapObject(value)); } - return values; - } catch (IndexOutOfBoundsException e) { throw new JavaFXLibraryNonFatalException("Out of table bounds: " + e.getMessage()); } catch (Exception e) { @@ -476,7 +475,9 @@ public List getTableColumnValues(Object locator, int column) { + "``column`` Integer value for the column") @ArgumentNames({ "table", "column" }) public List getTableColumnCells(Object locator, int column) { + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting table \"" + locator + "\" cells from column \"" + column + "\"."); TableView table = (TableView) objectToNode(locator); List columnCells = new ArrayList<>(); VirtualFlow vf = (VirtualFlow) ( (TableViewSkin) table.getSkin() ).getChildren().get( 1 ); @@ -503,17 +504,16 @@ public List getTableColumnCells(Object locator, int column) { + "| ${cell text}= | Get Node Text | &{row cells}[column name] | # assuming that cell is a node that has a text value |\n") @ArgumentNames({ "table", "row" }) public List getTableRowValues(Object locator, int rowNumber) { - RobotLog.info("Getting values from table row: " + rowNumber); + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting table \"" + locator + "\" values from row \"" + rowNumber + "\"."); TableView table = (TableView) objectToNode(locator); Object row = table.getItems().get(rowNumber); List values = new ArrayList<>(); - for(Object tableColumn : table.getColumns()){ values.add( ((TableColumn)tableColumn).getCellObservableValue(row).getValue()); } return values; - } catch (ClassCastException cce){ throw new JavaFXLibraryNonFatalException("Unable to handle argument as TableView!"); } @@ -529,17 +529,15 @@ public List getTableRowValues(Object locator, int rowNumber) { + "| ${cell text}= | Get Node Text | &{row cells}[column name] | # assuming that cell is a node that has a text value |\n") @ArgumentNames({ "table", "row" }) public Map getTableRowCells(Object locator, int row) { - RobotLog.info("Getting cell nodes from table row: " + row); - + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting table \"" + locator + "\" cells from row \"" + row + "\"."); TableView table = (TableView) objectToNode(locator); Map cells = new HashMap<>(); - for (int i = 0; i < table.getColumns().size(); i++){ cells.put(getTableColumnName(table, i), mapObject(getTableRowCell(table, row, i))); } return cells; - } catch (ClassCastException cce){ throw new JavaFXLibraryNonFatalException("Unable to handle argument as TableView!"); } @@ -550,7 +548,9 @@ public Map getTableRowCells(Object locator, int row) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "table" }) public int getTableColumnCount(Object locator){ + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting table \"" + locator + "\" column count."); TableView table = (TableView) objectToNode(locator); return table.getColumns().size(); } catch (ClassCastException cce){ @@ -589,6 +589,8 @@ public String getScreenshotDirectory(){ + "``fieldName`` is a String specifying which field value should be read") @ArgumentNames({ "object", "fieldName" }) public Object getObjectProperty(Object object, String fieldName) { + checkObjectArgumentNotNull(object); + RobotLog.info("Getting object \"" + object + "\" property from field \"" + fieldName + "\"."); return mapObject(getFieldsValue(object, object.getClass(), fieldName)); } @@ -599,13 +601,14 @@ public void printObjectProperties(Object object) { printFields(object, object.getClass()); } - @RobotKeyword("Gets the max value for a given scrollbar. \n\n" + "``locator`` is either a _query_ or _Object:Node_ for identifying the ScrollBar element, see " + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({"locator"}) public Double getScrollBarMaxValue(Object locator){ + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting scroll bar max value from locator \"" + locator + "\"."); ScrollBar scrollBar = (ScrollBar) objectToNode(locator); return scrollBar.getMax(); } catch (ClassCastException cce) { @@ -618,7 +621,9 @@ public Double getScrollBarMaxValue(Object locator){ + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({"locator"}) public Double getScrollBarMinValue(Object locator){ + checkObjectArgumentNotNull(locator); try{ + RobotLog.info("Getting scroll bar min value from locator \"" + locator + "\"."); ScrollBar scrollBar = (ScrollBar) objectToNode(locator); return scrollBar.getMin(); } catch (ClassCastException cce) { @@ -631,7 +636,9 @@ public Double getScrollBarMinValue(Object locator){ + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({"locator"}) public Double getScrollBarValue(Object locator){ + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting scroll bar value from locator \"" + locator + "\"."); ScrollBar scrollBar = (ScrollBar) objectToNode(locator); return scrollBar.getValue(); } catch (ClassCastException cce) { @@ -644,11 +651,11 @@ public Double getScrollBarValue(Object locator){ + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public Boolean getCheckBoxSelection(Object locator) { - + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting check box selection from locator \"" + locator + "\"."); CheckBox box = (CheckBox) objectToNode(locator); return box.isSelected(); - } catch (ClassCastException cce) { throw new JavaFXLibraryNonFatalException("Given locator could not be handled as CheckBox!", cce); } @@ -659,27 +666,26 @@ public Boolean getCheckBoxSelection(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public Object getSelectedRadioButton(Object locator) { - + checkObjectArgumentNotNull(locator); try{ + RobotLog.info("Getting selected radio button from locator \"" + locator + "\"."); RadioButton rb = (RadioButton)objectToNode(locator); return rb.getToggleGroup().getSelectedToggle(); - } catch (ClassCastException cce) { throw new JavaFXLibraryNonFatalException("Unable to handle given locator as RadioButton!"); } } - @RobotKeyword("Returns the current value of given spinner element. \n" + "``locator`` is either a _query_ or _Object:Node_ for identifying the Spinner element, see " + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public Object getSpinnerValue(Object locator) { - + checkObjectArgumentNotNull(locator); try{ + RobotLog.info("Getting spinner value from locator \"" + locator + "\"."); Spinner spinner = (Spinner) objectToNode(locator); return spinner.getValueFactory().getValue(); - }catch (ClassCastException cce){ throw new JavaFXLibraryNonFatalException("Given locator could not be handled as Spinner!", cce); } @@ -693,11 +699,11 @@ public Object getSpinnerValue(Object locator) { + "| Dictionary Should Contain Key | ${tabs} | tab name | \n") @ArgumentNames({ "locator" }) public Map getTabPaneTabs(Object locator) { - RobotLog.info("Getting a dictionary for all tabs in TabPane: " + locator); + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting a dictionary for all tabs in TabPane: " + locator); TabPane tabPane = (TabPane) objectToNode(locator); Map tabs = new HashMap<>(); - int i = tabPane.getTabs().size() - 1; for (Node node : tabPane.getChildrenUnmodifiable()) { if(node.getStyleClass().contains("tab-content-area")) { @@ -705,11 +711,8 @@ public Map getTabPaneTabs(Object locator) { i--; } } - return tabs; - } catch (ClassCastException cce) { - throw new JavaFXLibraryNonFatalException("Given locator: \"" + locator + "\" could not be handled as TabPane!", cce); } } @@ -722,14 +725,13 @@ public Map getTabPaneTabs(Object locator) { + "| Dictionary Should contain Key | ${tab} | tab name | \n") @ArgumentNames({ "locator" }) public Map getSelectedTabPaneTab(Object locator) { - RobotLog.info("Getting the selected tab from TabPane: " + locator); - Map tab = new HashMap<>(); - + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting the selected tab from TabPane: " + locator); + Map tab = new HashMap<>(); TabPane tabPane = (TabPane) objectToNode(locator); tab.put(getSelectedTabName(tabPane), mapObject(getSelectedTab(tabPane))); return tab; - } catch (ClassCastException cce) { throw new JavaFXLibraryNonFatalException("Given locator: \"" + locator + "\" could not be handled as TabPane!", cce); } @@ -744,10 +746,10 @@ public Map getSelectedTabPaneTab(Object locator) { + "| Select Tab Pane Tab | \\#tab-id | tab name | \n") @ArgumentNames({"locator", "tabName"}) public void selectTabPaneTab (Object locator, String tabName) { - RobotLog.info("Selecting tab: \"" + tabName + "\" from TabPane: \"" + locator + "\""); + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Selecting tab: \"" + tabName + "\" from TabPane: \"" + locator + "\""); Node headerArea = getTabPaneHeaderArea((TabPane) objectToNode(locator)); - for (Node node : headerArea.lookupAll(".tab .tab-label")) { if( node instanceof Labeled){ String tabLabel = ((Labeled)node).getText(); @@ -761,7 +763,6 @@ public void selectTabPaneTab (Object locator, String tabName) { } } throw new JavaFXLibraryNonFatalException("Unable to find a tab with name: " + tabName); - } catch (ClassCastException cce) { throw new JavaFXLibraryNonFatalException("Given locator: \"" + locator + "\" could not be handled as TabPane!", cce); } @@ -772,7 +773,9 @@ public void selectTabPaneTab (Object locator, String tabName) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({"locator"}) public Double getScrollPaneVerticalValue(Object locator){ + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting scroll pane vertical value from locator \"" + locator + "\"."); ScrollPane pane = (ScrollPane) objectToNode(locator); return pane.getVvalue(); } catch (ClassCastException cce) { @@ -785,7 +788,9 @@ public Double getScrollPaneVerticalValue(Object locator){ + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({"locator"}) public Double getScrollPaneHorizontalValue(Object locator){ + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting scroll pane horizontal value from locator \"" + locator + "\"."); ScrollPane pane = (ScrollPane) objectToNode(locator); return pane.getHvalue(); } catch (ClassCastException cce) { @@ -800,7 +805,9 @@ public Double getScrollPaneHorizontalValue(Object locator){ + "| ${date}= | Get Selected Date Picker Date | \\#datepicker-id | \n") @ArgumentNames({"locator"}) public Object getSelectedDatePickerDate(Object locator) { + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting selected date picker date from locator \"" + locator + "\"."); DatePicker dp = (DatePicker) objectToNode(locator); return mapObject(dp.getValue()); } catch (ClassCastException cce) { @@ -815,7 +822,9 @@ public Object getSelectedDatePickerDate(Object locator) { + "| Clear Text Input | .text-field | \n") @ArgumentNames({ "locator" }) public void clearTextInput(Object locator) { + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Clearing input text from locator \"" + locator + "\"."); TextInputControl textInputControl = (TextInputControl) objectToNode(locator); new ClickRobot().clickOn(textInputControl, "DIRECT"); new KeyboardRobot().selectAll(); @@ -835,15 +844,13 @@ public void clearTextInput(Object locator) { + "| Click On | &{menu items}[menu item name] | \n\n") @ArgumentNames({"locator="}) public Map getContextMenuItems(Window window){ + RobotLog.info("Getting context menu items from window \"" + window + "\"."); if (!(window instanceof ContextMenu)) throw new JavaFXLibraryNonFatalException("Unable to handle target as ContextMenu!"); - Map menuItems = new HashMap<>(); Set nodes = robot.rootNode(window).lookupAll(".menu-item"); - for (Node node : nodes) menuItems.put(getMenuItemText(node), mapObject(node)); - return menuItems; } @@ -855,6 +862,7 @@ public Map getContextMenuItems(Window window){ + "| Select Context Menu Item | menu item name |") @ArgumentNames({"item"}) public void selectContextMenuItem(String item){ + RobotLog.info("Selecting context menu item \"" + item + "\"."); List windows = robot.listTargetWindows(); ListIterator li = windows.listIterator(windows.size()); while (li.hasPrevious()) { @@ -866,7 +874,6 @@ public void selectContextMenuItem(String item){ } } } - throw new JavaFXLibraryNonFatalException("unable to find menu item: " + item); } @@ -875,7 +882,9 @@ public void selectContextMenuItem(String item){ + " `3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public Object getProgressBarValue(Object locator) { + checkObjectArgumentNotNull(locator); try{ + RobotLog.info("Getting progress bar value from locator \"" + locator + "\"."); ProgressBar pb = (ProgressBar) objectToNode(locator); return mapObject(pb.getProgress()); diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java index 1ad4417..bfbb4c5 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java @@ -41,9 +41,9 @@ public class Find { + "using the found HBox as a root node, while looking for a node matching the XPath.\n\n") @ArgumentNames({"query", "failIfNotFound=False", "root="}) public Object find(String query, boolean failIfNotFound, Parent root) { - RobotLog.info("Trying to find the first node matching the query: \"" + query + "\", failIfNotFound=\"" + - failIfNotFound + "\", root=\"" + root + "\""); try { + RobotLog.info("Trying to find the first node matching the query: \"" + query + "\", failIfNotFound=\"" + + failIfNotFound + "\", root=\"" + root + "\""); if (root != null) { return mapObject(new Finder().find(query, root)); } else { diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index 9533d4d..81f3415 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -89,10 +89,11 @@ public void waitUntilElementDoesNotExists(String locator, int timeout, String ti + "`3. Locating JavaFX Nodes`. \n\n" + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n\n") @ArgumentNames({"locator", "timeout=5"}) - public void waitUntilNodeIsVisible(Object locator, int timeout) { + public Object waitUntilNodeIsVisible(Object locator, int timeout) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Waiting for node \"" + locator + "\" to be visible, timeout=\"" + timeout + "\"."); - waitUntilVisible(locator, timeout); + return mapObject(waitUntilVisible(locator, timeout)); } catch (IllegalArgumentException | NullPointerException e) { throw new JavaFXLibraryNonFatalException(""); } @@ -103,10 +104,11 @@ public void waitUntilNodeIsVisible(Object locator, int timeout) { + "`3. Locating JavaFX Nodes`. \n\n" + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n\n") @ArgumentNames({"locator", "timeout=5"}) - public void waitUntilNodeIsNotVisible(Object locator, int timeout) { + public Object waitUntilNodeIsNotVisible(Object locator, int timeout) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Waiting for node \"" + locator + "\" to be invisible, timeout=\"" + timeout + "\"."); - waitUntilInvisible(locator, timeout); + return mapObject(waitUntilInvisible(locator, timeout)); } catch (IllegalArgumentException | NullPointerException e) { throw new JavaFXLibraryNonFatalException(""); } @@ -117,10 +119,11 @@ public void waitUntilNodeIsNotVisible(Object locator, int timeout) { + "`3. Locating JavaFX Nodes`. \n\n" + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n\n") @ArgumentNames({"locator", "timeout=5"}) - public void waitUntilNodeIsEnabled(Object locator, int timeout) { + public Object waitUntilNodeIsEnabled(Object locator, int timeout) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Waiting for node \"" + locator + "\" to be enabled, timeout=\"" + timeout + "\"."); - waitUntilEnabled(locator, timeout); + return mapObject(waitUntilEnabled(locator, timeout)); } catch (IllegalArgumentException | NullPointerException e){ throw new JavaFXLibraryNonFatalException(""); } @@ -131,10 +134,11 @@ public void waitUntilNodeIsEnabled(Object locator, int timeout) { + "`3. Locating JavaFX Nodes`. \n\n" + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n\n") @ArgumentNames({"locator", "timeout=5"}) - public void waitUntilNodeIsNotEnabled(Object locator, int timeout) { + public Object waitUntilNodeIsNotEnabled(Object locator, int timeout) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Waiting for node \"" + locator + "\" to be disabled, timeout=\"" + timeout + "\"."); - waitUntilDisabled(locator, timeout); + return mapObject(waitUntilDisabled(locator, timeout)); } catch (IllegalArgumentException | NullPointerException e){ throw new JavaFXLibraryNonFatalException(""); } @@ -145,6 +149,7 @@ public void waitUntilNodeIsNotEnabled(Object locator, int timeout) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void nodeShouldBeVisible(Object locator) { + checkObjectArgumentNotNull(locator); RobotLog.info("Checking that locator node is visible: \"" + locator + "\"."); verifyThat(objectToNode(locator), isVisible() ); } @@ -154,6 +159,7 @@ public static void nodeShouldBeVisible(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void nodeShouldNotBeVisible(Object locator) { + checkObjectArgumentNotNull(locator); RobotLog.info("Checking that locator node is not visible: \"" + locator + "\"."); verifyThat(objectToNode(locator), isInvisible() ); } @@ -163,6 +169,7 @@ public static void nodeShouldNotBeVisible(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void nodeShouldBeFocused(Object locator) { + checkObjectArgumentNotNull(locator); RobotLog.info("Checking that locator node is focused: \"" + locator + "\"."); verifyThat(objectToNode(locator), isFocused() ); } @@ -172,6 +179,7 @@ public static void nodeShouldBeFocused(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void nodeShouldNotBeFocused(Object locator) { + checkObjectArgumentNotNull(locator); RobotLog.info("Checking that locator node is not focused: \"" + locator + "\"."); verifyThat(objectToNode(locator), isNotFocused() ); } @@ -181,6 +189,7 @@ public static void nodeShouldNotBeFocused(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void nodeShouldBeEnabled(Object locator) { + checkObjectArgumentNotNull(locator); RobotLog.info("Checking that locator node is enabled: \"" + locator + "\"."); verifyThat(objectToNode(locator), isEnabled() ); } @@ -190,6 +199,7 @@ public static void nodeShouldBeEnabled(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void nodeShouldNotBeEnabled(Object locator) { + checkObjectArgumentNotNull(locator); RobotLog.info("Checking that locator node is not enabled: \"" + locator + "\"."); verifyThat(objectToNode(locator), NodeMatchers.isDisabled() ); } @@ -199,6 +209,7 @@ public static void nodeShouldNotBeEnabled(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void nodeShouldBeHoverable(Object locator) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Checking that locator node is hoverable: \"" + locator + "\"."); verifyThat(objectToNode(locator), ExtendedNodeMatchers.isHoverable()); @@ -215,6 +226,7 @@ public static void nodeShouldBeHoverable(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void nodeShouldNotBeHoverable(Object locator) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Checking that locator node is not hoverable: \"" + locator + "\"."); verifyThat(objectToNode(locator), ExtendedNodeMatchers.isHoverable()); @@ -230,6 +242,7 @@ public static void nodeShouldNotBeHoverable(Object locator) { + "``text`` is the String to be searched for") @ArgumentNames({ "locator", "text" }) public static void nodeShouldHaveText(Object locator, String text) { + checkObjectArgumentNotNull(locator); RobotLog.info("Checking that locator node \"" + locator + "\" has text \"" + text + "\"."); Object node = objectToNode(locator); @@ -249,6 +262,7 @@ else if (node instanceof TextFlow) + "``text`` is the String to be searched for") @ArgumentNames({ "locator", "text" }) public static void nodeShouldNotHaveText(Object locator, String text) { + checkObjectArgumentNotNull(locator); RobotLog.info("Checking that locator node \"" + locator + "\" does not have text \"" + text + "\"."); Object node = objectToNode(locator); @@ -266,6 +280,7 @@ else if (node instanceof TextFlow) + "``window`` is the _Object:Window_ that specifies which window should be visible, see `3.2 Using locators as keyword arguments`.") @ArgumentNames({ "window" }) public static void windowShouldBeVisible(Object window) { + checkObjectArgumentNotNull(window); RobotLog.info("Checking if window \"" + window + "\" is visible."); verifyThat((Window) window, WindowMatchers.isShowing()); } @@ -274,6 +289,7 @@ public static void windowShouldBeVisible(Object window) { + "``window`` is the _Object:Window_ that specifies which window should be not visible, see `3.2 Using locators as keyword arguments`.") @ArgumentNames({ "window" }) public static void windowShouldNotBeVisible(Object window) { + checkObjectArgumentNotNull(window); RobotLog.info("Checking if window \"" + window + "\" is not visible."); verifyThat((Window) window, WindowMatchers.isNotShowing()); } @@ -282,6 +298,7 @@ public static void windowShouldNotBeVisible(Object window) { + "``window`` is the _Object:Window_ that specifies which window should be focused, see `3.2 Using locators as keyword arguments`.") @ArgumentNames({ "window" }) public static void windowShouldBeFocused(Object window) { + checkObjectArgumentNotNull(window); RobotLog.info("Checking if window \"" + window + "\" is focused."); verifyThat((Window) window, WindowMatchers.isFocused()); } @@ -290,6 +307,7 @@ public static void windowShouldBeFocused(Object window) { + "``window`` is the _Object:Window_ that specifies which window should be focused, see `3.2 Using locators as keyword arguments`.") @ArgumentNames({ "window" }) public static void windowShouldNotBeFocused(Object window) { + checkObjectArgumentNotNull(window); RobotLog.info("Checking if window \"" + window + "\" is not focused."); verifyThat((Window) window, WindowMatchers.isNotFocused()); } @@ -369,6 +387,7 @@ public void imagesShouldNotMatch(Image image1, Image image2, double percentage) + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void radioButtonShouldBeSelected(Object locator) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Checking that radio button is selected: \"" + locator + "\"."); verifyThat((RadioButton) objectToNode(locator), ToggleMatchers.isSelected()); @@ -382,6 +401,7 @@ public static void radioButtonShouldBeSelected(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void radioButtonShouldNotBeSelected(Object locator) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Checking that radio button is not selected: \"" + locator + "\"."); verifyThat((RadioButton) objectToNode(locator), ToggleMatchers.isNotSelected()); @@ -395,6 +415,7 @@ public static void radioButtonShouldNotBeSelected(Object locator) { + "`3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void toggleButtonShouldBeSelected(Object locator) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Checking that toggle button is selected: \"" + locator + "\"."); verifyThat((ToggleButton) objectToNode(locator), ToggleMatchers.isSelected()); @@ -408,6 +429,7 @@ public static void toggleButtonShouldBeSelected(Object locator) { + " `3. Locating JavaFX Nodes`. \n\n") @ArgumentNames({ "locator" }) public static void toggleButtonShouldNotBeSelected(Object locator) { + checkObjectArgumentNotNull(locator); try{ RobotLog.info("Checking that toggle button is not selected: \"" + locator + "\"."); verifyThat((ToggleButton) objectToNode(locator), ToggleMatchers.isNotSelected()); @@ -422,6 +444,7 @@ public static void toggleButtonShouldNotBeSelected(Object locator) { + "``timeout`` is an integer value for timeout in seconds, defaults to 20 seconds.") @ArgumentNames({ "locator", "timeout=20" }) public static void waitUntilProgressBarIsFinished(Object locator, int timeout) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Waiting until progressbar is finished: \"" + locator + "\", timeout=\"" + timeout + "\"."); ProgressBar pb = (ProgressBar) objectToNode(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java index 8bd8810..c2cf76a 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java @@ -105,9 +105,9 @@ public Object createRectangle(double minX, double minY, double width, double hei + "| Should Be Equal | ${bounds} | ${target} | \n") @ArgumentNames({ "locator" }) public Object getBounds(Object locator) { - RobotLog.info("Getting bounds using locator \"" + locator + "\""); - // TODO: Test if Window and Scene objects get correct Bound locations on scaled displays + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting bounds using locator \"" + locator + "\""); if (locator instanceof Window) { Window window = (Window) locator; return mapObject(new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight())); @@ -116,10 +116,8 @@ public Object getBounds(Object locator) { return mapObject(new BoundingBox(scene.getX() + scene.getWindow().getX(), scene.getY() + scene.getWindow().getY(), scene.getWidth(), scene.getHeight())); } - if (locator instanceof String) - return getBounds(waitUntilExists((String) locator)); - + return getBounds(objectToNode((String) locator)); Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "bounds", locator.getClass()); BoundsQuery bounds = (BoundsQuery) method.invoke(robot, locator); return HelperFunctions.mapObject(bounds.query()); @@ -127,10 +125,8 @@ public Object getBounds(Object locator) { } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("getBounds: Could not execute move to using locator \"" + locator + "\": " + e.getCause().getMessage()); - } catch (JavaFXLibraryNonFatalException e){ throw e; - } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Couldn't find \"" + locator + "\"", e); } diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java index 1ed4555..8e1adc7 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java @@ -49,6 +49,7 @@ public class ClickRobot extends TestFxAdapter { + "| Click On | .css-name | Motion=VERTICAL_FIRST | \n") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface clickOn(Object locator, String motion) { + checkObjectArgumentNotNull(locator); try { Object target = checkClickTarget(locator); RobotLog.info("Clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); @@ -57,6 +58,8 @@ public FxRobotInterface clickOn(Object locator, String motion) { return (FxRobotInterface) method.invoke(robot, target, getMotion(motion), new MouseButton[]{MouseButton.PRIMARY}); } catch (IllegalAccessException | InvocationTargetException | JavaFXLibraryNonFatalException e) { throw new JavaFXLibraryNonFatalException("Click On failed: " + e.getMessage(), e); + } catch (Exception e) { + throw new JavaFXLibraryNonFatalException("Click On failed: " + e.getMessage(), e); } } @@ -67,6 +70,7 @@ public FxRobotInterface clickOn(Object locator, String motion) { + "is usually HORIZONTAL_FIRST.\n\n") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface rightClickOn(Object locator, String motion) { + checkObjectArgumentNotNull(locator); try { Object target = checkClickTarget(locator); RobotLog.info("Right clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); @@ -83,6 +87,7 @@ public FxRobotInterface rightClickOn(Object locator, String motion) { + "``motion`` defines the path for mouse to move to a target location. Default value is _DIRECT_.") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface doubleClickOn(Object locator, String motion) { + checkObjectArgumentNotNull(locator); try { Object target = checkClickTarget(locator); RobotLog.info("Double clicking on target \"" + target + "\", motion=\"" + getMotion(motion) + "\""); @@ -144,7 +149,7 @@ public FxRobotInterface rightClickOnMouseButton() { public FxRobotInterface clickOnCoordinates(int x, int y, String motion) { try { RobotLog.info("Clicking on coordinates x=\"" + x + "\"" + ", y=\"" + y + "\"" + " and motion=\"" + motion + "\""); - checkClickLocation(x, y); + checkObjectInsideActiveWindow(x, y); return robot.clickOn(x, y, getMotion(motion), MouseButton.PRIMARY); } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) { @@ -160,7 +165,7 @@ public FxRobotInterface clickOnCoordinates(int x, int y, String motion) { @ArgumentNames({ "x", "y", "motion=DIRECT" }) public FxRobotInterface doubleClickOnCoordinates(int x, int y, String motion) { try { - checkClickLocation(x, y); + checkObjectInsideActiveWindow(x, y); RobotLog.info("Double clicking on coordinates x=\"" + x + "\"" + ", y=\"" + y + "\"" + " and motion=\"" + motion + "\""); return robot.doubleClickOn(x, y, getMotion(motion), MouseButton.PRIMARY); } catch (Exception e) { @@ -177,7 +182,7 @@ public FxRobotInterface doubleClickOnCoordinates(int x, int y, String motion) { @ArgumentNames({ "x", "y", "motion=DIRECT" }) public FxRobotInterface rightClickOnCoordinates(int x, int y, String motion) { try { - checkClickLocation(x, y); + checkObjectInsideActiveWindow(x, y); RobotLog.info("Right clicking on coordinates x=\"" + x + "\"" + ", y=\"" + y + "\"" + " and motion=\"" + motion + "\""); return robot.rightClickOn(x, y, getMotion(motion)); } catch (Exception e) { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java index dd18b05..b0a0ea2 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java @@ -48,6 +48,7 @@ public class DragRobot extends TestFxAdapter { + "| Drag From | ${node} | SECONDARY | \n") @ArgumentNames({ "locator", "button=PRIMARY" }) public FxRobotInterface dragFrom(Object locator, String button) { + checkObjectArgumentNotNull(locator); try { Object target = checkClickTarget(locator); RobotLog.info("Dragging from \"" + target + "\"" + " with button=\"" + button + "\""); @@ -66,6 +67,7 @@ public FxRobotInterface dragFrom(Object locator, String button) { + "| Drop To | \\#some-node-id | \n") @ArgumentNames({ "locator" }) public FxRobotInterface dropTo(Object locator) { + checkObjectArgumentNotNull(locator); try { Object target = asyncFx(() -> checkClickTarget(locator)).get(); RobotLog.info("Dropping to \"" + target + "\""); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java index 8a97ca4..3ba409b 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java @@ -216,6 +216,7 @@ public void writeFast(String text) { + "| Write To | .css-name | Robot Framework | \n") @ArgumentNames({ "locator", "text" }) public void writeTo(Object locator, String text) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Writing \"" + text + "\" to " + locator); asyncFx(() -> clickRobot.clickOn(locator,"DIRECT")).get(); @@ -239,11 +240,13 @@ public void selectAll() { robot.push(KeyCode.CONTROL, KeyCode.A); } - @RobotKeyword("Sets the time waited between every character when typing\n\n" + - "``milliseconds`` is the time waited between each character in milliseconds.") + @RobotKeyword("Sets the time waited between every character when typing. Returns previous value.\n\n" + + "``milliseconds`` is the time waited between each character in milliseconds.") @ArgumentNames({ "milliseconds" }) - public void setWriteSpeed(int milliseconds) { + public int setWriteSpeed(int milliseconds) { + int oldSleepMillis = this.sleepMillis; this.sleepMillis = milliseconds; + return oldSleepMillis; } } \ No newline at end of file diff --git a/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java index b238b8b..4016449 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java @@ -47,6 +47,7 @@ public class MoveRobot extends TestFxAdapter { + "| Move To | ${POINT} | VERTICAL_FIRST | | # moves mouse on top of given Point object by moving first vertically and then horizontally |") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface moveTo(Object locator, String motion) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Moving to target \"" + locator + "\" using motion: \"" + getMotion(motion) + "\""); if (locator instanceof String) { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java index 35d45e1..8d65a29 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java @@ -52,12 +52,13 @@ public class NodeLookup extends TestFxAdapter { + "| ${root} | Get Root Node Of | \\#some-node-id | \n" ) @ArgumentNames({"locator"}) public Object getRootNodeOf(Object locator) { + checkObjectArgumentNotNull(locator); try { + RobotLog.info("Getting root node of target \"" + locator + "\""); if (locator instanceof String) { Node node = objectToNode(locator); return getRootNodeOf(node); } - RobotLog.info("Getting root node of target \"" + locator + "\""); Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "rootNode", locator.getClass()); return HelperFunctions.mapObject(method.invoke(robot, locator)); } catch (IllegalAccessException | InvocationTargetException e) { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java b/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java index 875dbd2..34b9f9b 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java @@ -46,6 +46,7 @@ public class PointLocation extends TestFxAdapter { + "| Move To | ${point query} | | | # moves to bottom right corner of a node that was stored in PointQuery object. |\n") @ArgumentNames({"locator"}) public Object pointTo(Object locator) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Creating a point query for target \"" + locator + "\""); if (locator instanceof String) diff --git a/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java b/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java index 5783a65..291846f 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java @@ -43,6 +43,7 @@ public class PointOffset extends TestFxAdapter { + "| ${point query offset}= | Call Method | ${point query} | getOffset | \n") @ArgumentNames({"locator", "offsetX", "offsetY"}) public Object pointToWithOffset(Object locator, double offsetX, double offsetY) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Creating a point query for target: \"" + locator + "\" with offset: [" + offsetX + ", " + offsetY + "]"); if (locator instanceof String) diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java index a5c0791..e880b87 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java @@ -84,10 +84,8 @@ public Object capturePrimaryScreen() { + "| ${capture}= | Capture Image | \\#id | logImage=False |\n" ) @ArgumentNames({"locator=target window", "logImage=True"}) public Object captureImage(Object locator, boolean logImage){ + checkObjectArgumentNotNull(locator); try { - if (locator == null) - throw new JavaFXLibraryNonFatalException("Unable to capture image, given locator was null!"); - RobotLog.info("Capturing screenshot from locator: \"" + locator + "\""); Image image; Bounds targetBounds = objectToBounds(locator); @@ -146,7 +144,7 @@ public Object captureImage(Object locator, boolean logImage){ + "| ${capture}= | Capture Scene Containing Node | ${node} | \n" ) @ArgumentNames({"locator", "logImage=True"}) public Object captureSceneContainingNode(Object locator) { - Scene scene = (Scene) useMappedObject(new ConvenienceKeywords().getScene(locator)); + Scene scene = (Scene) useMappedObject(new ConvenienceKeywords().getScene(mapObject(locator))); return this.captureImage(scene,true); } diff --git a/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java b/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java index 7cccdab..8fcadec 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java @@ -18,7 +18,7 @@ package javafxlibrary.keywords.Keywords; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; -import javafxlibrary.utils.HelperFunctions; +import static javafxlibrary.utils.HelperFunctions.*; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; import org.apache.commons.lang3.reflect.MethodUtils; @@ -39,7 +39,7 @@ public class WindowLookup extends TestFxAdapter { + "| Log List | ${windows} | \n") public List listWindows() { try { - return HelperFunctions.mapObjects(robot.listWindows()); + return mapObjects(robot.listWindows()); } catch (Exception e) { if(e instanceof JavaFXLibraryNonFatalException) throw e; @@ -50,7 +50,7 @@ public List listWindows() { @RobotKeyword("Returns a list of windows that are ordered by proximity to the last target window.\n\n") public List listTargetWindows() { try { - return HelperFunctions.mapObjects(robot.listTargetWindows()); + return mapObjects(robot.listTargetWindows()); } catch (Exception e) { if(e instanceof JavaFXLibraryNonFatalException) throw e; @@ -78,22 +78,23 @@ public List listTargetWindows() { ) @ArgumentNames({"locator"}) public Object getWindow(Object locator) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Getting window using locator \"" + locator + "\""); if (locator instanceof String) { if (((String) locator).startsWith("pattern=")) { locator = ((String) locator).replace("pattern=",""); - return HelperFunctions.mapObject(robot.window((String) locator)); + return mapObject(robot.window((String) locator)); } else if ( ((String) locator).matches("[0-9]+")) { return getWindow(Integer.parseInt(locator.toString())); } else { if (((String) locator).startsWith("title=")) locator = ((String) locator).replace("title=", ""); - return HelperFunctions.mapObject(robot.window((String) locator)); + return mapObject(robot.window((String) locator)); } } Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "window", locator.getClass()); - return HelperFunctions.mapObject(method.invoke(robot, locator)); + return mapObject(method.invoke(robot, locator)); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute get window using locator \"" + locator + "\""); } catch (Exception e) { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java b/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java index 49dd46f..b5d6c67 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java @@ -22,7 +22,7 @@ import java.util.regex.Pattern; import javafx.application.Platform; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; -import javafxlibrary.utils.HelperFunctions; +import static javafxlibrary.utils.HelperFunctions.*; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; import org.apache.commons.lang3.reflect.MethodUtils; @@ -38,7 +38,7 @@ public class WindowTargeting extends TestFxAdapter { + "| ${window}= | Get Target Window | \n") public Object getTargetWindow() { try { - return HelperFunctions.mapObject(robot.targetWindow()); + return mapObject(robot.targetWindow()); } catch (Exception e) { if(e instanceof JavaFXLibraryNonFatalException) throw e; @@ -66,6 +66,7 @@ public Object getTargetWindow() { ) @ArgumentNames("locator") public void setTargetWindow(Object locator) { + checkObjectArgumentNotNull(locator); try { RobotLog.info("Setting target window according to locator \"" + locator + "\""); if (locator instanceof String) { diff --git a/src/main/java/javafxlibrary/utils/HelperFunctions.java b/src/main/java/javafxlibrary/utils/HelperFunctions.java index b7a8438..c222c20 100644 --- a/src/main/java/javafxlibrary/utils/HelperFunctions.java +++ b/src/main/java/javafxlibrary/utils/HelperFunctions.java @@ -55,6 +55,7 @@ import org.testfx.service.query.PointQuery; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.jar.JarEntry; @@ -70,11 +71,7 @@ public class HelperFunctions { private static boolean safeClicking = true; - private static int waitUntilTimeout = 5; - - public static Node waitUntilExists(String target) { - return waitUntilExists(target, waitUntilTimeout, "SECONDS"); - } + private static int libraryKeywordTimeout = 10; public static Node waitUntilExists(String target, int timeout, String timeUnit) { try { @@ -85,6 +82,8 @@ public static Node waitUntilExists(String target, int timeout, String timeUnit) // TODO: Add null checks for node.getScene() waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> hasValidCoordinates(node)).get()); return node; + } catch (InterruptedException |ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("Given element \"" + target + "\" was not found (" + iee.getCause().toString() + ")."); } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; } catch (TimeoutException te) { @@ -462,41 +461,39 @@ public static void setSafeClicking(boolean value) { safeClicking = value; } - public static void setWaitUntilTimeout(int value) { - waitUntilTimeout = value; + public static void setLibraryKeywordTimeout(int value) { + libraryKeywordTimeout = value; } - public static int getWaitUntilTimeout() { - return waitUntilTimeout; + public static int getLibraryKeywordTimeout() { + return libraryKeywordTimeout; } - public static long getWaitUntilTimeout(TimeUnit timeUnit) { - return timeUnit.convert(waitUntilTimeout, TimeUnit.SECONDS); + public static long getLibraryKeywordTimeout(TimeUnit timeUnit) { + return timeUnit.convert(libraryKeywordTimeout, TimeUnit.SECONDS); } - public static void checkClickLocation(int x, int y) { - checkClickLocation(new Point2D(x,y)); + public static void checkObjectInsideActiveWindow(int x, int y) { + checkObjectInsideActiveWindow(new Point2D(x,y)); } - - public static void checkClickLocation(Object object) { + public static void checkObjectInsideActiveWindow(Object object) { try { if (safeClicking) { RobotLog.trace("Checking if target \"" + object.toString() + "\" is within active window"); - Point2D point = getCenterPoint(objectToBounds(object)); if (!visibleWindowsContain(robot.listWindows(), point)) { - throw new JavaFXLibraryNonFatalException("Can't click " + object.getClass().getSimpleName() + " at [" + point.getX() + ", " + point.getY() + "]: out of window bounds. " + + throw new JavaFXLibraryNonFatalException("can't click " + object.getClass().getSimpleName() + " at [" + point.getX() + ", " + point.getY() + "]: out of window bounds. " + "To enable clicking outside of visible window bounds use keyword `Set Safe Clicking` with argument `off`"); } - RobotLog.trace("Target location checks out OK, it is within active window"); + RobotLog.trace("Object is within active window"); } } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("Click target location check failed: " + e.getMessage(), e); + throw new JavaFXLibraryNonFatalException("object inside active window check failed: " + e.getMessage(), e); } } - public static void bringClickLocationToFront(Object object) { + public static void bringObjectsWindowToFront(Object object) { try { new ConvenienceKeywords().bringStageToFront((Stage) objectToNode(object).getScene().getWindow()); } catch (Exception e) { @@ -506,19 +503,19 @@ public static void bringClickLocationToFront(Object object) { public static Object checkClickTarget(Object target) { try { - if (target instanceof String || target instanceof Node) { - target = objectToBounds(target); - //target = waitUntilEnabled(waitUntilVisible(target, waitUntilTimeout), waitUntilTimeout); - bringClickLocationToFront(target); + target = objectToNode(target); + if (!Matchers.is(isVisible()).matches(target) || !Matchers.is(isEnabled()).matches(target)) { + throw new JavaFXLibraryNonFatalException("target \"" + target + "\" not visible or enabled!"); + } } - checkClickLocation(target); + bringObjectsWindowToFront(target); + checkObjectInsideActiveWindow(target); return target; - } catch (JavaFXLibraryNonFatalException jfxe) { throw jfxe; } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("Click target check failed: " + e.getMessage(), e); + throw new JavaFXLibraryNonFatalException("click target check failed: " + e.getMessage(), e); } } @@ -586,21 +583,19 @@ public static String getVersion() { } public static Node objectToNode(Object target) { - if (target instanceof String) { Node node = createFinder().find((String) target); if (node == null) { - throw new JavaFXLibraryNonFatalException("Unable to find node for query \"" + target + "\""); + throw new JavaFXLibraryNonFatalException("unable to find node for query \"" + target + "\""); } return node; - //return waitUntilExists((String) target, waitUntilTimeout, "SECONDS"); } else if (target instanceof Node) { return (Node) target; } else if (target == null) { - throw new JavaFXLibraryNonFatalException("Target object was empty (null)"); + throw new JavaFXLibraryNonFatalException("target object was empty (null)"); } else - throw new JavaFXLibraryNonFatalException("Given target \"" + target.getClass().getName() + + throw new JavaFXLibraryNonFatalException("given target \"" + target.getClass().getName() + "\" is not an instance of Node or a query string for node!"); } @@ -629,7 +624,7 @@ public static Bounds objectToBounds(Object object) { Rectangle2D r2 = (Rectangle2D) object; return new BoundingBox(r2.getMinX(), r2.getMinY(), r2.getWidth(), r2.getHeight()); } else - throw new JavaFXLibraryNonFatalException("Unsupported parameter type: " + object.getClass().getName()); + throw new JavaFXLibraryNonFatalException("unsupported parameter type: " + object.getClass().getName()); } private static String remainingQueries(String query) { @@ -1000,6 +995,11 @@ public static Object checkMethodArgument(String argument) { return argument; } } + + public static void checkObjectArgumentNotNull(Object object) { + if (object == null) + throw new IllegalArgumentException("Object is null!"); + } } diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickLocationTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickLocationTest.java index 893d9f6..1dd2255 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickLocationTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickLocationTest.java @@ -58,7 +58,7 @@ public void cleanup() { @Test public void checkClickLocation_IsWithinVisibleWindow() { setBoundsQueryExpectations(30, 30); - HelperFunctions.checkClickLocation(30, 30); + HelperFunctions.checkObjectInsideActiveWindow(30, 30); Assert.assertThat(outContent.toString(), endsWith("*TRACE* Target location checks out OK, it is within active window\n")); } @@ -70,7 +70,7 @@ public void checkClickLocation_IsOutsideVisibleWindow() throws Exception { thrown.expect(JavaFXLibraryNonFatalException.class); thrown.expectMessage(target); - HelperFunctions.checkClickLocation(30, 800); + HelperFunctions.checkObjectInsideActiveWindow(30, 800); } private void setBoundsQueryExpectations(double minX, double minY) { diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickTargetTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickTargetTest.java index 66476ef..62f6df3 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickTargetTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickTargetTest.java @@ -36,7 +36,7 @@ public void setup() { windows = new ArrayList<>(); windows.add(stage); button = new Button(); - HelperFunctions.setWaitUntilTimeout(0); + HelperFunctions.setLibraryKeywordTimeout(0); } private void setupStageTests(int x, int y, int width, int height) { @@ -80,7 +80,7 @@ Node waitUntilExists(String target, int timeout, String timeUnit) { return button; } }; - HelperFunctions.setWaitUntilTimeout(1); + HelperFunctions.setLibraryKeywordTimeout(1); setupStageTests(300, 300, 50, 50); Button result = (Button) HelperFunctions.checkClickTarget(".button"); Assert.assertEquals(button, result); diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/HelperFunctionsTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/HelperFunctionsTest.java index a9738aa..950eacd 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/HelperFunctionsTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/HelperFunctionsTest.java @@ -22,8 +22,8 @@ public void helperFunctions_setSafeClickingOn() { @Test public void helperFunctions_setWaitUntilTimeout() { - HelperFunctions.setWaitUntilTimeout(2); - Integer result = (Integer) HelperFunctions.getFieldsValue(null, HelperFunctions.class, "waitUntilTimeout"); + HelperFunctions.setLibraryKeywordTimeout(2); + Integer result = (Integer) HelperFunctions.getFieldsValue(null, HelperFunctions.class, "libraryKeywordTimeout"); Assert.assertEquals(2, (int) result); } } diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToBoundsTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToBoundsTest.java index fdd2fa6..1640ef8 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToBoundsTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToBoundsTest.java @@ -125,7 +125,7 @@ public void objectToBounds_Rectangle2D() { @Test public void objectToBounds_UnsupportedType() { thrown.expect(JavaFXLibraryNonFatalException.class); - thrown.expectMessage("Unsupported parameter type: java.lang.Integer"); + thrown.expectMessage("unsupported parameter type: java.lang.Integer"); HelperFunctions.objectToBounds(22); } } diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java index 66b0a09..f56b65b 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java @@ -54,14 +54,14 @@ public void objectToNode_Node() { @Test public void objectToNode_InvalidType() { thrown.expect(JavaFXLibraryNonFatalException.class); - thrown.expectMessage("Given target \"java.lang.Integer\" is not an instance of Node or a query string for node!"); + thrown.expectMessage("given target \"java.lang.Integer\" is not an instance of Node or a query string for node!"); HelperFunctions.objectToNode(new Integer("2009")); } @Test public void objectToNode_NullObject() { thrown.expect(JavaFXLibraryNonFatalException.class); - thrown.expectMessage("Target object was empty (null)"); + thrown.expectMessage("target object was empty (null)"); HelperFunctions.objectToNode(null); } } diff --git a/src/test/robotframework/acceptance/0_ClickRobotTest.robot b/src/test/robotframework/acceptance/0_ClickRobotTest.robot index 20291be..803049b 100644 --- a/src/test/robotframework/acceptance/0_ClickRobotTest.robot +++ b/src/test/robotframework/acceptance/0_ClickRobotTest.robot @@ -217,18 +217,18 @@ Right Click On Coordinates Click On ID That Does Not Exist [Tags] smoke negative ${MSG} Run Keyword And Expect Error * Click On id=idThatDoesNotExist - Should Be Equal ${MSG} Click On failed: Unable to find node for query "id=idThatDoesNotExist" + Should Be Equal ${MSG} Click On failed: unable to find node for query "id=idThatDoesNotExist" Click On Unreachable Point [Tags] smoke negative ${POINT} Create Point ${0} ${-20} ${MSG} Run Keyword And Expect Error * Click On ${POINT} - Should Start With ${MSG} Click On failed: Click target location check failed: Can't click Point2D at [0.0, -20.0]: out of window bounds. + Should Start With ${MSG} Click On failed: object inside active window check failed: can't click Point2D at [0.0, -20.0]: out of window bounds. Click On Unreachable Coordinates [Tags] smoke negative ${MSG} Run Keyword And Expect Error * Click On Coordinates ${0} ${-20} - Should Start With ${MSG} Click target location check failed: Can't click Point2D at [0.0, -20.0]: out of window bounds. + Should Start With ${MSG} object inside active window check failed: can't click Point2D at [0.0, -20.0]: out of window bounds. Click On Mouse Button That Does Not Exist [Tags] smoke negative @@ -245,7 +245,7 @@ Click On ID That Does Not Exist With Safe Clicking Off Set Safe Clicking OFF ${MSG} Run Keyword And Expect Error * Click On id=idThatDoesNotExist Set Safe Clicking ON - Should Be Equal ${MSG} Click On failed: Unable to find node for query "id=idThatDoesNotExist" + Should Be Equal ${MSG} Click On failed: unable to find node for query "id=idThatDoesNotExist" Click On Unsupported Type [Tags] smoke negative @@ -253,7 +253,7 @@ Click On Unsupported Type ${IMAGE} Capture Image ${NODE} ${PIXELREADER} Call Object Method ${IMAGE} getPixelReader ${MSG} Run Keyword And Expect Error * Click On ${PIXELREADER} - Should Start With ${MSG} Click On failed: Click target location check failed: Unsupported parameter type: + Should Start With ${MSG} Click On failed: object inside active window check failed: unsupported parameter type: *** Keywords *** Setup all tests @@ -262,7 +262,6 @@ Setup all tests Set Screenshot Directory ${OUTPUT_DIR}${/}report-images Set Scene Values Set Window Values - Set Timeout 1 Setup test case Reset Counters diff --git a/src/test/robotframework/acceptance/BoundsLocationTest.robot b/src/test/robotframework/acceptance/BoundsLocationTest.robot index 9c82cc5..49c3b6d 100644 --- a/src/test/robotframework/acceptance/BoundsLocationTest.robot +++ b/src/test/robotframework/acceptance/BoundsLocationTest.robot @@ -87,12 +87,11 @@ Compare Different Size Bounds Get Bounds Of Id That Does Not Exist [Tags] smoke negative ${MSG} Run Keyword And Expect Error * Get Bounds id=idThatDoesNotExist - Should Be Equal ${MSG} Given element "id=idThatDoesNotExist" was not found within given timeout of 1 SECONDS + Should Be Equal ${MSG} unable to find node for query "id=idThatDoesNotExist" *** Keywords *** Setup all tests Import JavaFXLibrary - Set Timeout 1 Launch Javafx Application ${TEST_APPLICATION} Set Screenshot Directory ${OUTPUT_DIR}${/}report-images Set Offsets diff --git a/src/test/robotframework/acceptance/DatePickerTest.robot b/src/test/robotframework/acceptance/DatePickerTest.robot index dce0a2d..2ca941e 100644 --- a/src/test/robotframework/acceptance/DatePickerTest.robot +++ b/src/test/robotframework/acceptance/DatePickerTest.robot @@ -34,7 +34,6 @@ Select JavaFX Release Date *** Keywords *** Setup all tests Import JavaFXLibrary - Set Timeout 0 Launch Javafx Application ${TEST_APPLICATION} Set Screenshot Directory ${OUTPUT_DIR}${/}report-images diff --git a/src/test/robotframework/acceptance/DragRobotTest.robot b/src/test/robotframework/acceptance/DragRobotTest.robot index 9ce1efe..9e330be 100644 --- a/src/test/robotframework/acceptance/DragRobotTest.robot +++ b/src/test/robotframework/acceptance/DragRobotTest.robot @@ -245,7 +245,6 @@ Drag From + Drop To Using XPath Query *** Keywords *** Setup all tests Import JavaFXLibrary - Set Timeout 0 Launch Javafx Application ${TEST_APPLICATION} Set Screenshot Directory ${OUTPUT_DIR}${/}report-images ${SCENE} Get Scene css=.button diff --git a/src/test/robotframework/acceptance/FindTest.robot b/src/test/robotframework/acceptance/FindTest.robot index d954d68..6d26c0a 100644 --- a/src/test/robotframework/acceptance/FindTest.robot +++ b/src/test/robotframework/acceptance/FindTest.robot @@ -345,7 +345,6 @@ Open Dialog In Window Management App Setup All Tests Import JavaFXLibrary - Set Timeout 0 Teardown all tests Close Javafx Application diff --git a/src/test/robotframework/acceptance/KeyboardRobotTest.robot b/src/test/robotframework/acceptance/KeyboardRobotTest.robot index 97d44e4..fecc0d6 100644 --- a/src/test/robotframework/acceptance/KeyboardRobotTest.robot +++ b/src/test/robotframework/acceptance/KeyboardRobotTest.robot @@ -83,7 +83,6 @@ Write special characters *** Keywords *** Setup all tests Import JavaFXLibrary - Set Timeout 0 Launch Javafx Application ${TEST_APPLICATION} Set Screenshot Directory ${OUTPUT_DIR}${/}report-images diff --git a/src/test/robotframework/acceptance/MiscTests.robot b/src/test/robotframework/acceptance/MiscTests.robot index c47866b..92690b1 100644 --- a/src/test/robotframework/acceptance/MiscTests.robot +++ b/src/test/robotframework/acceptance/MiscTests.robot @@ -114,23 +114,19 @@ Set Node Visibility (Call Method With Argument Types That Require Casting) Check That Element Is Hoverable [Tags] smoke demo-set Set Test Application javafxlibrary.testapps.TestClickRobot - Set Timeout 3 ${target_node}= Find id=resetButton Call Object Method In Fx Application Thread ${target_node} setVisible (boolean)false Run Keyword And Expect Error * Node Should Be Hoverable ${target_node} Call Object Method In Fx Application Thread ${target_node} setVisible (boolean)true Node Should Be Hoverable id=resetButton - [Teardown] Set Timeout 0 Check That Element Is Not Hoverable [Tags] smoke demo-set negative Set Test Application javafxlibrary.testapps.TestClickRobot - Set Timeout 3 ${target_node}= Find id=resetButton Run Keyword And Expect Error * Node Should Not Be Hoverable ${target_node} Call Object Method In Fx Application Thread ${target_node} setVisible (boolean)false Node Should Not Be Hoverable id=resetButton - [Teardown] Set Timeout 0 Find From Node [Tags] smoke @@ -375,10 +371,27 @@ Is not Java agent ${IS_JAVA_AGENT} = Is Java Agent Should Be Equal ${False} ${IS_JAVA_AGENT} +Library Keyword Timeout Should Not Happen + [Tags] smoke + ${old_timeout}= Set Timeout 2 + Set Test Application javafxlibrary.testapps.TestKeyboardRobot + Run Keyword And Expect Error Given element "id=doesNotExist" was not found within given timeout of 3 SECONDS + ... Wait Until Element Exists id=doesNotExist timeout=3 + [Teardown] Set Timeout ${old_timeout} + +Library Keyword Timeout Should Happen + [Tags] smoke + ${old_timeout}= Set Timeout 2 + ${old_write_speed}= Set Write Speed 1000 + Set Test Application javafxlibrary.testapps.TestKeyboardRobot + Clear Textarea + Run Keyword And Expect Error Library keyword timeout (2s) for keyword: write + ... Write Robot Framework + [Teardown] Run Keywords Set Timeout ${old_timeout} AND Set Write Speed ${old_write_speed} + *** Keywords *** Setup All Tests Import JavaFXLibrary - Set Timeout 0 Get First Player ${TABLE} Find id=table @@ -396,3 +409,7 @@ Reset Node Id To Yellow Call Object Method ${node} setId yellow ${after_reset} Find id=yellow Should Be Equal ${after_reset} ${original} + +Clear Textarea + Click On id=resetButton + Click On id=textArea \ No newline at end of file diff --git a/src/test/robotframework/acceptance/MoveRobotTest.robot b/src/test/robotframework/acceptance/MoveRobotTest.robot index 9a53a06..e0a74b3 100644 --- a/src/test/robotframework/acceptance/MoveRobotTest.robot +++ b/src/test/robotframework/acceptance/MoveRobotTest.robot @@ -87,13 +87,12 @@ Move To Window Move To Nonexistent Location [Tags] smoke - Run Keyword And Expect Error Unable to find node for query "css=\#rectangleNOTfound" - ... Move To css=\#rectangleNOTfound + Run Keyword And Expect Error unable to find node for query "id=rectangleNOTfound" + ... Move To id=rectangleNOTfound *** Keywords *** Setup all tests Import JavaFXLibrary - Set Timeout 0 Launch Javafx Application ${TEST_APPLICATION} Set Screenshot Directory ${OUTPUT_DIR}${/}report-images Set Scene Bounds Values diff --git a/src/test/robotframework/acceptance/NodeLookupTest.robot b/src/test/robotframework/acceptance/NodeLookupTest.robot index 1c7b053..be5daeb 100644 --- a/src/test/robotframework/acceptance/NodeLookupTest.robot +++ b/src/test/robotframework/acceptance/NodeLookupTest.robot @@ -47,14 +47,13 @@ Root Node Of XPath Query Root Node Of Node That Does Not Exist [Tags] smoke negative ${MSG} Run Keyword And Expect Error * Get Root Node Of id=non-existent-node-id - Should Contain ${MSG} Unable to find node for query "id=non-existent-node-id" + Should Contain ${MSG} unable to find node for query "id=non-existent-node-id" *** Keywords *** Setup all tests Import JavaFXLibrary Launch Javafx Application ${TEST_APPLICATION} Set Screenshot Directory ${OUTPUT_DIR}${/}report-images - Set Timeout 1 Teardown all tests Close Javafx Application diff --git a/src/test/robotframework/acceptance/PointLocationTest.robot b/src/test/robotframework/acceptance/PointLocationTest.robot index 701c5aa..0c3c39a 100644 --- a/src/test/robotframework/acceptance/PointLocationTest.robot +++ b/src/test/robotframework/acceptance/PointLocationTest.robot @@ -103,6 +103,12 @@ Point To Class Move To ${POINTQUERY} Verify String id=locationLabel 25 | 475 +Point To Empty String + [Tags] smoke + Move To Top Left Corner + ${MSG}= Run Keyword And Expect Error * Point To ${EMPTY} + Should Start With ${MSG} Illegal arguments for keyword 'pointTo' + # PointOffset test cases Point To Point With Offset [Tags] smoke @@ -173,7 +179,6 @@ Set New Target Position *** Keywords *** Setup all tests Import JavaFXLibrary - Set Timeout 0 Launch Javafx Application ${TEST_APPLICATION} Set Screenshot Directory ${OUTPUT_DIR}${/}report-images Set Decoration Values diff --git a/src/test/robotframework/acceptance/ScreenCapturingTest.robot b/src/test/robotframework/acceptance/ScreenCapturingTest.robot index b415c9d..d7a11a1 100644 --- a/src/test/robotframework/acceptance/ScreenCapturingTest.robot +++ b/src/test/robotframework/acceptance/ScreenCapturingTest.robot @@ -95,7 +95,6 @@ Try To Compare Different Size Images *** Keywords *** Setup all tests Import JavaFXLibrary - Set Timeout 0 Launch Javafx Application ${TEST_APPLICATION} Set Screenshot Directory ${OUTPUT_DIR}${/}report-images Set comparison path From 901ecc08eaeb1563c5897197077f9e5722b1488f Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Fri, 9 Oct 2020 16:32:31 +0300 Subject: [PATCH 06/19] fix hover related kw's --- pom.xml | 6 +- src/main/java/JavaFXLibrary.java | 2 + .../AdditionalKeywords/Verifiers.java | 56 +++++++++++++++---- .../keywords/Keywords/KeyboardRobot.java | 7 ++- .../matchers/ExtendedNodeMatchers.java | 1 - .../robotframework/acceptance/MiscTests.robot | 10 +++- 6 files changed, 63 insertions(+), 19 deletions(-) diff --git a/pom.xml b/pom.xml index ae34b49..f2bffd3 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ org.robotframework javafxlibrary jar - 0.6.1-SNAPSHOT + 0.6.0 UTF-8 1.44 @@ -271,7 +271,11 @@ smoke + + + + not-ready diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index 7121a1b..fd6ad52 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -85,6 +85,8 @@ public class JavaFXLibrary extends AnnotationLibrary { add("launchSwingApplicationInSeparateThread"); add("logApplicationClasspath"); add("logSystemProperties"); + add("nodeShouldBeHoverable"); + add("nodeShouldNotBeHoverable"); add("setImageLogging"); add("setSafeClicking"); add("setScreenshotDirectory"); diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index 81f3415..0390b74 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -29,6 +29,7 @@ import javafxlibrary.matchers.ToggleMatchers; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; +import jnr.ffi.annotations.In; import org.robotframework.javalib.annotation.*; import org.testfx.matcher.base.NodeMatchers; import org.testfx.matcher.base.WindowMatchers; @@ -40,10 +41,15 @@ import org.testfx.service.support.impl.PixelMatcherRgb; import org.hamcrest.core.IsNot; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + import static org.junit.Assert.*; import static org.testfx.api.FxAssert.verifyThat; import static org.testfx.matcher.base.NodeMatchers.*; import static javafxlibrary.utils.HelperFunctions.*; +import static org.testfx.util.WaitForAsyncUtils.asyncFx; +import static org.testfx.util.WaitForAsyncUtils.waitForFxEvents; @RobotKeywords public class Verifiers extends TestFxAdapter { @@ -212,12 +218,25 @@ public static void nodeShouldBeHoverable(Object locator) { checkObjectArgumentNotNull(locator); try { RobotLog.info("Checking that locator node is hoverable: \"" + locator + "\"."); - verifyThat(objectToNode(locator), ExtendedNodeMatchers.isHoverable()); - } catch (AssertionError ae){ - Node node = getHoveredNode(); - RobotLog.info("Given locator node: \"" + locator + "\" was not hoverable! Instead, following " + - "node was found: \"" + node + "\"."); - throw ae; + Node node = asyncFx(() -> objectToNode(locator)).get(); + asyncFx(() -> new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT")).get(); + waitForFxEvents(5); + String status; + status = asyncFx(() -> { + try { + verifyThat(node, ExtendedNodeMatchers.isHoverable()); + return "success"; + } catch (AssertionError ae) { + Node hoveredNode = getHoveredNode(); + RobotLog.info("Given locator node: \"" + locator + "\" was not hoverable! Instead, following " + + "node was found: \"" + hoveredNode + "\"."); + return ae.getMessage(); + } + }).get(); + if (status!="success") throw new JavaFXLibraryNonFatalException(status); + } catch (InterruptedException | ExecutionException iee) { + RobotLog.trace("not hoverable"); + throw new JavaFXLibraryNonFatalException("Node not hoverable: ", iee.getCause()); } } @@ -228,11 +247,26 @@ public static void nodeShouldBeHoverable(Object locator) { public static void nodeShouldNotBeHoverable(Object locator) { checkObjectArgumentNotNull(locator); try { - RobotLog.info("Checking that locator node is not hoverable: \"" + locator + "\"."); - verifyThat(objectToNode(locator), ExtendedNodeMatchers.isHoverable()); - throw new JavaFXLibraryNonFatalException("Expected that \"" + locator + "\" is not hoverable - failed!"); - } catch (AssertionError ae){ - RobotLog.info("Was not hoverable."); + RobotLog.info("Checking that locator node is hoverable: \"" + locator + "\"."); + Node node = asyncFx(() -> objectToNode(locator)).get(); + asyncFx(() -> new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT")).get(); + waitForFxEvents(5); + String status; + status = asyncFx(() -> { + try { + verifyThat(node, ExtendedNodeMatchers.isHoverable()); + return "success"; + } catch (AssertionError ae) { + Node hoveredNode = getHoveredNode(); + RobotLog.info("Given locator node: \"" + locator + "\" was not hoverable! Instead, following " + + "node was found: \"" + hoveredNode + "\"."); + return ae.getMessage(); + } + }).get(); + if (status=="success") throw new JavaFXLibraryNonFatalException("Expected that \"" + locator + "\" is not hoverable - failed!"); + } catch (InterruptedException | ExecutionException iee) { + RobotLog.trace("not hoverable"); + throw new JavaFXLibraryNonFatalException("Node not hoverable: ", iee.getCause()); } } diff --git a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java index 3ba409b..9858911 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java @@ -220,15 +220,16 @@ public void writeTo(Object locator, String text) { try { RobotLog.info("Writing \"" + text + "\" to " + locator); asyncFx(() -> clickRobot.clickOn(locator,"DIRECT")).get(); + waitForFxEvents(5); asyncFx(() -> write(text)).get(); waitForFxEvents(3); } catch (InterruptedException | ExecutionException iee) { - RobotLog.trace("failed"); - throw new JavaFXLibraryNonFatalException("Unable to write to: " + locator, iee); + RobotLog.trace("exception details: " + iee.getCause()); + throw new JavaFXLibraryNonFatalException("Unable to write to: " + locator); } catch (Exception e) { if(e instanceof JavaFXLibraryNonFatalException) throw e; - throw new JavaFXLibraryNonFatalException("Unable to write to: " + locator, e); + throw new JavaFXLibraryNonFatalException("Unable to write to: " + locator); } } diff --git a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java index 8dd3c88..2baa83d 100644 --- a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java +++ b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java @@ -49,7 +49,6 @@ public void describeMismatch(Object object, Description description) { private static boolean hoverable(Node node) { try { - new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT"); return node.isHover(); } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; diff --git a/src/test/robotframework/acceptance/MiscTests.robot b/src/test/robotframework/acceptance/MiscTests.robot index 92690b1..c4ede59 100644 --- a/src/test/robotframework/acceptance/MiscTests.robot +++ b/src/test/robotframework/acceptance/MiscTests.robot @@ -112,21 +112,25 @@ Set Node Visibility (Call Method With Argument Types That Require Casting) Node Should Be Visible ${node} Check That Element Is Hoverable - [Tags] smoke demo-set + [Tags] smoke demo-set hoverable Set Test Application javafxlibrary.testapps.TestClickRobot ${target_node}= Find id=resetButton Call Object Method In Fx Application Thread ${target_node} setVisible (boolean)false + Move To Coordinates x=0 y=0 Run Keyword And Expect Error * Node Should Be Hoverable ${target_node} Call Object Method In Fx Application Thread ${target_node} setVisible (boolean)true + Move To Coordinates x=0 y=0 Node Should Be Hoverable id=resetButton Check That Element Is Not Hoverable - [Tags] smoke demo-set negative + [Tags] smoke demo-set negative hoverable Set Test Application javafxlibrary.testapps.TestClickRobot ${target_node}= Find id=resetButton + Move To Coordinates x=0 y=0 Run Keyword And Expect Error * Node Should Not Be Hoverable ${target_node} Call Object Method In Fx Application Thread ${target_node} setVisible (boolean)false - Node Should Not Be Hoverable id=resetButton + Move To Coordinates x=0 y=0 + Node Should Not Be Hoverable id=resetButton Find From Node [Tags] smoke From cade3b3a3e5d6bcb519fcfe158fb54c14eabd4a4 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Tue, 13 Oct 2020 17:47:36 +0300 Subject: [PATCH 07/19] waitUntilExists and waitUntilDoesNotExist improvements --- .../javafxlibrary/utils/HelperFunctions.java | 43 +++++++++++++++---- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/src/main/java/javafxlibrary/utils/HelperFunctions.java b/src/main/java/javafxlibrary/utils/HelperFunctions.java index c222c20..3646d55 100644 --- a/src/main/java/javafxlibrary/utils/HelperFunctions.java +++ b/src/main/java/javafxlibrary/utils/HelperFunctions.java @@ -77,21 +77,37 @@ public static Node waitUntilExists(String target, int timeout, String timeUnit) try { RobotLog.trace("Waiting until target \"" + target + "\" becomes existent, timeout=" + timeout + ", timeUnit=" + timeUnit); - waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) != null).get()); - Node node = asyncFx(() -> createFinder().find(target)).get(); // TODO: Add null checks for node.getScene() - waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> hasValidCoordinates(node)).get()); + waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> { + try { + Node toBeCheckedNode = createFinder().find(target); + if (toBeCheckedNode == null) throw new JavaFXLibraryNonFatalException("target not found!"); + hasValidCoordinates(toBeCheckedNode); + return true; + } catch (Exception e) { + RobotLog.trace("waitFor loop: Given element \"" + target + "\" was not found (" + e.getCause() + ")."); + return false; + } + }).get()); + Node node = asyncFx(() -> { + try { + Node finalNode = createFinder().find(target); + return finalNode; + } catch (Exception e) { + return null; + } + }).get(); + if (node == null) throw new JavaFXLibraryNonFatalException("Given element \"" + target + "\" was first found but not anymore."); return node; } catch (InterruptedException |ExecutionException iee) { - throw new JavaFXLibraryNonFatalException("Given element \"" + target + "\" was not found (" + iee.getCause().toString() + ")."); + throw new JavaFXLibraryNonFatalException("Given element \"" + target + "\" was not found (" + iee.getCause() + ")."); } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; } catch (TimeoutException te) { throw new JavaFXLibraryTimeoutException("Given element \"" + target + "\" was not found within given timeout of " + timeout + " " + timeUnit); } catch (Exception e) { - RobotLog.trace("Exception in waitUntilExists: " + e.getCause().toString()); - throw new JavaFXLibraryNonFatalException("Given element \"" + target + "\" was not found (" + e.getCause().toString() + ")."); + throw new JavaFXLibraryNonFatalException("Exception in waitUntilExists: " + e.getCause() + "\n" + e); } } @@ -99,15 +115,24 @@ public static void waitUntilDoesNotExists(String target, int timeout, String tim try { RobotLog.trace("Waiting until target \"" + target + "\" becomes non existent, timeout=" + timeout + ", timeUnit=" + timeUnit); - waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) == null).get()); + waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> { + try { + Node foundNode = createFinder().find(target); + if (foundNode == null) return true; + return false; + } catch (Exception e) { + RobotLog.trace("Exception in waitUntilDoesNotExists: " + e.getCause() + "\n" + e); + return false; + } + }).get()); + RobotLog.info("Target does not exist."); } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; } catch (TimeoutException te) { throw new JavaFXLibraryTimeoutException("Given element \"" + target + "\" was still found within given timeout of " + timeout + " " + timeUnit); } catch (Exception e) { - RobotLog.trace("Exception in waitUntilDoesNotExists: " + e + "\n" + e.getCause().toString()); - throw new JavaFXLibraryNonFatalException("Given element \"" + target + "\" was still found.", e); + throw new JavaFXLibraryNonFatalException("Exception in waitUntilDoesNotExists: " + e.getCause() + "\n" + e); } } From 6f73c9173e1c23a875b7a3e068c923f0fecda426 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Mon, 19 Oct 2020 17:49:56 +0300 Subject: [PATCH 08/19] wait until keywords to have overall timeout value, improved printout --- .../ConvenienceKeywords.java | 2 +- .../AdditionalKeywords/Verifiers.java | 72 ++++++---- .../javafxlibrary/utils/HelperFunctions.java | 130 +++++++++++------- .../javafxlibrary/utils/finder/Finder.java | 27 ++-- .../WaitUntilDisabledTest.java | 6 +- .../WaitUntilEnabledTest.java | 6 +- .../WaitUntilInvisibleTest.java | 6 +- .../WaitUntilVisibleTest.java | 6 +- 8 files changed, 148 insertions(+), 107 deletions(-) diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index 90103bd..21b04f4 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -60,7 +60,7 @@ public void bringStageToFront(Stage stage) { RobotLog.info("Bringing following Stage to front: \"" + stage + "\""); try { robot.targetWindow(stage); - Platform.runLater(stage::toFront); + stage.toFront(); } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Unable to bring stage to front.", e); } diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index 0390b74..e2c6376 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -29,7 +29,6 @@ import javafxlibrary.matchers.ToggleMatchers; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; -import jnr.ffi.annotations.In; import org.robotframework.javalib.annotation.*; import org.testfx.matcher.base.NodeMatchers; import org.testfx.matcher.base.WindowMatchers; @@ -42,7 +41,6 @@ import org.hamcrest.core.IsNot; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; import static org.junit.Assert.*; import static org.testfx.api.FxAssert.verifyThat; @@ -54,7 +52,7 @@ @RobotKeywords public class Verifiers extends TestFxAdapter { - @RobotKeyword("Waits until given element can be found.\n\n" + @RobotKeyword("Waits until given element can be found. Returns found node.\n\n" + "``locator`` is either a _query_ or _Object:Node_ for identifying the element, see " + "`3. Locating JavaFX Nodes`. \n\n" + "``timeout`` is the maximum waiting time value, defaults to 10 \n\n" @@ -65,7 +63,7 @@ public class Verifiers extends TestFxAdapter { @ArgumentNames({"locator", "timeout=10", "timeUnit=SECONDS"}) public Object waitUntilElementExists(String locator, int timeout, String timeUnit) { try { - RobotLog.info("Waiting until page contains element: \"" + locator + "\", timeout=\"" + timeout + "\", timeUnit=\"" + timeUnit + "\"."); + RobotLog.info("Waiting until element exists: \"" + locator + "\", timeout=\"" + timeout + "\", timeUnit=\"" + timeUnit + "\"."); return mapObject(waitUntilExists(locator, timeout, timeUnit)); } catch (IllegalArgumentException | NullPointerException e) { throw new JavaFXLibraryNonFatalException("Something went wrong while waiting element \"" + locator + "\" to appear.", e ); @@ -83,68 +81,84 @@ public Object waitUntilElementExists(String locator, int timeout, String timeUni @ArgumentNames({"locator", "timeout=10", "timeUnit=SECONDS"}) public void waitUntilElementDoesNotExists(String locator, int timeout, String timeUnit) { try { - RobotLog.info("Waiting until page does not contains element: \"" + locator + "\", timeout=\"" + timeout + "\", timeUnit= \"" + timeUnit + "\"."); + RobotLog.info("Waiting until element does not exists: \"" + locator + "\", timeout=\"" + timeout + "\", timeUnit= \"" + timeUnit + "\"."); waitUntilDoesNotExists(locator, timeout, timeUnit); } catch (IllegalArgumentException | NullPointerException e) { throw new JavaFXLibraryNonFatalException("Something went wrong while waiting element \"" + locator + "\" to disappear.", e ); } } - @RobotKeyword("Waits until a node located by given locator becomes visible. \n\n" + @RobotKeyword("Waits until a node located by given locator becomes visible. Returns found node.\n\n" + "``locator`` is either a _query_ or _Object:Node_ for identifying the element, see " + "`3. Locating JavaFX Nodes`. \n\n" - + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n\n") - @ArgumentNames({"locator", "timeout=5"}) - public Object waitUntilNodeIsVisible(Object locator, int timeout) { + + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + + "\nExample:\n" + + "| Wait Until Node Is Visible | \\#some-node-id | \n" + + "| Wait Until Node Is Visible | \\#some-node-id | 200 | MILLISECONDS | \n") + @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) + public Object waitUntilNodeIsVisible(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); try { - RobotLog.info("Waiting for node \"" + locator + "\" to be visible, timeout=\"" + timeout + "\"."); - return mapObject(waitUntilVisible(locator, timeout)); + RobotLog.info("Waiting for node \"" + locator + "\" to be visible, timeout=\"" + timeout + "\", timeUnit= \"" + timeUnit + "\"."); + return mapObject(waitUntilVisible(locator, timeout, timeUnit)); } catch (IllegalArgumentException | NullPointerException e) { throw new JavaFXLibraryNonFatalException(""); } } - @RobotKeyword("Waits until a node located by given locator becomes invisible. \n\n" + @RobotKeyword("Waits until a node located by given locator becomes invisible. Returns found node.\n\n" + "``locator`` is either a _query_ or _Object:Node_ for identifying the element, see " + "`3. Locating JavaFX Nodes`. \n\n" - + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n\n") - @ArgumentNames({"locator", "timeout=5"}) - public Object waitUntilNodeIsNotVisible(Object locator, int timeout) { + + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + + "\nExample:\n" + + "| Wait Until Node Is Not Visible | \\#some-node-id | \n" + + "| Wait Until Node Is Not Visible | \\#some-node-id | 200 | MILLISECONDS | \n") + @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) + public Object waitUntilNodeIsNotVisible(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); try { - RobotLog.info("Waiting for node \"" + locator + "\" to be invisible, timeout=\"" + timeout + "\"."); - return mapObject(waitUntilInvisible(locator, timeout)); + RobotLog.info("Waiting for node \"" + locator + "\" to be invisible, timeout=\"" + timeout + "\", timeUnit= \"" + timeUnit + "\"."); + return mapObject(waitUntilNotVisible(locator, timeout, timeUnit)); } catch (IllegalArgumentException | NullPointerException e) { throw new JavaFXLibraryNonFatalException(""); } } - @RobotKeyword("Waits until a node located using given locator becomes enabled. \n\n" + @RobotKeyword("Waits until a node located using given locator becomes enabled. Returns found node.\n\n" + "``locator`` is either a _query_ or _Object:Node_ for identifying the element, see " + "`3. Locating JavaFX Nodes`. \n\n" - + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n\n") - @ArgumentNames({"locator", "timeout=5"}) - public Object waitUntilNodeIsEnabled(Object locator, int timeout) { + + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + + "\nExample:\n" + + "| Wait Until Node Is Enabled | \\#some-node-id | \n" + + "| Wait Until Node Is Enabled | \\#some-node-id | 200 | MILLISECONDS | \n") + @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) + public Object waitUntilNodeIsEnabled(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); try { - RobotLog.info("Waiting for node \"" + locator + "\" to be enabled, timeout=\"" + timeout + "\"."); - return mapObject(waitUntilEnabled(locator, timeout)); + RobotLog.info("Waiting for node \"" + locator + "\" to be enabled, timeout=\"" + timeout + "\", timeUnit= \"" + timeUnit + "\"."); + return mapObject(waitUntilEnabled(locator, timeout, timeUnit)); } catch (IllegalArgumentException | NullPointerException e){ throw new JavaFXLibraryNonFatalException(""); } } - @RobotKeyword("Waits until a node located using given locator becomes disabled. \n\n" + @RobotKeyword("Waits until a node located using given locator becomes disabled. Returns found node.\n\n" + "``locator`` is either a _query_ or _Object:Node_ for identifying the element, see " + "`3. Locating JavaFX Nodes`. \n\n" - + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n\n") - @ArgumentNames({"locator", "timeout=5"}) - public Object waitUntilNodeIsNotEnabled(Object locator, int timeout) { + + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + + "\nExample:\n" + + "| Wait Until Node Is Not Enabled | \\#some-node-id | \n" + + "| Wait Until Node Is Not Enabled | \\#some-node-id | 200 | MILLISECONDS | \n") + @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) + public Object waitUntilNodeIsNotEnabled(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); try { - RobotLog.info("Waiting for node \"" + locator + "\" to be disabled, timeout=\"" + timeout + "\"."); - return mapObject(waitUntilDisabled(locator, timeout)); + RobotLog.info("Waiting for node \"" + locator + "\" to be disabled, timeout=\"" + timeout + "\", timeUnit= \"" + timeUnit + "\"."); + return mapObject(waitUntilDisabled(locator, timeout, timeUnit)); } catch (IllegalArgumentException | NullPointerException e){ throw new JavaFXLibraryNonFatalException(""); } diff --git a/src/main/java/javafxlibrary/utils/HelperFunctions.java b/src/main/java/javafxlibrary/utils/HelperFunctions.java index 3646d55..9468551 100644 --- a/src/main/java/javafxlibrary/utils/HelperFunctions.java +++ b/src/main/java/javafxlibrary/utils/HelperFunctions.java @@ -48,16 +48,18 @@ import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.*; import java.lang.*; import javafx.scene.input.KeyCode; import org.testfx.service.query.PointQuery; +import org.testfx.util.WaitForAsyncUtils; import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -65,8 +67,8 @@ import static javafxlibrary.utils.TestFxAdapter.objectMap; import static javafxlibrary.utils.TestFxAdapter.robot; import static org.testfx.matcher.base.NodeMatchers.*; -import static org.testfx.util.WaitForAsyncUtils.waitFor; import static org.testfx.util.WaitForAsyncUtils.asyncFx; +import static org.testfx.util.WaitForAsyncUtils.waitFor; public class HelperFunctions { @@ -75,29 +77,42 @@ public class HelperFunctions { public static Node waitUntilExists(String target, int timeout, String timeUnit) { try { - RobotLog.trace("Waiting until target \"" + target + "\" becomes existent, timeout=" + RobotLog.trace("waitUntilExists: Waiting until target \"" + target + "\" becomes existent, timeout=" + timeout + ", timeUnit=" + timeUnit); + Node node; + Instant start = Instant.now(); + Integer originalTimeout = timeout; // TODO: Add null checks for node.getScene() - waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> { - try { - Node toBeCheckedNode = createFinder().find(target); - if (toBeCheckedNode == null) throw new JavaFXLibraryNonFatalException("target not found!"); - hasValidCoordinates(toBeCheckedNode); - return true; - } catch (Exception e) { - RobotLog.trace("waitFor loop: Given element \"" + target + "\" was not found (" + e.getCause() + ")."); - return false; - } - }).get()); - Node node = asyncFx(() -> { - try { - Node finalNode = createFinder().find(target); - return finalNode; - } catch (Exception e) { - return null; + while(true) { + originalTimeout = (int) (TimeUnit.MILLISECONDS.convert((int) originalTimeout,TimeUnit.valueOf(timeUnit)) - ChronoUnit.MILLIS.between(start,Instant.now())); + if (originalTimeout <= 0) throw new TimeoutException("waitUntilExists: time out!"); + waitFor(originalTimeout, TimeUnit.MILLISECONDS, () -> asyncFx(() -> { + try { + Node toBeCheckedNode = createFinder().find(target); + if (toBeCheckedNode == null) throw new JavaFXLibraryNonFatalException("target not found!"); + hasValidCoordinates(toBeCheckedNode); + return true; + } catch (Exception e) { + RobotLog.trace("waitForExists loop: Given element \"" + target + "\" was not found (" + e.getCause() + ")."); + return false; + } + }).get()); + node = asyncFx(() -> { + try { + Node finalNode = createFinder().find(target); + RobotLog.info("node \"" + finalNode + "\" was found."); + return finalNode; + } catch (Exception e) { + return null; + } + }).get(); + if (node == null) { + sleepFor(50); + RobotLog.trace("waitForExists loop: node was null, retry..."); + continue; } - }).get(); - if (node == null) throw new JavaFXLibraryNonFatalException("Given element \"" + target + "\" was first found but not anymore."); + else break; + } return node; } catch (InterruptedException |ExecutionException iee) { throw new JavaFXLibraryNonFatalException("Given element \"" + target + "\" was not found (" + iee.getCause() + ")."); @@ -113,7 +128,7 @@ public static Node waitUntilExists(String target, int timeout, String timeUnit) public static void waitUntilDoesNotExists(String target, int timeout, String timeUnit) { try { - RobotLog.trace("Waiting until target \"" + target + "\" becomes non existent, timeout=" + RobotLog.trace("waitUntilDoesNotExists: Waiting until target \"" + target + "\" becomes non existent, timeout=" + timeout + ", timeUnit=" + timeUnit); waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> { try { @@ -136,15 +151,20 @@ public static void waitUntilDoesNotExists(String target, int timeout, String tim } } - // TODO: Take same parameters as waitUntilExists in all waitUntil methods - public static Node waitUntilVisible(Object target, int timeout) { + public static Node waitUntilVisible(Object target, int timeout, String timeUnit) { try { + Instant start = Instant.now(); // if target is a query string, let's try to find the relevant node - if (target instanceof String) - target = waitUntilExists((String) target, timeout, "SECONDS"); + if (target instanceof String) { + target = waitUntilExists((String) target, timeout, timeUnit); + } final Object finalTarget = target; - RobotLog.trace("Waiting until target \"" + target + "\" becomes visible, timeout=" + timeout); - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isVisible()).matches(finalTarget)).get()); + // decrease already used time from timeout + long keywordTimeout = TimeUnit.MILLISECONDS.convert(timeout, TimeUnit.valueOf(timeUnit)) - ChronoUnit.MILLIS.between(start, Instant.now()); + if (keywordTimeout <= 0) throw new TimeoutException("waitUntilVisible: time out!"); + RobotLog.trace("waitUntilVisible: Waiting until target \"" + finalTarget + "\" becomes visible, timeout=" + keywordTimeout + ", timeUnit=MILLISECONDS"); + waitFor((int) keywordTimeout, TimeUnit.MILLISECONDS, () -> asyncFx(() -> Matchers.is(isVisible()).matches(finalTarget)).get()); + RobotLog.info("node \"" + finalTarget + "\" was visible."); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -156,14 +176,20 @@ public static Node waitUntilVisible(Object target, int timeout) { } } - public static Node waitUntilInvisible(Object target, int timeout) { + public static Node waitUntilNotVisible(Object target, int timeout, String timeUnit) { try { + Instant start = Instant.now(); // if target is a query string, let's try to find the relevant node - if (target instanceof String) - target = waitUntilExists((String) target, timeout, "SECONDS"); + if (target instanceof String) { + target = waitUntilExists((String) target, timeout, timeUnit); + } final Object finalTarget = target; - RobotLog.trace("Waiting until target \"" + target + "\" becomes invisible, timeout=" + timeout); - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isInvisible()).matches(finalTarget)).get()); + // decrease already used time from timeout + long keywordTimeout = TimeUnit.MILLISECONDS.convert(timeout, TimeUnit.valueOf(timeUnit)) - ChronoUnit.MILLIS.between(start, Instant.now()); + if (keywordTimeout <= 0) throw new TimeoutException("waitUntilNotVisible: time out!"); + RobotLog.trace("waitUntilNotVisible: Waiting until target \"" + target + "\" becomes invisible, timeout=" + keywordTimeout + ", timeUnit=MILLISECONDS"); + waitFor(keywordTimeout, TimeUnit.MILLISECONDS, () -> asyncFx(() -> Matchers.is(isInvisible()).matches(finalTarget)).get()); + RobotLog.info("node \"" + finalTarget + "\" is not visible."); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -175,14 +201,20 @@ public static Node waitUntilInvisible(Object target, int timeout) { } } - public static Node waitUntilEnabled(Object target, int timeout) { + // TODO: Take same parameters as waitUntilExists in all waitUntil methods + public static Node waitUntilEnabled(Object target, int timeout, String timeUnit) { try { + Instant start = Instant.now(); // if target is a query string, let's try to find the relevant node if (target instanceof String) - target = waitUntilExists((String) target, timeout, "SECONDS"); + target = waitUntilExists((String) target, timeout, timeUnit); final Object finalTarget = target; - RobotLog.trace("Waiting until target \"" + target + "\" becomes enabled, timeout=" + timeout); - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> (Matchers.is(isEnabled()).matches(finalTarget))).get()); + // decrease already used time from timeout + long keywordTimeout = TimeUnit.MILLISECONDS.convert(timeout, TimeUnit.valueOf(timeUnit)) - ChronoUnit.MILLIS.between(start, Instant.now()); + if (keywordTimeout <= 0) throw new TimeoutException("waitUntilEnabled: time out!"); + RobotLog.trace("waitUntilEnabled: Waiting until target \"" + target + "\" becomes enabled, timeout=" + keywordTimeout + ", timeUnit=MILLISECONDS"); + waitFor(keywordTimeout, TimeUnit.MILLISECONDS, () -> asyncFx(() -> (Matchers.is(isEnabled()).matches(finalTarget))).get()); + RobotLog.info("node \"" + finalTarget + "\" is enabled."); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -194,14 +226,19 @@ public static Node waitUntilEnabled(Object target, int timeout) { } } - public static Node waitUntilDisabled(Object target, int timeout) { + public static Node waitUntilDisabled(Object target, int timeout, String timeUnit) { try { + Instant start = Instant.now(); // if target is a query string, let's try to find the relevant node if (target instanceof String) - target = waitUntilExists((String) target, timeout, "SECONDS"); + target = waitUntilExists((String) target, timeout, timeUnit); final Object finalTarget = target; - RobotLog.trace("Waiting until target \"" + target + "\" becomes disabled, timeout=" + timeout); - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isDisabled()).matches(finalTarget)).get()); + // decrease already used time from timeout + long keywordTimeout = TimeUnit.MILLISECONDS.convert(timeout, TimeUnit.valueOf(timeUnit)) - ChronoUnit.MILLIS.between(start, Instant.now()); + if (keywordTimeout <= 0) throw new TimeoutException("waitUntilDisabled: time out!"); + RobotLog.trace("waitUntilDisabled: Waiting until target \"" + target + "\" becomes disabled, timeout=" + keywordTimeout + ", timeUnit=MILLISECONDS"); + waitFor(keywordTimeout, TimeUnit.MILLISECONDS, () -> asyncFx(() -> Matchers.is(isDisabled()).matches(finalTarget)).get()); + RobotLog.info("node \"" + finalTarget + "\" is disabled."); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -530,9 +567,8 @@ public static Object checkClickTarget(Object target) { try { if (target instanceof String || target instanceof Node) { target = objectToNode(target); - if (!Matchers.is(isVisible()).matches(target) || !Matchers.is(isEnabled()).matches(target)) { - throw new JavaFXLibraryNonFatalException("target \"" + target + "\" not visible or enabled!"); - } + if (!Matchers.is(isVisible()).matches(target)) throw new JavaFXLibraryNonFatalException("target \"" + target + "\" not visible!"); + if (!Matchers.is(isEnabled()).matches(target)) throw new JavaFXLibraryNonFatalException("target \"" + target + "\" not enabled!"); } bringObjectsWindowToFront(target); checkObjectInsideActiveWindow(target); diff --git a/src/main/java/javafxlibrary/utils/finder/Finder.java b/src/main/java/javafxlibrary/utils/finder/Finder.java index 23f053e..a812d8e 100644 --- a/src/main/java/javafxlibrary/utils/finder/Finder.java +++ b/src/main/java/javafxlibrary/utils/finder/Finder.java @@ -41,22 +41,19 @@ public Node find(String query) { // TODO: Remove old style lookup queries // Use TestFX lookup for queries with no prefixes if (!QueryParser.startsWithPrefix(query)) { - //RobotLog.warn("You are using deprecated lookup queries! See library documentation for information about " + - // "the updated lookup query syntax."); + RobotLog.info("You are using to be deprecated lookup queries! See library documentation for information about " + + "the updated lookup query syntax."); return robot.lookup(query).query(); } - List windows = robot.listTargetWindows(); RobotLog.debug("Finding with query \"" + query + "\" from " + windows.size() + " windows"); - for (Window window : windows) { RobotLog.debug("Finding from window " + window); Node result = find(query, window.getScene().getRoot()); if (result != null) return result; - } - RobotLog.debug("Find finished, nothing was found with query: " + query); + RobotLog.info("Find finished, nothing was found with query: " + query); return null; } @@ -64,22 +61,19 @@ public Node find(String query, Parent root) { // TODO: Remove old style lookup queries // Use TestFX lookup for queries with no prefixes if (!QueryParser.startsWithPrefix(query)) { - //RobotLog.warn("You are using deprecated lookup queries! See library documentation for information about " + - // "the updated lookup query syntax."); + RobotLog.info("You are using to be deprecated lookup queries! See library documentation for information about " + + "the updated lookup query syntax."); return robot.from(root).lookup(query).query(); } - this.queries = QueryParser.getIndividualQueries(query); return find(root, 0); } private Node find(Parent root, int queryIndex) { Query query = new Query(queries[queryIndex]); - if (queryIndex < queries.length - 1) { Set nodes = new LinkedHashSet<>(executeFindAll(root, query)); nodes.remove(root); - for (Node node : nodes) { if (node instanceof Parent) { Node result = find((Parent) node, queryIndex + 1); @@ -98,14 +92,12 @@ public Set findAll(String query) { // TODO: Remove old style lookup queries // Use TestFX lookup for queries with no prefixes if (!QueryParser.startsWithPrefix(query)) { - //RobotLog.warn("You are using deprecated lookup queries! See library documentation for information about " + - // "the updated lookup query syntax."); + RobotLog.info("You are using to be deprecated lookup queries! See library documentation for information about " + + "the updated lookup query syntax."); return robot.lookup(query).queryAll(); } - List windows = robot.listTargetWindows(); RobotLog.debug("Finding All with query \"" + query + "\" from " + windows.size() + " windows"); - for (Window window : windows) { RobotLog.debug("Finding all from window " + window); findAll(query, window.getScene().getRoot()); @@ -117,11 +109,10 @@ public Set findAll(String query, Parent root) { // TODO: Remove old style lookup queries // Use TestFX lookup for queries with no prefixes if (!QueryParser.startsWithPrefix(query)) { - //RobotLog.warn("You are using deprecated lookup queries! See library documentation for information about " + - // "the updated lookup query syntax."); + RobotLog.info("You are using to be deprecated lookup queries! See library documentation for information about " + + "the updated lookup query syntax."); return robot.from(root).lookup(query).query(); } - this.queries = QueryParser.getIndividualQueries(query); return findAll(root, 0); } diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDisabledTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDisabledTest.java index ae8962f..bf60d5e 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDisabledTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDisabledTest.java @@ -34,7 +34,7 @@ Node waitUntilExists(String target, int timeout, String timeUnit) { @Test public void waitUntilDisabled_IsNotEnabled() { button.setDisable(true); - Node node = HelperFunctions.waitUntilDisabled(".button", 1); + Node node = HelperFunctions.waitUntilDisabled(".button", 1, "SECONDS"); Assert.assertEquals(button, node); } @@ -43,7 +43,7 @@ public void waitUntilDisabled_IsNotEnabledWithDelay() { button.setDisable(false); Thread t = disableButtonAfterTimeout(); t.start(); - Node node = HelperFunctions.waitUntilDisabled(".button", 1); + Node node = HelperFunctions.waitUntilDisabled(".button", 1, "SECONDS"); Assert.assertEquals(button, node); } @@ -52,7 +52,7 @@ public void waitUntilDisabled_IsEnabled() { button.setDisable(false); thrown.expect(JavaFXLibraryTimeoutException.class); thrown.expectMessage("Given target \"" + button + "\" did not become disabled within given timeout of 1 seconds."); - HelperFunctions.waitUntilDisabled(".button", 1); + HelperFunctions.waitUntilDisabled(".button", 1, "SECONDS"); } private Thread disableButtonAfterTimeout() { diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilEnabledTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilEnabledTest.java index 60713f7..6fedc0b 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilEnabledTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilEnabledTest.java @@ -31,7 +31,7 @@ Node waitUntilExists(String target, int timeout, String timeUnit) { @Test public void waitUntilEnabled_IsEnabled() { - Node node = HelperFunctions.waitUntilEnabled(".button", 1); + Node node = HelperFunctions.waitUntilEnabled(".button", 1, "SECONDS"); Assert.assertEquals(button, node); } @@ -40,7 +40,7 @@ public void waitUntilEnabled_IsEnabledWithDelay() { button.setDisable(true); Thread t = enableButtonAfterTimeout(); t.start(); - Node node = HelperFunctions.waitUntilEnabled(".button", 1); + Node node = HelperFunctions.waitUntilEnabled(".button", 1, "SECONDS"); Assert.assertEquals(button, node); } @@ -49,7 +49,7 @@ public void waitUntilEnabled_IsNotEnabled() { button.setDisable(true); thrown.expect(JavaFXLibraryTimeoutException.class); thrown.expectMessage("Given target \"" + button + "\" did not become enabled within given timeout of 1 seconds."); - HelperFunctions.waitUntilEnabled(".button", 1); + HelperFunctions.waitUntilEnabled(".button", 1, "SECONDS"); } private Thread enableButtonAfterTimeout() { diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilInvisibleTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilInvisibleTest.java index 8e98ef3..71829d5 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilInvisibleTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilInvisibleTest.java @@ -34,7 +34,7 @@ Node waitUntilExists(String target, int timeout, String timeUnit) { @Test public void waitUntilInvisible_IsInvisible() { button.setVisible(false); - Node node = HelperFunctions.waitUntilInvisible(".button", 1); + Node node = HelperFunctions.waitUntilNotVisible(".button", 1, "SECONDS"); Assert.assertEquals(button, node); } @@ -45,7 +45,7 @@ public void waitUntilInvisible_IsInvisibleWithDelay() { Thread t = setInvisibleAfterTimeout(); t.start(); - Node node = HelperFunctions.waitUntilInvisible(".button", 1); + Node node = HelperFunctions.waitUntilNotVisible(".button", 1, "SECONDS"); Assert.assertEquals(button, node); } @@ -54,7 +54,7 @@ public void waitUntilInvisible_IsVisible() { button.setVisible(true); thrown.expect(JavaFXLibraryTimeoutException.class); thrown.expectMessage("Given target \"" + button + "\" did not become invisible within given timeout of 1 SECONDS"); - HelperFunctions.waitUntilInvisible(".button", 1); + HelperFunctions.waitUntilNotVisible(".button", 1, "SECONDS"); } private Thread setInvisibleAfterTimeout() { diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilVisibleTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilVisibleTest.java index 3c98475..5723628 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilVisibleTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilVisibleTest.java @@ -30,7 +30,7 @@ Node waitUntilExists(String target, int timeout, String timeUnit) { @Test public void waitUntilVisible_IsVisible() { - Node node = HelperFunctions.waitUntilVisible(".button", 1); + Node node = HelperFunctions.waitUntilVisible(".button", 1, "SECONDS"); Assert.assertEquals(button, node); } @@ -41,7 +41,7 @@ public void waitUntilVisible_IsVisibleWithDelay() { Thread t = setVisibleAfterTimeout(); t.start(); - Node node = HelperFunctions.waitUntilVisible(".button", 1); + Node node = HelperFunctions.waitUntilVisible(".button", 1, "SECONDS"); Assert.assertEquals(button, node); } @@ -50,7 +50,7 @@ public void waitUntilVisible_IsNotVisible() { button.setVisible(false); thrown.expect(JavaFXLibraryTimeoutException.class); thrown.expectMessage("Given target \"" + button + "\" did not become visible within given timeout of 1 SECONDS"); - HelperFunctions.waitUntilVisible(".button", 1); + HelperFunctions.waitUntilVisible(".button", 1, "SECONDS"); } private Thread setVisibleAfterTimeout() { From 3efb7a0d222bf174f55c9d26752cc7ba3bb1eaca Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Tue, 20 Oct 2020 11:01:36 +0300 Subject: [PATCH 09/19] fix text prefix to check for quotation marks --- src/main/java/javafxlibrary/utils/finder/QueryParser.java | 1 + .../java/javafxlibrary/utils/finder/QueryParserTest.java | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/main/java/javafxlibrary/utils/finder/QueryParser.java b/src/main/java/javafxlibrary/utils/finder/QueryParser.java index 22f1760..592e090 100644 --- a/src/main/java/javafxlibrary/utils/finder/QueryParser.java +++ b/src/main/java/javafxlibrary/utils/finder/QueryParser.java @@ -117,6 +117,7 @@ protected static String removePrefix(String query, FindPrefix prefix) { case XPATH: return query.substring(6); case TEXT: + if (!query.matches("text=\".*\"")) throw new IllegalArgumentException("\"text\" query prefix is missing quotation marks."); return query.substring(6, query.length() - 1); case PSEUDO: return query.substring(7); diff --git a/src/test/java/javafxlibrary/utils/finder/QueryParserTest.java b/src/test/java/javafxlibrary/utils/finder/QueryParserTest.java index be6b010..6bd3a2b 100644 --- a/src/test/java/javafxlibrary/utils/finder/QueryParserTest.java +++ b/src/test/java/javafxlibrary/utils/finder/QueryParserTest.java @@ -39,6 +39,13 @@ public void getIndividualQueries_ContainsQuotes() { Assert.assertArrayEquals(target, result); } + @Test + public void getIndividualQueries_TextWithoutQuotes() { + thrown.expect(IllegalArgumentException.class); + thrown.expectMessage("\"text\" query prefix is missing quotation marks."); + String query = QueryParser.removePrefix("text=this is not allowed", FindPrefix.TEXT); + } + @Test public void getPrefix_AcceptedValues() { Assert.assertEquals(FindPrefix.ID, QueryParser.getPrefix("id=nodeId")); From 08ad7b95d80fb0ae669e9a6e9e62d5eff616c659 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Fri, 23 Oct 2020 13:43:06 +0300 Subject: [PATCH 10/19] fix text prefix to have support for apostrophe (') also --- src/main/java/javafxlibrary/utils/finder/QueryParser.java | 2 +- src/test/java/javafxlibrary/utils/finder/QueryParserTest.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/javafxlibrary/utils/finder/QueryParser.java b/src/main/java/javafxlibrary/utils/finder/QueryParser.java index 592e090..5566cba 100644 --- a/src/main/java/javafxlibrary/utils/finder/QueryParser.java +++ b/src/main/java/javafxlibrary/utils/finder/QueryParser.java @@ -117,7 +117,7 @@ protected static String removePrefix(String query, FindPrefix prefix) { case XPATH: return query.substring(6); case TEXT: - if (!query.matches("text=\".*\"")) throw new IllegalArgumentException("\"text\" query prefix is missing quotation marks."); + if (!query.matches("text=[\"|'].*[\"|']")) throw new IllegalArgumentException("\"text\" query prefix is missing quotation marks."); return query.substring(6, query.length() - 1); case PSEUDO: return query.substring(7); diff --git a/src/test/java/javafxlibrary/utils/finder/QueryParserTest.java b/src/test/java/javafxlibrary/utils/finder/QueryParserTest.java index 6bd3a2b..8ced5d1 100644 --- a/src/test/java/javafxlibrary/utils/finder/QueryParserTest.java +++ b/src/test/java/javafxlibrary/utils/finder/QueryParserTest.java @@ -27,8 +27,8 @@ public void startsWithPrefix_InvalidValue() { @Test public void getIndividualQueries_ContainsSpaces() { - String[] result = QueryParser.getIndividualQueries("xpath=SomeNode[@text=\"test text\"] text=\"text with spaces\" id=sub"); - String[] target = { "xpath=SomeNode[@text=\"test text\"]", "text=\"text with spaces\"", "id=sub" }; + String[] result = QueryParser.getIndividualQueries("xpath=SomeNode[@text=\"test text\"] text=\"text with spaces\" text='text with apostrophe' id=sub"); + String[] target = { "xpath=SomeNode[@text=\"test text\"]", "text=\"text with spaces\"", "text='text with apostrophe'", "id=sub" }; Assert.assertArrayEquals(target, result); } From cea7c8c9b3ed3301f3daaba1c2a920a8790c19c6 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Fri, 23 Oct 2020 15:19:16 +0300 Subject: [PATCH 11/19] cleanup --- src/main/java/JavaFXLibrary.java | 40 ++++++++++++++------------------ 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index fd6ad52..d7c32db 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -145,11 +145,9 @@ public static String loadRobotLibraryVersion() { @Override public Object runKeyword(String keywordName, List args, Map kwargs) { - if (kwargs == null) { kwargs = new HashMap(); } - List finalArgs; Map finalKwargs; @@ -162,22 +160,21 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { finalKwargs = kwargs; } + // Run keyword either in async or asyncFx thread with or without timeout + // Execution collects retval and retExcep from keyword AtomicReference retval = new AtomicReference<>(); AtomicReference retExcep = new AtomicReference<>(); - RobotLog.ignoreDuplicates(); -// try { -// RobotLog.ignoreDuplicates(); -// // timeout + 500 ms so that underlying timeout has a chance to expire first -// waitFor(getWaitUntilTimeout(TimeUnit.MILLISECONDS) + 500, TimeUnit.MILLISECONDS, () -> { try { if (noWrappedAsyncFxKeywords.contains(keywordName)) { + // no asyncFx thread if (noLibraryKeywordTimeoutKeywords.contains(keywordName)) { + // without timeout retval.set(super.runKeyword(keywordName, finalArgs, finalKwargs)); } else { - retval.set(waitForAsync(getLibraryKeywordTimeout(TimeUnit.MILLISECONDS) + 500, () -> { + // in async thread + retval.set(waitForAsync(getLibraryKeywordTimeout(TimeUnit.MILLISECONDS), () -> { try { - //super.runKeyword(keywordName, finalArgs, finalKwargs); return super.runKeyword(keywordName, finalArgs, finalKwargs); } catch (RuntimeException rte) { retExcep.set(rte); @@ -186,32 +183,31 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { })); } } else { - retval.set(waitForAsyncFx(getLibraryKeywordTimeout(TimeUnit.MILLISECONDS) + 500, () -> { - try { - return super.runKeyword(keywordName, finalArgs, finalKwargs); - } catch (RuntimeException rte) { - retExcep.set(rte); - return null; - } - })); - waitForFxEvents( 3); + // in asyncFx thread + retval.set(waitForAsyncFx(getLibraryKeywordTimeout(TimeUnit.MILLISECONDS), () -> { + try { + return super.runKeyword(keywordName, finalArgs, finalKwargs); + } catch (RuntimeException rte) { + retExcep.set(rte); + return null; + } + })); + waitForFxEvents( 5); } - //return true; } catch (JavaFXLibraryTimeoutException jfxte) { // timeout already expired, catch exception and jump out retExcep.set(jfxte); - //throw jfxte; } catch (RuntimeException rte) { // catch exception and continue trying retExcep.set(rte); - //return false; } + + // in failure take screenshot and handle exception if(retExcep.get()!=null) { RobotLog.reset(); RuntimeException e = retExcep.get(); // TODO: to asyncFx thread runOnFailure.runOnFailure(); - if (e.getCause() instanceof JavaFXLibraryFatalException) { RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary FATAL exception: \n" + Throwables.getStackTraceAsString(e)); throw e; From bb61985b57c037eff17abdfe63aaa27f8b514ea1 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Mon, 26 Oct 2020 15:39:13 +0200 Subject: [PATCH 12/19] fix verifiers hoverable kw's, add test --- .../AdditionalKeywords/Verifiers.java | 20 +++++++++++++++++-- .../robotframework/acceptance/MiscTests.robot | 7 +++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index e2c6376..2f0bdb5 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -232,7 +232,15 @@ public static void nodeShouldBeHoverable(Object locator) { checkObjectArgumentNotNull(locator); try { RobotLog.info("Checking that locator node is hoverable: \"" + locator + "\"."); - Node node = asyncFx(() -> objectToNode(locator)).get(); + Node node = asyncFx(() -> { + try { + return objectToNode(locator); + } catch (Exception e) { + RobotLog.info("Locator not found: " + e.getCause()); + return null; + } + }).get(); + if (node==null) throw new JavaFXLibraryNonFatalException("Given locator \"" + locator + "\" was not found."); asyncFx(() -> new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT")).get(); waitForFxEvents(5); String status; @@ -262,7 +270,15 @@ public static void nodeShouldNotBeHoverable(Object locator) { checkObjectArgumentNotNull(locator); try { RobotLog.info("Checking that locator node is hoverable: \"" + locator + "\"."); - Node node = asyncFx(() -> objectToNode(locator)).get(); + Node node = asyncFx(() -> { + try { + return objectToNode(locator); + } catch (Exception e) { + RobotLog.info("Locator not found: " + e.getCause()); + return null; + } + }).get(); + if (node==null) throw new JavaFXLibraryNonFatalException("Given locator \"" + locator + "\" was not found."); asyncFx(() -> new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT")).get(); waitForFxEvents(5); String status; diff --git a/src/test/robotframework/acceptance/MiscTests.robot b/src/test/robotframework/acceptance/MiscTests.robot index c4ede59..97d61b9 100644 --- a/src/test/robotframework/acceptance/MiscTests.robot +++ b/src/test/robotframework/acceptance/MiscTests.robot @@ -122,6 +122,7 @@ Check That Element Is Hoverable Move To Coordinates x=0 y=0 Node Should Be Hoverable id=resetButton + Check That Element Is Not Hoverable [Tags] smoke demo-set negative hoverable Set Test Application javafxlibrary.testapps.TestClickRobot @@ -132,6 +133,12 @@ Check That Element Is Not Hoverable Move To Coordinates x=0 y=0 Node Should Not Be Hoverable id=resetButton +Test Verify Keywords With Non-existing Locator + [Tags] smoke demo-set negative testitesti + Set Test Application javafxlibrary.testapps.TestClickRobot + Run Keyword And Expect Error Given locator "id=doesNotExist" was not found. Node Should Be Hoverable id=doesNotExist + Run Keyword And Expect Error Given locator "id=doesNotExist" was not found. Node Should Not Be Hoverable id=doesNotExist + Find From Node [Tags] smoke Set Test Application javafxlibrary.testapps.TestBoundsLocation From 04a2e97fb05c1a9538d3a0787156a29c3a4a8509 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Tue, 27 Oct 2020 10:10:19 +0200 Subject: [PATCH 13/19] fix push many times kw --- src/main/java/JavaFXLibrary.java | 1 + .../java/javafxlibrary/keywords/Keywords/KeyboardRobot.java | 4 +++- src/test/robotframework/acceptance/MiscTests.robot | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index d7c32db..ce72776 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -87,6 +87,7 @@ public class JavaFXLibrary extends AnnotationLibrary { add("logSystemProperties"); add("nodeShouldBeHoverable"); add("nodeShouldNotBeHoverable"); + add("pushManyTimes"); add("setImageLogging"); add("setSafeClicking"); add("setScreenshotDirectory"); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java index 9858911..3a0a194 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java @@ -107,9 +107,11 @@ public void pushManyTimes(int times, String... keys) { RobotLog.info("Pushing combination: \"" + Arrays.asList(keys) + "\" for \"" + times + "\" times."); try { for (int i = 0; i < times; i++) { - robot.push(getKeyCode(keys)); + asyncFx(() -> robot.push(getKeyCode(keys))).get(); sleepFor(50); } + } catch (InterruptedException | ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("Unable to push: " + Arrays.asList(keys), iee.getCause()); } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) throw e; diff --git a/src/test/robotframework/acceptance/MiscTests.robot b/src/test/robotframework/acceptance/MiscTests.robot index 97d61b9..d3d688e 100644 --- a/src/test/robotframework/acceptance/MiscTests.robot +++ b/src/test/robotframework/acceptance/MiscTests.robot @@ -134,7 +134,7 @@ Check That Element Is Not Hoverable Node Should Not Be Hoverable id=resetButton Test Verify Keywords With Non-existing Locator - [Tags] smoke demo-set negative testitesti + [Tags] smoke demo-set negative Set Test Application javafxlibrary.testapps.TestClickRobot Run Keyword And Expect Error Given locator "id=doesNotExist" was not found. Node Should Be Hoverable id=doesNotExist Run Keyword And Expect Error Given locator "id=doesNotExist" was not found. Node Should Not Be Hoverable id=doesNotExist From c70767daa86c3587cb71ce635877847496719f43 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Thu, 29 Oct 2020 21:42:22 +0200 Subject: [PATCH 14/19] fix osx tests --- src/main/java/JavaFXLibrary.java | 3 ++ .../AdditionalKeywords/Verifiers.java | 12 +++---- .../keywords/Keywords/DragRobot.java | 31 ++++++++++++++++--- .../robotframework/acceptance/FindTest.robot | 2 ++ .../robotframework/acceptance/MiscTests.robot | 1 - 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index ce72776..e05f12f 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -73,6 +73,8 @@ public class JavaFXLibrary extends AnnotationLibrary { add("clearObjectMap"); add("closeJavafxApplication"); add("closeSwingApplication"); + add("dragFrom"); + add("dropBy"); add("dropTo"); add("getCurrentApplication"); add("getLibraryVersion"); @@ -85,6 +87,7 @@ public class JavaFXLibrary extends AnnotationLibrary { add("launchSwingApplicationInSeparateThread"); add("logApplicationClasspath"); add("logSystemProperties"); + add("moveTo"); add("nodeShouldBeHoverable"); add("nodeShouldNotBeHoverable"); add("pushManyTimes"); diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index 2f0bdb5..ee1e09d 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -36,6 +36,7 @@ import org.testfx.matcher.control.TextMatchers; import org.testfx.matcher.control.TextFlowMatchers; import org.testfx.matcher.control.TextInputControlMatchers; +import org.testfx.robot.Motion; import org.testfx.service.support.PixelMatcherResult; import org.testfx.service.support.impl.PixelMatcherRgb; import org.hamcrest.core.IsNot; @@ -241,10 +242,9 @@ public static void nodeShouldBeHoverable(Object locator) { } }).get(); if (node==null) throw new JavaFXLibraryNonFatalException("Given locator \"" + locator + "\" was not found."); - asyncFx(() -> new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT")).get(); + robot.moveTo(node, Motion.DIRECT); waitForFxEvents(5); - String status; - status = asyncFx(() -> { + String status = asyncFx(() -> { try { verifyThat(node, ExtendedNodeMatchers.isHoverable()); return "success"; @@ -255,7 +255,7 @@ public static void nodeShouldBeHoverable(Object locator) { return ae.getMessage(); } }).get(); - if (status!="success") throw new JavaFXLibraryNonFatalException(status); + if (!status.equals("success")) throw new JavaFXLibraryNonFatalException(status); } catch (InterruptedException | ExecutionException iee) { RobotLog.trace("not hoverable"); throw new JavaFXLibraryNonFatalException("Node not hoverable: ", iee.getCause()); @@ -279,7 +279,7 @@ public static void nodeShouldNotBeHoverable(Object locator) { } }).get(); if (node==null) throw new JavaFXLibraryNonFatalException("Given locator \"" + locator + "\" was not found."); - asyncFx(() -> new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT")).get(); + robot.moveTo(node, Motion.DIRECT); waitForFxEvents(5); String status; status = asyncFx(() -> { @@ -293,7 +293,7 @@ public static void nodeShouldNotBeHoverable(Object locator) { return ae.getMessage(); } }).get(); - if (status=="success") throw new JavaFXLibraryNonFatalException("Expected that \"" + locator + "\" is not hoverable - failed!"); + if (status.equals("success")) throw new JavaFXLibraryNonFatalException("Expected that \"" + locator + "\" is not hoverable - failed!"); } catch (InterruptedException | ExecutionException iee) { RobotLog.trace("not hoverable"); throw new JavaFXLibraryNonFatalException("Node not hoverable: ", iee.getCause()); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java index b0a0ea2..f11384e 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java @@ -50,13 +50,25 @@ public class DragRobot extends TestFxAdapter { public FxRobotInterface dragFrom(Object locator, String button) { checkObjectArgumentNotNull(locator); try { - Object target = checkClickTarget(locator); + Object target = asyncFx(() -> { + try { + return checkClickTarget(locator); + } catch (Exception e) { + RobotLog.info("Locator not found: " + e.getCause()); + return null; + } + }).get(); + if (target==null) throw new JavaFXLibraryNonFatalException("Given locator \"" + locator + "\" was not found."); RobotLog.info("Dragging from \"" + target + "\"" + " with button=\"" + button + "\""); + // TODO: Below needs to be put to asyncFx thread instead of below Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "drag", target.getClass(), MouseButton.class); return (FxRobotInterface) method.invoke(robot, target, new MouseButton[]{MouseButton.valueOf(button)}); + } catch (InterruptedException | ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("Could not execute drag from using locator \"" + locator + "\" " + + "as it is not clickable: " + iee.getCause()); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute drag from using locator \"" + locator + "\" " + - "and button " + button + ": " + e.getCause().getMessage(), e); + "and button " + button + ": " + e.getCause()); } } @@ -69,17 +81,25 @@ public FxRobotInterface dragFrom(Object locator, String button) { public FxRobotInterface dropTo(Object locator) { checkObjectArgumentNotNull(locator); try { - Object target = asyncFx(() -> checkClickTarget(locator)).get(); + Object target = asyncFx(() -> { + try { + return checkClickTarget(locator); + } catch (Exception e) { + RobotLog.info("Locator not found: " + e.getCause()); + return null; + } + }).get(); + if (target==null) throw new JavaFXLibraryNonFatalException("Given locator \"" + locator + "\" was not found."); RobotLog.info("Dropping to \"" + target + "\""); // TODO: Below needs to be put to asyncFx thread instead of below Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "dropTo", target.getClass()); return (FxRobotInterface) method.invoke(robot, target); } catch (InterruptedException | ExecutionException iee) { throw new JavaFXLibraryNonFatalException("Drop To target check failed for locator \"" + locator + "\" " + - ": " + iee.getCause().getMessage(), iee); + ": " + iee.getCause()); } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Could not execute drop to using locator \"" + locator + "\" " + - ": " + e.getCause().getMessage(), e); + ": " + e.getCause()); } } @@ -120,6 +140,7 @@ public FxRobotInterface drop() { public FxRobotInterface dropBy(int x, int y) { try { RobotLog.info("Dropping by x=\"" + x + "\" and y=\"" + y + "\""); + // TODO: Below needs to be put to asyncFx thread instead of below return robot.dropBy(x, y); } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) { diff --git a/src/test/robotframework/acceptance/FindTest.robot b/src/test/robotframework/acceptance/FindTest.robot index 6d26c0a..9c291e0 100644 --- a/src/test/robotframework/acceptance/FindTest.robot +++ b/src/test/robotframework/acceptance/FindTest.robot @@ -133,6 +133,7 @@ Find With Pseudo Class ${root} Find css=VBox HBox VBox HBox StackPane ${target} Find xpath=//Text[@text="150x150"] Move To ${target} + Wait Until Element Exists pseudo=hover ${result} Find pseudo=hover false ${root} Should Be Equal ${result} ${target} @@ -141,6 +142,7 @@ Find All With Pseudo Class Set Test Application ${BOUNDS_APP} ${node} Find xpath=//Text[@text="300x300"] Move To ${node} + Wait Until Element Exists pseudo=hover @{hovered} Find All pseudo=hover # Nodes behind have the hover pseudostate too, Find All returns all of these except the one used as a root in lookup Length Should Be ${hovered} 3 diff --git a/src/test/robotframework/acceptance/MiscTests.robot b/src/test/robotframework/acceptance/MiscTests.robot index d3d688e..bb50b21 100644 --- a/src/test/robotframework/acceptance/MiscTests.robot +++ b/src/test/robotframework/acceptance/MiscTests.robot @@ -122,7 +122,6 @@ Check That Element Is Hoverable Move To Coordinates x=0 y=0 Node Should Be Hoverable id=resetButton - Check That Element Is Not Hoverable [Tags] smoke demo-set negative hoverable Set Test Application javafxlibrary.testapps.TestClickRobot From 2ea2f8bcfe580427e0f1d7a1da1ffcb840ef1b00 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Fri, 30 Oct 2020 14:14:39 +0200 Subject: [PATCH 15/19] screenshots to asyncfx thread also in kw failure, fix hover kw's once more --- pom.xml | 8 +--- src/main/java/JavaFXLibrary.java | 5 ++- .../ConvenienceKeywords.java | 1 - .../AdditionalKeywords/Verifiers.java | 30 +++++++++---- .../keywords/Keywords/BoundsLocation.java | 2 +- .../keywords/Keywords/ClickRobot.java | 2 - .../keywords/Keywords/ScreenCapturing.java | 42 ++++++++++++++----- 7 files changed, 59 insertions(+), 31 deletions(-) diff --git a/pom.xml b/pom.xml index f2bffd3..115ba0f 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ org.robotframework javafxlibrary jar - 0.6.0 + 0.7.0-SNAPSHOT UTF-8 1.44 @@ -271,11 +271,7 @@ smoke - - - - not-ready @@ -385,7 +381,7 @@ junit junit - 4.13 + 4.13.1 org.testfx diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index e05f12f..f21272d 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -70,11 +70,13 @@ public class JavaFXLibrary extends AnnotationLibrary { static List noWrappedAsyncFxKeywords = new ArrayList() {{ add("callObjectMethodInFxApplicationThread"); + add("captureImage"); + add("capturePrimaryScreen"); + add("captureSceneContainingNode"); add("clearObjectMap"); add("closeJavafxApplication"); add("closeSwingApplication"); add("dragFrom"); - add("dropBy"); add("dropTo"); add("getCurrentApplication"); add("getLibraryVersion"); @@ -210,7 +212,6 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { if(retExcep.get()!=null) { RobotLog.reset(); RuntimeException e = retExcep.get(); - // TODO: to asyncFx thread runOnFailure.runOnFailure(); if (e.getCause() instanceof JavaFXLibraryFatalException) { RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary FATAL exception: \n" + Throwables.getStackTraceAsString(e)); diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index 21b04f4..ccd387d 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -19,7 +19,6 @@ import com.sun.javafx.scene.control.skin.TableViewSkin; import com.sun.javafx.scene.control.skin.VirtualFlow; -import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.css.PseudoClass; import javafx.geometry.BoundingBox; diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index ee1e09d..57ddf3e 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -242,8 +242,13 @@ public static void nodeShouldBeHoverable(Object locator) { } }).get(); if (node==null) throw new JavaFXLibraryNonFatalException("Given locator \"" + locator + "\" was not found."); - robot.moveTo(node, Motion.DIRECT); - waitForFxEvents(5); + if (isMac()) { + // TODO: why asyncFx thread does not work in mac? + robot.moveTo(node, Motion.DIRECT); + } else { + asyncFx(() -> robot.moveTo(node, Motion.DIRECT)).get(); + waitForFxEvents(5); + } String status = asyncFx(() -> { try { verifyThat(node, ExtendedNodeMatchers.isHoverable()); @@ -256,9 +261,10 @@ public static void nodeShouldBeHoverable(Object locator) { } }).get(); if (!status.equals("success")) throw new JavaFXLibraryNonFatalException(status); + RobotLog.info("Locator node is hoverable."); } catch (InterruptedException | ExecutionException iee) { - RobotLog.trace("not hoverable"); - throw new JavaFXLibraryNonFatalException("Node not hoverable: ", iee.getCause()); + RobotLog.trace("nodeShouldBeHoverable: failed in asyncFx thread"); + throw new JavaFXLibraryNonFatalException("Node Should Be Hoverable keyword failed: ", iee.getCause()); } } @@ -269,7 +275,7 @@ public static void nodeShouldBeHoverable(Object locator) { public static void nodeShouldNotBeHoverable(Object locator) { checkObjectArgumentNotNull(locator); try { - RobotLog.info("Checking that locator node is hoverable: \"" + locator + "\"."); + RobotLog.info("Checking that locator node is not hoverable: \"" + locator + "\"."); Node node = asyncFx(() -> { try { return objectToNode(locator); @@ -279,8 +285,13 @@ public static void nodeShouldNotBeHoverable(Object locator) { } }).get(); if (node==null) throw new JavaFXLibraryNonFatalException("Given locator \"" + locator + "\" was not found."); - robot.moveTo(node, Motion.DIRECT); - waitForFxEvents(5); + if (isMac()) { + // TODO: why asyncFx thread does not work in mac? + robot.moveTo(node, Motion.DIRECT); + } else { + asyncFx(() -> robot.moveTo(node, Motion.DIRECT)).get(); + waitForFxEvents(5); + } String status; status = asyncFx(() -> { try { @@ -294,9 +305,10 @@ public static void nodeShouldNotBeHoverable(Object locator) { } }).get(); if (status.equals("success")) throw new JavaFXLibraryNonFatalException("Expected that \"" + locator + "\" is not hoverable - failed!"); + RobotLog.info("Locator node is not hoverable."); } catch (InterruptedException | ExecutionException iee) { - RobotLog.trace("not hoverable"); - throw new JavaFXLibraryNonFatalException("Node not hoverable: ", iee.getCause()); + RobotLog.trace("nodeShouldNotBeHoverable: failed in asyncFx thread"); + throw new JavaFXLibraryNonFatalException("Node Should Not Be Hoverable keyword failed: ", iee.getCause()); } } diff --git a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java index c2cf76a..898beae 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java @@ -117,7 +117,7 @@ public Object getBounds(Object locator) { scene.getWindow().getY(), scene.getWidth(), scene.getHeight())); } if (locator instanceof String) - return getBounds(objectToNode((String) locator)); + return getBounds(objectToNode(locator)); Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "bounds", locator.getClass()); BoundsQuery bounds = (BoundsQuery) method.invoke(robot, locator); return HelperFunctions.mapObject(bounds.query()); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java index 8e1adc7..c010982 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java @@ -58,8 +58,6 @@ public FxRobotInterface clickOn(Object locator, String motion) { return (FxRobotInterface) method.invoke(robot, target, getMotion(motion), new MouseButton[]{MouseButton.PRIMARY}); } catch (IllegalAccessException | InvocationTargetException | JavaFXLibraryNonFatalException e) { throw new JavaFXLibraryNonFatalException("Click On failed: " + e.getMessage(), e); - } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("Click On failed: " + e.getMessage(), e); } } diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java index e880b87..b5683e8 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java @@ -25,6 +25,7 @@ import javafxlibrary.keywords.AdditionalKeywords.ConvenienceKeywords; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; +import jnr.ffi.annotations.In; import org.apache.commons.io.FileUtils; import org.robotframework.javalib.annotation.ArgumentNames; import org.robotframework.javalib.annotation.RobotKeyword; @@ -43,7 +44,9 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.Base64; +import java.util.concurrent.ExecutionException; +import static org.testfx.util.WaitForAsyncUtils.*; import static javafxlibrary.utils.HelperFunctions.*; @RobotKeywords @@ -66,8 +69,17 @@ else if (value.toLowerCase().equals("diskonly")) @RobotKeyword("Returns a screenshot from whole primary screen. Note that this shows also other applications that are open.") public Object capturePrimaryScreen() { + try { GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - return this.captureImage(new Rectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()),true); + Rectangle2D target = asyncFx(() -> new Rectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight())).get(); + return this.captureImage(target,true); + } catch (InterruptedException | ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("Unable to get Rectangle2D: " + iee.getCause()); + } catch (Exception e) { + if (e instanceof JavaFXLibraryNonFatalException) + throw e; + throw new JavaFXLibraryNonFatalException("Unable to take capture: ", e.getCause()); + } } @RobotKeyword("Returns a screenshot of the given locator, or if not given from whole active window.\n\n" @@ -88,12 +100,12 @@ public Object captureImage(Object locator, boolean logImage){ try { RobotLog.info("Capturing screenshot from locator: \"" + locator + "\""); Image image; - Bounds targetBounds = objectToBounds(locator); String logPath; - - image = robot.capture(targetBounds).getImage(); Path path = createNewImageFileNameWithPath(); - robotContext().getCaptureSupport().saveImage(image, path); + + Bounds targetBounds = asyncFx(() -> objectToBounds(locator)).get(); + image = asyncFx(() -> robot.capture(targetBounds).getImage()).get(); + asyncFx(() -> robotContext().getCaptureSupport().saveImage(image, path)).get(); if (getCurrentSessionScreenshotDirectoryInLogs() != null) { logPath = getCurrentSessionScreenshotDirectoryInLogs()+"/"+path.getFileName(); @@ -129,12 +141,14 @@ public Object captureImage(Object locator, boolean logImage){ } } return mapObject(image); - } catch (IOException e) { - throw new JavaFXLibraryNonFatalException("Unable to take capture : \"" + locator + "\"", e); + } catch (InterruptedException | ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("Unable to take capture (asyncFx thread failed): ", iee.getCause()); + } catch (IOException ioe) { + throw new JavaFXLibraryNonFatalException("Unable to take capture (IOException): \"" + locator + "\"", ioe.getCause()); } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) throw e; - throw new JavaFXLibraryNonFatalException("Unable to take capture : \"" + locator + "\"", e); + throw new JavaFXLibraryNonFatalException("Unable to take capture: \"" + locator + "\"", e.getCause()); } } @@ -144,8 +158,16 @@ public Object captureImage(Object locator, boolean logImage){ + "| ${capture}= | Capture Scene Containing Node | ${node} | \n" ) @ArgumentNames({"locator", "logImage=True"}) public Object captureSceneContainingNode(Object locator) { - Scene scene = (Scene) useMappedObject(new ConvenienceKeywords().getScene(mapObject(locator))); - return this.captureImage(scene,true); + try { + Scene scene = asyncFx(() -> (Scene) useMappedObject(new ConvenienceKeywords().getScene(mapObject(locator)))).get(); + return this.captureImage(scene, true); + } catch (InterruptedException | ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("Unable to get scene: " + iee.getCause()); + } catch (Exception e) { + if (e instanceof JavaFXLibraryNonFatalException) + throw e; + throw new JavaFXLibraryNonFatalException("Unable to take capture: \"" + locator + "\"", e.getCause()); + } } @RobotKeyword("Loads an image from the given _path_ in hard drive \n\n" From 6044db47a07fb3c38fe51a260f0aa26ddd02a696 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Fri, 30 Oct 2020 21:28:18 +0200 Subject: [PATCH 16/19] fix osx part and go throught documentation --- src/main/java/JavaFXLibrary.java | 1 + .../ApplicationLauncher.java | 4 +- .../ConvenienceKeywords.java | 29 +++++---- .../keywords/AdditionalKeywords/Find.java | 6 +- .../AdditionalKeywords/Verifiers.java | 30 +++++---- .../keywords/Keywords/ClickRobot.java | 4 +- .../keywords/Keywords/DragRobot.java | 8 +-- .../keywords/Keywords/KeyboardRobot.java | 2 +- .../keywords/Keywords/NodeLookup.java | 4 +- .../keywords/Keywords/ScreenCapturing.java | 2 +- .../keywords/Keywords/WindowLookup.java | 2 +- .../keywords/Keywords/WindowTargeting.java | 2 +- src/main/java/libdoc-documentation.txt | 64 ++++++++++--------- 13 files changed, 83 insertions(+), 75 deletions(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index f21272d..13adf21 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -78,6 +78,7 @@ public class JavaFXLibrary extends AnnotationLibrary { add("closeSwingApplication"); add("dragFrom"); add("dropTo"); + add("dropToCoordinates"); add("getCurrentApplication"); add("getLibraryVersion"); add("getScreenshot Directory"); diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java index 811b499..e5e7ab1 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java @@ -67,7 +67,7 @@ public void launchJavafxApplication(String appName, String... appArgs) { + "``appName`` is the name of the application to launch. \n\n" + "``appArgs`` is a list of arguments to be passed for the application. \n\n" + "Example:\n" - + "| Launch Swing Application | _javafxlibrary.testapps.SwingApplication |\n" + + "| Launch Swing Application | _javafxlibrary.testapps.SwingApplication_ |\n" + "| Launch Swing Application | _TestApplication.jar_ |\n") @ArgumentNames({ "appName", "*args" }) public void launchSwingApplication(String appName, String... appArgs) { @@ -87,7 +87,7 @@ public void launchSwingApplication(String appName, String... appArgs) { + "``appName`` is the name of the application to launch. \n\n" + "``appArgs`` is a list of arguments to be passed for the application. \n\n" + "Example:\n" - + "| Launch Swing Application In Separate Thread | _javafxlibrary.testapps.SwingApplication |\n" + + "| Launch Swing Application In Separate Thread | _javafxlibrary.testapps.SwingApplication_ |\n" + "| Launch Swing Application In Separate Thread | _TestApplication.jar_ |\n") @ArgumentNames({ "appName", "*args" }) public void launchSwingApplicationInSeparateThread(String appName, String... appArgs) { diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index ccd387d..5d32917 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -72,7 +72,7 @@ public void bringStageToFront(Stage stage) { + "If argument type is boolean, byte, char, double, float, int, long or short, it must have \"casting instructions\" " + "in front of it, e.g. _\"(boolean)false\"_.\n\n" + "\nExample:\n" - + "| ${node}= | Find | \\#node-id | \n" + + "| ${node}= | Find | id=node-id | \n" + "| ${max height}= | Call Object Method | ${node} | maxHeight | (double)10 | \n" + "| ${node text}= | Call Object Method | ${node} | getText | \n") @ArgumentNames({ "object", "method", "*arguments=" }) @@ -91,7 +91,7 @@ public Object callObjectMethod(Object object, String method, Object... arguments @RobotKeyword("Calls given method in FX Application Thread using Platform.runLater(). See `Call Object Method` " + "for further documentation.\n\n" + "\nExample:\n" - + "| ${node}= | Find | \\#node-id | \n" + + "| ${node}= | Find | id=node-id | \n" + "| Call Object Method In Fx Application Thread | ${node} | maxHeight | (boolean)false | \n") @ArgumentNames({ "object", "method", "*arguments=" }) public void callObjectMethodInFxApplicationThread(Object object, String method, Object... arguments) { @@ -106,9 +106,9 @@ public void callObjectMethodInFxApplicationThread(Object object, String method, @RobotKeyword("Lists methods available for given node.\n" + "``node`` is the Object:Node which methods to list, see `3.2 Using locators as keyword arguments`. \n\n" + "When working with custom components you may use this keyword to discover methods you can call " - + "with `Call Method` keyword.\n\n" + + "with `Call Object Method` or `Call Object Method In Fx Application Thread` keyword.\n\n" + "Example:\n" - + "| List Component Methods | ${my node} |\n") + + "| List Node Methods | ${my node} |\n") @ArgumentNames({ "node" }) public String[] listNodeMethods(Node node) { try { @@ -150,7 +150,7 @@ private String getMethodDescription(Method m) { + "Optional argument ``root`` is the starting point from where to start listing child nodes, " + "see `3.2 Using locators as keyword arguments`. Defaults to root node of current window. \n\n" + "\nExample:\n" - + "| ${my node}= | Find | \\#node-id | \n" + + "| ${my node}= | Find | id=node-id | \n" + "| Print Child Nodes | ${my node} | \n") @ArgumentNames({ "root=" }) public void printChildNodes(Object root) { @@ -167,7 +167,7 @@ public void printChildNodes(Object root) { + "Optional argument ``root`` is the starting point from where to start listing child nodes, " + "see `3.2 Using locators as keyword arguments`. Defaults to root node of current window. \n\n" + "\nExample:\n" - + "| ${my node}= | Find | \\#node-id | \n" + + "| ${my node}= | Find | id=node-id | \n" + "| Log FXML | ${my node} | \n") @ArgumentNames({"root="}) public void logFXML(Object root) { @@ -204,6 +204,7 @@ public void setSafeClicking(String value) { + "| ${old_timeout}= | Set Timeout | 20 | \n" + "| Click On | id=myidthatshallcomeavailable | | \n" + "| [Teardown] | Set Timeout | ${old_timeout} | \n") + @ArgumentNames({ "timeout" }) public Integer setTimeout(int timeout) { RobotLog.info("Setting timeout to " + timeout + "s"); Integer oldTimeoutValue = getLibraryKeywordTimeout(); @@ -312,7 +313,7 @@ public String getNodeImageUrl(Object locator) { @RobotKeyword("Returns the parent node of node. \n\n" + "``locator`` is either a _query_ or _Object_ for a node whose getParent method will be called, see " + "`3. Locating JavaFX Nodes`. \n\n") - @ArgumentNames({ "node" }) + @ArgumentNames({ "locator" }) public Object getNodeParent(Object locator) { checkObjectArgumentNotNull(locator); Node node = objectToNode(locator); @@ -498,7 +499,7 @@ public List getTableColumnCells(Object locator, int column) { + "`3. Locating JavaFX Nodes`. \n\n" + "``row`` Integer value for the column" + "\nExample:\n" - + "| ${row cells}= | Get Table Row Cells | \\#table-id | ${2} | \n" + + "| ${row cells}= | Get Table Row Cells | id=table-id | ${2} | \n" + "| Dictionary Should Contain Key | ${row cells} | column name | \n" + "| ${cell text}= | Get Node Text | &{row cells}[column name] | # assuming that cell is a node that has a text value |\n") @ArgumentNames({ "table", "row" }) @@ -523,7 +524,7 @@ public List getTableRowValues(Object locator, int rowNumber) { + "`3. Locating JavaFX Nodes`. \n\n" + "``row`` Integer value for the column" + "\nExample:\n" - + "| ${row cells}= | Get Table Row Cells | \\#table-id | ${2} | \n" + + "| ${row cells}= | Get Table Row Cells | id=table-id | ${2} | \n" + "| Dictionary Should Contain Key | ${row cells} | column name | \n" + "| ${cell text}= | Get Node Text | &{row cells}[column name] | # assuming that cell is a node that has a text value |\n") @ArgumentNames({ "table", "row" }) @@ -694,7 +695,7 @@ public Object getSpinnerValue(Object locator) { + "``locator`` is either a _query_ or _Object:Node_ for identifying the TabPane element, see " + "`3. Locating JavaFX Nodes`. \n\n" + "\nExample:\n" - + "| ${tabs}= | Get Tab pane Tabs | \\#tab-pane-id | \n" + + "| ${tabs}= | Get Tab pane Tabs | id=tab-pane-id | \n" + "| Dictionary Should Contain Key | ${tabs} | tab name | \n") @ArgumentNames({ "locator" }) public Map getTabPaneTabs(Object locator) { @@ -720,7 +721,7 @@ public Map getTabPaneTabs(Object locator) { + "``locator`` is either a _query_ or _Object:Node_ for identifying the TabPane element, see " + "`3. Locating JavaFX Nodes`. \n\n" + "\nExample:\n" - + "| ${tab}= | Get Tab Pane Selected Tab | \\#pane-id | \n" + + "| ${tab}= | Get Tab Pane Selected Tab | id=pane-id | \n" + "| Dictionary Should contain Key | ${tab} | tab name | \n") @ArgumentNames({ "locator" }) public Map getSelectedTabPaneTab(Object locator) { @@ -742,7 +743,7 @@ public Map getSelectedTabPaneTab(Object locator) { + "``tabName`` is the name of the tab to be selected\n" + "\nExamples:\n" + "| Select Tab Pane Tab | ${Tab Pane} | tab name | \n" - + "| Select Tab Pane Tab | \\#tab-id | tab name | \n") + + "| Select Tab Pane Tab | id=tab-id | tab name | \n") @ArgumentNames({"locator", "tabName"}) public void selectTabPaneTab (Object locator, String tabName) { checkObjectArgumentNotNull(locator); @@ -837,7 +838,7 @@ public void clearTextInput(Object locator) { + "Optional parameter ``locator`` is an _Object:Window_ for specifying which contextMenu(window) items should be collected. " + "Default value is the last window returned by `Get Target Windows` -keyword. \n" + "\nExamples:\n" - + "| Click On | \\#menu-button-id | \n" + + "| Click On | id=menu-button-id | \n" + "| ${menu items}= | Get Context Menu Items | \n" + "| Dictionary Should Contain Key | ${menu items} | menu item name" + "| Click On | &{menu items}[menu item name] | \n\n") @@ -857,7 +858,7 @@ public Map getContextMenuItems(Window window){ + "``item`` is the name for the Context Menu item to be clicked. This keyword clicks the first menu item that matches the given " + "item name. Search of an item is started from the last target window.\n\n" + "Example:\n" - + "| Click On | \\#menu-button-id | \n" + + "| Click On | id=menu-button-id | \n" + "| Select Context Menu Item | menu item name |") @ArgumentNames({"item"}) public void selectContextMenuItem(String item){ diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java index bfbb4c5..9c6a643 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java @@ -25,15 +25,13 @@ public class Find { + "``root`` is an optional argument pointing to the element which is used as the origin of the lookup. If " + "root is defined only its children can be found. By default nodes are being looked from everywhere.\n\n" + "\nExample:\n" - + "| ${my node}= | Find | some text | | | # finds node containing text _some text_ |\n" - + "| ${my node}= | Find | .css | | | # finds node with matching style class |\n" - + "| ${my node}= | Find | \\#id | | | # finds node with matching _id_ |\n" + + "| ${my node}= | Find | text=\"some text\" | | | # finds node containing text _some text_ |\n" + "| ${my node}= | Find | css=VBox | | | # finds node matching the CSS selector |\n" + "| ${my node}= | Find | id=id | | | # finds node with matching _id_ |\n" + "| ${my node}= | Find | xpath=//Rectangle | | | # finds node matching the XPath |\n" + "| ${my node}= | Find | class=javafx.scene.shape.Rectangle | | | # finds node that is instance of the class |\n" + "| ${my node}= | Find | pseudo=hover | | | # finds node containing the given pseudo class state |\n" - + "| ${my node}= | Find | \\#id | True | | # this search fails if nothing is found |\n" + + "| ${my node}= | Find | id=id | True | | # this search fails if nothing is found |\n" + "| ${my node}= | Find | css=VBox | False | ${root} | # finds node matching the CSS selector from the children of given root |\n\n" + "Or chaining multiple queries together:\n" + "| ${my node}= | Find | css=VBox HBox xpath=//Rectangle[@width=\"600.0\"] | \n" diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index 57ddf3e..edfecd4 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -59,8 +59,10 @@ public class Verifiers extends TestFxAdapter { + "``timeout`` is the maximum waiting time value, defaults to 10 \n\n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Element Exists | \\#some-node-id | \n" - + "| Wait Until Element Exists | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Element Exists | id=some-node-id | \n" + + "| Wait Until Element Exists | id=some-node-id | 200 | MILLISECONDS | \n" + + "| ${node}= | Wait Until Element Exists | css=VBox | \n" + + "| Click On | ${node} | \n") @ArgumentNames({"locator", "timeout=10", "timeUnit=SECONDS"}) public Object waitUntilElementExists(String locator, int timeout, String timeUnit) { try { @@ -77,8 +79,8 @@ public Object waitUntilElementExists(String locator, int timeout, String timeUni + "``timeout`` is the maximum waiting time value, defaults to 10 \n\n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Element Does Not Exists | \\#some-node-id | \n" - + "| Wait Until Element Does Not Exists | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Element Does Not Exists | id=some-node-id | \n" + + "| Wait Until Element Does Not Exists | id=some-node-id | 200 | MILLISECONDS | \n") @ArgumentNames({"locator", "timeout=10", "timeUnit=SECONDS"}) public void waitUntilElementDoesNotExists(String locator, int timeout, String timeUnit) { try { @@ -95,8 +97,10 @@ public void waitUntilElementDoesNotExists(String locator, int timeout, String ti + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Node Is Visible | \\#some-node-id | \n" - + "| Wait Until Node Is Visible | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Node Is Visible | id=some-node-id | \n" + + "| Wait Until Node Is Visible | id=some-node-id | 200 | MILLISECONDS | \n" + + "| ${node}= | Wait Until Node Is Visible | css=VBox | \n" + + "| Click On | ${node} | \n") @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) public Object waitUntilNodeIsVisible(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); @@ -114,8 +118,8 @@ public Object waitUntilNodeIsVisible(Object locator, int timeout, String timeUni + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Node Is Not Visible | \\#some-node-id | \n" - + "| Wait Until Node Is Not Visible | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Node Is Not Visible | id=some-node-id | \n" + + "| Wait Until Node Is Not Visible | id=some-node-id | 200 | MILLISECONDS | \n") @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) public Object waitUntilNodeIsNotVisible(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); @@ -133,8 +137,10 @@ public Object waitUntilNodeIsNotVisible(Object locator, int timeout, String time + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Node Is Enabled | \\#some-node-id | \n" - + "| Wait Until Node Is Enabled | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Node Is Enabled | id=some-node-id | \n" + + "| Wait Until Node Is Enabled | id=some-node-id | 200 | MILLISECONDS | \n" + + "| ${node}= | Wait Until Node Is Enabled | css=VBox | \n" + + "| Click On | ${node} | \n") @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) public Object waitUntilNodeIsEnabled(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); @@ -152,8 +158,8 @@ public Object waitUntilNodeIsEnabled(Object locator, int timeout, String timeUni + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Node Is Not Enabled | \\#some-node-id | \n" - + "| Wait Until Node Is Not Enabled | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Node Is Not Enabled | id=some-node-id | \n" + + "| Wait Until Node Is Not Enabled | id=some-node-id | 200 | MILLISECONDS | \n") @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) public Object waitUntilNodeIsNotEnabled(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java index c010982..c0c09d8 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java @@ -45,8 +45,8 @@ public class ClickRobot extends TestFxAdapter { + "\nExample:\n" + "| Click On | ${node} | \n" + "| Click On | ${point} | \n" - + "| Click On | \\#node-id | \n" - + "| Click On | .css-name | Motion=VERTICAL_FIRST | \n") + + "| Click On | id=node-id | \n" + + "| Click On | css=.css-name | Motion=VERTICAL_FIRST | \n") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface clickOn(Object locator, String motion) { checkObjectArgumentNotNull(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java index f11384e..b6c6342 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java @@ -44,7 +44,7 @@ public class DragRobot extends TestFxAdapter { + "`3. Locating JavaFX Nodes`. \n\n" + "Optional parameter ``button`` is the mouse button to be used, defaults to PRIMARY. See `5. Used ENUMs` for different MouseButtons\n\n" + "\nExample:\n" - + "| ${node}= | Find | \\#some-node-id | \n" + + "| ${node}= | Find | id=some-node-id | \n" + "| Drag From | ${node} | SECONDARY | \n") @ArgumentNames({ "locator", "button=PRIMARY" }) public FxRobotInterface dragFrom(Object locator, String button) { @@ -76,7 +76,7 @@ public FxRobotInterface dragFrom(Object locator, String button) { + "``locator`` is either a _query_ or _Object:Bounds, Node, Point2D, PointQuery, Scene, Window_ for identifying the element, see " + "`3. Locating JavaFX Nodes`. \n\n" + "\nExample:\n" - + "| Drop To | \\#some-node-id | \n") + + "| Drop To | id=some-node-id | \n") @ArgumentNames({ "locator" }) public FxRobotInterface dropTo(Object locator) { checkObjectArgumentNotNull(locator); @@ -134,7 +134,7 @@ public FxRobotInterface drop() { + "Integer argument ``x`` is the amount how much to move the mouse horizontally\n" + "Integer argument ``y`` is the amount how much to move the mouse vertically.\n" + "\nExample:\n" - + "| Drag From | \\#node-id .css-name | \n" + + "| Drag From | id=node-id css=.css-name | \n" + "| Drop By | -300 | 0 | \n") @ArgumentNames({ "x", "y" }) public FxRobotInterface dropBy(int x, int y) { @@ -175,7 +175,7 @@ public FxRobotInterface dragFromCoordinates(int x, int y, String... buttons) { + "Integer argument ``x`` sets the target point for x -coordinate\n\n" + "Integer argument ``y`` sets the target point for y -coordinate\n\n" + "\nExample:\n" - + "| Drag From | \\#node-id | \n" + + "| Drag From | id=node-id | \n" + "| Drop To | 100 | 100 | \n") @ArgumentNames({ "x", "y" }) public FxRobotInterface dropToCoordinates(int x, int y) { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java index 3a0a194..3d37c93 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java @@ -215,7 +215,7 @@ public void writeFast(String text) { + "`3. Locating JavaFX Nodes`. \n\n" + "``text`` is the text characters to write\n" + "\nExample: \n" - + "| Write To | .css-name | Robot Framework | \n") + + "| Write To | css=.css-name | Robot Framework | \n") @ArgumentNames({ "locator", "text" }) public void writeTo(Object locator, String text) { checkObjectArgumentNotNull(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java index 8d65a29..439e355 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java @@ -46,10 +46,10 @@ public class NodeLookup extends TestFxAdapter { + "| ${some scene}= | Get Nodes Scene | ${some node} | \n" + "| ${root} | Get Root Node Of | ${some scene} | \n" + "Node:\n" - + "| ${some node}= | find | \\#some-node-id | \n" + + "| ${some node}= | find | id=some-node-id | \n" + "| ${root} | Get Root Node Of | ${some node} | \n" + "Query:\n" - + "| ${root} | Get Root Node Of | \\#some-node-id | \n" ) + + "| ${root} | Get Root Node Of | id=some-node-id | \n" ) @ArgumentNames({"locator"}) public Object getRootNodeOf(Object locator) { checkObjectArgumentNotNull(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java index b5683e8..8449cda 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java @@ -93,7 +93,7 @@ public Object capturePrimaryScreen() { + "| ${capture}= | Capture Image | ${node} | \n" + "| ${capture}= | Capture Image | ${window} | \n" + "| ${capture}= | Capture Image | | \n" - + "| ${capture}= | Capture Image | \\#id | logImage=False |\n" ) + + "| ${capture}= | Capture Image | id=id | logImage=False |\n" ) @ArgumentNames({"locator=target window", "logImage=True"}) public Object captureImage(Object locator, boolean logImage){ checkObjectArgumentNotNull(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java b/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java index 8fcadec..9675a15 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java @@ -70,7 +70,7 @@ public List listTargetWindows() { + "| ${window}= | Get Window | 0 | \n" + "| ${window}= | Get Window | ${2} | \n\n" + "Node:\n" - + "| ${some_node}= | Find | \\#some_id | \n" + + "| ${some_node}= | Find | id=some_id | \n" + "| ${window}= | Get Window | ${some_node} | \n\n" + "Scene: \n" + "| ${some_scene}= | Get Nodes Scene | ${some_node} | \n" diff --git a/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java b/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java index b5d6c67..4194b5f 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java @@ -58,7 +58,7 @@ public Object getTargetWindow() { + "| Set Target Window | 0 | \n" + "| Set Target Window | ${2} | \n\n" + "Node:\n" - + "| ${some_node}= | Find | \\#some_id | \n" + + "| ${some_node}= | Find | id=some_id | \n" + "| Set Target Window | ${some_node} | \n\n" + "Scene: \n" + "| ${some_scene}= | Get Nodes Scene | ${some_node} | \n" diff --git a/src/main/java/libdoc-documentation.txt b/src/main/java/libdoc-documentation.txt index 99c3d9a..3525950 100644 --- a/src/main/java/libdoc-documentation.txt +++ b/src/main/java/libdoc-documentation.txt @@ -3,20 +3,13 @@ JavaFXLibrary can be run with both Jython and Python version of Robot Framework In short, this library is a wrapper for [https://github.com/TestFX/TestFX|TestFX], which is a Java library for testing JavaFX UI applications. - - == 1. Preparations before running the tests == - JavaFXLibrary needs to be compiled and packaged. [https://github.com/eficode/JavaFXLibrary/releases/latest|Download JAR release] or clone the [https://github.com/eficode/JavaFXLibrary.git|repository] and run _mvn package_ from the root folder. - The tested application and the JavaFXLibrary jars need to be added to CLASSPATH. +- Once the library jar -file is available, the library can be taken into use in two ways: *Local mode* with _Jython_ and *Remote mode* with both _Jython_ and _Python_ version of Robot Framework. +== 2.1 Usage in local mode(Jython only) == - -== 2. Using the library == -Once the library jar -file is available, the library can be taken into use in two ways: *Local mode* with _Jython_ and -*Remote mode* with both _Jython_ and _Python_ version of Robot Framework. - - -=== 2.1 Usage in local mode(Jython only) === First, the JavaFXLibrary needs to be taken into use in the settings table. | *Settings* | *Value* | | Library | JavaFXLibrary | @@ -25,7 +18,18 @@ Experimental headless mode can be activated at the import time by setting first | *Settings* | *Value* | | Library | JavaFXLibrary | ${True} | -=== 2.2 Usage in remote mode(Jython & Python) === +== 2.2 Launch application == + +In test data start application like this (setting of classpath is optional if Java Agent is used) + +| *Keyword* | *Argument* | +| Set To Classpath | your_application.jar | +| Launch Javafx Application | org.yourmodule.mainClass | + +== 2.3 Usage in remote mode(Jython & Python) == + +=== 2.3.1 Start remote library manually === + When using the test library in remote mode, the library needs to be started at the remote end first. This can be done as follows: - _java -jar javafxlibrary-.jar_ This will start the remote server listening at default port number 8270. @@ -47,30 +51,27 @@ Experimental headless mode can be activated in remote mode at the import time by | *Settings* | *Value* | | Library | Remote | http://localhost:8270 | ${True} | WITH NAME | JavaFXLibrary | -Experimental Java agent support +Launch application like in `2.2 Launch application`. + +=== 2.3.2 Start remote library as Java Agent === Library can be used as java agent. Launch application with `-javaagent:/path/to/javafxlibrary-.jar`. Default port is 8270 and can be changed with adding `=` to java agent command. Only remote library is supported. -Using `Launch JavaFX Application` is still required but instead of starting new application keyword initializes Stage for library. +Using `Launch JavaFX Application` is still required but instead of starting new application keyword initializes Stage +for library (see `2.2 Launch application`). == 3. Locating JavaFX Nodes == === 3.1 Locator syntax === -JavaFXLibrary uses TestFX lookup queries as the default way of locating JavaFX Nodes in the UI. These queries are very -similar to normal CSS-selectors used in JavaFX, but come with some modifications. Note that the '#'-character must be escaped as it begins a comment in Robot Framework. -| *Example Query* | *Description* | -| Submit | [https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Labeled.html|Labeled] nodes like Buttons and Labels can be located with plain text | -| VBox HBox Button | *Using class names as selectors does not work.* TestFX tries to find a node containing text "_VBox HBox Button_" instead | -| .vBox .hBox \#submitButton | Style classes and IDs can be used just like in CSS | - -Smaller applications with a clear, well-defined structure and more or less static content can easily be tested using -only the default locator queries. However, as the application grows in size and its UI is starting to have a lot of -dynamic content, things tend to become a bit more difficult. To tackle this, JavaFXLibrary offers additional query types -for locating objects: *id*, *css*, *class*, *text*, *xpath* and *pseudo*. Query type is defined by using a prefix. +JavaFXLibrary offers different query types for locating objects: *id*, *css*, *class*, *text*, *xpath* and *pseudo*. +Query type is defined by using a prefix. Note that in css type id's '#'-character must be escaped as it begins a +comment in Robot Framework. + | *Example Query* | *Description* | | id=submitButton | Returns a node with id submitButton. Basically same as default query "_\#submitButton_". | | css=VBox > .customStyle | Returns a node matching the CSS selector. | +| css=.customStyle \#button-id | Combined CSS selector with id. | | class=javafx.scene.shape.Rectangle | Returns a node that is an instance of the given class. | -| text="Submit" | Returns a node with text value _Submit_. The value must be inside quotation marks. Works only with Labeled nodes, and is basically the same as the default locator with plain text. | +| text="Submit" | Returns a node with text value _Submit_. The value must be inside quotation marks. Works only with [https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Labeled.html|Labeled] nodes. | | text="Text with \"quotation\" marks" | Text value can contain spaces and quotation marks, but inner quotation marks must be escaped using _'\'_ backslash character. | | xpath=//Rectangle[@fill="0xff1493ff"] | Returns a Rectangle that has fill value _0xff1493ff_. See `3.3 About XPath queries` for more details about using xpath queries. | | pseudo=hover;focused | Returns a node that contains pseudo class states _hover_ and _focused_. See `3.4 About Pseudo queries` for more details about using pseudo queries. | @@ -80,9 +81,9 @@ is used as the root of the next query. Only queries with prefixes can be chained | *Example Query* | *Description* | | xpath=/VBox/HBox[4] css=Label | Finds 4th HBox child of VBox and returns the Label it contains. | | css=VBox HBox xpath=//Rectangle[@width="600.0"] | Finds the HBox using CSS query and proceeds to find a Rectangle that is 600px wide and is located in the HBox. | +| id=main-view css=.save-button | Finds css .button under id main-view. | | class=com.eficode.WrapperNode css=.styleClass text="toggle fullscreen" | Returns a node containing text _toggle fullscreen_ that has a parent which contains style class _styleClass_ and has a parent that is an instance of the WrapperNode class. | - === 3.2 Using locators as keyword arguments === Locators can be given as arguments for every JavaFXLibrary keyword that accepts a node as an argument. This is useful as it helps keeping the test case implementations cleaner and easier to read. However sometimes it is more convenient to have @@ -91,12 +92,12 @@ a reference to the node saved in a variable. `Find` and `Find All` -keywords can | | Click On | submit | # Clicks on node containing text _submit_ | | | Click On | xpath=//Button[@text="submit"] | # Clicks on button containing text _submit_ | | | | -| ${node}= | Find | submit | # Finds node containing text _submit_ and returns it | +| ${node}= | Find | text="submit" | # Finds node containing text _submit_ and returns it | | | Click On | ${node} | # Click on the node that was found earlier | If we want to click every Button of the application, we could use Find All and call the click on keyword in a for loop: | *Return value* | *Keyword* | *Argument* | | *Description* | -| ${buttons}= | Find All | .button | | # Get all button nodes to @{buttons} list variable | +| ${buttons}= | Find All | css=.button | | # Get all button nodes to @{buttons} list variable | | FOR | ${button} | IN | @{buttons} | | | Click On | ${button} | | # Click each button in for loop | | END | | | @@ -116,7 +117,6 @@ used to differentiate nodes. To see the generated FXML used for the lookup, use is large, it might be easier to first get a parent node closer to the actual point of interest and use it as a root for the Log FXML keyword. This way the output will be easier to read and the log.html wont be millions of lines long. - === 3.4 About Pseudo queries === All lookup queries return the first matching node, unless used with `Find All` keyword in which case all of the matches will be returned. Usually this is not a problem, but pseudo classes require a some additional thought and care. For example @@ -129,6 +129,9 @@ root for the lookup. Root can be given as an argument for both Find keywords, or before using pseudo-query. Multiple pseudo-classes can be given in a single query to further narrow the amount of matching nodes by using ';' separator, e.g. _pseudo=hover;focused_. +=== 3.5 Getting node parent === +Sometimes you can find row according to value under it but want to get the row node. Use `Get Node Parent` keyword to +get parent node of wanted node (can be repeated if higher in node tree). == 4. Argument types and return value types == JavaFXLibrary has built in support for [https://github.com/robotframework/jrobotremoteserver|jrobotremoteserver], which provides @@ -145,17 +148,16 @@ object methods available for Nodes. Let's take an example of a table that can contain complex objects, not just simple string values: | *Return value* | *Keyword* | *Argument* | *Argument* | *Description* | -| ${table cells}= | Get Table Row Cells | \#table-id | 2 | # table cell Nodes are stored in map and string representations are returned | +| ${table cells}= | Get Table Row Cells | id=table-id | 2 | # table cell Nodes are stored in map and string representations are returned | | | Node Should Be Enabled | ${table cells}[column 0] | | # Library takes the string value as an argument and converts it back to Node | | | Node Should Have Text | ${table cells}[column 1] | some text | | | | | Click On | ${table cells}[column 2] | | # in case this cell is clickable | -| ${cell buttons}= | Find All From Node | ${table cells}[column 3] | .button | # Finds all buttons from table cell Node | +| ${cell buttons}= | Find All | css=.button | root=${table cells}[column 3] | # Finds all buttons from table cell Node | | | Click On | ${cell buttons}[0] | | | Most of the JavaFXLibrary keywords can use locators directly e.g. `Click On` keyword can take just css selector as an argument, but in some cases it can be convenient to be able to pass in a 'Node' as an argument, especially when dealing with complex data structures. - == 5. Used ENUMs == | *Definition* | *Values* | | [https://github.com/TestFX/TestFX/blob/master/subprojects/testfx-core/src/main/java/org/testfx/robot/Motion.java|Motion] | DEFAULT, DIRECT, HORIZONTAL_FIRST, VERTICAL_FIRST | From d3e49a33986d8bb65fa23fa6684a5a1e7da9b01e Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Fri, 30 Oct 2020 21:28:18 +0200 Subject: [PATCH 17/19] fix osx part and go throught documentation --- src/main/java/JavaFXLibrary.java | 1 + .../ApplicationLauncher.java | 4 +- .../ConvenienceKeywords.java | 29 +++++---- .../keywords/AdditionalKeywords/Find.java | 6 +- .../AdditionalKeywords/Verifiers.java | 30 +++++---- .../keywords/Keywords/ClickRobot.java | 4 +- .../keywords/Keywords/DragRobot.java | 8 +-- .../keywords/Keywords/KeyboardRobot.java | 2 +- .../keywords/Keywords/NodeLookup.java | 4 +- .../keywords/Keywords/ScreenCapturing.java | 2 +- .../keywords/Keywords/WindowLookup.java | 2 +- .../keywords/Keywords/WindowTargeting.java | 2 +- src/main/java/libdoc-documentation.txt | 64 ++++++++++--------- 13 files changed, 83 insertions(+), 75 deletions(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index f21272d..13adf21 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -78,6 +78,7 @@ public class JavaFXLibrary extends AnnotationLibrary { add("closeSwingApplication"); add("dragFrom"); add("dropTo"); + add("dropToCoordinates"); add("getCurrentApplication"); add("getLibraryVersion"); add("getScreenshot Directory"); diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java index 811b499..e5e7ab1 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java @@ -67,7 +67,7 @@ public void launchJavafxApplication(String appName, String... appArgs) { + "``appName`` is the name of the application to launch. \n\n" + "``appArgs`` is a list of arguments to be passed for the application. \n\n" + "Example:\n" - + "| Launch Swing Application | _javafxlibrary.testapps.SwingApplication |\n" + + "| Launch Swing Application | _javafxlibrary.testapps.SwingApplication_ |\n" + "| Launch Swing Application | _TestApplication.jar_ |\n") @ArgumentNames({ "appName", "*args" }) public void launchSwingApplication(String appName, String... appArgs) { @@ -87,7 +87,7 @@ public void launchSwingApplication(String appName, String... appArgs) { + "``appName`` is the name of the application to launch. \n\n" + "``appArgs`` is a list of arguments to be passed for the application. \n\n" + "Example:\n" - + "| Launch Swing Application In Separate Thread | _javafxlibrary.testapps.SwingApplication |\n" + + "| Launch Swing Application In Separate Thread | _javafxlibrary.testapps.SwingApplication_ |\n" + "| Launch Swing Application In Separate Thread | _TestApplication.jar_ |\n") @ArgumentNames({ "appName", "*args" }) public void launchSwingApplicationInSeparateThread(String appName, String... appArgs) { diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index ccd387d..5d32917 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -72,7 +72,7 @@ public void bringStageToFront(Stage stage) { + "If argument type is boolean, byte, char, double, float, int, long or short, it must have \"casting instructions\" " + "in front of it, e.g. _\"(boolean)false\"_.\n\n" + "\nExample:\n" - + "| ${node}= | Find | \\#node-id | \n" + + "| ${node}= | Find | id=node-id | \n" + "| ${max height}= | Call Object Method | ${node} | maxHeight | (double)10 | \n" + "| ${node text}= | Call Object Method | ${node} | getText | \n") @ArgumentNames({ "object", "method", "*arguments=" }) @@ -91,7 +91,7 @@ public Object callObjectMethod(Object object, String method, Object... arguments @RobotKeyword("Calls given method in FX Application Thread using Platform.runLater(). See `Call Object Method` " + "for further documentation.\n\n" + "\nExample:\n" - + "| ${node}= | Find | \\#node-id | \n" + + "| ${node}= | Find | id=node-id | \n" + "| Call Object Method In Fx Application Thread | ${node} | maxHeight | (boolean)false | \n") @ArgumentNames({ "object", "method", "*arguments=" }) public void callObjectMethodInFxApplicationThread(Object object, String method, Object... arguments) { @@ -106,9 +106,9 @@ public void callObjectMethodInFxApplicationThread(Object object, String method, @RobotKeyword("Lists methods available for given node.\n" + "``node`` is the Object:Node which methods to list, see `3.2 Using locators as keyword arguments`. \n\n" + "When working with custom components you may use this keyword to discover methods you can call " - + "with `Call Method` keyword.\n\n" + + "with `Call Object Method` or `Call Object Method In Fx Application Thread` keyword.\n\n" + "Example:\n" - + "| List Component Methods | ${my node} |\n") + + "| List Node Methods | ${my node} |\n") @ArgumentNames({ "node" }) public String[] listNodeMethods(Node node) { try { @@ -150,7 +150,7 @@ private String getMethodDescription(Method m) { + "Optional argument ``root`` is the starting point from where to start listing child nodes, " + "see `3.2 Using locators as keyword arguments`. Defaults to root node of current window. \n\n" + "\nExample:\n" - + "| ${my node}= | Find | \\#node-id | \n" + + "| ${my node}= | Find | id=node-id | \n" + "| Print Child Nodes | ${my node} | \n") @ArgumentNames({ "root=" }) public void printChildNodes(Object root) { @@ -167,7 +167,7 @@ public void printChildNodes(Object root) { + "Optional argument ``root`` is the starting point from where to start listing child nodes, " + "see `3.2 Using locators as keyword arguments`. Defaults to root node of current window. \n\n" + "\nExample:\n" - + "| ${my node}= | Find | \\#node-id | \n" + + "| ${my node}= | Find | id=node-id | \n" + "| Log FXML | ${my node} | \n") @ArgumentNames({"root="}) public void logFXML(Object root) { @@ -204,6 +204,7 @@ public void setSafeClicking(String value) { + "| ${old_timeout}= | Set Timeout | 20 | \n" + "| Click On | id=myidthatshallcomeavailable | | \n" + "| [Teardown] | Set Timeout | ${old_timeout} | \n") + @ArgumentNames({ "timeout" }) public Integer setTimeout(int timeout) { RobotLog.info("Setting timeout to " + timeout + "s"); Integer oldTimeoutValue = getLibraryKeywordTimeout(); @@ -312,7 +313,7 @@ public String getNodeImageUrl(Object locator) { @RobotKeyword("Returns the parent node of node. \n\n" + "``locator`` is either a _query_ or _Object_ for a node whose getParent method will be called, see " + "`3. Locating JavaFX Nodes`. \n\n") - @ArgumentNames({ "node" }) + @ArgumentNames({ "locator" }) public Object getNodeParent(Object locator) { checkObjectArgumentNotNull(locator); Node node = objectToNode(locator); @@ -498,7 +499,7 @@ public List getTableColumnCells(Object locator, int column) { + "`3. Locating JavaFX Nodes`. \n\n" + "``row`` Integer value for the column" + "\nExample:\n" - + "| ${row cells}= | Get Table Row Cells | \\#table-id | ${2} | \n" + + "| ${row cells}= | Get Table Row Cells | id=table-id | ${2} | \n" + "| Dictionary Should Contain Key | ${row cells} | column name | \n" + "| ${cell text}= | Get Node Text | &{row cells}[column name] | # assuming that cell is a node that has a text value |\n") @ArgumentNames({ "table", "row" }) @@ -523,7 +524,7 @@ public List getTableRowValues(Object locator, int rowNumber) { + "`3. Locating JavaFX Nodes`. \n\n" + "``row`` Integer value for the column" + "\nExample:\n" - + "| ${row cells}= | Get Table Row Cells | \\#table-id | ${2} | \n" + + "| ${row cells}= | Get Table Row Cells | id=table-id | ${2} | \n" + "| Dictionary Should Contain Key | ${row cells} | column name | \n" + "| ${cell text}= | Get Node Text | &{row cells}[column name] | # assuming that cell is a node that has a text value |\n") @ArgumentNames({ "table", "row" }) @@ -694,7 +695,7 @@ public Object getSpinnerValue(Object locator) { + "``locator`` is either a _query_ or _Object:Node_ for identifying the TabPane element, see " + "`3. Locating JavaFX Nodes`. \n\n" + "\nExample:\n" - + "| ${tabs}= | Get Tab pane Tabs | \\#tab-pane-id | \n" + + "| ${tabs}= | Get Tab pane Tabs | id=tab-pane-id | \n" + "| Dictionary Should Contain Key | ${tabs} | tab name | \n") @ArgumentNames({ "locator" }) public Map getTabPaneTabs(Object locator) { @@ -720,7 +721,7 @@ public Map getTabPaneTabs(Object locator) { + "``locator`` is either a _query_ or _Object:Node_ for identifying the TabPane element, see " + "`3. Locating JavaFX Nodes`. \n\n" + "\nExample:\n" - + "| ${tab}= | Get Tab Pane Selected Tab | \\#pane-id | \n" + + "| ${tab}= | Get Tab Pane Selected Tab | id=pane-id | \n" + "| Dictionary Should contain Key | ${tab} | tab name | \n") @ArgumentNames({ "locator" }) public Map getSelectedTabPaneTab(Object locator) { @@ -742,7 +743,7 @@ public Map getSelectedTabPaneTab(Object locator) { + "``tabName`` is the name of the tab to be selected\n" + "\nExamples:\n" + "| Select Tab Pane Tab | ${Tab Pane} | tab name | \n" - + "| Select Tab Pane Tab | \\#tab-id | tab name | \n") + + "| Select Tab Pane Tab | id=tab-id | tab name | \n") @ArgumentNames({"locator", "tabName"}) public void selectTabPaneTab (Object locator, String tabName) { checkObjectArgumentNotNull(locator); @@ -837,7 +838,7 @@ public void clearTextInput(Object locator) { + "Optional parameter ``locator`` is an _Object:Window_ for specifying which contextMenu(window) items should be collected. " + "Default value is the last window returned by `Get Target Windows` -keyword. \n" + "\nExamples:\n" - + "| Click On | \\#menu-button-id | \n" + + "| Click On | id=menu-button-id | \n" + "| ${menu items}= | Get Context Menu Items | \n" + "| Dictionary Should Contain Key | ${menu items} | menu item name" + "| Click On | &{menu items}[menu item name] | \n\n") @@ -857,7 +858,7 @@ public Map getContextMenuItems(Window window){ + "``item`` is the name for the Context Menu item to be clicked. This keyword clicks the first menu item that matches the given " + "item name. Search of an item is started from the last target window.\n\n" + "Example:\n" - + "| Click On | \\#menu-button-id | \n" + + "| Click On | id=menu-button-id | \n" + "| Select Context Menu Item | menu item name |") @ArgumentNames({"item"}) public void selectContextMenuItem(String item){ diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java index bfbb4c5..9c6a643 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java @@ -25,15 +25,13 @@ public class Find { + "``root`` is an optional argument pointing to the element which is used as the origin of the lookup. If " + "root is defined only its children can be found. By default nodes are being looked from everywhere.\n\n" + "\nExample:\n" - + "| ${my node}= | Find | some text | | | # finds node containing text _some text_ |\n" - + "| ${my node}= | Find | .css | | | # finds node with matching style class |\n" - + "| ${my node}= | Find | \\#id | | | # finds node with matching _id_ |\n" + + "| ${my node}= | Find | text=\"some text\" | | | # finds node containing text _some text_ |\n" + "| ${my node}= | Find | css=VBox | | | # finds node matching the CSS selector |\n" + "| ${my node}= | Find | id=id | | | # finds node with matching _id_ |\n" + "| ${my node}= | Find | xpath=//Rectangle | | | # finds node matching the XPath |\n" + "| ${my node}= | Find | class=javafx.scene.shape.Rectangle | | | # finds node that is instance of the class |\n" + "| ${my node}= | Find | pseudo=hover | | | # finds node containing the given pseudo class state |\n" - + "| ${my node}= | Find | \\#id | True | | # this search fails if nothing is found |\n" + + "| ${my node}= | Find | id=id | True | | # this search fails if nothing is found |\n" + "| ${my node}= | Find | css=VBox | False | ${root} | # finds node matching the CSS selector from the children of given root |\n\n" + "Or chaining multiple queries together:\n" + "| ${my node}= | Find | css=VBox HBox xpath=//Rectangle[@width=\"600.0\"] | \n" diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index 57ddf3e..edfecd4 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -59,8 +59,10 @@ public class Verifiers extends TestFxAdapter { + "``timeout`` is the maximum waiting time value, defaults to 10 \n\n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Element Exists | \\#some-node-id | \n" - + "| Wait Until Element Exists | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Element Exists | id=some-node-id | \n" + + "| Wait Until Element Exists | id=some-node-id | 200 | MILLISECONDS | \n" + + "| ${node}= | Wait Until Element Exists | css=VBox | \n" + + "| Click On | ${node} | \n") @ArgumentNames({"locator", "timeout=10", "timeUnit=SECONDS"}) public Object waitUntilElementExists(String locator, int timeout, String timeUnit) { try { @@ -77,8 +79,8 @@ public Object waitUntilElementExists(String locator, int timeout, String timeUni + "``timeout`` is the maximum waiting time value, defaults to 10 \n\n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Element Does Not Exists | \\#some-node-id | \n" - + "| Wait Until Element Does Not Exists | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Element Does Not Exists | id=some-node-id | \n" + + "| Wait Until Element Does Not Exists | id=some-node-id | 200 | MILLISECONDS | \n") @ArgumentNames({"locator", "timeout=10", "timeUnit=SECONDS"}) public void waitUntilElementDoesNotExists(String locator, int timeout, String timeUnit) { try { @@ -95,8 +97,10 @@ public void waitUntilElementDoesNotExists(String locator, int timeout, String ti + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Node Is Visible | \\#some-node-id | \n" - + "| Wait Until Node Is Visible | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Node Is Visible | id=some-node-id | \n" + + "| Wait Until Node Is Visible | id=some-node-id | 200 | MILLISECONDS | \n" + + "| ${node}= | Wait Until Node Is Visible | css=VBox | \n" + + "| Click On | ${node} | \n") @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) public Object waitUntilNodeIsVisible(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); @@ -114,8 +118,8 @@ public Object waitUntilNodeIsVisible(Object locator, int timeout, String timeUni + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Node Is Not Visible | \\#some-node-id | \n" - + "| Wait Until Node Is Not Visible | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Node Is Not Visible | id=some-node-id | \n" + + "| Wait Until Node Is Not Visible | id=some-node-id | 200 | MILLISECONDS | \n") @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) public Object waitUntilNodeIsNotVisible(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); @@ -133,8 +137,10 @@ public Object waitUntilNodeIsNotVisible(Object locator, int timeout, String time + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Node Is Enabled | \\#some-node-id | \n" - + "| Wait Until Node Is Enabled | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Node Is Enabled | id=some-node-id | \n" + + "| Wait Until Node Is Enabled | id=some-node-id | 200 | MILLISECONDS | \n" + + "| ${node}= | Wait Until Node Is Enabled | css=VBox | \n" + + "| Click On | ${node} | \n") @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) public Object waitUntilNodeIsEnabled(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); @@ -152,8 +158,8 @@ public Object waitUntilNodeIsEnabled(Object locator, int timeout, String timeUni + "``timeout`` is the maximum waiting time in seconds, defaults to 5. \n" + "``timeUnit`` is the time unit to be used, defaults to SECONDS, see `5. Used ENUMs` for more options for _timeUnit_. \n\n" + "\nExample:\n" - + "| Wait Until Node Is Not Enabled | \\#some-node-id | \n" - + "| Wait Until Node Is Not Enabled | \\#some-node-id | 200 | MILLISECONDS | \n") + + "| Wait Until Node Is Not Enabled | id=some-node-id | \n" + + "| Wait Until Node Is Not Enabled | id=some-node-id | 200 | MILLISECONDS | \n") @ArgumentNames({"locator", "timeout=5", "timeUnit=SECONDS"}) public Object waitUntilNodeIsNotEnabled(Object locator, int timeout, String timeUnit) { checkObjectArgumentNotNull(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java index c010982..c0c09d8 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java @@ -45,8 +45,8 @@ public class ClickRobot extends TestFxAdapter { + "\nExample:\n" + "| Click On | ${node} | \n" + "| Click On | ${point} | \n" - + "| Click On | \\#node-id | \n" - + "| Click On | .css-name | Motion=VERTICAL_FIRST | \n") + + "| Click On | id=node-id | \n" + + "| Click On | css=.css-name | Motion=VERTICAL_FIRST | \n") @ArgumentNames({ "locator", "motion=DIRECT" }) public FxRobotInterface clickOn(Object locator, String motion) { checkObjectArgumentNotNull(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java index f11384e..b6c6342 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/DragRobot.java @@ -44,7 +44,7 @@ public class DragRobot extends TestFxAdapter { + "`3. Locating JavaFX Nodes`. \n\n" + "Optional parameter ``button`` is the mouse button to be used, defaults to PRIMARY. See `5. Used ENUMs` for different MouseButtons\n\n" + "\nExample:\n" - + "| ${node}= | Find | \\#some-node-id | \n" + + "| ${node}= | Find | id=some-node-id | \n" + "| Drag From | ${node} | SECONDARY | \n") @ArgumentNames({ "locator", "button=PRIMARY" }) public FxRobotInterface dragFrom(Object locator, String button) { @@ -76,7 +76,7 @@ public FxRobotInterface dragFrom(Object locator, String button) { + "``locator`` is either a _query_ or _Object:Bounds, Node, Point2D, PointQuery, Scene, Window_ for identifying the element, see " + "`3. Locating JavaFX Nodes`. \n\n" + "\nExample:\n" - + "| Drop To | \\#some-node-id | \n") + + "| Drop To | id=some-node-id | \n") @ArgumentNames({ "locator" }) public FxRobotInterface dropTo(Object locator) { checkObjectArgumentNotNull(locator); @@ -134,7 +134,7 @@ public FxRobotInterface drop() { + "Integer argument ``x`` is the amount how much to move the mouse horizontally\n" + "Integer argument ``y`` is the amount how much to move the mouse vertically.\n" + "\nExample:\n" - + "| Drag From | \\#node-id .css-name | \n" + + "| Drag From | id=node-id css=.css-name | \n" + "| Drop By | -300 | 0 | \n") @ArgumentNames({ "x", "y" }) public FxRobotInterface dropBy(int x, int y) { @@ -175,7 +175,7 @@ public FxRobotInterface dragFromCoordinates(int x, int y, String... buttons) { + "Integer argument ``x`` sets the target point for x -coordinate\n\n" + "Integer argument ``y`` sets the target point for y -coordinate\n\n" + "\nExample:\n" - + "| Drag From | \\#node-id | \n" + + "| Drag From | id=node-id | \n" + "| Drop To | 100 | 100 | \n") @ArgumentNames({ "x", "y" }) public FxRobotInterface dropToCoordinates(int x, int y) { diff --git a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java index 3a0a194..3d37c93 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/KeyboardRobot.java @@ -215,7 +215,7 @@ public void writeFast(String text) { + "`3. Locating JavaFX Nodes`. \n\n" + "``text`` is the text characters to write\n" + "\nExample: \n" - + "| Write To | .css-name | Robot Framework | \n") + + "| Write To | css=.css-name | Robot Framework | \n") @ArgumentNames({ "locator", "text" }) public void writeTo(Object locator, String text) { checkObjectArgumentNotNull(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java index 8d65a29..439e355 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java @@ -46,10 +46,10 @@ public class NodeLookup extends TestFxAdapter { + "| ${some scene}= | Get Nodes Scene | ${some node} | \n" + "| ${root} | Get Root Node Of | ${some scene} | \n" + "Node:\n" - + "| ${some node}= | find | \\#some-node-id | \n" + + "| ${some node}= | find | id=some-node-id | \n" + "| ${root} | Get Root Node Of | ${some node} | \n" + "Query:\n" - + "| ${root} | Get Root Node Of | \\#some-node-id | \n" ) + + "| ${root} | Get Root Node Of | id=some-node-id | \n" ) @ArgumentNames({"locator"}) public Object getRootNodeOf(Object locator) { checkObjectArgumentNotNull(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java index b5683e8..8449cda 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java @@ -93,7 +93,7 @@ public Object capturePrimaryScreen() { + "| ${capture}= | Capture Image | ${node} | \n" + "| ${capture}= | Capture Image | ${window} | \n" + "| ${capture}= | Capture Image | | \n" - + "| ${capture}= | Capture Image | \\#id | logImage=False |\n" ) + + "| ${capture}= | Capture Image | id=id | logImage=False |\n" ) @ArgumentNames({"locator=target window", "logImage=True"}) public Object captureImage(Object locator, boolean logImage){ checkObjectArgumentNotNull(locator); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java b/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java index 8fcadec..9675a15 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/WindowLookup.java @@ -70,7 +70,7 @@ public List listTargetWindows() { + "| ${window}= | Get Window | 0 | \n" + "| ${window}= | Get Window | ${2} | \n\n" + "Node:\n" - + "| ${some_node}= | Find | \\#some_id | \n" + + "| ${some_node}= | Find | id=some_id | \n" + "| ${window}= | Get Window | ${some_node} | \n\n" + "Scene: \n" + "| ${some_scene}= | Get Nodes Scene | ${some_node} | \n" diff --git a/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java b/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java index b5d6c67..4194b5f 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/WindowTargeting.java @@ -58,7 +58,7 @@ public Object getTargetWindow() { + "| Set Target Window | 0 | \n" + "| Set Target Window | ${2} | \n\n" + "Node:\n" - + "| ${some_node}= | Find | \\#some_id | \n" + + "| ${some_node}= | Find | id=some_id | \n" + "| Set Target Window | ${some_node} | \n\n" + "Scene: \n" + "| ${some_scene}= | Get Nodes Scene | ${some_node} | \n" diff --git a/src/main/java/libdoc-documentation.txt b/src/main/java/libdoc-documentation.txt index 99c3d9a..3525950 100644 --- a/src/main/java/libdoc-documentation.txt +++ b/src/main/java/libdoc-documentation.txt @@ -3,20 +3,13 @@ JavaFXLibrary can be run with both Jython and Python version of Robot Framework In short, this library is a wrapper for [https://github.com/TestFX/TestFX|TestFX], which is a Java library for testing JavaFX UI applications. - - == 1. Preparations before running the tests == - JavaFXLibrary needs to be compiled and packaged. [https://github.com/eficode/JavaFXLibrary/releases/latest|Download JAR release] or clone the [https://github.com/eficode/JavaFXLibrary.git|repository] and run _mvn package_ from the root folder. - The tested application and the JavaFXLibrary jars need to be added to CLASSPATH. +- Once the library jar -file is available, the library can be taken into use in two ways: *Local mode* with _Jython_ and *Remote mode* with both _Jython_ and _Python_ version of Robot Framework. +== 2.1 Usage in local mode(Jython only) == - -== 2. Using the library == -Once the library jar -file is available, the library can be taken into use in two ways: *Local mode* with _Jython_ and -*Remote mode* with both _Jython_ and _Python_ version of Robot Framework. - - -=== 2.1 Usage in local mode(Jython only) === First, the JavaFXLibrary needs to be taken into use in the settings table. | *Settings* | *Value* | | Library | JavaFXLibrary | @@ -25,7 +18,18 @@ Experimental headless mode can be activated at the import time by setting first | *Settings* | *Value* | | Library | JavaFXLibrary | ${True} | -=== 2.2 Usage in remote mode(Jython & Python) === +== 2.2 Launch application == + +In test data start application like this (setting of classpath is optional if Java Agent is used) + +| *Keyword* | *Argument* | +| Set To Classpath | your_application.jar | +| Launch Javafx Application | org.yourmodule.mainClass | + +== 2.3 Usage in remote mode(Jython & Python) == + +=== 2.3.1 Start remote library manually === + When using the test library in remote mode, the library needs to be started at the remote end first. This can be done as follows: - _java -jar javafxlibrary-.jar_ This will start the remote server listening at default port number 8270. @@ -47,30 +51,27 @@ Experimental headless mode can be activated in remote mode at the import time by | *Settings* | *Value* | | Library | Remote | http://localhost:8270 | ${True} | WITH NAME | JavaFXLibrary | -Experimental Java agent support +Launch application like in `2.2 Launch application`. + +=== 2.3.2 Start remote library as Java Agent === Library can be used as java agent. Launch application with `-javaagent:/path/to/javafxlibrary-.jar`. Default port is 8270 and can be changed with adding `=` to java agent command. Only remote library is supported. -Using `Launch JavaFX Application` is still required but instead of starting new application keyword initializes Stage for library. +Using `Launch JavaFX Application` is still required but instead of starting new application keyword initializes Stage +for library (see `2.2 Launch application`). == 3. Locating JavaFX Nodes == === 3.1 Locator syntax === -JavaFXLibrary uses TestFX lookup queries as the default way of locating JavaFX Nodes in the UI. These queries are very -similar to normal CSS-selectors used in JavaFX, but come with some modifications. Note that the '#'-character must be escaped as it begins a comment in Robot Framework. -| *Example Query* | *Description* | -| Submit | [https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Labeled.html|Labeled] nodes like Buttons and Labels can be located with plain text | -| VBox HBox Button | *Using class names as selectors does not work.* TestFX tries to find a node containing text "_VBox HBox Button_" instead | -| .vBox .hBox \#submitButton | Style classes and IDs can be used just like in CSS | - -Smaller applications with a clear, well-defined structure and more or less static content can easily be tested using -only the default locator queries. However, as the application grows in size and its UI is starting to have a lot of -dynamic content, things tend to become a bit more difficult. To tackle this, JavaFXLibrary offers additional query types -for locating objects: *id*, *css*, *class*, *text*, *xpath* and *pseudo*. Query type is defined by using a prefix. +JavaFXLibrary offers different query types for locating objects: *id*, *css*, *class*, *text*, *xpath* and *pseudo*. +Query type is defined by using a prefix. Note that in css type id's '#'-character must be escaped as it begins a +comment in Robot Framework. + | *Example Query* | *Description* | | id=submitButton | Returns a node with id submitButton. Basically same as default query "_\#submitButton_". | | css=VBox > .customStyle | Returns a node matching the CSS selector. | +| css=.customStyle \#button-id | Combined CSS selector with id. | | class=javafx.scene.shape.Rectangle | Returns a node that is an instance of the given class. | -| text="Submit" | Returns a node with text value _Submit_. The value must be inside quotation marks. Works only with Labeled nodes, and is basically the same as the default locator with plain text. | +| text="Submit" | Returns a node with text value _Submit_. The value must be inside quotation marks. Works only with [https://docs.oracle.com/javase/8/javafx/api/javafx/scene/control/Labeled.html|Labeled] nodes. | | text="Text with \"quotation\" marks" | Text value can contain spaces and quotation marks, but inner quotation marks must be escaped using _'\'_ backslash character. | | xpath=//Rectangle[@fill="0xff1493ff"] | Returns a Rectangle that has fill value _0xff1493ff_. See `3.3 About XPath queries` for more details about using xpath queries. | | pseudo=hover;focused | Returns a node that contains pseudo class states _hover_ and _focused_. See `3.4 About Pseudo queries` for more details about using pseudo queries. | @@ -80,9 +81,9 @@ is used as the root of the next query. Only queries with prefixes can be chained | *Example Query* | *Description* | | xpath=/VBox/HBox[4] css=Label | Finds 4th HBox child of VBox and returns the Label it contains. | | css=VBox HBox xpath=//Rectangle[@width="600.0"] | Finds the HBox using CSS query and proceeds to find a Rectangle that is 600px wide and is located in the HBox. | +| id=main-view css=.save-button | Finds css .button under id main-view. | | class=com.eficode.WrapperNode css=.styleClass text="toggle fullscreen" | Returns a node containing text _toggle fullscreen_ that has a parent which contains style class _styleClass_ and has a parent that is an instance of the WrapperNode class. | - === 3.2 Using locators as keyword arguments === Locators can be given as arguments for every JavaFXLibrary keyword that accepts a node as an argument. This is useful as it helps keeping the test case implementations cleaner and easier to read. However sometimes it is more convenient to have @@ -91,12 +92,12 @@ a reference to the node saved in a variable. `Find` and `Find All` -keywords can | | Click On | submit | # Clicks on node containing text _submit_ | | | Click On | xpath=//Button[@text="submit"] | # Clicks on button containing text _submit_ | | | | -| ${node}= | Find | submit | # Finds node containing text _submit_ and returns it | +| ${node}= | Find | text="submit" | # Finds node containing text _submit_ and returns it | | | Click On | ${node} | # Click on the node that was found earlier | If we want to click every Button of the application, we could use Find All and call the click on keyword in a for loop: | *Return value* | *Keyword* | *Argument* | | *Description* | -| ${buttons}= | Find All | .button | | # Get all button nodes to @{buttons} list variable | +| ${buttons}= | Find All | css=.button | | # Get all button nodes to @{buttons} list variable | | FOR | ${button} | IN | @{buttons} | | | Click On | ${button} | | # Click each button in for loop | | END | | | @@ -116,7 +117,6 @@ used to differentiate nodes. To see the generated FXML used for the lookup, use is large, it might be easier to first get a parent node closer to the actual point of interest and use it as a root for the Log FXML keyword. This way the output will be easier to read and the log.html wont be millions of lines long. - === 3.4 About Pseudo queries === All lookup queries return the first matching node, unless used with `Find All` keyword in which case all of the matches will be returned. Usually this is not a problem, but pseudo classes require a some additional thought and care. For example @@ -129,6 +129,9 @@ root for the lookup. Root can be given as an argument for both Find keywords, or before using pseudo-query. Multiple pseudo-classes can be given in a single query to further narrow the amount of matching nodes by using ';' separator, e.g. _pseudo=hover;focused_. +=== 3.5 Getting node parent === +Sometimes you can find row according to value under it but want to get the row node. Use `Get Node Parent` keyword to +get parent node of wanted node (can be repeated if higher in node tree). == 4. Argument types and return value types == JavaFXLibrary has built in support for [https://github.com/robotframework/jrobotremoteserver|jrobotremoteserver], which provides @@ -145,17 +148,16 @@ object methods available for Nodes. Let's take an example of a table that can contain complex objects, not just simple string values: | *Return value* | *Keyword* | *Argument* | *Argument* | *Description* | -| ${table cells}= | Get Table Row Cells | \#table-id | 2 | # table cell Nodes are stored in map and string representations are returned | +| ${table cells}= | Get Table Row Cells | id=table-id | 2 | # table cell Nodes are stored in map and string representations are returned | | | Node Should Be Enabled | ${table cells}[column 0] | | # Library takes the string value as an argument and converts it back to Node | | | Node Should Have Text | ${table cells}[column 1] | some text | | | | | Click On | ${table cells}[column 2] | | # in case this cell is clickable | -| ${cell buttons}= | Find All From Node | ${table cells}[column 3] | .button | # Finds all buttons from table cell Node | +| ${cell buttons}= | Find All | css=.button | root=${table cells}[column 3] | # Finds all buttons from table cell Node | | | Click On | ${cell buttons}[0] | | | Most of the JavaFXLibrary keywords can use locators directly e.g. `Click On` keyword can take just css selector as an argument, but in some cases it can be convenient to be able to pass in a 'Node' as an argument, especially when dealing with complex data structures. - == 5. Used ENUMs == | *Definition* | *Values* | | [https://github.com/TestFX/TestFX/blob/master/subprojects/testfx-core/src/main/java/org/testfx/robot/Motion.java|Motion] | DEFAULT, DIRECT, HORIZONTAL_FIRST, VERTICAL_FIRST | From d1e5b52c00f353a41d9feab087cf0e0a5a7ab356 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Mon, 2 Nov 2020 16:55:48 +0200 Subject: [PATCH 18/19] fix RunOnFailure to not store screenshot as mapObject to save memory --- .../ConvenienceKeywords.java | 3 -- .../AdditionalKeywords/RunOnFailure.java | 4 +- .../keywords/Keywords/NodeLookup.java | 2 +- .../keywords/Keywords/PointLocation.java | 4 +- .../keywords/Keywords/PointOffset.java | 2 +- .../keywords/Keywords/ScreenCapturing.java | 47 ++++++++++++++----- 6 files changed, 40 insertions(+), 22 deletions(-) diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index 5d32917..9b3e666 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -486,9 +486,7 @@ public List getTableColumnCells(Object locator, int column) { RobotLog.info("Index number: " + i); columnCells.add(mapObject(vf.getCell(i).getChildrenUnmodifiable().get(column))); } - return mapObjects(columnCells); - } catch (ClassCastException cce) { throw new JavaFXLibraryNonFatalException("Unable to handle argument as TableView!"); } @@ -887,7 +885,6 @@ public Object getProgressBarValue(Object locator) { RobotLog.info("Getting progress bar value from locator \"" + locator + "\"."); ProgressBar pb = (ProgressBar) objectToNode(locator); return mapObject(pb.getProgress()); - } catch (ClassCastException cce) { throw new JavaFXLibraryNonFatalException("Unable to handle given locator as ProgressBar!"); } diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java index 48f9fde..10c3a67 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java @@ -62,9 +62,7 @@ public void runOnFailure() { if (robot == null) { RobotLog.error("FxRobot not initialized, launch test application with the library"); } else { - GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - RobotLog.debug("Capturing screenshot from primary screen with resolution "+gd.getDisplayMode().getWidth()+"x"+gd.getDisplayMode().getHeight()+"."); - new ScreenCapturing().captureImage(new Rectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight()),true); + new ScreenCapturing().capturePrimaryScreen(true, false); } } catch (Exception e) { RobotLog.error("Error when capturing screenshot. Actual error: "+e.getMessage()); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java index 439e355..c0a8319 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/NodeLookup.java @@ -60,7 +60,7 @@ public Object getRootNodeOf(Object locator) { return getRootNodeOf(node); } Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "rootNode", locator.getClass()); - return HelperFunctions.mapObject(method.invoke(robot, locator)); + return mapObject(method.invoke(robot, locator)); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute get root node of using locator \"" + locator + "\": " + e.getCause().getMessage()); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java b/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java index 34b9f9b..9661de7 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java @@ -52,7 +52,7 @@ public Object pointTo(Object locator) { if (locator instanceof String) locator = objectToNode(locator); Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "point", locator.getClass()); - return HelperFunctions.mapObject(method.invoke(robot, locator)); + return mapObject(method.invoke(robot, locator)); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute point to using locator \"" + locator + "\": " + e.getCause().getMessage()); @@ -67,7 +67,7 @@ public Object pointTo(Object locator) { public Object pointToCoordinates(int x, int y) { try { RobotLog.info("Returning a pointquery to coordinates: [" + x + ", " + y + "]"); - return HelperFunctions.mapObject(robot.point(x, y)); + return mapObject(robot.point(x, y)); } catch (Exception e) { if(e instanceof JavaFXLibraryNonFatalException) throw e; diff --git a/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java b/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java index 291846f..b89d16a 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/PointOffset.java @@ -50,7 +50,7 @@ public Object pointToWithOffset(Object locator, double offsetX, double offsetY) locator = objectToNode(locator); Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "offset", locator.getClass(), double.class, double.class); - return HelperFunctions.mapObject(method.invoke(robot, locator, offsetX, offsetY)); + return mapObject(method.invoke(robot, locator, offsetX, offsetY)); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute 'point to with offset' using locator \"" + locator + "\": " + e.getCause().getMessage()); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java index 8449cda..e89dd15 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java @@ -67,12 +67,21 @@ else if (value.toLowerCase().equals("diskonly")) "\"EMBEDDED\" or \"DISKONLY\""); } - @RobotKeyword("Returns a screenshot from whole primary screen. Note that this shows also other applications that are open.") - public Object capturePrimaryScreen() { + @RobotKeyword("Returns a screenshot from whole primary screen. Note that this shows also other applications that are open.\n\n" + + "``logImage`` is a boolean value that specifies whether a captured image is also printed to test execution log. \n\n " + + "``mapObject`` is a boolean value that specifies whether a captured image is saved as mapobject and returned from keyword. " + + "This uses Java heap memory which can result problems if large amount of image capture is done. If set False keyword returns null and image " + + "is not stored in library bookkeeping. \n\n " + + "\nExample:\n" + + "| ${capture}= | Capture Primary Screen | \n" + + "| ${capture}= | Capture Primary Screen | logImage=False |\n" + + "| | Capture Primary Screen | logImage=true | mapObject=false |\n") + @ArgumentNames({"logImage=True", "mapObject=True"}) + public Object capturePrimaryScreen(boolean logImage, boolean mapObject) { try { GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); Rectangle2D target = asyncFx(() -> new Rectangle2D(0, 0, gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight())).get(); - return this.captureImage(target,true); + return this.captureImage(target,logImage, mapObject); } catch (InterruptedException | ExecutionException iee) { throw new JavaFXLibraryNonFatalException("Unable to get Rectangle2D: " + iee.getCause()); } catch (Exception e) { @@ -86,16 +95,20 @@ public Object capturePrimaryScreen() { + "Note that active window might only be part of the visible window, it e.g. dialog is active.\n\n" + "``locator`` is either a _query_ or _Object:Bounds, Node, Point2D, Rectangle, PointQuery, Scene, Window_ for identifying the element, see " + "`3. Locating JavaFX Nodes`. \n\n" - + "Argument ``logImage`` is a boolean value that specifies whether a captured image is also printed to test execution log. \n\n " + + "``logImage`` is a boolean value that specifies whether a captured image is also printed to test execution log. \n\n " + + "``mapObject`` is a boolean value that specifies whether a captured image is saved as mapobject and returned from keyword. " + + "This uses Java heap memory which can result problems if large amount of image capture is done. If set False keyword returns null and image " + + "is not stored in library bookkeeping. \n\n " + "\nExample:\n" + "| ${region}= | Create Rectangle | 11 | 22 | 33 | 44 | \n" + "| ${capture}= | Capture Image | ${region} | \n" + "| ${capture}= | Capture Image | ${node} | \n" + "| ${capture}= | Capture Image | ${window} | \n" + "| ${capture}= | Capture Image | | \n" - + "| ${capture}= | Capture Image | id=id | logImage=False |\n" ) - @ArgumentNames({"locator=target window", "logImage=True"}) - public Object captureImage(Object locator, boolean logImage){ + + "| ${capture}= | Capture Image | id=id | logImage=False |\n" + + "| | Capture Image | id=id | logImage=true | mapObject=false |\n" ) + @ArgumentNames({"locator=target window", "logImage=True", "mapObject=True"}) + public Object captureImage(Object locator, boolean logImage, boolean mapObject){ checkObjectArgumentNotNull(locator); try { RobotLog.info("Capturing screenshot from locator: \"" + locator + "\""); @@ -140,7 +153,11 @@ public Object captureImage(Object locator, boolean logImage){ + ""); } } - return mapObject(image); + if(mapObject) { + return mapObject(image); + } else { + return null; + } } catch (InterruptedException | ExecutionException iee) { throw new JavaFXLibraryNonFatalException("Unable to take capture (asyncFx thread failed): ", iee.getCause()); } catch (IOException ioe) { @@ -154,13 +171,19 @@ public Object captureImage(Object locator, boolean logImage){ @RobotKeyword("Returns a screenshot of the scene containing given locator.\n\n" + "``locator`` is a query locator, see `3.1 Locator syntax`.\n\n " + + "``logImage`` is a boolean value that specifies whether a captured image is also printed to test execution log. \n\n " + + "``mapObject`` is a boolean value that specifies whether a captured image is saved as mapobject and returned from keyword. " + + "This uses Java heap memory which can result problems if large amount of image capture is done. If set False keyword returns null and image " + + "is not stored in library bookkeeping. \n\n " + "\nExample:\n" - + "| ${capture}= | Capture Scene Containing Node | ${node} | \n" ) - @ArgumentNames({"locator", "logImage=True"}) - public Object captureSceneContainingNode(Object locator) { + + "| ${capture}= | Capture Scene Containing Node | ${node} | \n" + + "| ${capture}= | Capture Scene Containing Node | id=id | logImage=False |\n" + + "| | Capture Scene Containing Node | id=id | logImage=true | mapObject=false |\n" ) + @ArgumentNames({"locator", "logImage=True", "mapObject=True"}) + public Object captureSceneContainingNode(Object locator, boolean logImage, boolean mapObject) { try { Scene scene = asyncFx(() -> (Scene) useMappedObject(new ConvenienceKeywords().getScene(mapObject(locator)))).get(); - return this.captureImage(scene, true); + return this.captureImage(scene, logImage, mapObject); } catch (InterruptedException | ExecutionException iee) { throw new JavaFXLibraryNonFatalException("Unable to get scene: " + iee.getCause()); } catch (Exception e) { From f4047486e748e06c8ba342028bcd546acbe15d8d Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Mon, 2 Nov 2020 17:20:04 +0200 Subject: [PATCH 19/19] remove comments --- .../acceptance/SwingApplicationWrapperTest.robot | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/robotframework/acceptance/SwingApplicationWrapperTest.robot b/src/test/robotframework/acceptance/SwingApplicationWrapperTest.robot index 5c2ad99..456545f 100644 --- a/src/test/robotframework/acceptance/SwingApplicationWrapperTest.robot +++ b/src/test/robotframework/acceptance/SwingApplicationWrapperTest.robot @@ -24,10 +24,7 @@ Swing Embedded JavaFX Type Test Run Keyword If ${headless} Set Tags monocle-issue Launch Swing Application javafxlibrary.testapps.SwingApplication Wait Until Keyword Succeeds 15 sec 250ms Find id=textField ${True} - #Click On id=textField Write To id=textField JavaFXLibrary - #Click On id=textField - #Write JavaFXLibrary Wait Until Keyword Succeeds 3 sec 250ms Text Value Should Be JavaFXLibrary Launch Swing Application Using External Wrapper Class