From abe28b5ef6a62aee93e4fa765746f0b3f9a6f5a2 Mon Sep 17 00:00:00 2001 From: Turo Soisenniemi Date: Wed, 2 Sep 2020 09:30:45 +0300 Subject: [PATCH 01/21] Experimental java agent support JavaFXLibrary can now be attached to process as java agent. --- README.md | 7 +- pom.xml | 3 + src/main/java/JavaFXLibrary.java | 58 +++++++++----- .../ApplicationLauncher.java | 78 ++++++++++--------- .../java/javafxlibrary/utils/Session.java | 65 +++++++++++++++- .../javafxlibrary/utils/TestFxAdapter.java | 61 ++++++++++----- .../robotframework/acceptance/MiscTests.robot | 6 ++ 7 files changed, 200 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index aea0021..6b26cec 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ Executing _test.sh_ runs the acceptance suite twice: first using JavaFXLibrary a If you want the suite to run only once, you can define which type of library to use by including **local** or **remote** as an argument. For example command `test.sh remote` will execute the suite only in remote mode. ## Experimental: Headless support -Library supports headless operation utilizing [Monocle](https://wiki.openjdk.java.net/display/OpenJFX/Monocle). The support for this is still at experimental level. +Library supports headless operation utilizing [Monocle](https://wiki.openjdk.java.net/display/OpenJFX/Monocle). The support for this is still at experimental level. ### Main issues with headless function * Scrolling doesn't work same way as with screen @@ -97,4 +97,7 @@ Remote: ``` *** Settings *** Library Remote http://127.0.0.1:8270 ${True} WITH NAME JavaFXLibrary -``` \ No newline at end of file +``` + +## Experimental: Java agent support +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 keyword is still required but instead of starting new application keyword initializes Stage for library. diff --git a/pom.xml b/pom.xml index 1aeb927..cc67a5d 100644 --- a/pom.xml +++ b/pom.xml @@ -221,6 +221,9 @@ true JavaFXLibrary + + JavaFXLibrary + diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index 63523a9..8e9dd06 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -56,12 +56,12 @@ public class JavaFXLibrary extends AnnotationLibrary { add("javafxlibrary/keywords/Keywords/*.class"); add("javafxlibrary/keywords/*.class"); add("javafxlibrary/tests/*.class"); - }}; + }}; public JavaFXLibrary() { - this(false); + this(false); } - + public JavaFXLibrary(boolean headless) { super(includePatterns); if (headless) { @@ -71,9 +71,9 @@ public JavaFXLibrary(boolean headless) { System.setProperty("prism.text", "t2k"); TestFxAdapter.isHeadless = true; } else { - //v4.0.15-alpha sets default robot as glass, which breaks rolling - //Forcing usage of awt robot as previous versions - System.setProperty("testfx.robot", "awt"); + // v4.0.15-alpha sets default robot as glass, which breaks rolling + // Forcing usage of awt robot as previous versions + System.setProperty("testfx.robot", "awt"); } } @@ -108,7 +108,6 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { finalKwargs = kwargs; } - AtomicReference retval = new AtomicReference<>(); AtomicReference retExcep = new AtomicReference<>(); @@ -121,12 +120,12 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { retval.set(super.runKeyword(keywordName, finalArgs, finalKwargs)); return true; - } catch (JavaFXLibraryTimeoutException jfxte){ + } catch (JavaFXLibraryTimeoutException jfxte) { // timeout already expired, catch exception and jump out retExcep.set(jfxte); throw jfxte; - } catch (RuntimeException e){ + } catch (RuntimeException e) { // catch exception and continue trying retExcep.set(e); return false; @@ -165,10 +164,11 @@ public Object runKeyword(String keywordName, List args) { 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"))) + if (!(keywordName.equals("callObjectMethod") || keywordName.equals("callObjectMethodInFxApplicationThread"))) { finalArgs = HelperFunctions.useMappedObjects(args); - else + } else { finalArgs = args; + } AtomicReference retval = new AtomicReference<>(); AtomicReference retExcep = new AtomicReference<>(); @@ -182,12 +182,12 @@ public Object runKeyword(String keywordName, List args) { retval.set(super.runKeyword(keywordName, finalArgs)); return true; - } catch (JavaFXLibraryTimeoutException jfxte){ + } catch (JavaFXLibraryTimeoutException jfxte) { // timeout already expired, catch exception and jump out retExcep.set(jfxte); throw jfxte; - } catch (RuntimeException e){ + } catch (RuntimeException e) { // catch exception and continue trying retExcep.set(e); return false; @@ -227,17 +227,16 @@ public String getKeywordDocumentation(String keywordName) { return "IOException occurred while reading the documentation file!"; } } else if (keywordName.equals("__init__")) { - try { + try { return FileUtils.readFileToString(new File("./src/main/java/libdoc-init-documentation.txt"), "utf-8"); } catch (IOException e) { e.printStackTrace(); return "IOException occurred while reading the init documentation file!"; } } else { - try { + try { return super.getKeywordDocumentation(keywordName); - } - catch (Exception e) { + } catch (Exception e) { return keywordName; } } @@ -258,6 +257,27 @@ public static JavaFXLibrary getLibraryInstance() throws ScriptException { } public static void main(String[] args) throws Exception { + startServer(args); + } + + public static void premain(String args) { + TestFxAdapter.isAgent = true; + Thread agentThread = new Thread(() -> { + try { + if (args == null) { + startServer(); + } else { + startServer(args); + } + } catch (Exception e) { + e.printStackTrace(); + } + }); + agentThread.setDaemon(true); + agentThread.start(); + } + + public static void startServer(String... args) throws Exception { int port = 8270; InetAddress ipAddr = InetAddress.getLocalHost(); @@ -266,8 +286,7 @@ public static void main(String[] args) throws Exception { System.out.println("----------------------------= JavaFXLibrary =-----------------------------"); if (args.length > 0) { port = Integer.parseInt(args[0]); - } - else { + } else { System.out.println("RemoteServer for JavaFXLibrary will be started at default port of: " + port + ".\n" + "If you wish to use another port, restart the library and give port number\n" + "as an argument."); @@ -294,4 +313,3 @@ public static void main(String[] args) throws Exception { } } } - diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java index 6c6a117..eae81ec 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java @@ -45,8 +45,8 @@ public class ApplicationLauncher extends TestFxAdapter { + "Example:\n" + "| Launch JavaFX Application | _javafxlibrary.testapps.MenuApp_ |\n" + "| Launch JavaFX Application | _TestApplication.jar_ |\n") - @ArgumentNames({"appName", "*args"}) - public void launchJavafxApplication(String appName, String... appArgs) { + @ArgumentNames({ "appName", "*args" }) + public void launchJavafxApplication(String appName, String... appArgs) { try { RobotLog.info("Starting application:" + appName); createNewSession(appName, appArgs); @@ -65,7 +65,7 @@ public void launchJavafxApplication(String appName, String... appArgs) { + "Example:\n" + "| Launch Swing Application | _javafxlibrary.testapps.SwingApplication |\n" + "| Launch Swing Application | _TestApplication.jar_ |\n") - @ArgumentNames({"appName", "*args"}) + @ArgumentNames({ "appName", "*args" }) public void launchSwingApplication(String appName, String... appArgs) { RobotLog.info("Starting application:" + appName); Class c = getMainClass(appName); @@ -84,7 +84,7 @@ public void launchSwingApplication(String appName, String... appArgs) { + "Example:\n" + "| Launch Swing Application In Separate Thread | _javafxlibrary.testapps.SwingApplication |\n" + "| Launch Swing Application In Separate Thread | _TestApplication.jar_ |\n") - @ArgumentNames({"appName", "*args"}) + @ArgumentNames({ "appName", "*args" }) public void launchSwingApplicationInSeparateThread(String appName, String... appArgs) { RobotLog.info("Starting application:" + appName); Class c = getMainClass(appName); @@ -95,10 +95,11 @@ public void launchSwingApplicationInSeparateThread(String appName, String... app private Class getMainClass(String appName) { try { - if (appName.endsWith(".jar")) + if (appName.endsWith(".jar")) { return getMainClassFromJarFile(appName); - else + } else { return Class.forName(appName); + } } catch (ClassNotFoundException e) { throw new JavaFXLibraryNonFatalException("Unable to launch application: " + appName, e); } @@ -112,41 +113,43 @@ private void _addPathToClassPath(String path) { try { Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); method.setAccessible(true); - method.invoke(classLoader, (new File(path)).toURI().toURL() ); + method.invoke(classLoader, (new File(path)).toURI().toURL()); } catch (Exception e) { - throw new JavaFXLibraryFatalException("Problem setting the classpath: " + path , e); + throw new JavaFXLibraryFatalException("Problem setting the classpath: " + path, e); } } @RobotKeyword("Loads given path to classpath.\n\n" - + "``path`` is the path to add.\n\n" - + "If directory path has asterisk(*) after directory separator all jar files are added from directory.\n" - + "\nExample:\n" - + "| Set To Classpath | C:${/}users${/}my${/}test${/}folder | \n" - + "| Set To Classpath | C:${/}users${/}my${/}test${/}folder${/}* | \n") - @ArgumentNames({"path"}) - public void setToClasspath(String path) { + + "``path`` is the path to add.\n\n" + + "If directory path has asterisk(*) after directory separator all jar files are added from directory.\n" + + "\nExample:\n" + + "| Set To Classpath | C:${/}users${/}my${/}test${/}folder | \n" + + "| Set To Classpath | C:${/}users${/}my${/}test${/}folder${/}* | \n") + @ArgumentNames({ "path" }) + public void setToClasspath(String path) { if (path.endsWith("*")) { - path = path.substring(0, path.length() - 1); - RobotLog.info("Adding all jars from directory: " + path); + path = path.substring(0, path.length() - 1); + RobotLog.info("Adding all jars from directory: " + path); - try { - File directory = new File(path); - File[] fileList = directory.listFiles(); - boolean jarsFound = false; - for (File file : Objects.requireNonNull(fileList)) { - if (file.getName().endsWith(".jar")) { - jarsFound = true; - _addPathToClassPath(file.getAbsolutePath()); - } - } - if(!jarsFound) throw new JavaFXLibraryNonFatalException("No jar files found from classpath: " + FileSystems.getDefault().getPath(path).normalize().toAbsolutePath().toString()); - } catch (NullPointerException e) { - throw new JavaFXLibraryFatalException("Directory not found: " + path + "\n" + e.getMessage(), e); - } - } - else { + try { + File directory = new File(path); + File[] fileList = directory.listFiles(); + boolean jarsFound = false; + for (File file : Objects.requireNonNull(fileList)) { + if (file.getName().endsWith(".jar")) { + jarsFound = true; + _addPathToClassPath(file.getAbsolutePath()); + } + } + if (!jarsFound) { + throw new JavaFXLibraryNonFatalException("No jar files found from classpath: " + + FileSystems.getDefault().getPath(path).normalize().toAbsolutePath().toString()); + } + } catch (NullPointerException e) { + throw new JavaFXLibraryFatalException("Directory not found: " + path + "\n" + e.getMessage(), e); + } + } else { _addPathToClassPath(path); } } @@ -157,8 +160,9 @@ public void logApplicationClasspath() { ClassLoader cl = ClassLoader.getSystemClassLoader(); URL[] urls = ((URLClassLoader) cl).getURLs(); RobotLog.info("Printing out classpaths: \n"); - for (URL url : urls) + for (URL url : urls) { RobotLog.info(url.getFile()); + } } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Unable to log application classpaths", e); } @@ -237,7 +241,6 @@ public void clearObjectMap() { objectMap.clear(); } - @RobotKeyword("Returns the class name of currently active JavaFX Application") public String getCurrentApplication() { try { @@ -258,4 +261,9 @@ public String currentApplication() { } } + @RobotKeyword("Returns if JavaFXLibrary is started as java agent.") + public boolean isJavaAgent() { + return TestFxAdapter.isAgent; + } + } diff --git a/src/main/java/javafxlibrary/utils/Session.java b/src/main/java/javafxlibrary/utils/Session.java index de9e0d0..e415c82 100644 --- a/src/main/java/javafxlibrary/utils/Session.java +++ b/src/main/java/javafxlibrary/utils/Session.java @@ -18,9 +18,11 @@ package javafxlibrary.utils; import javafx.application.Application; +import javafx.collections.ObservableList; import javafx.scene.input.KeyCode; import javafx.scene.input.MouseButton; import javafx.stage.Stage; +import javafx.stage.Window; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import org.testfx.api.FxRobot; import org.testfx.api.FxToolkit; @@ -28,6 +30,11 @@ import javax.swing.*; import java.awt.*; import java.awt.event.WindowEvent; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Iterator; +import java.util.Optional; import java.util.concurrent.TimeoutException; public class Session { @@ -42,7 +49,7 @@ public Session(String appName, String... appArgs) { try { // start the client this.primaryStage = FxToolkit.registerPrimaryStage(); - this.sessionApplication = FxToolkit.setupApplication((Class)Class.forName(appName), appArgs); + this.sessionApplication = FxToolkit.setupApplication((Class) Class.forName(appName), appArgs); this.sessionRobot = new FxRobot(); this.applicationName = appName; this.screenshotDirectory = System.getProperty("user.dir") + "/report-images/"; @@ -80,6 +87,24 @@ public Session(Application application) { } + /** + * Used when JavaFXLibrary is attached with java agent + */ + public Session(String applicationName) { + try { + Optional existingStage = getExistingPrimaryStage(); + if (!existingStage.isPresent()) { + throw new JavaFXLibraryNonFatalException("Could not hook to existing application: stage not found"); + } + this.primaryStage = FxToolkit.registerStage(existingStage::get); + this.sessionRobot = new FxRobot(); + this.applicationName = applicationName; + this.screenshotDirectory = System.getProperty("user.dir") + "/report-images/"; + } catch (Exception e) { + throw new JavaFXLibraryNonFatalException("Problem launching the application: " + e.getMessage(), e); + } + } + public void closeApplication() { try { FxToolkit.hideStage(); @@ -106,4 +131,42 @@ public void closeSwingApplication() { closeApplication(); } + + /** + * When running JavaFXLibrary as java agent this method tries to find first showing stage. + */ + private Optional getExistingPrimaryStage() { + + try { + ObservableList windows; + // getWindows method is added in Java 9 + windows = (ObservableList) Window.class.getMethod("getWindows") + .invoke(null); + return windows.stream() + .filter(Stage.class::isInstance) + .map(Stage.class::cast) + .filter(Stage::isShowing) + .findFirst(); + } catch (InvocationTargetException | IllegalArgumentException | IllegalAccessException | NoSuchMethodException e) { + // java 8 implementation + try { + Iterator it = (Iterator) Window.class.getMethod("impl_getWindows") + .invoke(null); + List windows = new ArrayList<>(); + while (it.hasNext()) { + windows.add(it.next()); + } + return windows.stream() + .filter(Stage.class::isInstance) + .map(Stage.class::cast) + .filter(Stage::isShowing) + .findFirst(); + } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | SecurityException ex) { + e.printStackTrace(); + } + + } + return Optional.empty(); + } } diff --git a/src/main/java/javafxlibrary/utils/TestFxAdapter.java b/src/main/java/javafxlibrary/utils/TestFxAdapter.java index 9220d86..59633ee 100644 --- a/src/main/java/javafxlibrary/utils/TestFxAdapter.java +++ b/src/main/java/javafxlibrary/utils/TestFxAdapter.java @@ -28,13 +28,18 @@ public class TestFxAdapter { - public static boolean isHeadless = false; + public static boolean isHeadless = false; + public static boolean isAgent = false; // current robot instance in use protected static FxRobotInterface robot; + public static void setRobot(FxRobotInterface robot) { TestFxAdapter.robot = robot; } - public static FxRobotInterface getRobot() { return robot; } + + public static FxRobotInterface getRobot() { + return robot; + } // TODO: consider adding support for multiple sessions private static Session activeSession = null; @@ -45,23 +50,36 @@ public static void setRobot(FxRobotInterface robot) { public static HashMap objectMap = new HashMap(); public void createNewSession(String appName, String... appArgs) { - - /* Applications using FXML-files for setting controllers must have - FXMLLoader.setDefaultClassLoader(getClass().getClassLoader()); - in their start method for the controller class to load properly */ - if (appName.endsWith(".jar")) { - Class mainClass = getMainClassFromJarFile(appName); - activeSession = new Session(mainClass, appArgs); + if (isAgent) { + createNewSession(appName.endsWith(".jar") ? getMainClassFromJarFile(appName).toString() : appName); } else { - activeSession = new Session(appName, appArgs); + /* + * Applications using FXML-files for setting controllers must have + * FXMLLoader.setDefaultClassLoader(getClass().getClassLoader()); in their start method for the controller + * class to load properly + */ + if (appName.endsWith(".jar")) { + Class mainClass = getMainClassFromJarFile(appName); + activeSession = new Session(mainClass, appArgs); + } else { + activeSession = new Session(appName, appArgs); + } + + setRobot(activeSession.sessionRobot); } - - setRobot(activeSession.sessionRobot); - } public void createNewSession(Application application) { - activeSession = new Session(application); + if (isAgent) { + createNewSession("JavaFXLibrary SwingWrapper"); + } else { + activeSession = new Session(application); + setRobot(activeSession.sessionRobot); + } + } + + private void createNewSession(String applicationName) { + activeSession = new Session(applicationName); setRobot(activeSession.sessionRobot); } @@ -74,26 +92,29 @@ public void deleteSwingSession() { } public String getCurrentSessionApplicationName() { - if (activeSession != null) + if (activeSession != null) { return activeSession.applicationName; + } return null; } public String getCurrentSessionScreenshotDirectory() { - if (activeSession != null) + if (activeSession != null) { return activeSession.screenshotDirectory; - else + } else { throw new JavaFXLibraryNonFatalException("Unable to get screenshot directory, no application is currently open!"); + } } - public void setCurrentSessionScreenshotDirectory(String dir){ + public void setCurrentSessionScreenshotDirectory(String dir) { if (activeSession != null) { File errDir = new File(dir); - if (!errDir.exists()) - if(!errDir.mkdirs()) { + if (!errDir.exists()) { + if (!errDir.mkdirs()) { RobotLog.warn("Screenshot directory \"" + dir + "\" creation failed!"); } + } activeSession.screenshotDirectory = dir; } else { throw new JavaFXLibraryNonFatalException("Unable to set screenshot directory, no application is currently open!"); diff --git a/src/test/robotframework/acceptance/MiscTests.robot b/src/test/robotframework/acceptance/MiscTests.robot index 3913494..c47866b 100644 --- a/src/test/robotframework/acceptance/MiscTests.robot +++ b/src/test/robotframework/acceptance/MiscTests.robot @@ -369,6 +369,12 @@ Get Scene (Window) ${result} Get Root Node Of ${scene} Should Be Equal ${target} ${result} +Is not Java agent + [Tags] smoke + Set Test Application javafxlibrary.testapps.TestBoundsLocation + ${IS_JAVA_AGENT} = Is Java Agent + Should Be Equal ${False} ${IS_JAVA_AGENT} + *** Keywords *** Setup All Tests Import JavaFXLibrary From a5972b677d50b0241c014628b541580aef68d34b Mon Sep 17 00:00:00 2001 From: Pasi Saikkonen Date: Wed, 2 Sep 2020 15:16:34 +0300 Subject: [PATCH 02/21] Fix docker-compose up Building the docker demo failed because of missing package versions for the newer Ubuntu image. Base image of the demo is now fixed to bionic tag since Ubuntu updates have broken the build earlier as well. Related Launchpad ticket: https://bugs.launchpad.net/ubuntu/+source/openjfx/+bug/1799946 --- Dockerfile_build | 4 ++-- docker/robot-javafx-demo/Dockerfile | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile_build b/Dockerfile_build index 6086fa9..834ae4e 100644 --- a/Dockerfile_build +++ b/Dockerfile_build @@ -14,8 +14,8 @@ RUN mvn package # ENTRYPOINT java -jar /code/target/javafxlibrary-*-SNAPSHOT-jar-with-dependencies.jar FROM ubuntu:16.04 -COPY --from=builder /code/target/javafxlibrary-*-jar-with-dependencies.jar /. -COPY --from=builder /code/target/javafxlibrary-*-tests.jar /. +COPY --from=builder /code/target/javafxlibrary-*-jar-with-dependencies.jar / +COPY --from=builder /code/target/javafxlibrary-*-tests.jar / RUN echo "Built following jar files" && ls -latr /*.jar COPY entrypoint_build.sh /. RUN apt-get -qq update && apt-get dist-upgrade -y && apt-get install -qq --no-install-recommends --allow-unauthenticated -y \ diff --git a/docker/robot-javafx-demo/Dockerfile b/docker/robot-javafx-demo/Dockerfile index 81e3eea..0e75a4a 100644 --- a/docker/robot-javafx-demo/Dockerfile +++ b/docker/robot-javafx-demo/Dockerfile @@ -1,4 +1,4 @@ -FROM dorowu/ubuntu-desktop-lxde-vnc +FROM dorowu/ubuntu-desktop-lxde-vnc:bionic ENV DEBIAN_FRONTEND noninteractive From 178164934802ffb3f5c8d35e4371c4ec9d0249b7 Mon Sep 17 00:00:00 2001 From: Turo Soisenniemi Date: Wed, 2 Sep 2020 15:56:14 +0300 Subject: [PATCH 03/21] Updated documentation (java agent) --- AUTHORS.txt | 2 +- src/main/java/libdoc-documentation.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 582711d..9a5e4bc 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -8,4 +8,4 @@ Tatu Lahtela Find All With Pseudo Class and Find Class keywords Sakari Hoisko Dockerized linux env with X Juho Lehtonen Optimized docker environment. Juho Saarinen Package improvements, initial monocle support, screenshot bug fix - +Turo Soisenniemi Initial java agent support diff --git a/src/main/java/libdoc-documentation.txt b/src/main/java/libdoc-documentation.txt index eb31ed2..e5b91ba 100644 --- a/src/main/java/libdoc-documentation.txt +++ b/src/main/java/libdoc-documentation.txt @@ -47,6 +47,12 @@ 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 + +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 keyword is still required but instead of starting new application keyword initializes Stage for library. + == 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 From 147bdb482e93f73a754314425bd0ed8c905add3a Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Tue, 15 Sep 2020 09:32:50 +0300 Subject: [PATCH 04/21] Add possibility to configure different directory for log.html in Set Screenshotdir, fixes #17 --- .../ConvenienceKeywords.java | 23 +++++++++++++++---- .../keywords/Keywords/ScreenCapturing.java | 11 +++++++-- .../java/javafxlibrary/utils/Session.java | 1 + .../javafxlibrary/utils/TestFxAdapter.java | 13 +++++++++-- 4 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index 5d40723..070bc06 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -776,11 +776,24 @@ public int getTableColumnCount(Object locator){ } @RobotKeyword("Sets the screenshot directory for current application\n\n" - + "``directory`` is a path to a folder which is to be set as current screenshot directory") - @ArgumentNames({ "directory" }) - public void setScreenshotDirectory(String dir){ - RobotLog.info("Setting new screenshot directory: " + dir); - setCurrentSessionScreenshotDirectory(dir); + + "Notice that relative paths are from current work dir of JavaFXLibrary:\n" + + "- In case of Java Agent it comes from Application Under Test (AUT).\n" + + "- In case of JavaFXLibrary is started with \"java -jar *\" command it uses the current working directory as source.\n" + + "``directory`` is a path to a folder which is to be set as current screenshot directory in host where " + + "JavaFXLibrary is run.\n\n" + + "``logDirectory`` is a path that is put to log.html files that can be used after screenshots are moved " + + "from target system to e.g. CI workspace. Typically this is relative path.\n\n\n" + + "Example:\n" + + "| Set Screenshot Directory | /Users/robotuser/output/AUT-screenshots/ | ./output/AUT-screenshots/ | \n" + + "or\n" + + "| Set Screenshot Directory | ./output/AUT-screenshots/ | \n") + @ArgumentNames({ "directory", "logDirectory=" }) + public void setScreenshotDirectory(String dir, String logDir){ + RobotLog.info("Setting screenshot directory to \"" + dir + "\"."); + if (logDir != null && !logDir.isEmpty()) { + RobotLog.info("Log directory is set to \"" + logDir + "\""); + } + setCurrentSessionScreenshotDirectory(dir, logDir); } @RobotKeyword("Gets the screenshot directory for current application") diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java index 1d4532f..ae2a27f 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java @@ -91,12 +91,19 @@ public Object captureImage(Object locator, boolean logImage){ 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); + if (getCurrentSessionScreenshotDirectoryInLogs() != null) { + logPath = getCurrentSessionScreenshotDirectoryInLogs()+"/"+path.getFileName(); + } else { + logPath = path.toString(); + } + if (logImage) { double printSize = targetBounds.getWidth() > 800 ? 800 : targetBounds.getWidth(); @@ -113,13 +120,13 @@ public Object captureImage(Object locator, boolean logImage){ RobotLog.warn("Capture temporary image \"" + imageFile.getAbsolutePath() + "\" deletion failed."); } } - RobotLog.html("" + RobotLog.html("" + "" + ""); } else { // diskonly option - RobotLog.html("" + RobotLog.html("" + "" + ""); } diff --git a/src/main/java/javafxlibrary/utils/Session.java b/src/main/java/javafxlibrary/utils/Session.java index e415c82..5950b0a 100644 --- a/src/main/java/javafxlibrary/utils/Session.java +++ b/src/main/java/javafxlibrary/utils/Session.java @@ -44,6 +44,7 @@ public class Session { public Application sessionApplication; public String applicationName; public String screenshotDirectory; + public String screenshotDirectoryInLogs; public Session(String appName, String... appArgs) { try { diff --git a/src/main/java/javafxlibrary/utils/TestFxAdapter.java b/src/main/java/javafxlibrary/utils/TestFxAdapter.java index 59633ee..bc4f084 100644 --- a/src/main/java/javafxlibrary/utils/TestFxAdapter.java +++ b/src/main/java/javafxlibrary/utils/TestFxAdapter.java @@ -106,8 +106,14 @@ public String getCurrentSessionScreenshotDirectory() { } } - public void setCurrentSessionScreenshotDirectory(String dir) { - + public String getCurrentSessionScreenshotDirectoryInLogs() { + if (activeSession != null) { + return activeSession.screenshotDirectoryInLogs; + } else { + throw new JavaFXLibraryNonFatalException("Unable to get screenshot directory in logs, no application is currently open!"); + } + } + public void setCurrentSessionScreenshotDirectory(String dir, String logDir) { if (activeSession != null) { File errDir = new File(dir); if (!errDir.exists()) { @@ -116,6 +122,9 @@ public void setCurrentSessionScreenshotDirectory(String dir) { } } activeSession.screenshotDirectory = dir; + if (logDir != null && !logDir.isEmpty()) { + activeSession.screenshotDirectoryInLogs = logDir; + } } else { throw new JavaFXLibraryNonFatalException("Unable to set screenshot directory, no application is currently open!"); } From 9ddf5acb71d187609942d94146c9ffc42d92e491 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Tue, 15 Sep 2020 09:49:09 +0300 Subject: [PATCH 05/21] java agent documentation fix --- src/main/java/libdoc-documentation.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/libdoc-documentation.txt b/src/main/java/libdoc-documentation.txt index e5b91ba..99c3d9a 100644 --- a/src/main/java/libdoc-documentation.txt +++ b/src/main/java/libdoc-documentation.txt @@ -47,11 +47,11 @@ 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 +Experimental Java agent support 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 keyword 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. == 3. Locating JavaFX Nodes == === 3.1 Locator syntax === From 45d4db95177709bd088908a590825b0e43d93824 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Tue, 15 Sep 2020 09:50:37 +0300 Subject: [PATCH 06/21] Set Classpath failure as warnings and add failIfNotFound argument --- .../ApplicationLauncher.java | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java index eae81ec..c108156 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java @@ -122,15 +122,19 @@ private void _addPathToClassPath(String path) { @RobotKeyword("Loads given path to classpath.\n\n" + "``path`` is the path to add.\n\n" + + "``failIfNotFound`` is either True or False, default False. In case of False error is written as warning.\n\n" + "If directory path has asterisk(*) after directory separator all jar files are added from directory.\n" + "\nExample:\n" + "| Set To Classpath | C:${/}users${/}my${/}test${/}folder | \n" - + "| Set To Classpath | C:${/}users${/}my${/}test${/}folder${/}* | \n") - @ArgumentNames({ "path" }) - public void setToClasspath(String path) { + + "| Set To Classpath | C:${/}users${/}my${/}test${/}folder${/}* | \n" + + "| Set To Classpath | C:${/}users${/}my${/}test${/}folder2${/}* | failIfNotFound=${True} | \n") + @ArgumentNames({ "path", "failIfNotFound=False" }) + public void setToClasspath(String path, boolean failIfNotFound) { + RobotLog.info("Setting \"" + path + "\" to classpath, failIfNotFound=\"" + failIfNotFound + "\""); if (path.endsWith("*")) { path = path.substring(0, path.length() - 1); - RobotLog.info("Adding all jars from directory: " + path); + RobotLog.info("Adding all jars from directory."); + String fullPath = FileSystems.getDefault().getPath(path).normalize().toAbsolutePath().toString(); try { File directory = new File(path); @@ -143,11 +147,20 @@ public void setToClasspath(String path) { } } if (!jarsFound) { - throw new JavaFXLibraryNonFatalException("No jar files found from classpath: " - + FileSystems.getDefault().getPath(path).normalize().toAbsolutePath().toString()); + String jarsNotFoundError = "No jar files found from classpath: " + fullPath; + if (failIfNotFound) { + throw new JavaFXLibraryNonFatalException(jarsNotFoundError); + } else { + RobotLog.warn(jarsNotFoundError); + } } } catch (NullPointerException e) { - throw new JavaFXLibraryFatalException("Directory not found: " + path + "\n" + e.getMessage(), e); + String directoryNotFoundError = "Directory not found: " + fullPath; + if (failIfNotFound) { + throw new JavaFXLibraryFatalException(directoryNotFoundError); + } else { + RobotLog.warn(directoryNotFoundError); + } } } else { _addPathToClassPath(path); From b5c06b3bb6e233f20c047531ac2f2b9c36cad5a8 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Tue, 15 Sep 2020 09:54:24 +0300 Subject: [PATCH 07/21] Use asyncFx for helperfunctions methods, remove waitForFxEvents usage, failure printout improvements --- .../keywords/Keywords/BoundsLocation.java | 2 +- .../keywords/Keywords/MoveRobot.java | 26 +++++++++---------- .../matchers/ExtendedNodeMatchers.java | 1 - .../javafxlibrary/utils/HelperFunctions.java | 16 +++++------- .../javafxlibrary/utils/finder/Finder.java | 1 - 5 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java index 71617ca..8804c04 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java @@ -125,7 +125,7 @@ public Object getBounds(Object locator) { return HelperFunctions.mapObject(bounds.query()); } catch (IllegalAccessException | InvocationTargetException e) { - throw new JavaFXLibraryNonFatalException("Could not execute move to using locator \"" + locator + "\": " + throw new JavaFXLibraryNonFatalException("getBounds: Could not execute move to using locator \"" + locator + "\": " + e.getCause().getMessage()); } catch (JavaFXLibraryNonFatalException e){ diff --git a/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java index 3da2df5..27ba604 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java @@ -48,23 +48,21 @@ 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) { - 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); - } - } - - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "moveTo", locator.getClass(), Motion.class); - 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); + } + } + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "moveTo", locator.getClass(), Motion.class); return (FxRobotInterface) method.invoke(robot, locator, getMotion(motion)); } catch (IllegalAccessException | InvocationTargetException e) { - throw new JavaFXLibraryNonFatalException("Could not execute move to using locator \"" + locator + "\" " + + throw new JavaFXLibraryNonFatalException("moveTo: Could not execute move to using locator \"" + locator + "\" " + "and motion " + motion + ": " + e.getCause().getMessage(), e); } } diff --git a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java index 89ae237..d2bd9c7 100644 --- a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java +++ b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java @@ -60,7 +60,6 @@ private static boolean hoverable(Node node) { waitFor(HelperFunctions.getWaitUntilTimeout(), HelperFunctions.getTimeUnit("SECONDS"), () -> { return asyncFx(() -> new javafxlibrary.keywords.Keywords.MoveRobot().moveTo(node, "DIRECT") != null).get(); }); - waitForFxEvents(); return node.isHover(); } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; diff --git a/src/main/java/javafxlibrary/utils/HelperFunctions.java b/src/main/java/javafxlibrary/utils/HelperFunctions.java index 1c82fc3..d3b3929 100644 --- a/src/main/java/javafxlibrary/utils/HelperFunctions.java +++ b/src/main/java/javafxlibrary/utils/HelperFunctions.java @@ -89,7 +89,6 @@ public static Node waitUntilExists(String target, int timeout, String timeUnit) Node node = asyncFx(() -> createFinder().find(target)).get(); // TODO: Add null checks for node.getScene() waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> hasValidCoordinates(node)).get()); - waitForFxEvents(); return node; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -97,8 +96,8 @@ public static Node waitUntilExists(String target, int timeout, String timeUnit) throw new JavaFXLibraryTimeoutException("Given element \"" + target + "\" was not found within given timeout of " + timeout + " " + timeUnit); } catch (Exception e) { - RobotLog.trace("Exception in waitUntilExists: " + e + "\n" + e.getCause().toString()); - throw new JavaFXLibraryNonFatalException("Given element \"" + target + "\" was not found.", e); + RobotLog.trace("Exception in waitUntilExists: " + e.getCause().toString()); + throw new JavaFXLibraryNonFatalException("Given element \"" + target + "\" was not found (" + e.getCause().toString() + ")."); } } @@ -108,7 +107,6 @@ public static void waitUntilDoesNotExists(String target, int timeout, String tim try { waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) == null).get()); - waitForFxEvents(); } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; } catch (TimeoutException te) { @@ -131,7 +129,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; @@ -153,7 +151,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; @@ -174,7 +172,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)); + waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> (Matchers.is(isEnabled()).matches(finalTarget))== true).get()); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -195,7 +193,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)); + waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isDisabled()).matches(finalTarget) == true).get()); return (Node) target; } catch (JavaFXLibraryNonFatalException nfe) { throw nfe; @@ -209,7 +207,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!"); } diff --git a/src/main/java/javafxlibrary/utils/finder/Finder.java b/src/main/java/javafxlibrary/utils/finder/Finder.java index e7687d3..23f053e 100644 --- a/src/main/java/javafxlibrary/utils/finder/Finder.java +++ b/src/main/java/javafxlibrary/utils/finder/Finder.java @@ -136,7 +136,6 @@ private Set findAll(Parent root, int queryIndex) { } else { results.addAll(nodes); } - return results; } From 09054a91a2d4f37aedfb8eaaad764d56ef5b9fad Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Tue, 15 Sep 2020 14:16:43 +0300 Subject: [PATCH 08/21] remove deprecated keywords and methods (enhancement #11) --- .../ApplicationLauncher.java | 11 - .../ConvenienceKeywords.java | 222 ------------------ .../AdditionalKeywords/Verifiers.java | 28 --- .../javafxlibrary/utils/HelperFunctions.java | 67 ------ .../DeprecatedFindKeywordsTest.java | 97 -------- 5 files changed, 425 deletions(-) delete mode 100644 src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/DeprecatedFindKeywordsTest.java diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java index c108156..282ad1d 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java @@ -263,17 +263,6 @@ public String getCurrentApplication() { } } - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use `Get Current Application` keyword instead.\n\n" - + "Returns the class name of currently active JavaFX Application\n") - public String currentApplication() { - try { - return getCurrentSessionApplicationName(); - } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("Problem getting current application name.", e); - } - } - @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 070bc06..e5e07c1 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -54,30 +54,6 @@ @RobotKeywords public class ConvenienceKeywords extends TestFxAdapter { - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use keyword `Find` instead.\n\n" + - "finder that mimics _xpath_ style search.\n\n" - + "``query`` is a query locator, see `3.1 Locator syntax`.\n\n" - + "``failIfNotFound`` specifies if keyword should fail if nothing is found. By default it's false and " - + "keyword returns null in case lookup returns nothing.\n\n" - + "\nExample:\n" - + " | ${node}= | Find With Path | .main-view[0] .split-pane[0] \\#node-id class=GridPane .toggle-button[3] sometext | ") - @ArgumentNames({"query", "failIfNotFound=False"}) - public Object findWithPath(String query, boolean failIfNotFound){ - - try { - return mapObject(findNode(query)); - - } catch (JavaFXLibraryNonFatalException e){ - if(failIfNotFound) - throw new JavaFXLibraryNonFatalException("Unable to find anything with query: \"" + query + "\""); - return ""; - - } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("Find operation failed for query: \"" + query + "\"", e); - } - } - @RobotKeyword("Brings the given stage to front\n\n" + "``stage`` is an Object:Stage to be set in front of others, see `3.2 Using locators as keyword arguments`. \n\n") @ArgumentNames({ "stage" }) @@ -130,116 +106,6 @@ public void callObjectMethodInFxApplicationThread(Object object, String method, callMethod(object, method, finalArgs, true); } - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use keyword `Find` instead.\n\n" - + "Returns the *first* node matching the query. \n\n" - + "``query`` is the Class name String to use in lookup.\n" - + "\nExample:\n" - + "| ${my node}= | Find | javafx.scene.control.Button | # button class |") - @ArgumentNames({ "query" }) - public Object findClass(final String query) { - try { - Class clazz = Class.forName(query); - InstanceOfMatcher matcher = new InstanceOfMatcher(clazz); - return mapObject(robot.lookup(matcher).query()); - } catch (Exception e) { - RobotLog.trace("Problem has occurred during node lookup: " + e); - return ""; - } - } - - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use keyword `Find All` instead.\n\n" - + "Returns *all* descendant nodes of given node matching the query. \n\n" - + "``node`` is the starting point Object:Node from where to start looking, see `3.2 Using locators as keyword arguments`. \n\n" - + "``query`` is a query locator, see `3.1 Locator syntax`.\n\n" - + "``failIfNotFound`` specifies if keyword should fail if nothing is found. By default it's false and " - + "keyword returns null in case lookup returns nothing.\n\n" - + "\nExample:\n" - + "| ${my nodes}= | Find All From Node | ${some node} | .css | \n" - + "See keyword `Find` for further examples of query usage.\n") - @ArgumentNames({ "node", "query", "failIfNotFound=False" }) - public List findAllFromNode(Object node, String query, boolean failIfNotFound) { - try { - if ( node instanceof Node ) { - RobotLog.info("Trying to find all nodes with query: \"" + query + "\" that are under starting " + - "point node: \"" + node + "\", failIfNotFound= \"" + failIfNotFound + "\""); - return mapObjects(((Node) node).lookupAll(query)); - } - // fail in case no valid node argument. - failIfNotFound = true; - throw new JavaFXLibraryNonFatalException("Illegal argument type for node."); - } catch (JavaFXLibraryNonFatalException e){ - if(failIfNotFound) - throw e; - return Collections.emptyList(); - - } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("Find all from node operation failed for node: \"" + node.toString() + - "\" and query: " + query, e); - } - } - - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use keyword `Find All` instead.\n\n" - + "Returns *all* nodes matching query AND given pseudo-class state. \r\n" - + "``query`` is a query locator, see `3.1 Locator syntax`.\n\n" - + "``pseudo`` is a String value specifying pseudo class value.\n\n" - + "``failIfNotFound`` specifies if keyword should fail if nothing is found. By default it's false and " - + "keyword returns null in case lookup returns nothing.\n\n" - + "\nExample:\n" - + "| ${my node}= | Find All With Pseudo Class | .check-box-tree-cell .check-box | selected | \n") - @ArgumentNames({ "query", "pseudo", "failIfNotFound=False" }) - public List findAllWithPseudoClass(String query, String pseudo, boolean failIfNotFound) { - RobotLog.info("Trying to find all nodes with query: \"" + query + "\" that has pseudoclass state as: \"" + - pseudo + "\", failIfNotFound= \"" + failIfNotFound + "\""); - try { - Set nodes = robot.lookup(query).queryAll(); - Set matches = nodes.stream() - .filter(n -> n.getPseudoClassStates().stream(). - map(PseudoClass::getPseudoClassName).anyMatch(pseudo::contains)) - .collect(Collectors.toSet()); - return mapObjects(matches); - - } catch (JavaFXLibraryNonFatalException e){ - if(failIfNotFound) - throw e; - return Collections.emptyList(); - - } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("Find all with pseudo class operation failed for query: \"" + - query + "\" and pseudo: \"" + pseudo + "\"", e); - } - } - - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use keyword `Find` instead.\n\n" - + "Returns the *first* descendant node of given node matching the query. \n\n" - + "``node`` is the starting point Object:Node from where to start looking, see `3.2 Using locators as keyword arguments`. \n\n" - + "``query`` is a query locator, see `3.1 Locator syntax`.\n\n" - + "``failIfNotFound`` specifies if keyword should fail if nothing is found. By default it's false and " - + "keyword returns null in case lookup returns nothing.\n\n" - + "\nExample:\n" - + "| ${my node}= | Find From Node | ${some node} | .css |\n" - + "See keyword `Find` for further examples of query usage.\n") - @ArgumentNames({ "node", "query", "failIfNotFound=False" }) - public Object findFromNode(Node node, String query, boolean failIfNotFound) { - RobotLog.info("Trying to find: \"" + query + "\" from node: \"" + node + "\", failIfNotFound= \"" + failIfNotFound + "\""); - try { - Node childNode = node.lookup(query); - return mapObject(childNode); - - } catch (JavaFXLibraryNonFatalException e){ - if(failIfNotFound) - throw e; - return ""; - - } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("Find from node operation failed for node: \"" + node + - "\" and query: " + query, e); - } - } - @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 " @@ -390,40 +256,6 @@ public Set getPseudoClassStates(Object locator) { } } - // TODO: Should this be deleted? Find All From Node has the same functionality - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use keyword `Find` instead.\n\n" - + "Returns *all* descendant nodes of given node matching the given Java class name. \n\n" - + "``locator`` is either a _query_ or _Object_ for node whose children will be queried, see " - + "`3.2 Using locators as keyword arguments`. \n\n" - + "``className`` is the Java class name to look for.\n" - + "\nExample:\n" - + "| ${panes}= | Get Node Children By Class Name | ${some node} | BorderPane | \n" - + "Returns an empty list if none is found. \n") - @ArgumentNames({ "node", "className" }) - public Set getNodeChildrenByClassName(Object locator, String className) { - Node node = objectToNode(locator); - RobotLog.info("Getting node: \"" + node + "\" children by class name: \"" + className + "\""); - try { - Set keys = new HashSet<>(); - Set childNodes = node.lookupAll("*"); - - for (Object o : childNodes) { - Node childNode = (Node) o; - if (childNode.getClass().getSimpleName().equals(className)) { - RobotLog.trace("Classname: \"" + className + "\" found: \"" + childNode + "\""); - keys.add(mapObject(childNode)); - } - } - return keys; - } catch (Exception e) { - if(e instanceof JavaFXLibraryNonFatalException) - throw e; - throw new JavaFXLibraryNonFatalException("Unable to get node children for node: \"" + node.toString() + - "\" with class name: " + className, e); - } - } - @RobotKeyword("Returns text value of the Node. \n\n" + "``locator`` is either a _query_ or _Object_ for a node whose getText method will be called, see " + "`3. Locating JavaFX Nodes`. \n\n") @@ -441,35 +273,6 @@ public String getNodeText(Object locator) { } } - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use keyword `Find` instead.\n\n" - + "Returns height value of the node. \n\n" - + "``locator`` is either a _query_ or _Object_ for a node whose getHeight method will be called, see " - + "`3. Locating JavaFX Nodes`. \n\n") - @ArgumentNames({ "locator" }) - public String getNodeHeight(Object locator) { - Node node = objectToNode(locator); - try { - Method[] methods = node.getClass().getMethods(); - for (Method m : methods) { - if (m.getName().equals("getHeight")) { - try { - Object result = m.invoke(node, (Object) null); - return result.toString(); - } catch (Exception e) { - throw new JavaFXLibraryNonFatalException("Problem calling method: .getHeight(): " + e.getMessage(), e); - } - } - } - throw new JavaFXLibraryNonFatalException( - "Get node height failed for node: \"" + node.toString() + "\". Element has no method getHeight()"); - } catch (Exception e) { - if (e instanceof JavaFXLibraryNonFatalException) - throw e; - throw new JavaFXLibraryNonFatalException("Unable to get node height for node: " + node.toString(), e); - } - } - @RobotKeyword("Returns image name and path of the node. \n\n" + "``locator`` is either a _query_ or _Object_ for a node whose getHeight method will be called, see " + "`3. Locating JavaFX Nodes`. \n\n" @@ -535,31 +338,6 @@ public String getObjectClassName(Object locator) { } } - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use keyword `Get Scene` instead.\n\n" - +"Returns given locators Scene object. \n\n" - + "``locator`` is either a _query_ or a _Node_, see `3.2 Using locators as keyword arguments`\n\n") - @ArgumentNames({ "locator" }) - public Object getNodesScene(Object locator) { - try { - if (locator instanceof Node){ - RobotLog.info("Getting a Scene object for a Node: \"" + locator + "\""); - return mapObject(((Node) locator).getScene()); - } else if (locator instanceof String) { - RobotLog.info("Getting a Scene object for a query: \"" + locator + "\""); - Node node = objectToNode(locator); - return mapObject(node.getScene()); - } - - throw new JavaFXLibraryNonFatalException("Locator type is not a Node or a query string!"); - - } catch (Exception e) { - if (e instanceof JavaFXLibraryNonFatalException) - throw e; - throw new JavaFXLibraryNonFatalException("Unable to get Scene object for locator: \"" + locator + "\"", e); - } - } - @RobotKeyword("Returns Scene of the given object. \n\n" + "``locator`` is either a _query_, a _Node_ or a _Window_, see `3.2 Using locators as keyword arguments`\n\n") @ArgumentNames({ "locator" }) diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index 869fdaa..f6181d3 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -158,16 +158,6 @@ public static void nodeShouldNotBeVisible(Object locator) { verifyThat(objectToNode(locator), isInvisible() ); } - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use `Node Should Not Be Visible` instead.\n\n" - + "Verifies that node is invisible. \n\n" - + "``locator`` is either a _query_ or _Object:Node_ for identifying the Node, see " - + "`3. Locating JavaFX Nodes`. \n\n") - @ArgumentNames({ "locator" }) - public static void nodeShouldBeInvisible(Object locator) { - verifyThat(objectToNode(locator), isInvisible() ); - } - @RobotKeyword("Verifies that node is focused. \n\n" + "``locator`` is either a _query_ or _Object:Node_ for identifying the Node, see " + "`3. Locating JavaFX Nodes`. \n\n") @@ -204,16 +194,6 @@ public static void nodeShouldNotBeEnabled(Object locator) { verifyThat(objectToNode(locator), NodeMatchers.isDisabled() ); } - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Use `Node Should Not Be Enabled` instead." - + "Verifies that node is disabled. \n\n" - + "``locator`` is either a _query_ or _Object:Node_ for identifying the Node, see " - + "`3. Locating JavaFX Nodes`. \n\n") - @ArgumentNames({ "locator" }) - public static void nodeShouldBeDisabled(Object locator) { - verifyThat(objectToNode(locator), NodeMatchers.isDisabled() ); - } - @RobotKeyword("Verifies that node is hoverable with mouse. \n\n" + "``locator`` is either a _query_ or _Object:Node_ for identifying the Node, see " + "`3. Locating JavaFX Nodes`. \n\n") @@ -290,14 +270,6 @@ public static void windowShouldBeVisible(Object window) { verifyThat((Window) window, WindowMatchers.isShowing()); } - @Deprecated - @RobotKeyword("*DEPRECATED in version 0.6.0!* Please use `Window Should Be Visible` instead.\n\n" +"Verifies that given window is showing. \n\n" - + "``window`` is the _Object:Window_ that specifies which window should be showing, see `3.2 Using locators as keyword arguments`.") - @ArgumentNames({ "window" }) - public static void windowShouldBeShowing(Object window) { - verifyThat((Window) window, WindowMatchers.isShowing()); - } - @RobotKeyword("Verifies that given window is not visible.\n\n" + "``window`` is the _Object:Window_ that specifies which window should be not visible, see `3.2 Using locators as keyword arguments`.") @ArgumentNames({ "window" }) diff --git a/src/main/java/javafxlibrary/utils/HelperFunctions.java b/src/main/java/javafxlibrary/utils/HelperFunctions.java index d3b3929..b932560 100644 --- a/src/main/java/javafxlibrary/utils/HelperFunctions.java +++ b/src/main/java/javafxlibrary/utils/HelperFunctions.java @@ -648,73 +648,6 @@ private static String remainingQueries(String query) { return queries[1]; } - - // Deprecated: Use javafxlibrary.utils.finder.finder instead - @Deprecated - public static Node findNode(Node node, String query) { - - RobotLog.info("Finding from node: " + node.toString() + " with query: " + query); - - if (query != null) { - List nodelist = new ArrayList<>(); - - String currentQuery = query.split(" ", 2)[0]; - String nextQuery = remainingQueries(query); - RobotLog.info("CurrentQuery: " + currentQuery + ", nextQuery: " + nextQuery); - - if (currentQuery.startsWith("class=")) { - nodelist.addAll(node.lookupAll((getQueryString(currentQuery)).replaceFirst("^class=", ""))); - } else { - nodelist.addAll(robot.from(node).lookup(getQueryString(currentQuery)).queryAll()); - } - - if (nodelist.isEmpty()) { - return null; - } else { - if (getQueryIndex(currentQuery) != -1) { - // if index [..] was given, continue search with only one match - return findNode(nodelist.get(getQueryIndex(currentQuery)), nextQuery); - } else { - // no index given, continue search with all matches - Node newNode; - - for (Node n : nodelist) { - newNode = findNode(n, nextQuery); - if (newNode != null) - return newNode; - } - return null; - } - } - } else { - return node; - } - } - - // Deprecated: Use javafxlibrary.utils.finder.finder instead - @Deprecated - public static Node findNode(String query) { - return findNode(robot.listTargetWindows().get(0).getScene().getRoot(), query); - } - - // Deprecated: Used only in deprecated method findNode - @Deprecated - public static String getQueryString(String query) { - return query.replaceAll("\\[\\d]$", ""); - } - - // Deprecated: Used only in deprecated method findNode - @Deprecated - public static int getQueryIndex(String query) { - Pattern pattern = Pattern.compile(".*\\[\\d]$"); - Matcher matcher = pattern.matcher(query); - if (matcher.matches()) { - String index = StringUtils.substringBetween(query, "[", "]"); - return Integer.parseInt(index); - } - return -1; - } - public static Node getHoveredNode() { return getHoveredNode(robot.listTargetWindows().get(0).getScene().getRoot()); } diff --git a/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/DeprecatedFindKeywordsTest.java b/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/DeprecatedFindKeywordsTest.java deleted file mode 100644 index c9ae337..0000000 --- a/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/DeprecatedFindKeywordsTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2017-2018 Eficode Oy - * Copyright 2018- Robot Framework Foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package javafxlibrary.keywords.AdditionalKeywordsTests.ConvenienceKeywords; - -import java.util.List; -import com.google.common.collect.ImmutableSet; -import javafx.css.PseudoClass; -import javafx.scene.control.Button; -import javafxlibrary.TestFxAdapterTest; -import javafxlibrary.keywords.AdditionalKeywords.ConvenienceKeywords; -import javafxlibrary.utils.HelperFunctions; -import mockit.Expectations; -import mockit.Mocked; -import org.junit.Assert; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.testfx.service.query.NodeQuery; - -public class DeprecatedFindKeywordsTest extends TestFxAdapterTest { - - @Mocked - NodeQuery rootQuery; - - private Button button; - private Button button2; - private static ConvenienceKeywords keywords; - - @BeforeClass - public static void setupKeywords() { - keywords = new ConvenienceKeywords(); - } - - @Before - public void setup() { - button = new Button(); - button2 = new Button(); - button.pseudoClassStateChanged(PseudoClass.getPseudoClass("selected"), true); - new Expectations() { - { - getRobot().lookup("rootId"); - result = rootQuery; - minTimes = 0; - } - }; - } - - @Test - public void findAllWithPseudoClass() { - expectTwoButtonsFromNodeQuery(); - List allWithPseudoClass = keywords.findAllWithPseudoClass("rootId", "selected", true); - Assert.assertEquals(HelperFunctions.mapObject(button), allWithPseudoClass.get(0)); - } - - @Test - public void findNoPseudoClasses() { - expectTwoButtonsFromNodeQuery(); - List hits = keywords.findAllWithPseudoClass("rootId", "something", false); - Assert.assertEquals(0, hits.size()); - } - - @Test - public void findNoNodes() { - new Expectations() { - { - rootQuery.queryAll(); - result = ImmutableSet.of(); - } - }; - List hits = keywords.findAllWithPseudoClass("rootId", "something", false); - Assert.assertEquals(0, hits.size()); - } - - private void expectTwoButtonsFromNodeQuery() { - new Expectations() { - { - rootQuery.queryAll(); - result = ImmutableSet.of(button, button2); - } - }; - } -} From dbce1c816a43e8c8106131bb3fd570c401168ee2 Mon Sep 17 00:00:00 2001 From: Pasi Saikkonen Date: Thu, 24 Sep 2020 15:02:50 +0300 Subject: [PATCH 09/21] Set empty string args as nulls, add message for IllegalArgumentException (#53) looks good to me --- src/main/java/JavaFXLibrary.java | 6 +++++- .../java/javafxlibrary/utils/HelperFunctions.java | 14 +++++++++++--- src/test/robotframework/acceptance/FindTest.robot | 12 ++++++++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index 8e9dd06..f56137c 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -124,7 +124,6 @@ public Object runKeyword(String keywordName, List args, Map kwargs) { // timeout already expired, catch exception and jump out retExcep.set(jfxte); throw jfxte; - } catch (RuntimeException e) { // catch exception and continue trying retExcep.set(e); @@ -142,6 +141,11 @@ 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 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; diff --git a/src/main/java/javafxlibrary/utils/HelperFunctions.java b/src/main/java/javafxlibrary/utils/HelperFunctions.java index b932560..506c7ed 100644 --- a/src/main/java/javafxlibrary/utils/HelperFunctions.java +++ b/src/main/java/javafxlibrary/utils/HelperFunctions.java @@ -902,7 +902,7 @@ public static Object[] useMappedObjects(Object[] arr) { if (objectMap.containsKey(o)) { replaced[i] = objectMap.get(o); } else { - replaced[i] = arr[i]; + replaced[i] = checkForNullArgument(arr[i]); } } } @@ -924,7 +924,7 @@ public static List useMappedObjects(List list) { if (objectMap.containsKey(o)) { replaced.set(i, objectMap.get(o)); } else { - replaced.set(i, o); + replaced.set(i, checkForNullArgument(o)); } } } @@ -944,13 +944,21 @@ public static Map useMappedObjects(Map map) { if (objectMap.containsKey(o)) { replaced.put(key, objectMap.get(o)); } else { - replaced.put(key, o); + replaced.put(key, checkForNullArgument(o)); } } } return replaced; } + public static Object checkForNullArgument(Object o) { + if (o.getClass() == String.class && o.equals("")) { + return null; + } + return o; + } + + public static Object[] checkMethodArguments(Object[] arguments) { Object[] replaced = new Object[arguments.length]; for (int i = 0; i < arguments.length; i++) { diff --git a/src/test/robotframework/acceptance/FindTest.robot b/src/test/robotframework/acceptance/FindTest.robot index 10aa0ac..d954d68 100644 --- a/src/test/robotframework/acceptance/FindTest.robot +++ b/src/test/robotframework/acceptance/FindTest.robot @@ -207,6 +207,18 @@ Previous Query Returns Nothing In Chained Selector With Find All When failIfNotF ${msg} Run Keyword And Expect Error * Find All css=VBox css=ZBox Pane id=lime true Should Be Equal Find operation failed for query: "css=VBox css=ZBox Pane id=lime" ${msg} +Find With Nonvalid Root + [Tags] smoke negative + Set Test Application ${BOUNDS_APP} + ${msg} Run Keyword And Expect Error * Find xpath=//Rectangle[@width="75.0"] false not-a-valid-root + Should Start With ${msg} Illegal arguments for keyword 'find' + +Find All With Nonvalid Root + [Tags] smoke negative + Set Test Application ${BOUNDS_APP} + ${msg} Run Keyword And Expect Error * Find All xpath=//Rectangle[@width="75.0"] false not-a-valid-root + Should Start With ${msg} Illegal arguments for keyword 'findAll' + Find Labeled Node With Text [Tags] smoke Set Test Application javafxlibrary.testapps.TestWindowManagement From 65d4ad9f9d4d709c04a318b922ccfc4fd8617ca5 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Mon, 2 Nov 2020 17:20:58 +0200 Subject: [PATCH 10/21] Development kw asyncfx wrap (#55) * wrap runKeyword to asyncFx so that JavaFX operations are done FX thread * more asyncfx fixes, went through whole code base * new wrapping to asyncfx thread * additional rewrite to have all needed kw's wrapped in FX thread, output and error handling improvements * handle null object properlty, timeout to be generic kw timeout, handle separately wait until kws, kw output improvements * fix hover related kw's * waitUntilExists and waitUntilDoesNotExist improvements * wait until keywords to have overall timeout value, improved printout * fix text prefix to check for quotation marks * fix text prefix to have support for apostrophe (') also * cleanup * fix verifiers hoverable kw's, add test * fix push many times kw * fix osx tests * screenshots to asyncfx thread also in kw failure, fix hover kw's once more * fix osx part and go throught documentation * fix osx part and go throught documentation * fix RunOnFailure to not store screenshot as mapObject to save memory * remove comments --- pom.xml | 4 +- src/main/java/JavaFXLibrary.java | 191 ++++++----- .../ApplicationLauncher.java | 67 +++- .../ConvenienceKeywords.java | 250 +++++++------- .../keywords/AdditionalKeywords/Find.java | 10 +- .../AdditionalKeywords/RunOnFailure.java | 7 +- .../AdditionalKeywords/Verifiers.java | 187 ++++++++--- .../keywords/Keywords/BoundsLocation.java | 14 +- .../keywords/Keywords/ClickRobot.java | 54 ++-- .../keywords/Keywords/DragRobot.java | 60 +++- .../keywords/Keywords/KeyboardRobot.java | 38 ++- .../keywords/Keywords/MouseRobot.java | 1 - .../keywords/Keywords/MoveRobot.java | 12 +- .../keywords/Keywords/NodeLookup.java | 25 +- .../keywords/Keywords/PointLocation.java | 19 +- .../keywords/Keywords/PointOffset.java | 18 +- .../keywords/Keywords/PointPosition.java | 1 - .../keywords/Keywords/ScreenCapturing.java | 97 ++++-- .../keywords/Keywords/ScrollRobot.java | 1 - .../keywords/Keywords/WindowLookup.java | 19 +- .../keywords/Keywords/WindowTargeting.java | 12 +- .../matchers/ExtendedNodeMatchers.java | 14 - .../javafxlibrary/utils/HelperFunctions.java | 304 +++++++++++------- .../javafxlibrary/utils/finder/Finder.java | 27 +- .../utils/finder/QueryParser.java | 1 + src/main/java/libdoc-documentation.txt | 64 ++-- ...aitForEventsInFxApplicationThreadTest.java | 6 +- .../CheckClickLocationTest.java | 6 +- .../CheckClickTargetTest.java | 4 +- .../GetMouseButtonsTest.java | 1 + .../HelperFunctionsTest.java | 4 +- .../ObjectToBoundsTest.java | 2 +- .../ObjectToNodeTest.java | 4 +- .../WaitUntilDisabledTest.java | 6 +- .../WaitUntilEnabledTest.java | 6 +- .../WaitUntilInvisibleTest.java | 6 +- .../WaitUntilVisibleTest.java | 6 +- .../utils/finder/QueryParserTest.java | 11 +- .../acceptance/0_ClickRobotTest.robot | 15 +- .../acceptance/BoundsLocationTest.robot | 3 +- .../acceptance/DatePickerTest.robot | 1 - .../acceptance/DragRobotTest.robot | 1 - .../robotframework/acceptance/FindTest.robot | 3 +- .../acceptance/KeyboardRobotTest.robot | 1 - .../acceptance/MenuAppTest.robot | 38 ++- .../robotframework/acceptance/MiscTests.robot | 43 ++- .../acceptance/MoveRobotTest.robot | 4 +- .../acceptance/NodeLookupTest.robot | 3 +- .../acceptance/PointLocationTest.robot | 7 +- .../acceptance/ScreenCapturingTest.robot | 1 - .../acceptance/ScrollRobotTest.robot | 8 +- 51 files changed, 976 insertions(+), 711 deletions(-) diff --git a/pom.xml b/pom.xml index cc67a5d..115ba0f 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ org.robotframework javafxlibrary jar - 0.5.5-SNAPSHOT + 0.7.0-SNAPSHOT UTF-8 1.44 @@ -381,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 f56137c..13adf21 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -41,8 +41,9 @@ import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import static org.testfx.util.WaitForAsyncUtils.*; import static javafxlibrary.utils.HelperFunctions.*; -import static org.testfx.util.WaitForAsyncUtils.waitFor; + import java.util.ResourceBundle; public class JavaFXLibrary extends AnnotationLibrary { @@ -51,6 +52,66 @@ 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("captureImage"); + add("capturePrimaryScreen"); + add("captureSceneContainingNode"); + add("clearObjectMap"); + add("closeJavafxApplication"); + add("closeSwingApplication"); + add("dragFrom"); + add("dropTo"); + add("dropToCoordinates"); + add("getCurrentApplication"); + add("getLibraryVersion"); + add("getScreenshot Directory"); + add("getScreenshotDirectory"); + add("getSystemProperty"); + add("isJavaAgent"); + add("launchJavafxApplication"); + add("launchSwingApplication"); + add("launchSwingApplicationInSeparateThread"); + add("logApplicationClasspath"); + add("logSystemProperties"); + add("moveTo"); + add("nodeShouldBeHoverable"); + add("nodeShouldNotBeHoverable"); + add("pushManyTimes"); + 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"); + add("writeTo"); + }}; + static List includePatterns = new ArrayList() {{ add("javafxlibrary/keywords/AdditionalKeywords/*.class"); add("javafxlibrary/keywords/Keywords/*.class"); @@ -91,11 +152,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; @@ -108,114 +167,70 @@ 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)); - 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; + } else { + // in async thread + retval.set(waitForAsync(getLibraryKeywordTimeout(TimeUnit.MILLISECONDS), () -> { + try { + return super.runKeyword(keywordName, finalArgs, finalKwargs); + } catch (RuntimeException rte) { + retExcep.set(rte); + return null; + } + })); } - }); - } 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; + // 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); } } 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); + } catch (RuntimeException rte) { + // catch exception and continue trying + retExcep.set(rte); } - RobotLog.reset(); - 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(super.runKeyword(keywordName, finalArgs)); - 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) { + // in failure take screenshot and handle exception + if(retExcep.get()!=null) { 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 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" + + " 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; } - } catch (JavaFXLibraryTimeoutException jfxte) { - RobotLog.reset(); - RobotLog.trace("JavaFXLibrary: Caught JavaFXLibrary TIMEOUT exception: \n" + Throwables.getStackTraceAsString(jfxte)); - throw jfxte; } 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..e5e7ab1 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(getLibraryKeywordTimeout()); RobotLog.info("Application: " + appName + " started."); } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Unable to launch application: " + appName, e); @@ -63,14 +67,15 @@ 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) { 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); + waitForEventsInFxApplicationThread(getLibraryKeywordTimeout()); RobotLog.info("Application: " + appName + " started."); } @@ -82,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) { @@ -90,6 +95,7 @@ public void launchSwingApplicationInSeparateThread(String appName, String... app Class c = getMainClass(appName); Application app = createThreadedWrapperApplication(c, appArgs); createNewSession(app); + waitForEventsInFxApplicationThread(getLibraryKeywordTimeout()); 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 e5e07c1..9b3e666 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; @@ -35,7 +34,7 @@ import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.keywords.Keywords.ClickRobot; import javafxlibrary.keywords.Keywords.KeyboardRobot; -import javafxlibrary.matchers.InstanceOfMatcher; +import static javafxlibrary.utils.HelperFunctions.*; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.RobotLog; import javafxlibrary.utils.TestFxAdapter; @@ -47,9 +46,8 @@ import java.lang.reflect.Method; import java.util.*; -import java.util.concurrent.Semaphore; -import java.util.stream.Collectors; -import static javafxlibrary.utils.HelperFunctions.*; + +import static org.testfx.util.WaitForAsyncUtils.waitForFxEvents; @RobotKeywords public class ConvenienceKeywords extends TestFxAdapter { @@ -61,7 +59,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); } @@ -74,61 +72,60 @@ 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=" }) 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; } @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) { // 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(3); } @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) { - 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) { @@ -153,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) { @@ -170,10 +167,11 @@ 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) { + RobotLog.info("Logging FXML of root \"" + root + "\"."); XPathFinder logger = new XPathFinder(); logger.setNodeLogging(false); RobotLog.info(logger.getFxml((Parent) objectToNode(root))); @@ -181,32 +179,37 @@ 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.") + @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" + + "| Click On | id=myidthatshallcomeavailable | | \n" + + "| [Teardown] | Set Timeout | ${old_timeout} | \n") @ArgumentNames({ "timeout" }) - public void setTimeout(int timeout) { + public Integer setTimeout(int timeout) { RobotLog.info("Setting timeout to " + timeout + "s"); - setWaitUntilTimeout(timeout); + Integer oldTimeoutValue = getLibraryKeywordTimeout(); + setLibraryKeywordTimeout(timeout); + return oldTimeoutValue; } /* @@ -218,9 +221,8 @@ public void 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 { @@ -246,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); } } @@ -261,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"); @@ -280,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) { @@ -309,10 +313,10 @@ 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); - try { RobotLog.info("Getting node parent object for: \"" + node + "\""); return mapObject(node.getParent()); @@ -328,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(); @@ -342,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){ @@ -352,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; @@ -367,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) { @@ -391,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" @@ -403,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) { @@ -425,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!"); } @@ -440,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) { @@ -471,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 ); @@ -480,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!"); } @@ -493,22 +497,21 @@ 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" }) 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!"); } @@ -519,22 +522,20 @@ 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" }) 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!"); } @@ -545,7 +546,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){ @@ -584,6 +587,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)); } @@ -594,13 +599,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) { @@ -613,7 +619,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) { @@ -626,7 +634,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) { @@ -639,11 +649,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); } @@ -654,27 +664,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); } @@ -684,15 +693,15 @@ 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) { - 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")) { @@ -700,11 +709,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); } } @@ -713,18 +719,17 @@ 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) { - 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); } @@ -736,13 +741,13 @@ 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) { - 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(); @@ -756,7 +761,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); } @@ -767,7 +771,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) { @@ -780,7 +786,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) { @@ -795,7 +803,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) { @@ -810,7 +820,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(); @@ -824,21 +836,19 @@ 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") @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; } @@ -846,10 +856,11 @@ 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){ + RobotLog.info("Selecting context menu item \"" + item + "\"."); List windows = robot.listTargetWindows(); ListIterator li = windows.listIterator(windows.size()); while (li.hasPrevious()) { @@ -861,7 +872,6 @@ public void selectContextMenuItem(String item){ } } } - throw new JavaFXLibraryNonFatalException("unable to find menu item: " + item); } @@ -870,51 +880,13 @@ 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()); - } catch (ClassCastException cce) { 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/Find.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Find.java index 1ad4417..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" @@ -41,9 +39,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/RunOnFailure.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java index 6e08c68..10c3a67 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/RunOnFailure.java @@ -24,7 +24,6 @@ import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; - import org.robotframework.javalib.annotation.RobotKeywords; @RobotKeywords @@ -47,7 +46,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); @@ -63,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/AdditionalKeywords/Verifiers.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java index f6181d3..edfecd4 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/Verifiers.java @@ -36,30 +36,37 @@ 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; +import java.util.concurrent.ExecutionException; + 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 { - @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" + "``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 { - 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 ); @@ -72,69 +79,93 @@ 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 { - 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 void 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 | 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); try { - RobotLog.info("Waiting for node \"" + locator + "\" to be visible, timeout=\"" + timeout + "\"."); - 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 void 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 | 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); try { - RobotLog.info("Waiting for node \"" + locator + "\" to be invisible, timeout=\"" + timeout + "\"."); - 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 void 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 | 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); try { - RobotLog.info("Waiting for node \"" + locator + "\" to be enabled, timeout=\"" + timeout + "\"."); - 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 void 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 | 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); try { - RobotLog.info("Waiting for node \"" + locator + "\" to be disabled, timeout=\"" + timeout + "\"."); - 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(""); } @@ -145,6 +176,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 +186,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 +196,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 +206,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 +216,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 +226,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,14 +236,41 @@ 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()); - } 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(() -> { + 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."); + 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()); + 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.equals("success")) throw new JavaFXLibraryNonFatalException(status); + RobotLog.info("Locator node is hoverable."); + } catch (InterruptedException | ExecutionException iee) { + RobotLog.trace("nodeShouldBeHoverable: failed in asyncFx thread"); + throw new JavaFXLibraryNonFatalException("Node Should Be Hoverable keyword failed: ", iee.getCause()); } } @@ -215,12 +279,42 @@ 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()); - throw new JavaFXLibraryNonFatalException("Expected \"" + locator + "\" to be not hoverable - failed!"); - } catch (AssertionError ae){ - // Was not hoverable, keyword should pass + 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."); + 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 { + 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.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("nodeShouldNotBeHoverable: failed in asyncFx thread"); + throw new JavaFXLibraryNonFatalException("Node Should Not Be Hoverable keyword failed: ", iee.getCause()); } } @@ -230,6 +324,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 +344,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 +362,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 +371,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 +380,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 +389,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 +469,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 +483,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 +497,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 +511,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 +526,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 8804c04..898beae 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/BoundsLocation.java @@ -18,6 +18,7 @@ package javafxlibrary.keywords.Keywords; import javafx.geometry.BoundingBox; +import javafx.geometry.Point2D; import javafx.geometry.Rectangle2D; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.utils.HelperFunctions; @@ -27,7 +28,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 +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(robot.bounds(minX, minY, width, height).query()); + return mapObject(new BoundingBox(minX, minY, width, height)); } catch (Exception e) { if ( e instanceof JavaFXLibraryNonFatalException ) throw e; @@ -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(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 33f7dfe..c0c09d8 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ClickRobot.java @@ -45,20 +45,19 @@ 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) { - 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); - + checkObjectArgumentNotNull(locator); 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); } } @@ -69,14 +68,14 @@ 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); + checkObjectArgumentNotNull(locator); 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); } } @@ -86,16 +85,15 @@ 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); - + checkObjectArgumentNotNull(locator); 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); } } @@ -107,10 +105,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()); } } @@ -146,10 +145,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 + "\""); + checkObjectInsideActiveWindow(x, y); return robot.clickOn(x, y, getMotion(motion), MouseButton.PRIMARY); } catch (Exception e) { if (e instanceof JavaFXLibraryNonFatalException) { @@ -164,8 +162,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 { + 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) { @@ -181,8 +179,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 { + 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 f4d66e3..b6c6342 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 { @@ -41,18 +44,31 @@ 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) { - Object target = checkClickTarget(locator); - RobotLog.info("Dragging from \"" + target + "\"" + " with button=\"" + button + "\""); - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "drag", target.getClass(), MouseButton.class); + checkObjectArgumentNotNull(locator); try { + 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()); } } @@ -60,18 +76,30 @@ 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) { - Object target = checkClickTarget(locator); - RobotLog.info("Dropping to \"" + target + "\""); - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "dropTo", target.getClass()); - + checkObjectArgumentNotNull(locator); try { + 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 (IllegalAccessException | InvocationTargetException e) { - throw new JavaFXLibraryNonFatalException("Could not execute drop to using locator \"" + locator + "\" " + - ": " + e.getCause().getMessage(), e); + } catch (InterruptedException | ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("Drop To target check failed for locator \"" + locator + "\" " + + ": " + iee.getCause()); + } catch (Exception e) { + throw new JavaFXLibraryNonFatalException("Could not execute drop to using locator \"" + locator + "\" " + + ": " + e.getCause()); } } @@ -106,13 +134,13 @@ 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) { - 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) { @@ -147,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 a19f80e..3d37c93 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 { @@ -103,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; @@ -150,7 +156,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."); @@ -162,8 +168,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" @@ -211,17 +215,23 @@ 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 FxRobotInterface writeTo(Object locator, String text) { - RobotLog.info("Writing \"" + text + "\" to " + locator); + public void writeTo(Object locator, String text) { + checkObjectArgumentNotNull(locator); try { - clickRobot.clickOn(locator,"DIRECT"); - return robot.write(text, sleepMillis); + 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("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); } } @@ -233,11 +243,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/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 27ba604..4016449 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 { @@ -48,16 +47,11 @@ 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) { - 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 = 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 9ef5c58..c0a8319 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,6 +30,8 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import static javafxlibrary.utils.HelperFunctions.*; + @RobotKeywords public class NodeLookup extends TestFxAdapter { @@ -45,23 +46,21 @@ 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) { - if (locator instanceof String) { - Node node = new Finder().find((String) locator); - 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()); + checkObjectArgumentNotNull(locator); try { - return HelperFunctions.mapObject(method.invoke(robot, locator)); + RobotLog.info("Getting root node of target \"" + locator + "\""); + if (locator instanceof String) { + Node node = objectToNode(locator); + return getRootNodeOf(node); + } + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "rootNode", locator.getClass()); + 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 b867321..9661de7 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/PointLocation.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,6 +29,8 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import static javafxlibrary.utils.HelperFunctions.*; + @RobotKeywords public class PointLocation extends TestFxAdapter { @@ -45,15 +46,13 @@ 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 = new Finder().find((String) locator); - - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "point", locator.getClass()); - + checkObjectArgumentNotNull(locator); try { - return HelperFunctions.mapObject(method.invoke(robot, locator)); + 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 mapObject(method.invoke(robot, locator)); } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("Could not execute point to using locator \"" + locator + "\": " + e.getCause().getMessage()); @@ -68,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 6101ec2..b89d16a 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,6 +29,8 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import static javafxlibrary.utils.HelperFunctions.*; + @RobotKeywords public class PointOffset extends TestFxAdapter { @@ -42,15 +43,14 @@ 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 = new Finder().find((String) locator); - - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "offset", - locator.getClass(), double.class, double.class); + checkObjectArgumentNotNull(locator); try { - return HelperFunctions.mapObject(method.invoke(robot, locator, offsetX, offsetY)); + 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 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/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..e89dd15 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScreenCapturing.java @@ -25,8 +25,8 @@ 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.apache.commons.io.IOUtils; import org.robotframework.javalib.annotation.ArgumentNames; import org.robotframework.javalib.annotation.RobotKeyword; import org.robotframework.javalib.annotation.RobotKeywords; @@ -44,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 @@ -65,38 +67,58 @@ 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(); - 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,logImage, mapObject); + } 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" + "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 | 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!"); - - RobotLog.info("Capturing screenshot from locator: \"" + locator + "\""); - Image image; - Bounds targetBounds = objectToBounds(locator); - String logPath; - + + "| ${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 { - image = robot.capture(targetBounds).getImage(); + RobotLog.info("Capturing screenshot from locator: \"" + locator + "\""); + Image image; + String logPath; 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(); @@ -131,25 +153,44 @@ public Object captureImage(Object locator, boolean logImage){ + ""); } } - return mapObject(image); - - } catch (IOException e) { - throw new JavaFXLibraryNonFatalException("Unable to take capture : \"" + locator + "\"", e); + 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) { + 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()); } } @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) { - Scene scene = (Scene) useMappedObject(new ConvenienceKeywords().getScene(locator)); - return this.captureImage(scene,true); + + "| ${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, logImage, mapObject); + } 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" 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..9675a15 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; @@ -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" @@ -78,24 +78,23 @@ public List listTargetWindows() { ) @ArgumentNames({"locator"}) public Object getWindow(Object locator) { - RobotLog.info("Getting window using locator \"" + 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 67f2d46..4194b5f 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; @@ -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" @@ -66,9 +66,9 @@ public Object getTargetWindow() { ) @ArgumentNames("locator") public void setTargetWindow(Object locator) { - RobotLog.info("Setting target window according to locator \"" + locator + "\""); - + checkObjectArgumentNotNull(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 +86,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 d2bd9c7..2baa83d 100644 --- a/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java +++ b/src/main/java/javafxlibrary/matchers/ExtendedNodeMatchers.java @@ -20,19 +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; -import static org.testfx.util.WaitForAsyncUtils.asyncFx; -import static org.testfx.util.WaitForAsyncUtils.waitForFxEvents; public class ExtendedNodeMatchers { @@ -50,22 +43,15 @@ 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("\""); - } }; } 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 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 506c7ed..9468551 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; @@ -49,87 +48,123 @@ 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.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.*; 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; -import static org.testfx.util.WaitForAsyncUtils.waitForFxEvents; +import static org.testfx.util.WaitForAsyncUtils.waitFor; 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) { - RobotLog.trace("Waiting until target \"" + target + "\" becomes existent, timeout=" - + timeout + ", timeUnit=" + timeUnit); - try { - waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) != null).get()); - Node node = asyncFx(() -> createFinder().find(target)).get(); + 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(() -> hasValidCoordinates(node)).get()); + 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; + } + else break; + } return node; + } catch (InterruptedException |ExecutionException iee) { + 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); } } public static void waitUntilDoesNotExists(String target, int timeout, String timeUnit) { - RobotLog.trace("Waiting until target \"" + target + "\" becomes non existent, timeout=" - + timeout + ", timeUnit=" + timeUnit); - try { - waitFor(timeout, getTimeUnit(timeUnit), () -> asyncFx(() -> createFinder().find(target) == null).get()); + RobotLog.trace("waitUntilDoesNotExists: Waiting until target \"" + target + "\" becomes non existent, timeout=" + + timeout + ", timeUnit=" + timeUnit); + 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); } } - // 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); - + public static Node waitUntilVisible(Object target, int timeout, String timeUnit) { try { - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isVisible()).matches(finalTarget)).get()); + 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, timeUnit); + } + final Object finalTarget = target; + // 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; @@ -141,17 +176,20 @@ 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); - + public static Node waitUntilNotVisible(Object target, int timeout, String timeUnit) { try { - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isInvisible()).matches(finalTarget)).get()); + 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, timeUnit); + } + final Object finalTarget = target; + // 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; @@ -163,16 +201,20 @@ 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); - + // TODO: Take same parameters as waitUntilExists in all waitUntil methods + public static Node waitUntilEnabled(Object target, int timeout, String timeUnit) { try { - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> (Matchers.is(isEnabled()).matches(finalTarget))== true).get()); + 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, timeUnit); + final Object finalTarget = target; + // 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; @@ -184,16 +226,19 @@ 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); - + public static Node waitUntilDisabled(Object target, int timeout, String timeUnit) { try { - waitFor(timeout, TimeUnit.SECONDS, () -> asyncFx(() -> Matchers.is(isDisabled()).matches(finalTarget) == true).get()); + 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, timeUnit); + final Object finalTarget = target; + // 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; @@ -333,7 +378,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())); } } @@ -477,61 +523,60 @@ 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) { - - 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"); + 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. " + + "To enable clicking outside of visible window bounds use keyword `Set Safe Clicking` with argument `off`"); + } + RobotLog.trace("Object is within active window"); } + } catch (Exception e) { + throw new JavaFXLibraryNonFatalException("object inside active window check failed: " + e.getMessage(), e); } - RobotLog.trace("Target location checks out OK, it is within active window"); } - public static void verifyClickLocationOnFront(Object object) { + public static void bringObjectsWindowToFront(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); } } public static Object checkClickTarget(Object target) { try { - if (target instanceof String || target instanceof Node) { - target = waitUntilEnabled(waitUntilVisible(target, waitUntilTimeout), waitUntilTimeout); - verifyClickLocationOnFront(target); + target = objectToNode(target); + 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!"); } - 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); } } @@ -549,7 +594,6 @@ 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)); } @@ -600,43 +644,48 @@ 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); + if (node == null) { + throw new JavaFXLibraryNonFatalException("unable to find node for query \"" + target + "\""); + } + return node; + } 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() + + 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) { - 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) { - Node node = waitUntilExists((String) object, waitUntilTimeout, "SECONDS"); - return robot.bounds(node).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) { @@ -1007,6 +1056,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/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/main/java/javafxlibrary/utils/finder/QueryParser.java b/src/main/java/javafxlibrary/utils/finder/QueryParser.java index 22f1760..5566cba 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/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 | 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/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickLocationTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CheckClickLocationTest.java index daa8897..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")); } @@ -66,11 +66,11 @@ 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); - 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/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/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 ce0d3d1..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 null"); + thrown.expectMessage("target object was empty (null)"); HelperFunctions.objectToNode(null); } } 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() { diff --git a/src/test/java/javafxlibrary/utils/finder/QueryParserTest.java b/src/test/java/javafxlibrary/utils/finder/QueryParserTest.java index be6b010..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); } @@ -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")); diff --git a/src/test/robotframework/acceptance/0_ClickRobotTest.robot b/src/test/robotframework/acceptance/0_ClickRobotTest.robot index 7b46658..803049b 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,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} 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} 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} 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 @@ -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} 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..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 @@ -345,7 +347,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/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/MiscTests.robot b/src/test/robotframework/acceptance/MiscTests.robot index c47866b..bb50b21 100644 --- a/src/test/robotframework/acceptance/MiscTests.robot +++ b/src/test/robotframework/acceptance/MiscTests.robot @@ -112,25 +112,31 @@ 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 - Set Timeout 3 ${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 - [Teardown] Set Timeout 0 Check That Element Is Not Hoverable - [Tags] smoke demo-set negative + [Tags] smoke demo-set negative hoverable Set Test Application javafxlibrary.testapps.TestClickRobot - Set Timeout 3 ${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 - [Teardown] Set Timeout 0 + 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 + 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 @@ -375,10 +381,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 +419,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 e36aa02..e0a74b3 100644 --- a/src/test/robotframework/acceptance/MoveRobotTest.robot +++ b/src/test/robotframework/acceptance/MoveRobotTest.robot @@ -87,12 +87,12 @@ 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 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 6350eff..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 any node with 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 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 From 8b3a17e8cfa066b13a95d0f01bc16352c99ecf64 Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Mon, 9 Nov 2020 13:03:46 +0200 Subject: [PATCH 11/21] fix moveTo keyword to use asyncFx thread, fixes #57 --- src/main/java/JavaFXLibrary.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index 13adf21..d7d5f6e 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -90,7 +90,6 @@ public class JavaFXLibrary extends AnnotationLibrary { add("launchSwingApplicationInSeparateThread"); add("logApplicationClasspath"); add("logSystemProperties"); - add("moveTo"); add("nodeShouldBeHoverable"); add("nodeShouldNotBeHoverable"); add("pushManyTimes"); From fd3eba14da2fe46ff7a544b9df1e79a0fe7392ad Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Mon, 9 Nov 2020 14:40:10 +0200 Subject: [PATCH 12/21] fix scroll keywords to do one tick at time from main thread, fix related tests --- src/main/java/JavaFXLibrary.java | 2 ++ .../keywords/Keywords/ScrollRobot.java | 25 ++++++++++++++++--- .../acceptance/ScrollRobotTest.robot | 8 +++--- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index d7d5f6e..9a62285 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -93,6 +93,8 @@ public class JavaFXLibrary extends AnnotationLibrary { add("nodeShouldBeHoverable"); add("nodeShouldNotBeHoverable"); add("pushManyTimes"); + add("scrollHorizontally"); + add("scrollVertically"); add("setImageLogging"); add("setSafeClicking"); add("setScreenshotDirectory"); diff --git a/src/main/java/javafxlibrary/keywords/Keywords/ScrollRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/ScrollRobot.java index 80e09c1..22af20e 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/ScrollRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/ScrollRobot.java @@ -25,6 +25,9 @@ import org.robotframework.javalib.annotation.ArgumentNames; import org.robotframework.javalib.annotation.RobotKeyword; import org.robotframework.javalib.annotation.RobotKeywords; +import java.util.concurrent.ExecutionException; +import static javafxlibrary.utils.HelperFunctions.sleepFor; +import static org.testfx.util.WaitForAsyncUtils.asyncFx; @RobotKeywords public class ScrollRobot extends TestFxAdapter { @@ -39,7 +42,13 @@ public class ScrollRobot extends TestFxAdapter { public void scrollVertically(String direction, int amount) { try { RobotLog.info("Scrolling \"" + direction + "\" by \"" + amount + "\" ticks."); - robot.scroll(amount, HelperFunctions.getVerticalDirection(direction)); + //Scrolling is done one tick at time from main thread as in asyncFx thread it would result only one visible scroll + for (int i = 0; i < amount; i++) { + asyncFx(() -> robot.scroll(1, HelperFunctions.getVerticalDirection(direction))).get(); + sleepFor(10); + } + } catch (InterruptedException | ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("Unable to scroll vertically!"); } catch (Exception e) { if(e instanceof JavaFXLibraryNonFatalException) throw e; @@ -62,9 +71,17 @@ public void scrollVertically(String direction, int amount) { public void scrollHorizontally(String direction, int amount) { try { RobotLog.info("Scrolling \"" + direction + "\" by \"" + amount + "\" ticks."); - robot.press(KeyCode.SHIFT); - robot.scroll(amount, HelperFunctions.getHorizontalDirection(direction)); - robot.release(KeyCode.SHIFT); + //Scrolling is done one tick at time from main thread as in asyncFx thread it would result only one visible scroll + for (int i = 0; i < amount; i++) { + asyncFx(() -> { + robot.press(KeyCode.SHIFT); + robot.scroll(1, HelperFunctions.getHorizontalDirection(direction)); + robot.release(KeyCode.SHIFT); + }).get(); + sleepFor(10); + } + } catch (InterruptedException | ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("Unable to scroll horizontally!"); } catch (Exception e) { if(e instanceof JavaFXLibraryNonFatalException) throw e; diff --git a/src/test/robotframework/acceptance/ScrollRobotTest.robot b/src/test/robotframework/acceptance/ScrollRobotTest.robot index cc114ed..e081994 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} 1 + Verify String ${VERTICAL_EVENTS} 25 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} 1 + Verify String ${VERTICAL_EVENTS} 25 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} 1 + Verify String ${HORIZONTAL_EVENTS} 25 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} 1 + Verify String ${HORIZONTAL_EVENTS} 10 *** Keywords *** Setup all tests From cd4549f76ffced8c6d084ef33eb31dd55bec1cec Mon Sep 17 00:00:00 2001 From: Sami Pesonen Date: Tue, 10 Nov 2020 15:26:06 +0200 Subject: [PATCH 13/21] fix moveTo in osx also --- src/main/java/JavaFXLibrary.java | 1 + .../keywords/Keywords/MoveRobot.java | 43 ++++++++++++++++--- .../robotframework/acceptance/FindTest.robot | 2 +- .../acceptance/MoveRobotTest.robot | 2 +- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index 9a62285..a7c4b4d 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -90,6 +90,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/Keywords/MoveRobot.java b/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java index 4016449..9d72377 100644 --- a/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java +++ b/src/main/java/javafxlibrary/keywords/Keywords/MoveRobot.java @@ -30,8 +30,10 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.concurrent.ExecutionException; import static javafxlibrary.utils.HelperFunctions.*; +import static org.testfx.util.WaitForAsyncUtils.asyncFx; @RobotKeywords public class MoveRobot extends TestFxAdapter { @@ -46,18 +48,49 @@ public class MoveRobot extends TestFxAdapter { + "| ${point} | Create Point | ${x} | ${y} | \n" + "| 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) { + public void moveTo(Object locator, String motion) { checkObjectArgumentNotNull(locator); try { RobotLog.info("Moving to target \"" + locator + "\" using motion: \"" + getMotion(motion) + "\""); + Object node; if (locator instanceof String) { - locator = objectToNode(locator); + 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."); + } else node = locator; + if (isMac()) { + // TODO: why asyncFx thread does not work in mac? + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "moveTo", node.getClass(), Motion.class); + method.invoke(robot, node, getMotion(motion)); + } else { + boolean success = asyncFx(() -> { + try { + Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "moveTo", node.getClass(), Motion.class); + method.invoke(robot, node, getMotion(motion)); + return true; + } catch (IllegalAccessException | InvocationTargetException e) { + RobotLog.trace("failed in asyncFx thread moveTo"); + return false; + } + }).get(); + if (!success) throw new JavaFXLibraryNonFatalException("moveTo: Could not execute move to using locator \"" + locator + "\" " + + "and motion " + motion); } - Method method = MethodUtils.getMatchingAccessibleMethod(robot.getClass(), "moveTo", locator.getClass(), Motion.class); - return (FxRobotInterface) method.invoke(robot, locator, getMotion(motion)); + } catch (InterruptedException | ExecutionException iee) { + throw new JavaFXLibraryNonFatalException("moveTo: Could not execute move to using locator \"" + locator + "\" " + + "and motion " + motion + " (asyncFx thread): " + iee.getCause()); + } catch (JavaFXLibraryNonFatalException e) { + throw e; } catch (IllegalAccessException | InvocationTargetException e) { throw new JavaFXLibraryNonFatalException("moveTo: Could not execute move to using locator \"" + locator + "\" " + - "and motion " + motion + ": " + e.getCause().getMessage(), e); + "and motion " + motion + ": " + e.getCause()); } } diff --git a/src/test/robotframework/acceptance/FindTest.robot b/src/test/robotframework/acceptance/FindTest.robot index 9c291e0..4264684 100644 --- a/src/test/robotframework/acceptance/FindTest.robot +++ b/src/test/robotframework/acceptance/FindTest.robot @@ -133,7 +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 + Wait Until Element Exists pseudo=hover ${result} Find pseudo=hover false ${root} Should Be Equal ${result} ${target} diff --git a/src/test/robotframework/acceptance/MoveRobotTest.robot b/src/test/robotframework/acceptance/MoveRobotTest.robot index e0a74b3..d9384c0 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 unable to find node for query "id=rectangleNOTfound" + Run Keyword And Expect Error Given locator "id=rectangleNOTfound" was not found. ... Move To id=rectangleNOTfound *** Keywords *** From f41626b77165f352c25810a1b1c13fefea939c34 Mon Sep 17 00:00:00 2001 From: Jari Parviainen Date: Fri, 22 Sep 2023 10:36:42 +0300 Subject: [PATCH 14/21] Java 8 version changes --- AUTHORS.txt | 1 + pom.xml | 15 +++++++++++++-- .../WaitForEventsInFxApplicationThreadTest.java | 4 ++-- .../matchers/InstanceOfMatcherTest.java | 4 ++-- .../HelperFunctionsTests/CallMethodTest.java | 4 ++-- .../HelperFunctionsTests/GetAllNodesTest.java | 4 ++-- .../HelperFunctionsTests/IsCompatibleTest.java | 4 ++-- .../HelperFunctionsTests/ObjectToNodeTest.java | 4 ++-- .../HelperFunctionsTests/ParseClassTest.java | 4 ++-- .../PrintTreeStructureTest.java | 4 ++-- .../WaitForProgressBarToFinishTest.java | 4 ++-- .../WaitUntilDisabledTest.java | 4 ++-- .../WaitUntilDoesNotExistsTest.java | 4 ++-- .../WaitUntilEnabledTest.java | 4 ++-- .../WaitUntilInvisibleTest.java | 4 ++-- .../WaitUntilVisibleTest.java | 4 ++-- 16 files changed, 42 insertions(+), 30 deletions(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index 9a5e4bc..d7c02be 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -9,3 +9,4 @@ Sakari Hoisko Dockerized linux env with X Juho Lehtonen Optimized docker environment. Juho Saarinen Package improvements, initial monocle support, screenshot bug fix Turo Soisenniemi Initial java agent support +Jari Parviainen Latest Java 8 version changes \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8a0a4ed..cfacef7 100644 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,12 @@ pasi.saikkonen@eficode.com Eficode http://www.eficode.com + + + Jari Parviainen + jari.parviainen@eficode.com + Eficode + http://www.eficode.com @@ -381,12 +387,17 @@ junit junit - 4.13.1 + 4.13.2 org.testfx testfx-core - 4.0.16-alpha + 4.0.17 + + + org.testfx + testfx-junit + 4.0.17 org.testfx diff --git a/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/WaitForEventsInFxApplicationThreadTest.java b/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/WaitForEventsInFxApplicationThreadTest.java index 440fa03..e79a329 100644 --- a/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/WaitForEventsInFxApplicationThreadTest.java +++ b/src/test/java/javafxlibrary/keywords/AdditionalKeywordsTests/ConvenienceKeywords/WaitForEventsInFxApplicationThreadTest.java @@ -19,7 +19,7 @@ import javafx.application.Platform; import javafx.scene.control.Button; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.keywords.AdditionalKeywords.ApplicationLauncher; import org.junit.Assert; @@ -28,7 +28,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -public class WaitForEventsInFxApplicationThreadTest extends TestFxAdapterTest { +public class WaitForEventsInFxApplicationThreadTest extends ApplicationTest { @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/src/test/java/javafxlibrary/matchers/InstanceOfMatcherTest.java b/src/test/java/javafxlibrary/matchers/InstanceOfMatcherTest.java index e513b16..40c4b48 100644 --- a/src/test/java/javafxlibrary/matchers/InstanceOfMatcherTest.java +++ b/src/test/java/javafxlibrary/matchers/InstanceOfMatcherTest.java @@ -19,11 +19,11 @@ import javafx.scene.control.Button; import javafx.scene.text.Text; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import org.junit.Assert; import org.junit.Test; -public class InstanceOfMatcherTest extends TestFxAdapterTest { +public class InstanceOfMatcherTest extends ApplicationTest { @Test public void matchesWithClass() { diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CallMethodTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CallMethodTest.java index bbc1d63..b5e7537 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CallMethodTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/CallMethodTest.java @@ -2,7 +2,7 @@ import javafx.scene.control.Button; import javafx.stage.Stage; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.utils.HelperFunctions; import org.junit.Assert; @@ -15,7 +15,7 @@ import static testutils.TestFunctions.setupStageInJavaFXThread; import static testutils.TestFunctions.waitForEventsInJavaFXThread; -public class CallMethodTest extends TestFxAdapterTest { +public class CallMethodTest extends ApplicationTest { @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetAllNodesTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetAllNodesTest.java index 704509f..f4959bc 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetAllNodesTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetAllNodesTest.java @@ -6,7 +6,7 @@ import javafx.scene.control.TextField; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.utils.HelperFunctions; import org.junit.Assert; import org.junit.Test; @@ -15,7 +15,7 @@ import java.util.Arrays; import java.util.List; -public class GetAllNodesTest extends TestFxAdapterTest { +public class GetAllNodesTest extends ApplicationTest { private List children = new ArrayList<>(); diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/IsCompatibleTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/IsCompatibleTest.java index 8a3acc2..e26c109 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/IsCompatibleTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/IsCompatibleTest.java @@ -1,14 +1,14 @@ package javafxlibrary.utils.HelperFunctionsTests; import javafx.scene.control.Button; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.utils.HelperFunctions; import org.junit.Assert; import org.junit.Test; import java.util.ArrayList; -public class IsCompatibleTest extends TestFxAdapterTest { +public class IsCompatibleTest extends ApplicationTest { @Test public void isCompatible_TestAllValidTypes() { diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java index f56b65b..e497fda 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ObjectToNodeTest.java @@ -2,7 +2,7 @@ import javafx.scene.Node; import javafx.scene.control.Button; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.utils.finder.Finder; import javafxlibrary.utils.HelperFunctions; @@ -15,7 +15,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -public class ObjectToNodeTest extends TestFxAdapterTest { +public class ObjectToNodeTest extends ApplicationTest { @Mocked private Finder finder; diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ParseClassTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ParseClassTest.java index 29c644a..28b241b 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ParseClassTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/ParseClassTest.java @@ -1,6 +1,6 @@ package javafxlibrary.utils.HelperFunctionsTests; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.utils.HelperFunctions; import org.junit.Assert; @@ -8,7 +8,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -public class ParseClassTest extends TestFxAdapterTest { +public class ParseClassTest extends ApplicationTest { @Rule public ExpectedException thrown = ExpectedException.none(); diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/PrintTreeStructureTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/PrintTreeStructureTest.java index 4e2c1c0..3bce7e3 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/PrintTreeStructureTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/PrintTreeStructureTest.java @@ -4,7 +4,7 @@ import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.utils.HelperFunctions; import org.junit.After; import org.junit.Assert; @@ -14,7 +14,7 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; -public class PrintTreeStructureTest extends TestFxAdapterTest { +public class PrintTreeStructureTest extends ApplicationTest { private final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); private final PrintStream originalOut = System.out; diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitForProgressBarToFinishTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitForProgressBarToFinishTest.java index 9101006..23ecf30 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitForProgressBarToFinishTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitForProgressBarToFinishTest.java @@ -1,7 +1,7 @@ package javafxlibrary.utils.HelperFunctionsTests; import javafx.scene.control.ProgressBar; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.utils.HelperFunctions; import org.junit.Before; @@ -9,7 +9,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -public class WaitForProgressBarToFinishTest extends TestFxAdapterTest { +public class WaitForProgressBarToFinishTest extends ApplicationTest { private ProgressBar progressBar; diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDisabledTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDisabledTest.java index bf60d5e..35fdad2 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDisabledTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDisabledTest.java @@ -2,7 +2,7 @@ import javafx.scene.Node; import javafx.scene.control.Button; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.exceptions.JavaFXLibraryTimeoutException; import javafxlibrary.utils.HelperFunctions; import mockit.Mock; @@ -13,7 +13,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -public class WaitUntilDisabledTest extends TestFxAdapterTest { +public class WaitUntilDisabledTest extends ApplicationTest { private Button button; diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDoesNotExistsTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDoesNotExistsTest.java index 15aaf8c..04e6891 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDoesNotExistsTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilDoesNotExistsTest.java @@ -2,7 +2,7 @@ import javafx.scene.Node; import javafx.scene.control.Button; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.exceptions.JavaFXLibraryTimeoutException; import javafxlibrary.utils.HelperFunctions; import javafxlibrary.utils.finder.Finder; @@ -15,7 +15,7 @@ import testutils.DelayedObject; import testutils.DelayedObjectRemoval; -public class WaitUntilDoesNotExistsTest extends TestFxAdapterTest { +public class WaitUntilDoesNotExistsTest extends ApplicationTest { @Mocked private Finder finder; private Button button; diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilEnabledTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilEnabledTest.java index 6fedc0b..1ce2f8a 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilEnabledTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilEnabledTest.java @@ -2,7 +2,7 @@ import javafx.scene.Node; import javafx.scene.control.Button; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.exceptions.JavaFXLibraryTimeoutException; import javafxlibrary.utils.HelperFunctions; @@ -11,7 +11,7 @@ import org.junit.*; import org.junit.rules.ExpectedException; -public class WaitUntilEnabledTest extends TestFxAdapterTest { +public class WaitUntilEnabledTest extends ApplicationTest { private Button button; diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilInvisibleTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilInvisibleTest.java index 71829d5..5df8796 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilInvisibleTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilInvisibleTest.java @@ -2,7 +2,7 @@ import javafx.scene.Node; import javafx.scene.control.Button; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.exceptions.JavaFXLibraryTimeoutException; import javafxlibrary.utils.HelperFunctions; import mockit.Mock; @@ -13,7 +13,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; -public class WaitUntilInvisibleTest extends TestFxAdapterTest { +public class WaitUntilInvisibleTest extends ApplicationTest { private Button button; diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilVisibleTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilVisibleTest.java index 5723628..19dc48e 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilVisibleTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/WaitUntilVisibleTest.java @@ -2,7 +2,7 @@ import javafx.scene.Node; import javafx.scene.control.Button; -import javafxlibrary.TestFxAdapterTest; +import org.testfx.framework.junit.ApplicationTest; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.exceptions.JavaFXLibraryTimeoutException; import javafxlibrary.utils.HelperFunctions; @@ -10,7 +10,7 @@ import org.junit.*; import org.junit.rules.ExpectedException; -public class WaitUntilVisibleTest extends TestFxAdapterTest { +public class WaitUntilVisibleTest extends ApplicationTest { private Button button; From a174761b0d4525fe969b199cdf65bad2bc0d80e3 Mon Sep 17 00:00:00 2001 From: Jari Parviainen Date: Wed, 27 Sep 2023 10:40:02 +0300 Subject: [PATCH 15/21] Updated version numbers in pom.xml --- pom.xml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 04f12db..1e37521 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ org.robotframework javafxlibrary jar - 0.7.1 + 0.7.1-SNAPSHOT UTF-8 1.44 @@ -86,7 +86,7 @@ org.apache.maven.plugins maven-source-plugin - 3.2.1 + 3.3.0 attach-sources @@ -99,7 +99,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.2.0 + 3.6.0 attach-javadocs @@ -113,7 +113,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.2.0 + 3.4.0 attach-artifacts @@ -139,7 +139,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.1.0 sign-artifacts @@ -191,7 +191,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + 3.1.2 -javaagent:${settings.localRepository}/org/jmockit/jmockit/${jmockit.version}/jmockit-${jmockit.version}.jar @@ -201,7 +201,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.8 + 1.6.13 true ossrh @@ -211,7 +211,7 @@ maven-compiler-plugin - 3.8.1 + 3.11.0 1.8 1.8 @@ -220,7 +220,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + 3.3.0 @@ -243,7 +243,7 @@ org.apache.maven.plugins maven-assembly-plugin - 3.3.0 + 3.6.0 package @@ -266,7 +266,7 @@ org.robotframework robotframework-maven-plugin - 1.7.1 + 2.1.0 acceptance tests @@ -326,7 +326,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.2.4 + 3.5.1 package @@ -376,7 +376,7 @@ org.apache.maven maven-model - 3.6.3 + 3.9.4 org.jmockit @@ -412,7 +412,7 @@ org.robotframework robotframework - 3.2.1 + 4.1.2 org.hamcrest @@ -427,12 +427,12 @@ org.apache.commons commons-lang3 - 3.11 + 3.13.0 commons-io commons-io - 2.7 + 2.13.0 From 4bc20bd6e94585619e9732d9954482e09998dc8c Mon Sep 17 00:00:00 2001 From: Jari Parviainen Date: Mon, 2 Oct 2023 09:56:13 +0300 Subject: [PATCH 16/21] Changes to the order of pom.xml tags --- pom.xml | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/pom.xml b/pom.xml index 1e37521..fa62627 100644 --- a/pom.xml +++ b/pom.xml @@ -267,6 +267,25 @@ org.robotframework robotframework-maven-plugin 2.1.0 + + + smoke + + + + not-ready + + + monocle-issue + + TRACE:INFO + false + + appJar:${project.build.directory}/${project.artifactId}*tests.jar + + acceptance tests @@ -274,22 +293,6 @@ run - - - smoke - - - not-ready - - - monocle-issue - - TRACE:INFO - false - - appJar:${project.build.directory}/${project.artifactId}*tests.jar - - documentation From 4d9f4a93cf511f26399a207ebf930195d9838e63 Mon Sep 17 00:00:00 2001 From: Jari Parviainen Date: Tue, 3 Oct 2023 12:09:13 +0300 Subject: [PATCH 17/21] Indent fixed in pom.xml --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index fa62627..4899915 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ Eficode http://www.eficode.com - + Jari Parviainen jari.parviainen@eficode.com Eficode From 0ff20de2bea751971194a6846ac9fce0e16404f1 Mon Sep 17 00:00:00 2001 From: Turo Soisenniemi Date: Tue, 4 Apr 2023 22:42:39 +0300 Subject: [PATCH 18/21] Cherry pick from java 11 support/Turmio --- pom.xml | 39 +++++++++++++++++-- src/main/java/JavaFXLibrary.java | 2 + .../ApplicationLauncher.java | 14 +++---- .../ConvenienceKeywords.java | 28 +++++++------ .../java/javafxlibrary/TestFxAdapterTest.java | 2 + .../TestKeyboardRobotController.java | 14 ++----- .../GetMouseButtonsTest.java | 5 ++- .../HelperFunctionsTests/MapObjectsTest.java | 2 +- .../robotframework/acceptance/MiscTests.robot | 2 +- 9 files changed, 73 insertions(+), 35 deletions(-) diff --git a/pom.xml b/pom.xml index 4899915..b80f258 100644 --- a/pom.xml +++ b/pom.xml @@ -213,8 +213,8 @@ maven-compiler-plugin 3.11.0 - 1.8 - 1.8 + 11 + 11 @@ -376,6 +376,39 @@ + + org.openjfx + javafx-graphics + 11 + win + + + org.openjfx + javafx-graphics + 11 + linux + + + org.openjfx + javafx-graphics + 11 + mac + + + org.openjfx + javafx-controls + 11 + + + org.openjfx + javafx-swing + 11 + + + org.openjfx + javafx-fxml + 11 + org.apache.maven maven-model @@ -405,7 +438,7 @@ org.testfx openjfx-monocle - 8u76-b04 + jdk-12.0.1+2 org.robotframework diff --git a/src/main/java/JavaFXLibrary.java b/src/main/java/JavaFXLibrary.java index 2b54eb9..20f4b64 100644 --- a/src/main/java/JavaFXLibrary.java +++ b/src/main/java/JavaFXLibrary.java @@ -128,6 +128,8 @@ public JavaFXLibrary(boolean headless) { if (headless) { System.setProperty("testfx.robot", "glass"); System.setProperty("testfx.headless", "true"); + System.setProperty("glass.platform", "Monocle"); + System.setProperty("monocle.platform", "Headless"); System.setProperty("prism.order", "sw"); System.setProperty("prism.text", "t2k"); TestFxAdapter.isHeadless = true; diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java index 57cc01f..3bf711e 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ApplicationLauncher.java @@ -112,14 +112,12 @@ private Class getMainClass(String appName) { } private void addPathToClassPath(String path) { - URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); RobotLog.info("Setting following path to classpath: " + path); try { - Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); - method.setAccessible(true); - method.invoke(classLoader, (new File(path)).toURI().toURL()); + URL[] urls = new URL[]{new File(path).toURI().toURL()}; + URLClassLoader classLoader = new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); } catch (Exception e) { throw new JavaFXLibraryFatalException("Problem setting the classpath: " + path, e); @@ -176,11 +174,11 @@ public void setToClasspath(String path, boolean failIfNotFound) { @RobotKeyword("Logs current classpath content") public void logApplicationClasspath() { try { - ClassLoader cl = ClassLoader.getSystemClassLoader(); - URL[] urls = ((URLClassLoader) cl).getURLs(); RobotLog.info("Printing out classpaths: \n"); - for (URL url : urls) { - RobotLog.info(url.getFile()); + + String classpathStr = System.getProperty("java.class.path"); + for (String classpathItem : classpathStr.split(System.getProperty("path.separator"))) { + RobotLog.info(classpathItem); } } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Unable to log application classpaths", e); diff --git a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java index 531cc7d..6615867 100644 --- a/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java +++ b/src/main/java/javafxlibrary/keywords/AdditionalKeywords/ConvenienceKeywords.java @@ -17,8 +17,8 @@ package javafxlibrary.keywords.AdditionalKeywords; -import com.sun.javafx.scene.control.skin.TableViewSkin; -import com.sun.javafx.scene.control.skin.VirtualFlow; +import javafx.scene.control.skin.TableViewSkin; +import javafx.scene.control.skin.VirtualFlow; import javafx.collections.ObservableList; import javafx.css.PseudoClass; import javafx.geometry.BoundingBox; @@ -279,7 +279,7 @@ public String getNodeText(Object locator) { @RobotKeyword("Returns image name and path of the node. \n\n" + "``locator`` is either a _query_ or _Object_ for a node whose getHeight method will be called, see " + "`3. Locating JavaFX Nodes`. \n\n" - + "Returns full image path by subsequently calling impl_getUrl -method. \n\n" + + "Returns full image path by subsequently calling getUrl -method. \n\n" + "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) { @@ -294,8 +294,7 @@ public String getNodeImageUrl(Object locator) { try { Object result = m.invoke(node, (Object) null); Image image = (Image) result; - RobotLog.trace("Calling deprecated method impl_getUrl() for image: \"" + image + "\""); - return image.impl_getUrl(); + return image.getUrl(); } catch (Exception e) { throw new JavaFXLibraryNonFatalException("Problem calling method: .getImage(): " + e.getMessage(), e); } @@ -477,15 +476,22 @@ public List getTableColumnValues(Object locator, int column) { public List getTableColumnCells(Object locator, int column) { checkObjectArgumentNotNull(locator); try { + List columnCells = new ArrayList<>(); + 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); - for (int i = vf.getFirstVisibleCell().getIndex(); i < vf.getLastVisibleCell().getIndex() + 1; i++) { - RobotLog.info("Index number: " + i); - columnCells.add(mapObject(vf.getCell(i).getChildrenUnmodifiable().get(column))); - } + Optional vf = table.getChildrenUnmodifiable().stream().filter(node -> node instanceof VirtualFlow).map(VirtualFlow.class::cast).findFirst(); + + vf.ifPresentOrElse(virtualFlow -> { + for (int i = virtualFlow.getFirstVisibleCell().getIndex(); i < virtualFlow.getLastVisibleCell().getIndex() + 1; i++) { + RobotLog.info("Index number: " + i); + columnCells.add(mapObject(virtualFlow.getCell(i).getChildrenUnmodifiable().get(column))); + } + }, () -> { + throw new JavaFXLibraryNonFatalException("Could not find VirtualFlow from Tableview!"); + }); + return mapObjects(columnCells); } catch (ClassCastException cce) { throw new JavaFXLibraryNonFatalException("Unable to handle argument as TableView!"); diff --git a/src/test/java/javafxlibrary/TestFxAdapterTest.java b/src/test/java/javafxlibrary/TestFxAdapterTest.java index e701f64..ab7f9f2 100644 --- a/src/test/java/javafxlibrary/TestFxAdapterTest.java +++ b/src/test/java/javafxlibrary/TestFxAdapterTest.java @@ -37,6 +37,8 @@ public FxRobotInterface getRobot() { public static void setupTests() { System.setProperty("testfx.robot", "glass"); System.setProperty("testfx.headless", "true"); + System.setProperty("glass.platform", "Monocle"); + System.setProperty("monocle.platform", "Headless"); System.setProperty("prism.order", "sw"); System.setProperty("prism.text", "t2k"); try { diff --git a/src/test/java/javafxlibrary/testapps/controllers/TestKeyboardRobotController.java b/src/test/java/javafxlibrary/testapps/controllers/TestKeyboardRobotController.java index 5b34556..a0a939f 100644 --- a/src/test/java/javafxlibrary/testapps/controllers/TestKeyboardRobotController.java +++ b/src/test/java/javafxlibrary/testapps/controllers/TestKeyboardRobotController.java @@ -17,7 +17,7 @@ package javafxlibrary.testapps.controllers; -import com.sun.javafx.scene.control.skin.TextAreaSkin; +import javafx.scene.control.skin.TextAreaSkin; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.Initializable; @@ -52,15 +52,9 @@ public void initialize(URL location, ResourceBundle resources) { @Override public void handle(KeyEvent event) { if (event.getCode().equals(KeyCode.TAB)) { - if (event.isShiftDown()) { - textArea.setText(textArea.getText() + " "); - textArea.positionCaret(textArea.getText().length()); - event.consume(); - } else { - TextAreaSkin skin = (TextAreaSkin) textArea.getSkin(); - skin.getBehavior().traverseNext(); - event.consume(); - } + textArea.setText(textArea.getText() + " "); + textArea.positionCaret(textArea.getText().length()); + event.consume(); } } }); diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetMouseButtonsTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetMouseButtonsTest.java index 6c4b434..ce86636 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetMouseButtonsTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/GetMouseButtonsTest.java @@ -3,6 +3,9 @@ import javafx.scene.input.MouseButton; import javafxlibrary.exceptions.JavaFXLibraryNonFatalException; import javafxlibrary.utils.HelperFunctions; + +import java.util.Arrays; + import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -50,7 +53,7 @@ public void getMouseButtons_MultipleValues() { 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]"); + thrown.expectMessage("\"HUGE_RED_ONE\" is not a valid MouseButton. Accepted values are: " + Arrays.asList(MouseButton.values())); HelperFunctions.getMouseButtons(new String[]{"HUGE_RED_ONE"}); } diff --git a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/MapObjectsTest.java b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/MapObjectsTest.java index 68ad01a..a00ae18 100644 --- a/src/test/java/javafxlibrary/utils/HelperFunctionsTests/MapObjectsTest.java +++ b/src/test/java/javafxlibrary/utils/HelperFunctionsTests/MapObjectsTest.java @@ -47,7 +47,7 @@ public void mapObjects_FromSet() { @Test public void mapObjects_FromQueue() { - Queue