From 06e493326396962530b7a52de168019bb4f0116d Mon Sep 17 00:00:00 2001 From: Jani Mikkonen Date: Wed, 3 Jul 2019 14:17:39 +0300 Subject: [PATCH] Add support for timeouts for attach applications Fixes #149 --- UIAutomationTest/delayed_start.bat | 4 +++ atests/attach_application.robot | 19 ++++++++++ atests/configuration.robot | 4 +-- atests/resource.robot | 2 ++ src/WhiteLibrary/keywords/application.py | 43 ++++++++++++++++++++--- src/WhiteLibrary/keywords/items/uiitem.py | 29 +++++---------- src/WhiteLibrary/utils/wait.py | 16 +++++++++ 7 files changed, 89 insertions(+), 28 deletions(-) create mode 100644 UIAutomationTest/delayed_start.bat create mode 100644 src/WhiteLibrary/utils/wait.py diff --git a/UIAutomationTest/delayed_start.bat b/UIAutomationTest/delayed_start.bat new file mode 100644 index 0000000..139fb3d --- /dev/null +++ b/UIAutomationTest/delayed_start.bat @@ -0,0 +1,4 @@ +pushd %~dp0 +ping 127.0.0.1 -n 6 +start /B bin\Debug\UIAutomationTest.exe +popd diff --git a/atests/attach_application.robot b/atests/attach_application.robot index 229a08c..fe873ac 100644 --- a/atests/attach_application.robot +++ b/atests/attach_application.robot @@ -14,7 +14,26 @@ Attach To A Running Application By Id Attach Application By Id ${test application id} Application Should Be Attached +Attach To A Running Application By Invalid Name + Run Keyword And Expect Error STARTS: Unable to locate application with identifier: ${INVALID TEST APPLICATION NAME} + ... Attach Application By Name ${INVALID TEST APPLICATION NAME} + +Attach To A Running Application By Invalid Name With Timeout + Run Keyword And Expect Error STARTS: Unable to locate application with identifier: ${INVALID TEST APPLICATION NAME} + ... Attach Application By Name ${INVALID TEST APPLICATION NAME} timeout=20 seconds + +Attach To Application With Timeout + [Setup] Delayed Start Test Application + [Timeout] 1 minute + [Teardown] Terminate Process delayed kill=true + Attach Application By Name ${TEST APPLICATION NAME} timeout=1 minute + Application Should Be Attached + Close Application + *** Keywords *** +Delayed Start Test Application + Start Process ${DELAYED TEST APPLICATION} alias=delayed stdout=${EXECDIR}${/}output${/}stdout.txt stderr=${EXECDIR}${/}output${/}stderr.txt + Start Test Application ${handle} = Start Process ${TEST APPLICATION} Set Test Variable ${handle} diff --git a/atests/configuration.robot b/atests/configuration.robot index ed2a70a..54ec485 100644 --- a/atests/configuration.robot +++ b/atests/configuration.robot @@ -6,7 +6,7 @@ Library OperatingSystem Library String Library WhiteLibrary Suite Setup None -Suite Teardown White Configuration Parameters Restore +Test Teardown White Configuration Parameters Restore *** Test Cases *** Set White Busy Timeout @@ -29,11 +29,9 @@ Set White Drag Step Count ${WHITE_DRAG_STEP_COUNT} Get White Drag Step Count Should Be Equal ${WHITE_DRAG_STEP_COUNT} ${3} - *** Keywords *** White Configuration Parameters Restore #These defaults are defined in White Stack source code. Set White Busy Timeout 5000 ms Set White Find Window Timeout 30000 ms Set White Double Click Interval 0 ms - diff --git a/atests/resource.robot b/atests/resource.robot index 00940ef..54b20d2 100644 --- a/atests/resource.robot +++ b/atests/resource.robot @@ -4,7 +4,9 @@ Library WhiteLibrary *** Variables *** ${TEST APPLICATION} ${EXECDIR}${/}UIAutomationTest${/}bin${/}Debug${/}UIAutomationTest.exe +${DELAYED TEST APPLICATION} ${EXECDIR}${/}UIAutomationTest${/}delayed_start.bat ${TEST APPLICATION NAME} UIAutomationTest +${INVALID TEST APPLICATION NAME} UIAutomationTestThatDoesNotExist ${TEST APPLICATION WINDOW TITLE} UI Automation Test Window ${TEST WHITE APPLICATION} ${EXECDIR}${/}WhiteTestApp${/}WpfTestApplication.exe ${TEST WHITE APPLICATION NAME} MainWindow diff --git a/src/WhiteLibrary/keywords/application.py b/src/WhiteLibrary/keywords/application.py index cc9a3a5..8c35fd6 100644 --- a/src/WhiteLibrary/keywords/application.py +++ b/src/WhiteLibrary/keywords/application.py @@ -1,8 +1,10 @@ from robot.api import logger # noqa: F401 #pylint: disable=unused-import +from robot.utils import timestr_to_secs from System.Diagnostics import ProcessStartInfo from WhiteLibrary.keywords.librarycomponent import LibraryComponent from WhiteLibrary.keywords.robotlibcore import keyword -from TestStack.White import Application +from WhiteLibrary.utils.wait import Wait +from TestStack.White import Application, WhiteException class ApplicationKeywords(LibraryComponent): @@ -26,27 +28,58 @@ def launch_application(self, sut_path, args=None): else: self.state.app = Application.Launch(sut_path) + @staticmethod + def _attach_application(sut_identifier, timeout=0): + exception_message = "Unable to locate application with identifier: {}".format(sut_identifier) + if timeout == 0: + try: + return Application.Attach(sut_identifier) + except WhiteException: + raise AssertionError(exception_message) + + # using hack due to 2.7 doesnt support nonlocal keyword + # and inner function cant modify primitive datatypes. + # aand im trying to avoid calling Application.Attach() + # multiple times as it allocates memory on python .net + # side. + + hack = {"sut": None} + + def search_application(): + try: + hack["sut"] = Application.Attach(sut_identifier) # noqa: F841 + return True + except WhiteException: + return False + + Wait.until_true(search_application, timeout, exception_message) + + return hack["sut"] + @keyword - def attach_application_by_name(self, sut_name): + def attach_application_by_name(self, sut_name, timeout=0): """Attaches a running application by name. ``sut_name`` is the name of the process. + ``timeout`` is the maximum time to wait as a Robot time string. (Optional) + Example: | Attach Application By Name | UIAutomationTest | """ - self.state.app = Application.Attach(sut_name) + self.state.app = self._attach_application(sut_name, timeout) @keyword - def attach_application_by_id(self, sut_id): + def attach_application_by_id(self, sut_id, timeout=0): """Attaches a running application by process id. ``sut_id`` is the application process id. + ``timeout`` is the maximum time to wait as a Robot time string. (Optional) Example: | Attach Application By Id | 12188 | """ - self.state.app = Application.Attach(int(sut_id)) + self.state.app = self._attach_application(int(sut_id), timeout) @keyword def close_application(self): diff --git a/src/WhiteLibrary/keywords/items/uiitem.py b/src/WhiteLibrary/keywords/items/uiitem.py index 62566da..c275f41 100644 --- a/src/WhiteLibrary/keywords/items/uiitem.py +++ b/src/WhiteLibrary/keywords/items/uiitem.py @@ -3,7 +3,7 @@ from WhiteLibrary.keywords.librarycomponent import LibraryComponent from WhiteLibrary.keywords.robotlibcore import keyword from WhiteLibrary.utils.click import Clicks -import time +from WhiteLibrary.utils.wait import Wait class UiItemKeywords(LibraryComponent): @@ -98,10 +98,10 @@ def wait_until_item_exists(self, locator, timeout): See `Waiting and timeouts` for more information about waiting in WhiteLibrary. """ - self._wait_until_true(lambda: self._item_exists(locator), - timeout, - u"Item with locator '{}' did not exist within {} seconds" - .format(locator, timestr_to_secs(timeout))) + Wait.until_true(lambda: self._item_exists(locator), + timeout, + u"Item with locator '{}' did not exist within {} seconds" + .format(locator, timestr_to_secs(timeout))) @keyword def wait_until_item_does_not_exist(self, locator, timeout): @@ -116,22 +116,11 @@ def wait_until_item_does_not_exist(self, locator, timeout): See `Waiting and timeouts` for more information about waiting in WhiteLibrary. """ - self._wait_until_true(lambda: not self._item_exists(locator), - timeout, - u"Item with locator '{}' still existed after {} seconds" - .format(locator, timestr_to_secs(timeout))) + Wait.until_true(lambda: not self._item_exists(locator), + timeout, + u"Item with locator '{}' still existed after {} seconds" + .format(locator, timestr_to_secs(timeout))) def _item_exists(self, locator): search_criteria = self.state._get_search_criteria(locator) return self.state.window.Exists(search_criteria) - - @staticmethod - def _wait_until_true(condition, timeout, error_msg): - timeout = timestr_to_secs(timeout) - max_wait = time.time() + timeout - while True: - if condition(): - break - if time.time() > max_wait: - raise AssertionError(error_msg) - time.sleep(0.1) diff --git a/src/WhiteLibrary/utils/wait.py b/src/WhiteLibrary/utils/wait.py new file mode 100644 index 0000000..79a5575 --- /dev/null +++ b/src/WhiteLibrary/utils/wait.py @@ -0,0 +1,16 @@ +import time +from robot.utils import timestr_to_secs + + +class Wait: + @staticmethod + def until_true(condition, timeout, error_msg): + """Helper to wait until given condition is met.""" + timeout = timestr_to_secs(timeout) + max_wait = time.time() + timeout + while True: + if condition(): + break + if time.time() > max_wait: + raise AssertionError(error_msg) + time.sleep(0.1)