diff --git a/docs/extending/EXTENDING_SELENIUMLIBRARY.rst b/docs/extending/EXTENDING_SELENIUMLIBRARY.rst new file mode 100644 index 000000000..f47b55ce9 --- /dev/null +++ b/docs/extending/EXTENDING_SELENIUMLIBRARY.rst @@ -0,0 +1,157 @@ +Extending SeleniumLibrary +========================= + +.. contents:: + +Introduction +------------ +Starting from SeleniumLibrary 3.0, the library has moved to use Robot Framework +`dynamic library API`_. To ease the usage of the dynamic library API, the SeleniumLibrary uses +a `PythonLibCore`_ project to handle the keyword keyword discovery, running the keywords and +other things to related to the dynamic library API. For more details please read the Robot +Framework `dynamic library API`_ documentation. + +Public API methods +------------------ +All the methods, which are exposed as keywords, are available in the library public API. Generally +keywords are converted to lover case and spaces are converted to underscores. Example `Open Browser`_ +keyword is available as ``open_browser`` method. Please note that method name can be overwritten +with the ``@keyword`` decorator, but in the SeleniumLibrary 3.0.0 release does not contain keywords +where the keyword name would differ from the method name (other than the keywords name case). + +Methods or attributes which are not keywords but are available in the public API +-------------------------------------------------------------------------------- +The SeleniumLibrary instance also contains methods and attributes which are not keywords but are +useful when extending the SeleniumLibrary. The available methods are: + +================ ============================================================= + Method Description +================ ============================================================= +find_element Finds first element matching ``locator``. +find_elements Find all elements matching ``locator``. +register_driver Add's a ``driver`` to the library WebDriverCache. +run_keyword Responsible for executing keywords. +failure_occurred Method that is executed when a SeleniumLibrary keyword fails. +================ ============================================================= + +Also there are the following public attributes available: + +========================= ================================================================ + Attribute Description +========================= ================================================================ +driver Current active driver. +timeout Default value for ``timeouts`` used with ``Wait ...`` keywords. +implicit_wait Default value for ``implicit wait`` used when locating elements. +run_on_failure_keyword Default action for the `run-on-failure functionality`. +screenshot_root_directory Location where possible screenshots are created +========================= ================================================================ + +For more details about the methods, please read the individual method documentation and many +of the attributes are explained in the library `keyword documentation`_. + +Extending SeleniumLibrary by pull requests +------------------------------------------ +Before making your own extension, private or public, please consider would the extension be +generally useful in the SeleniumLibrary. If the extension would be useful for others, then please +create an issue and a pull request. More details how SeleniumLibrary project handles the +enhancement requests can be read from the CONTRIBUTING.rst `Enhancement requests`_ chapter. + +General prinsibles for extending SeleniumLibrary +------------------------------------------------ +The prinsibles described in the Robot Framework User Guide, `Extending existing test libraries`_ +chapter also apply when extending the SeleniumLibrary. There are two different ways to +extend the SeleniumLibrary. + +1) Create a library which also the contains existing SeleniumLibrary keywords, example by using `inheritance`_. +2) Create library which contains only new keywords. + +When creating a library, which also includes the existing SeleniumLibrary keywords, there are +extra steps which needs to be taken account, because SeleniunLibrary uses `PythonLibCore`_ +and the `dynamic library API`_. All methods which should be published as keywords must be +decorated with ``@keyword`` decorator. The ``@keyword`` decorator can be imported in following way:: + + from SeleniumLibrary.base import keyword + +Keywords should be inside of a ``class`` and the ``add_library_components`` method +must be called to add keywords. The ``add_library_components`` method is inherited from the +`PythonLibCore`_ project and the method must contains list of classes which contains the +new keywords. + +Creating a new library by using inheritance +------------------------------------------- +Perhaps the easiest way to extend the SeleniumLibrary is to inherit the SeleniumLibrary and add +new keywords methods to a new library. The `inheritance example`_ shows how to declare new +keyword ``Get Browser Desired Capabilities`` and how to overwrite existing ``Open Browser`` keyword. + +Because the ``InheritSeleniumLibrary`` class foes not overwrite the SeleniumLibrary ``init`` method, +the ``add_library_components`` is called automatically. Then the ``InheritSeleniumLibrary`` class methods +which are decorated with ``@keyword`` decorator are added to the ``InheritSeleniumLibrary`` +library keywords. Also existing keywords from SeleniumLibrary are added as library keywords. + +Because the methods are not anymore directly available in the SeleniumLibrary class, it not +anymore possible to call the original method example like this:: + + super(ClassName, self).open_browser(url, browser, alias, remote_url, + desired_capabilities, ff_profile_dir) + +Instead user must call the method from the class instance which implements the keyword, example:: + + browser_management = BrowserManagementKeywords(self) + browser_management.open_browser(url, 'chrome') + +Creating a new library from multiple classes +-------------------------------------------- +Decomposition is a good way to split library to smaller name spaces and it usually eases the +library testing. The `decomposition example`_ shows how the ``Get Browser Desired Capabilities`` +and ``Open Browser`` keywords can divided to own classes. + +The example also shows the usage of the ``context`` object and the `LibraryComponent`_ class. +The ``context`` object is a instance of the SeleniunLibrary which provides access to the +SeleniumLibrary methods for the ``BrowserKeywords`` and ``DesiredCapabilitiesKeywords`` classes. +Example ``context`` object provides access to the Selenium WebDriver instance. + +The ``LibraryComponent`` is a wrapper class, which provides easier shortcuts the ``context`` object +methods and example provides general logging methods. Example the Selenium WebDriver instance in +the context: ``self.ctx.driver``, but the ``LibraryComponent`` provides a shortcut and it can be +accessed with: ``self.driver`` + + +Creating a new library by getting active library instance +--------------------------------------------------------- +Getting the active library instance provides way to create a new library that it does not +automatically contain keywords from the SeleniumLibrary. This eases the name space +handling and if only new keywords are created, user does not have to prefix the keywords with the +library name. This way also allows user to freely choose the Robot Framework `library API`_. +The `instance example`_ shows a way how the active SeleniumLibrary is get from the Robot Framework. +The example shows how to declare ``Get Browser Desired Capabilities`` and ``Open Browser`` keywords +to the new library and the `instance example`_ uses the `static keyword API`_ to declare new +keywords. + +Extending the SeleniumLibrary dynamically +----------------------------------------- +It is possible to modify keywords directly in the SeleniumLibrary. This can be done calling +``add_library_components`` method from the SeleniumLibrary instance and then using the +``BuiltIn().reload_library()`` method to reload the SeleniumLibrary. + +The `modify SeleniumLibrary`_ example uses the Robot Framework `Listener API`_ to extend the +SeleniumLibrary with a keywords in the ``KeywordClass``. The ``NewKeywords``class is library +which is also a listener and it uses the ``start_suite`` method to trigger the modifications +to the SeleniumLibrary. The example gets the active library instance from the Robot Framework +and uses the ``add_library_components`` method to add the ``KeywordClass`` keywords in to +the SeleniumLibrary directly. + +.. _dynamic library API: http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#dynamic-library-api +.. _PythonLibCore: https://github.com/robotframework/PythonLibCore +.. _Open Browser: http://robotframework.org/SeleniumLibrary/SeleniumLibrary.html#Open%20Browser +.. _keyword documentation: http://robotframework.org/SeleniumLibrary/SeleniumLibrary.html +.. _Enhancement requests: https://github.com/robotframework/SeleniumLibrary/blob/master/CONTRIBUTING.rst#enhancement-requests +.. _Extending existing test libraries: http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#extending-existing-test-libraries +.. _inheritance: https://github.com/robotframework/SeleniumLibrary/blob/master/docs/extending/examples/inheritance/InheritSeleniumLibrary.py +.. _inheritance example: https://github.com/robotframework/SeleniumLibrary/blob/master/docs/extending/examples/inheritance/InheritSeleniumLibrary.py +.. _decomposition example: https://github.com/robotframework/SeleniumLibrary/blob/master/docs/extending/examples/decomposition/Decomposition.py +.. _instance example: https://github.com/robotframework/SeleniumLibrary/blob/master/docs/extending/examples/get_instance/GetSeleniumLibraryInstance.py +.. _LibraryComponent: https://github.com/robotframework/SeleniumLibrary/blob/master/src/SeleniumLibrary/base/librarycomponent.py +.. _library API: http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#different-test-library-apis +.. _static keyword API: http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#creating-static-keywords +.. _modify SeleniumLibrary: https://github.com/robotframework/SeleniumLibrary/blob/master/docs/extending/examples/modify_seleniumlibrary/NewKeywords.py +.. _Listener API: http://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#listener-interface diff --git a/docs/extending/examples/decomposition/Decomposition.py b/docs/extending/examples/decomposition/Decomposition.py new file mode 100644 index 000000000..6be35969a --- /dev/null +++ b/docs/extending/examples/decomposition/Decomposition.py @@ -0,0 +1,38 @@ +from SeleniumLibrary import SeleniumLibrary +from SeleniumLibrary.base import keyword, LibraryComponent +from SeleniumLibrary.keywords import BrowserManagementKeywords + + +class BrowserKeywords(LibraryComponent): + + def __init__(self, ctx): + LibraryComponent.__init__(self, ctx) + + @keyword + def open_browser(self, host): + url = 'http://{}.com/'.format(host) + browser_management = BrowserManagementKeywords(self.ctx) + browser_management.open_browser(url, 'chrome') + + +class DesiredCapabilitiesKeywords(LibraryComponent): + + def __init__(self, ctx): + LibraryComponent.__init__(self, ctx) + + @keyword + def get_browser_desired_capabilities(self): + self.info('Getting currently open browser desired capabilities') + return self.driver.desired_capabilities + + +class Decomposition(SeleniumLibrary): + + def __init__(self, timeout=5.0, implicit_wait=0.0, + run_on_failure='Capture Page Screenshot', + screenshot_root_directory=None): + SeleniumLibrary.__init__(self, timeout=timeout, implicit_wait=implicit_wait, + run_on_failure=run_on_failure, + screenshot_root_directory=screenshot_root_directory) + self.add_library_components([BrowserKeywords(self), + DesiredCapabilitiesKeywords(self)]) diff --git a/docs/extending/examples/decomposition/decomposition.robot b/docs/extending/examples/decomposition/decomposition.robot new file mode 100644 index 000000000..e62c78e46 --- /dev/null +++ b/docs/extending/examples/decomposition/decomposition.robot @@ -0,0 +1,9 @@ +*** Settings *** +Library ./Decomposition.py + +*** Test Cases *** +Decomposition Example + Open Browser google + ${capabilities} = Get Browser Desired Capabilities + Log ${capabilities} + [Teardown] Close Browser diff --git a/docs/extending/examples/get_instance/GetSeleniumLibraryInstance.py b/docs/extending/examples/get_instance/GetSeleniumLibraryInstance.py new file mode 100644 index 000000000..c734a12ab --- /dev/null +++ b/docs/extending/examples/get_instance/GetSeleniumLibraryInstance.py @@ -0,0 +1,14 @@ +from robot.api import logger +from robot.libraries.BuiltIn import BuiltIn + + +def open_browser(host): + url = 'http://{}.com/'.format(host) + sl = BuiltIn().get_library_instance('SeleniumLibrary') + sl.open_browser(url, 'chrome') + + +def get_browser_desired_capabilities(): + logger.info('Getting currently open browser desired capabilities') + sl = BuiltIn().get_library_instance('SeleniumLibrary') + return sl.driver.desired_capabilities diff --git a/docs/extending/examples/get_instance/instance.robot b/docs/extending/examples/get_instance/instance.robot new file mode 100644 index 000000000..716d6bbcd --- /dev/null +++ b/docs/extending/examples/get_instance/instance.robot @@ -0,0 +1,10 @@ +*** Settings *** +Library SeleniumLibrary +Library ./GetSeleniumLibraryInstance.py + +*** Test Cases *** +Use InheritSeleniumLibrary Open Browser Keyword + GetSeleniumLibraryInstance.Open Browser google + ${capabilities} = GetSeleniumLibraryInstance.Get Browser Desired Capabilities + Log ${capabilities} + [Teardown] Close Browser \ No newline at end of file diff --git a/docs/extending/examples/inheritance/InheritSeleniumLibrary.py b/docs/extending/examples/inheritance/InheritSeleniumLibrary.py new file mode 100644 index 000000000..634b4ce79 --- /dev/null +++ b/docs/extending/examples/inheritance/InheritSeleniumLibrary.py @@ -0,0 +1,26 @@ +from robot.api import logger +from SeleniumLibrary import SeleniumLibrary +from SeleniumLibrary.base import keyword +from SeleniumLibrary.keywords import BrowserManagementKeywords + + +class InheritSeleniumLibrary(SeleniumLibrary): + + @keyword + def open_browser(self, host): + url = 'http://{}.com/'.format(host) + browser_management = BrowserManagementKeywords(self) + browser_management.open_browser(url, 'chrome') + + @keyword + def get_browser_desired_capabilities(self): + logger.info('Getting currently open browser desired capabilities') + return self.driver.desired_capabilities + + def not_keywords_but_public_methods(self): + logger.info('Python public method not a keyword, because it is not ' + 'decorated with @keyword decorator') + + def _private_method_are_not_keywords(self): + logger.info('Python private method is not a keyword, because it is not ' + 'decorated with @keyword decorator') diff --git a/docs/extending/examples/inheritance/inheritance.robot b/docs/extending/examples/inheritance/inheritance.robot new file mode 100644 index 000000000..83a6522dd --- /dev/null +++ b/docs/extending/examples/inheritance/inheritance.robot @@ -0,0 +1,9 @@ +*** Settings *** +Library ./InheritSeleniumLibrary.py + +*** Test Cases *** +Use InheritSeleniumLibrary Open Browser Keyword + Open Browser google + ${capabilities} = Get Browser Desired Capabilities + Log ${capabilities} + [Teardown] Close Browser \ No newline at end of file diff --git a/docs/extending/examples/modify_seleniumlibrary/NewKeywords.py b/docs/extending/examples/modify_seleniumlibrary/NewKeywords.py new file mode 100644 index 000000000..78965834f --- /dev/null +++ b/docs/extending/examples/modify_seleniumlibrary/NewKeywords.py @@ -0,0 +1,37 @@ +from robot.api import logger +from robot.libraries.BuiltIn import BuiltIn +from SeleniumLibrary.base import keyword +from SeleniumLibrary.base import LibraryComponent +from SeleniumLibrary.keywords import BrowserManagementKeywords + + +class KeywordClass(LibraryComponent): + + def __init__(self, ctx): + LibraryComponent.__init__(self, ctx) + + @keyword + def open_browser(self, host): + logger.info('This is keyword from KeywordClass') + url = 'http://{}.com/'.format(host) + browser_management = BrowserManagementKeywords(self.ctx) + browser_management.open_browser(url, 'chrome') + + @keyword + def get_browser_desired_capabilities(self): + logger.info('Getting currently open browser desired capabilities') + return self.driver.desired_capabilities + + +class NewKeywords(object): + + ROBOT_LISTENER_API_VERSION = 2 + + def __init__(self): + self.ROBOT_LIBRARY_LISTENER = self + + def start_suite(self, name, attributes): + sl = BuiltIn().get_library_instance('SeleniumLibrary') + sl.add_library_components([KeywordClass(sl)]) + BuiltIn().reload_library('SeleniumLibrary') + self.added = True diff --git a/docs/extending/examples/modify_seleniumlibrary/modify_sl.robot b/docs/extending/examples/modify_seleniumlibrary/modify_sl.robot new file mode 100644 index 000000000..7916ec47a --- /dev/null +++ b/docs/extending/examples/modify_seleniumlibrary/modify_sl.robot @@ -0,0 +1,10 @@ +*** Settings *** +Library SeleniumLibrary +Library ./NewKeywords.py + +*** Test Cases *** +Use New Keywords From SeleniumLibrary + SeleniumLibrary.Open Browser google + ${capabilities} = SeleniumLibrary.Get Browser Desired Capabilities + Log ${capabilities} + [Teardown] Close Browser