diff --git a/AngularJSLibrary/__init__.py b/AngularJSLibrary/__init__.py index 2b32da3..965b2e9 100644 --- a/AngularJSLibrary/__init__.py +++ b/AngularJSLibrary/__init__.py @@ -1,10 +1,14 @@ from robot.api import logger -from robot.libraries.BuiltIn import BuiltIn +from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError from robot.utils import timestr_to_secs from selenium.webdriver.support.ui import WebDriverWait from selenium.common.exceptions import TimeoutException from SeleniumLibrary.locators import ElementFinder +try: + from exceptions import AttributeError +except ImportError: + pass import time js_wait_for_angularjs = """ @@ -24,6 +28,12 @@ var waiting = true; var callback = function () {waiting = false;} var el = document.querySelector(arguments[0]); + if (!el) { + throw new Error('Unable to find root selector that is given by importing the library using "' + + arguments[0] + + '". Please refer to the AngularJS library documentation' + + ' for more information on how to resolve this error.') + } if (window.angular && !(window.angular.version && window.angular.version.major > 1)) { /* ng1 */ @@ -85,6 +95,14 @@ def stripcurly(binding): def is_boolean(item): return isinstance(item,bool) +def get_driver_obj(lib): + try: + driver_obj = lib._current_browser() + except AttributeError: + driver_obj = lib.driver + + return driver_obj + class ngElementFinder(ElementFinder): def __init__(self, root_selector, ignore_implicit_angular_wait=False): super(ngElementFinder, self).__init__(self._s2l) @@ -98,8 +116,8 @@ def find(self, locator, tag=None, first_only=True, required=True, if not self.ignore_implicit_angular_wait: try: - WebDriverWait(self._s2l._current_browser(), timeout, 0.2)\ - .until_not(lambda x: self._s2l._current_browser().execute_script(js_wait_for_angular, self.root_selector)) + WebDriverWait(self._sldriver, timeout, 0.2)\ + .until_not(lambda x: self._sldriver.execute_script(js_wait_for_angular, self.root_selector)) except TimeoutException: pass elements = ElementFinder.find(self, locator, tag, first_only, required, @@ -111,10 +129,10 @@ def _find_by_default(self, criteria, tag, constraints, parent): criteria = stripcurly(criteria) return self._find_by_binding(criteria, tag, constraints, parent) else: - ElementFinder._find_by_default(self, criteria, tag, constraints, parent) + return ElementFinder._find_by_default(self, criteria, tag, constraints, parent) def _find_by_binding(self, criteria, tag, constraints, parent): - return self._s2l._current_browser().execute_script(""" + return self._sldriver.execute_script(""" var binding = '%s'; var bindings = document.getElementsByClassName('ng-binding'); var matches = []; @@ -135,12 +153,23 @@ def _find_by_binding(self, criteria, tag, constraints, parent): @property def _s2l(self): - return BuiltIn().get_library_instance('SeleniumLibrary') + try: + return BuiltIn().get_library_instance('SeleniumLibrary') + except RobotNotRunningError: + from SeleniumLibrary import SeleniumLibrary + return SeleniumLibrary() + + @property + def _sldriver(self): + try: + return self._s2l._current_browser() + except AttributeError: + return self._s2l.driver class AngularJSLibrary: ROBOT_LIBRARY_SCOPE = 'GLOBAL' - ROBOT_LIBRARY_VERSION = '0.0.7dev1' + ROBOT_LIBRARY_VERSION = '0.0.11dev1' def __init__(self, root_selector=None, @@ -149,21 +178,29 @@ def __init__(self, ): """AngularJSLibrary can be imported with optional arguments. - `root_selector` is the locator of the root angular object. If none is given it defaults to `[ng-app]`. + ``root_selector`` is the locator of the root angular object. If none is given it defaults to ``[ng-app]``. + For more information please refer to the following documentation: - $rootElement - AngularJS API documentation - https://docs.angularjs.org/api/ng/service/$rootElement - ngApp - AngularJS API documentation - https://docs.angularjs.org/api/ng/directive/ngApp - Not Yet Implemented - `implicit_angular_wait` is the implicit timeout that AngularJS library + $rootElement - [https://code.angularjs.org/snapshot-stable/docs/api/ng/service/$rootElement|AngularJS API documentation] + + ngApp - [https://code.angularjs.org/snapshot-stable/docs/api/ng/directive/ngApp|AngularJS API documentation] + + Not Yet Implemented - ``implicit_angular_wait`` is the implicit timeout that AngularJS library waits for angular to finish rendering and waits for any outstanding $http calls. - `ignore_implicit_angular_wait` is a flag which when set to True the AngularJS Library will not wait + ``ignore_implicit_angular_wait`` is a flag which when set to True the AngularJS Library will not wait for Angular $timeouts nor $http calls to complete when finding elements by locator. As noted in the Protractor documentation "this should be used only when necessary, such as when a page continuously - polls an API using $timeout." The default value is False. + polls an API using $timeout." The default value is False. Note, up through the current version, there is a + discrepancy between the ``Set Ignore Implicit Angular Wait`` keyword argument and the equivalent import library argument. The ``Set Ignore Implicit Angular Wait`` keyword has a strict + requirement that the ``ignore`` argument must be a Python boolean where as the imported library argument + accepts and Robot Framework Boolean arguments as outlined in the BuiltIn Library documentation. + This discrepancy may be resolved in a future release. Examples: - | Library `|` AngularJSLibrary `|` ignore_implicit_angular_wait=${true} | # Will not wait for angular syncronization + | Library | AngularJSLibrary | root_selector=[ng-version] | # Use [ng-version] as root element selector instead of the default [ng-app] | + | Library | AngularJSLibrary | ignore_implicit_angular_wait=${true} | # Will not wait for angular syncronization | """ @@ -190,7 +227,14 @@ def __init__(self, # Wait For Angular def wait_for_angular(self, timeout=None, error=None): + """ + An explicit wait allowing Angular queue to empty. + With the implicit wait functionality it is expected that most of the + situations where waiting is needed will be handled "automatically" by + the "hidden" implicit wait. Thus it is expected that this keyword will + be rarely used. + """ # Determine timeout and error timeout = timeout or self._s2l.get_selenium_timeout() timeout = timestr_to_secs(timeout) @@ -198,8 +242,8 @@ def wait_for_angular(self, timeout=None, error=None): 'the page after specified timeout.') try: - WebDriverWait(self._s2l._current_browser(), timeout, 0.2)\ - .until_not(lambda x: self._s2l._current_browser().execute_script(js_wait_for_angular, self.root_selector)) + WebDriverWait(self._sldriver, timeout, 0.2)\ + .until_not(lambda x: self._sldriver.execute_script(js_wait_for_angular, self.root_selector)) except TimeoutException: pass #if self.trackOutstandingTimeouts: @@ -210,6 +254,16 @@ def wait_for_angular(self, timeout=None, error=None): #raise TimeoutException(error) def set_ignore_implicit_angular_wait(self, ignore): + """ + Turns off the implicit wait by setting ``ignore`` to ${True}. The + implicit wait can be re-enabled by setting ``ignore`` to ${False}. + Note the value for ``ignore`` must be a Python boolean, meaning + either ${True} or ${False} or equivalent, for this + keyword. + + This is helpful when navigating between a Angular site and a + non-angular website within the same script. + """ if not is_boolean(ignore): raise TypeError("Ignore must be boolean, got %s." % type_name(ignore)) @@ -219,7 +273,7 @@ def set_ignore_implicit_angular_wait(self, ignore): # Locators def _find_by_binding(self, browser, criteria, tag, constrains): - return self._s2l._current_browser().execute_script(""" + return self._sldriver.execute_script(""" var binding = '%s'; var bindings = document.getElementsByClassName('ng-binding'); var matches = []; @@ -242,7 +296,7 @@ def _find_by_model(self, parent, criteria, tag, constraints): prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-']#, 'ng\\:'] for prefix in prefixes: selector = '[%smodel="%s"]' % (prefix, criteria) - elements = self._s2l._current_browser().execute_script("""return document.querySelectorAll('%s');""" % selector); + elements = self._sldriver.execute_script("""return document.querySelectorAll('%s');""" % selector); if len(elements): return ElementFinder(self._s2l)._filter_elements(elements, tag, constraints) raise ValueError("Element locator '" + criteria + "' did not match any elements.") @@ -251,7 +305,7 @@ def _find_by_ng_repeater(self, parent, criteria, tag, constraints): repeater_row_col = self._parse_ng_repeat_locator(criteria) js_repeater_str = self._reconstruct_js_locator(repeater_row_col) - elements = self._s2l._current_browser().execute_script( + elements = self._sldriver.execute_script( js_repeater_min + """var ng_repeat = new byRepeaterInner(true);""" + """return ng_repeat%s.getElements();""" % (js_repeater_str), @@ -266,7 +320,7 @@ def _find_by_ng_repeater(self, parent, criteria, tag, constraints): # Helper Methods def _exec_js(self, code): - return self._s2l._current_browser().execute_script(code) + return self._sldriver.execute_script(code) def _parse_ng_repeat_locator(self, criteria): def _startswith(str,sep): @@ -347,4 +401,15 @@ def _reconstruct_js_locator(self, loc_dict): @property def _s2l(self): - return BuiltIn().get_library_instance('SeleniumLibrary') + try: + return BuiltIn().get_library_instance('SeleniumLibrary') + except RobotNotRunningError: + from SeleniumLibrary import SeleniumLibrary + return SeleniumLibrary() + + @property + def _sldriver(self): + try: + return self._s2l._current_browser() + except AttributeError: + return self._s2l.driver diff --git a/AngularJSLibrary/angular.robot b/AngularJSLibrary/angular.robot index effc9e6..a5f1fcf 100644 --- a/AngularJSLibrary/angular.robot +++ b/AngularJSLibrary/angular.robot @@ -183,7 +183,14 @@ Should Return All Elements When Unmodified [Setup] Start ng-repeat Test Locator Should Match X Times repeater=bloop in days 15 +Should Locate Element Using Default Strategy + Click Element disabledButton +Should Locate Element Using id= Strategy + Click Element id=disabledButton + +Should Locate Element Using id: Strategy + Click Element id:disabledButton *** Keywords *** Start Angular Test diff --git a/AngularJSLibrary/angular_wait.robot b/AngularJSLibrary/angular_wait.robot index 725fdd1..303bdca 100644 --- a/AngularJSLibrary/angular_wait.robot +++ b/AngularJSLibrary/angular_wait.robot @@ -1,118 +1,143 @@ *** Settings *** -Test Setup Go To http://${SERVER}/testapp/ng1/alt_root_index.html#/async +Suite Setup Go To Async Page and Wait +Test Setup Go To Async Page and Wait Resource ../resource.robot Library AngularJSLibrary +Documentation This test suite for validating the waiting for angular functionality +... Based off of protractor\spec\basic\synchronize_spec.js + *** Test Cases *** Waits For Http Calls [Documentation] `Wait For Angular` should delay for 2 seconds. - Wait For Angular Element Text Should Be binding=slowHttpStatus not started - Click Button css=[ng-click="slowHttp()"] Wait For Angular timeout=20sec + + Element Text Should Be binding=slowHttpStatus done + +Implicitly Waits For Http Calls + [Documentation] Second `Element Text Should Be` (done) should delay for 2 seconds. + Element Text Should Be binding=slowHttpStatus not started + Click Button css=[ng-click="slowHttp()"] Element Text Should Be binding=slowHttpStatus done +Implicitly Waits For Http Calls (without Binding locators) + [Documentation] Second `Element Text Should Be` (done) should delay for 2 seconds. + Element Text Should Be css=[ng-bind="slowHttpStatus"] not started + Click Button css=[ng-click="slowHttp()"] + Element Text Should Be css=[ng-bind="slowHttpStatus"] done + Waits For Long Javascript Execution [Documentation] This test will take variable amount of time but should not ... take more than about five seconds. - Wait For Angular Element Text Should Be binding=slowFunctionStatus not started - Click Button css=[ng-click="slowFunction()"] Wait For Angular + Element Text Should Be binding=slowFunctionStatus done +Implicitly Waits For Long Javascript Execution + [Documentation] This test will take variable amount of time but should not + ... take more than about five seconds. + + Element Text Should Be css=[ng-bind="slowFunctionStatus"] not started + Click Button css=[ng-click="slowFunction()"] + Element Text Should Be css=[ng-bind="slowFunctionStatus"] done + DOES NOT wait for timeout [Documentation] The `Wait For Angular` keyword should return immediately ... and not wait for a javascript timeout. - Wait For Angular Element Text Should Be binding=slowTimeoutStatus not started - Click Button css=[ng-click="slowTimeout()"] Wait For Angular + Element Text Should Be binding=slowTimeoutStatus pending... +Implicitly DOES NOT wait for timeout + [Documentation] The second `Element Text Should Be` keyword (pending...) + ... should return immediately and not wait for a javascript timeout. + Element Text Should Be css=[ng-bind="slowTimeoutStatus"] not started + Click Button css=[ng-click="slowTimeout()"] + Element Text Should Be css=[ng-bind="slowTimeoutStatus"] pending... + Waits For $timeout - [Documentation] Test should take around 4 seconds. - Wait For Angular + [Documentation] `Wait For Angular` should delay for 4 seconds. Element Text Should Be binding=slowAngularTimeoutStatus not started - Click Button css=[ng-click="slowAngularTimeout()"] Wait For Angular timeout=30sec + Element Text Should Be binding=slowAngularTimeoutStatus done +Implicitly Waits For $timeout + [Documentation] Second `Element Text Should Be` (done) should delay for 4 seconds. + Element Text Should Be css=[ng-bind="slowAngularTimeoutStatus"] not started + Click Button css=[ng-click="slowAngularTimeout()"] + Element Text Should Be css=[ng-bind="slowAngularTimeoutStatus"] done + Waits For $timeout Then A Promise - [Documentation] Test should take around 4 seconds. - Wait For Angular + [Documentation] `Wait For Angular` should delay for around 4 seconds. Element Text Should Be binding=slowAngularTimeoutPromiseStatus not started - Click Button css=[ng-click="slowAngularTimeoutPromise()"] Wait For Angular timeout=30sec + Element Text Should Be binding=slowAngularTimeoutPromiseStatus done +Implicitly Waits For $timeout Then A Promise + [Documentation] Second `Element Text Should Be` (done) should delay for around 4 seconds. + Element Text Should Be css=[ng-bind="slowAngularTimeoutPromiseStatus"] not started + Click Button css=[ng-click="slowAngularTimeoutPromise()"] + Element Text Should Be css=[ng-bind="slowAngularTimeoutPromiseStatus"] done + Waits For Long Http Call Then A Promise [Documentation] `Wait For Angular` should delay for 2 seconds. - Wait For Angular Element Text Should Be binding=slowHttpPromiseStatus not started - Click Button css=[ng-click="slowHttpPromise()"] Wait For Angular timeout=30sec + Element Text Should Be binding=slowHttpPromiseStatus done +Implicitly Waits For Long Http Call Then A Promise + [Documentation] Second `Element Text Should Be` (done) should delay for 2 seconds. + Element Text Should Be css=[ng-bind="slowHttpPromiseStatus"] not started + Click Button css=[ng-click="slowHttpPromise()"] + Element Text Should Be css=[ng-bind="slowHttpPromiseStatus"] done + Waits For Slow Routing Changes - Wait For Angular + [Documentation] `Wait For Angular` should delay for around 5 seconds. Element Text Should Be binding=routingChangeStatus not started - Click Button css=[ng-click="routingChange()"] Wait For Angular timeout=30sec + + Page Should Contain polling mechanism + +Implicitly Waits For Slow Routing Changes + [Documentation] Second `Element Text Should Be` (done) should delay for around 5 seconds. + Element Text Should Be binding=routingChangeStatus not started + Click Button css=[ng-click="routingChange()"] Page Should Contain polling mechanism Waits For Slow Ng-Include Templates To Load - Wait For Angular + [Documentation] `Wait For Angular` should delay for around 2 seconds. Element Text Should Be css=.included fast template contents - Click Button css=[ng-click="changeTemplateUrl()"] Wait For Angular timeout=30sec + Element Text Should Be css=.included slow template contents -Wait Times Out - Wait For Angular - Element Text Should Be binding=slowAngularTimeoutStatus not started - - Click Button css=[ng-click="slowAngularTimeout()"] - - Run Keyword And Expect Error * Wait For Angular timeout=1sec - -Log Pending Http Calls - Wait For Angular - Element Text Should Be binding=slowHttpPromiseStatus not started - - Click Button css=[ng-click="slowHttpPromise()"] - - Run Keyword And Expect Error * Wait For Angular timeout=1sec - -Implicit Wait For Angular On Timeout - Wait For Angular - - Click Button css=[ng-click="slowAngularTimeout()"] - - Click Button css=[ng-click="slowAngularTimeoutHideButton()"] - -Implicit Wait For Angular On Timeout With Promise - Wait For Angular - - Click Button css=[ng-click="slowAngularTimeoutPromise()"] - - Click Button css=[ng-click="slowAngularTimeoutPromiseHideButton()"] +Implicitly Waits For Slow Ng-Include Templates To Load + [Documentation] Second `Element Text Should Be` (done) should delay for around 2 seconds. + Element Text Should Be css=.included fast template contents + Click Button css=[ng-click="changeTemplateUrl()"] + Element Text Should Be css=.included slow template contents Toggle Implicit Wait For Angular Flag Element Should Not Be Visible css=[ng-click="slowAngularTimeoutHideButton()"] @@ -134,4 +159,9 @@ Toggle Implicit Wait For Angular Flag Click Button css=[ng-click="slowAngularTimeoutHideButton()"] - Element Should Not Be Visible css=[ng-click="slowAngularTimeoutHideButton()"] \ No newline at end of file + Element Should Not Be Visible css=[ng-click="slowAngularTimeoutHideButton()"] + +*** Keywords *** +Go To Async Page and Wait + Go To http://${SERVER}/testapp/ng1/alt_root_index.html#/async + Wait For Angular \ No newline at end of file diff --git a/AngularJSLibrary/demo_ngcdk_dialog.robot b/AngularJSLibrary/demo_ngcdk_dialog.robot new file mode 100644 index 0000000..59c8ba0 --- /dev/null +++ b/AngularJSLibrary/demo_ngcdk_dialog.robot @@ -0,0 +1,12 @@ +*** Settings *** +Library SeleniumLibrary +Library AngularJSLibrary root_selector=material-docs-app + +*** Test Cases *** +Add Favorite Animal To Dialog + Open Browser https://material.angular.io/cdk/dialog/examples Chrome + Input Text //input[@for='dialog-user-name'] Robot Framework + Click Button Pick one + Input Text //input[@for='favorite-animal'] Aibo + Click Button OK + Element Text Should Be //cdk-dialog-overview-example/ol/li[3] You chose: Aibo \ No newline at end of file diff --git a/AngularJSLibrary/demo_phonecat.robot b/AngularJSLibrary/demo_phonecat.robot new file mode 100644 index 0000000..8ce76eb --- /dev/null +++ b/AngularJSLibrary/demo_phonecat.robot @@ -0,0 +1,10 @@ +*** Settings *** +Library SeleniumLibrary +Library AngularJSLibrary root_selector=[ng-app] + +*** Test Cases *** +Search Through The Phone Catalog For Samsung Phones + Open Browser http://angular.github.io/angular-phonecat/step-14/app Chrome + Input Text //input Samsung + Click Link Samsung Galaxy Tab™ + Element Text Should Be css:phone-detail h1 Samsung Galaxy Tab™ \ No newline at end of file diff --git a/AngularJSLibrary/demo_waitforangular.robot b/AngularJSLibrary/demo_waitforangular.robot new file mode 100644 index 0000000..b799e84 --- /dev/null +++ b/AngularJSLibrary/demo_waitforangular.robot @@ -0,0 +1,13 @@ +*** Settings *** +Library SeleniumLibrary +Library AngularJSLibrary ignore_implicit_angular_wait=True + +*** Test Cases *** +Search Through The Phone Catalog For Samsung Phones + Open Browser http://angular.github.io/angular-phonecat/step-14/app Chrome + Wait For Angular + Input Text //input Samsung + Wait For Angular + Click Link Samsung Galaxy Tab™ + Wait For Angular + Element Text Should Be css:phone-detail h1 Samsung Galaxy Tab™ \ No newline at end of file diff --git a/AngularJSLibrary/test_angular2.robot b/AngularJSLibrary/test_angular2.robot index a6ca1a7..1a74669 100644 --- a/AngularJSLibrary/test_angular2.robot +++ b/AngularJSLibrary/test_angular2.robot @@ -1,5 +1,5 @@ *** Settings *** -Library Selenium2Library +Library SeleniumLibrary Library AngularJSLibrary *** Test Case *** diff --git a/AngularJSLibrary/testserver.py.patch b/AngularJSLibrary/testserver.py.patch new file mode 100644 index 0000000..8aa66b0 --- /dev/null +++ b/AngularJSLibrary/testserver.py.patch @@ -0,0 +1,49 @@ +diff --git a/atest/resources/testserver/testserver.py b/atest/resources/testserver/testserver.py +index 565c650..8e3d82b 100644 +--- a/atest/resources/testserver/testserver.py ++++ b/atest/resources/testserver/testserver.py +@@ -3,6 +3,7 @@ + + import os + import sys ++from time import sleep + + from http.client import HTTPConnection + from http.server import SimpleHTTPRequestHandler, HTTPServer +@@ -21,6 +22,36 @@ class StoppableHttpRequestHandler(SimpleHTTPRequestHandler): + def do_POST(self): + self.do_GET() + ++ def do_GET(self): ++ """Response pages for Angular tests. ++ ++ Added by AngularJSLibrary ++ """ ++ if self.path.endswith('/fastcall'): ++ self.send_response(200) ++ self.send_header('Content-type', 'text/html') ++ self.end_headers() ++ self.wfile.write('done') ++ elif self.path.endswith('/slowcall'): ++ sleep(2) ++ self.send_response(200) ++ self.send_header('Content-type', 'text/html') ++ self.end_headers() ++ self.wfile.write('finally done') ++ elif self.path.endswith('/fastTemplateUrl'): ++ self.send_response(200) ++ self.send_header('Content-type', 'text/html') ++ self.end_headers() ++ self.wfile.write(b'fast template contents') ++ elif self.path.endswith('/slowTemplateUrl'): ++ sleep(2) ++ self.send_response(200) ++ self.send_header('Content-type', 'text/html') ++ self.end_headers() ++ self.wfile.write(b'slow template contents') ++ else: ++ SimpleHTTPRequestHandler.do_GET(self) ++ + + class ThreadingHttpServer(ThreadingMixIn, HTTPServer): + pass diff --git a/CHANGES.rst b/CHANGES.rst index 1f5a939..c3cf2e2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,9 +1,50 @@ Changelog ========= -0.0.7 (unreleased) +0.0.10 (2019-07-31) +--------- +Changes: + +- Updated library and keyword documentation. + [aaltat][emanlove] + +- Added error and informative message when unable to find root element or root component. + [anthonyfromtheuk][HelioGuilherme66][emanlove] + +- Modified for Python 3 compatibility + [emanlove] + +- Documented discrepancy between the ``Set Ignore Implicit Angular Wait`` keyword argument and the equivalent import library argument. + [HelioGuilherme66][aaltat][emanlove] + +- Update for compatibility with SeleniumLibrary 4.0. + [aaltat][emanlove] + +- Fixed major issue with setup test environment under Windows documentation. + [emanlove] + +0.0.9 (2018-09-08) ------------------ Fixes: +- Fixed issue when importing library into RIDE. + [pekkaklarck][emanlove] + +0.0.8 (2018-08-03) +------------------ +Fixes: + +- Fixed issue when no locator strategy was specified. + [emanlove] + +0.0.7 (2018-03-31) +------------------ +Changes: + +- Added support for SeleniumLibrary and dropped support for Selenium2Library. + [emanlove] + +Fixes: + - [Minor] Corrected error message. [emanlove] diff --git a/DEVELOPERS.rst b/DEVELOPERS.rst index c8e6c05..2df97c1 100644 --- a/DEVELOPERS.rst +++ b/DEVELOPERS.rst @@ -37,3 +37,77 @@ been made, one can use git tag -a v0.0.5 -m "0.0.5 release" to tag a specified commit. + +Steps to update keyword documentation +------------------------------------- + +.. code:: bash + + git checkout v0.0.5 + python -m robot.libdoc AngularJSLibrary docs/test.html + +Current Steps to Setup Development Environment and Run Tests +------------------------------------------------------------ +Here are the current (as of July 25, 2022, selenium==4.3.0, robotframework-seleniumlibrary==6.1.0.dev1, protractor==6.0.0) instructions for setting up the development environment and running the tests + +.. code:: bash + + mkdir ng-test + cd ng-test/ + git clone https://github.com/robotframework/SeleniumLibrary.git rf-sl + git clone https://github.com/MarketSquare/robotframework-angularjs.git rf-ng + git clone https://github.com/angular/protractor.git ptor + + virtualenv -p /usr/bin/python3.9 cl-py39-env + source cl-py39-env/bin/activate + pip install robotframework robotstatuschecker mockito selenium requests pytest + + patch rf-sl/atest/resources/testserver/testserver.py rf-ng/AngularJSLibrary/testserver.py.patch + + cp -R ptor/testapp rf-sl/atest/resources/. + + cp rf-ng/AngularJSLibrary/async.html rf-sl/atest/resources/testapp/ng1/async/. + cp rf-ng/AngularJSLibrary/async.js rf-sl/atest/resources/testapp/ng1/async/. + + cp rf-ng/AngularJSLibrary/angular.robot rf-sl/atest/acceptance/locators/. + cp rf-ng/AngularJSLibrary/angular_wait.robot rf-sl/atest/acceptance/keywords/. + + cd rf-sl + python atest/run.py FF --suite angular --pythonpath ../rf-ng + python atest/run.py FF --suite angular_wait --pythonpath ../rf-ng + +or if you are using Windows + +.. code:: bat + + mkdir test-ng + cd test-ng + + git clone https://github.com/robotframework/SeleniumLibrary.git rf-sl + git clone https://github.com/Selenium2Library/robotframework-angularjs.git rf-ng + git clone https://github.com/angular/protractor.git ptor + + virtualenv -p C:\Python27\python.exe --no-site-packages cl-py27-env + cl-py27-env\Scripts\activate + + pip install robotframework robotstatuschecker mockito selenium + + REM There is no default patch command under MS Dos so this step needs + REM to be manually implemented. + REM patch rf-sl/atest/resources/testserver/testserver.py rf-ng/AngularJSLibrary/testserver.py.patch + + xcopy ptor\testapp rf-sl\atest\resources\testapp\ /E /Y /F + copy /Y rf-ng\AngularJSLibrary\async.html rf-sl\atest\resources\testapp\ng1\async\. + copy /Y rf-ng\AngularJSLibrary\async.js rf-sl\atest\resources\testapp\ng1\async\. + copy rf-ng\AngularJSLibrary\angular.robot rf-sl\atest\acceptance\locators\. + copy rf-ng\AngularJSLibrary\angular_wait.robot rf-sl\atest\acceptance\keywords\. + +and then to run the tests + +.. code:: bat + + cd rf-sl + python atest\run.py FF --nounit --suite angular --pythonpath ..\rf-ng + python atest\run.py FF --nounit --suite angular_wait --pythonpath ..\rf-ng + +noting in the commands above the addition of :code:`--nounit` argument to forgo running the unit tests. diff --git a/LICENSE b/LICENSE index ea1006b..17a143e 100644 --- a/LICENSE +++ b/LICENSE @@ -206,11 +206,13 @@ AngularJSLibrary\ng-repeater.js: AngularJSLibrary\ng-repeater.min.js: AngularJSLibrary\__init__.py (certain javascript portions): +Various code examples (as noted in the documentation): The MIT License - Copyright (c) 2010-2015 Google, Inc. - + Copyright (c) 2010-2020 Google LLC. http://angularjs.org + Copyright (c) 2010-2022 Google LLC. http://angular.io/license + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights diff --git a/README.rst b/README.rst index 2dfec0f..d7f1023 100644 --- a/README.rst +++ b/README.rst @@ -1,10 +1,24 @@ AngularJSLibrary - robotframework-angularjs =========================================== -An AngularJS extension to Robotframework's Selenium2Library - -What is included ----------------- -AngularJSLibrary provides keywords for finding elements by binding, model, and repeater. The library also provides a keyword for waiting on angular. +An AngularJS and Angular extension to Robotframework's SeleniumLibrary. +AngularJSLibrary primarily provides functionality to deal with **waiting** and +thus timing issue when testing Angular based websites. The library does this by +providing first an implicit wait and, subsequently, an explicit keyword for +waiting on angular. + +About this library +------------------ +The AngularJSLibrary, despite the name including JS, supports testing against +both Angular 2.0+ (known as simply Angular) and Angular 1.0 (also known as +Angular JS). + +This library is considered mature and feature complete. Ongoing support is +provided through the Robot Framework community Slack. Thus it may appear +to be abandoned or neglected for which it is not. + +**Please carefully read through this README in its entirety**. It covers how +to configure and import the library into your test scripts, use and understand +its key functionality, as well as troubleshooting and debugging information. Installation ------------ @@ -21,33 +35,280 @@ Alternatively, to install from source: python setup.py install + +Identifying the Angular root element +------------------------------------ +Prior to importing the library, one must identify the Angular root element or root +component. For more information about + +Here are a few examples of Angular sites and their corresponding root elements or +components. The first example is from the `AngularJS.org PhoneCat tutorial `_. +The base html code is + +.. code:: html + + + + + + + +
+
+
+ + + + +In the PhoneCat tutorial the html element with the ng-app attribute is the root +element. Thus for this website the root selector would be :code:`[ng-app]`. The +next example is the `Getting started with Angular tutorial `_ +on angular.io site. It's main html looks like + +.. code:: html + + + + + + + + + + + +Here the root component is the app-root element and thus the root selector for +this website would be :code:`app-root`. The last example is the `example tab of +the Dialog UI component `_ +within the Angular.io Component Dev Kit (CDK). + +.. code:: html + + + + + + + + + + + + +The root component for the Dialog component example page is the material-docs-app +element. The root selector will be :code:`material-docs-app`. + +Now we will use the root selector when we import the library. + +Importing the library +--------------------- +The proper name for importing the library is :code:`AngularJSLibrary`. You will +need to include the SeleniumLibrary **before** you import the AngularJSLibrary. +The first of two library options is `root_selector`. So using our first example, +the PhoneCat tutorial from AngularJS.org above, our import may look like, + +.. code:: robotframework + + *** Settings *** + Library SeleniumLibrary + Library AngularJSLibrary root_selector=[ng-app] + + *** Test Cases *** + Search Through The Phone Catalog For Samsung Phones + Open Browser http://angular.github.io/angular-phonecat/step-14/app Chrome + Input Text //input Samsung + Click Link Samsung Galaxy Tab™ + Element Text Should Be css:phone-detail h1 Samsung Galaxy Tab™ + +As the default value for the root_selector argument is :code:`[ng-app]`, for +the PhoneCat tutorial we did not need to specify the root_selector and could +have written the Library import as + +.. code:: robotframework + + *** Settings *** + Library SeleniumLibrary + Library AngularJSLibrary + *** Test Cases *** + Search Through The Phone Catalog For Samsung Phones + Open Browser http://angular.github.io/angular-phonecat/step-14/app Chrome + Input Text //input Samsung + Click Link Samsung Galaxy Tab™ + Element Text Should Be css:phone-detail h1 Samsung Galaxy Tab™ -Keyword Usage -------------- -In order to use the keywords you have to include AngularJSLibrary in the settings section of your test. Note will will need to include the Selenium2Library **before** you import the AngularJSLibrary. +*If you get an "Unable to find root selector ..." error* then you should re-check +your root_selector. Note that unlike locators used with the SeleniumLibrary the +root_selector **should not** contain the css locator prefix. + +The second library option, ignore_implicit_angular_wait, is a flag which when +set to True the AngularJS Library will not wait for Angular $timeouts nor +$http calls to complete when finding elements by locator. The default value is +False. + +*If the application under test starts on a non angular page,* for example a +login page that is not angular which leads into an angular app, then one should +start with the implicit angular wait turned off. For example, .. code:: robotframework *** Settings *** - Library Selenium2Library - Library AngularJSLibrary - ... + Library SeleniumLibrary + Library AngularJSLibrary ignore_implicit_angular_wait=True *** Test Cases *** - Go To localhost:8080 - Wait for Angular - ... + Login Into Non Angular Page + # ... +Usage of the Waiting functionality +---------------------------------- +The AngularJS Library provides two types of waiting: a built-in implicit wait +that automatically waits when using a locator strategy and then an explicit +keyword that one calls out or writes into their script. In the tutorial and +examples above the scripts there aren't any expicit wait calls. Here instead +the script is relying on the implicit wait which by default is turned on. +This means as soon as you import the library you will have waiting enabled. -The new locator strategies include +This can be demostrated by importing the library with the implicit wait turned +off and using instead the library's explicit `Wait For Angular` keyword. -.. code:: +.. code:: robotframework + + *** Settings *** + Library SeleniumLibrary + Library AngularJSLibrary ignore_implicit_angular_wait=True + + *** Test Cases *** + Search Through The Phone Catalog For Samsung Phones + Open Browser http://angular.github.io/angular-phonecat/step-14/app Chrome + Wait For Angular + Input Text //input Samsung + Wait For Angular + Click Link Samsung Galaxy Tab™ + Wait For Angular + Element Text Should Be css:phone-detail h1 Samsung Galaxy Tab™ + +With the implicit wait functionality it is expected that most of the situations +where waiting is needed will be handled "automatically" by this "hidden" implicit +wait. Thus if one examined your test case they would not see many, if any, +`Wait For Angular` keywords but instead would see actions keywords with no +"waiting" keywords in between actions. There are times, though, when one needs to +explicitly call out to wait for angular. For example when using a SeleniumLibrary +keyword that does not use a locator strategy, like :code:`Alert Should Be Present` +and :code:`Page should contain`, or if you use webelement. + +In addition to the option to turn off the implicit wait on library import, you +may turn it off using the :code:`Set Ignore Implicit Angular Wait` keyword with +an argument of :code:`${True}`. + + +Understanding and verifying the angular waits +--------------------------------------------- +Although the waits seem like "Magic" they are not. Let's look into how the +waits are implimented and work to gain insight as to how they work. The waits, +both the implicit and explicit, poll what I call the "angular queue". +Technically it is checking that angular has "finished rendering and has no +outstanding $http or $timeout calls". It does this by checking the +`notifyWhenNoOutstandingRequests` function for AngularJS applications. For +Angular applications the library is checking the `isStable` function on the +Angular Testibility service. + +This can be seen within the log file by setting the loglevel to DEBUG or TRACE. +Rerunning the PhoneCat demo (:code:`robot --loglevel DEBUG demo_phonecat.robot`) +one should see in the log file - binding= - model= - repeater= +.. code:: robotframework + 20:01:04.658 INFO Typing text 'Samsung' into text field '//input'. + 20:01:04.658 DEBUG POST http://localhost:50271/session/f75e7aaf5a00c717ae5e4af34a6ce516540611dae4b7f6079ce1a753c308cde2/execute/sync {"script": "...snip..."]} + 20:01:04.661 DEBUG http://localhost:50271 "POST /session/f75e7aaf5a00c717ae5e4af34a6ce516540611dae4b7f6079ce1a753c308cde2/execute/sync HTTP/1.1" 200 14 + 20:01:04.661 DEBUG Remote response: status=200 | data={"value":true} | headers=HTTPHeaderDict({'Content-Length': '14', 'Content-Type': 'application/json; charset=utf-8', 'cache-control': 'no-cache'}) + 20:01:04.661 DEBUG Finished Request + +For space reasons I snipped out the core script on the POST execute/sync line. +One should see these lines repeated several times over. This is the polling the +library is doing to see if the application is ready to test. It will repeat +this query till either it returns true or it will repeat till the "give up" +timeout. If it gives up, it will silently and gracefully fail continuing onto +the actions it was waiting to perform. It is important for the user of this +library to see and understand, at a basic level, this functionality. As the +primary usage are these implicit, and thus hidden, waits it is key to see how +to check the library is operating properly and when it is waiting. + +*When using the AngularJS Library, if all waits timeout then the AngularJS +Library may not wait properly with that application under test.* This, +recalling all previously outlined information, is telling you that the +Angular app is constantly busy. This can happen depending on how the angular +application is designed. It may also affect only a portion of the application +so it is important to test out various parts of the application. + +Further debugging techniques +---------------------------- +In addition to using the AngularJS Library, one can use the Browser's DevTools +as a way to test out and demonstrate the core operation of the library against +an application. To be clear, this is not library code but similar Javascript +code which one uses outside of robot to exhibit, to a dev team for example, +what the library is seeing when it querys the application. When viewing the +application under test open the DevTools, preferably under Chrome, and on the +Console tab type the following, + +If the application is built with AngularJS or Angular 1.x then the script is + +.. code:: javascript + + var callback = function () {console.log('*')} + var el = document.querySelector('[ng-app]'); + var h = setInterval(function w4ng() { + console.log('.'); + try { + angular.element(el).injector().get('$browser'). + notifyWhenNoOutstandingRequests(callback); + } catch (err) { + console.log(err.message); + callback(err.message); + } + }, 10); + +For Angular v2+ then the script is + +.. code:: javascript + + var callback = function () {console.log('*')} + var el = document.querySelector('material-docs-app'); + var h = setInterval(function w4ng() { + console.log('.'); + try { + var readyToTest = window.getAngularTestability(el).isStable(); + } catch (err) { + console.log(err.message); + callback(err.message); + } + if (!readyToTest) { + callback() + } else { + console.log('.'); + } + }, 10); + +This will display a :code:`.` when "stable". Otherwise it will show a :code:`*` +when "busy". To shut down the javascript interval and stop this script type on +the console prompt :code:`clearInterval(h);`. [Chrome Browser is preferred +because repeated output within its DevTools console will be displayed as a +single line with a count versus a new line for each output making it much +easier to see and read.] I have personally used this myself both in developing +this library as well as demonstrating to various Angular developers how a +design/implementation is blocking testability. + +Additional Angular Specific Locator Strategies +------------------------------------------------- +**Note: It is no longer recommended to use these angular specific locator +strategies. Although functional, the SeleniumLibrary locator strategies are more +than sufficient and in most cases easier to use then these strategies. For backward +compatablity reasons these will be left in but it is strongly recommended not to +use.** + +The library provides three new locator strategies, including ``binding``, +``model``, and ``repeater``. For example, you can look for an Angular ng-binding using @@ -82,17 +343,29 @@ One can also find elements by model Finally there is the strategy of find by repeat. This takes the general form of :rf:`repeater=some ngRepeat directive@row[n]@column={{ngBinding}}`. Here we specify the directive as well as the row, an zero-based index, and the column, an ngBinding. Using this full format will return, if exists the element matching the directive, row and column binding. One does not need to specify the row and column but can specify either both, one or the other or neither. In such cases the locator may return list of elements or even a list of list of elements. Also the ordering of row and column does not matter; using :rf:`repeater=baz in days@row[0]@column=b` is the same as :rf:`repeater=baz in days@column=b @row[0]`. + Getting Help ------------ -If you need help with AngularJSLibrary, Selenium2Library, or Robot Framework usage, please post to the `user group for Robot Framework `_. +If you need help with AngularJSLibrary, SeleniumLibrary, or Robot Framework usage, please reach out within the Robot Framework community `Slack `_. + + +Keyword Documentation +--------------------- +The keyword documentation can be found on the `Github project page `_. + Testing ------- -For information on how we test the AngularJSLibrary see the `Testing.rst`_ file. +For information on how we test the AngularJSLibrary see the `Testing.rst `_ file. + References ---------- -`Selenium2Library `_: Web testing library for Robot Framework +`SeleniumLibrary `_: Web testing library for Robot Framework `Protractor `_: E2E test framework for Angular apps + +`isStable reference `_ + +`Angular Testability service `_ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..4220fdd --- /dev/null +++ b/docs/index.html @@ -0,0 +1,912 @@ + + + + + + + + + + + + + + + + + + + +Codestin Search App + + + +
+

Opening library documentation failed

+
    +
  • Verify that you have JavaScript enabled in your browser.
  • +
  • Make sure you are using a modern enough browser. If using Internet Explorer, version 8 or newer is required.
  • +
  • Check are there messages in your browser's JavaScript error log. Please report the problem if you suspect you have encountered a bug.
  • +
+
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/setup.py b/setup.py index 317112f..afd9efa 100644 --- a/setup.py +++ b/setup.py @@ -1,8 +1,8 @@ -"""An AngularJS extension to Robotframework's Selenium2Library +"""An AngularJS/Angular extension to Robotframework's SeleniumLibrary See: http://robotframework.org/ -https://github.com/Selenium2Library/robotframework-angularjs +https://github.com/MarketSquare/robotframework-angularjs """ from setuptools import setup, find_packages @@ -17,19 +17,21 @@ setup( name='robotframework-angularjs', - version='0.0.7dev1', - description="""An AngularJS extension to Robotframework's Selenium2Library""", + version='1.0.0', + description="""An AngularJS/Angular extension to Robotframework's SeleniumLibrary""", long_description=long_description, - url='https://github.com/Selenium2Library/robotframework-angularjs', + long_description_content_type='text/x-rst', + url='https://github.com/MarketSquare/robotframework-angularjs', author='Zephraph, Ed Manlove', - author_email='zephraph@gmail.com, devPyPlTw@verizon.net', + author_email='emanlove@verizon.net', license='Apache License 2.0', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 6 - Mature', 'Framework :: Robot Framework', 'License :: OSI Approved :: Apache Software License', 'Operating System :: OS Independent', 'Programming Language :: Python', + 'Programming Language :: Python :: 3', 'Topic :: Software Development :: Testing', ], keywords='robotframework testing testautomation angular selenium webdriver',