Tools to ease creating larger test libraries for Robot Framework using Python. The Robot Framework hybrid and dynamic library API gives more flexibility for library than the static library API, but they also sets requirements for libraries which needs to be implemented in the library side. PythonLibCore eases the problem by providing simpler interface and handling all the requirements towards the Robot Framework library APIs.
Code is stable and is already used by SeleniumLibrary and Browser library. Project supports two latest version of Robot Framework.
There are two ways to use PythonLibCore, either by
HybridCore or by using DynamicCore. HybridCore provides support for
the hybrid library API and DynamicCore provides support for dynamic library API.
Consult the Robot Framework User
Guide,
for choosing the correct API for library.
Regardless which library API is chosen, both have similar requirements.
- Library must inherit either the
HybridCoreorDynamicCore. - Library keywords must be decorated with Robot Framework @keyword decorator.
- Provide a list of class instances implementing keywords to
library_componentsargument in theHybridCoreorDynamicCore__init__.
It is also possible implement keywords in the library main class, by marking method with
@keyword as keywords. It is not required pass main library instance in the
library_components argument.
All keyword, also keywords implemented in the classes outside of the main library are available in the library instance as methods. This automatically publish library keywords in as methods in the Python public API.
The example in below demonstrates how the PythonLibCore can be used with a library.
To install this library, run the following command in your terminal:
pip install robotframework-pythonlibcoreThis command installs the latest version of robotframework-pythonlibcore, ensuring you have all the current features and updates.
"""Main library."""fromrobotlibcoreimportDynamicCorefrommystuffimportLibrary1, Library2classMyLibrary(DynamicCore):
"""General library documentation."""def__init__(self):
libraries= [Library1(), Library2()]
DynamicCore.__init__(self, libraries)
@keyworddefkeyword_in_main(self):
pass"""Library components."""fromrobotlibcoreimportkeywordclassLibrary1(object):
@keyworddefexample(self):
"""Keyword documentation."""pass@keyworddefanother_example(self, arg1, arg2='default'):
passdefnot_keyword(self):
passclassLibrary2(object):
@keyword('Custom name')defthis_name_is_not_used(self):
pass@keyword(tags=['tag', 'another'])deftags(self):
passIt is possible to create plugin API to a library by using PythonLibCore. This allows extending library with external Python classes. Plugins can be imported during library import time, example by defining argument in library [__init__]{.title-ref} which allows defining the plugins. It is possible to define multiple plugins, by separating plugins with with comma. Also it is possible to provide arguments to plugin by separating arguments with semicolon.
fromrobot.api.decoimportkeyword# noqa F401fromrobotlibcoreimportDynamicCore, PluginParserfrommystuffimportLibrary1, Library2classPluginLib(DynamicCore):
def__init__(self, plugins):
plugin_parser=PluginParser()
libraries= [Library1(), Library2()]
parsed_plugins=plugin_parser.parse_plugins(plugins)
libraries.extend(parsed_plugins)
DynamicCore.__init__(self, libraries)When plugin class can look like this:
classMyPlugi:
@keyworddefplugin_keyword(self):
return123Then Library can be imported in Robot Framework side like this:
Library${CURDIR}/PluginLib.py plugins=${CURDIR}/MyPlugin.pyPLC supports
library listeners,
also listener can be defined in the class that defines keywords. PLC will automatically detect
is class is also listener and set the ROBOT_LIBRARY_LISTENER as a list. List will contains all
the class instances that are marked as listeners.
Example:
fromrobot.running.modelimportTestCasefromrobot.result.modelimportTestCaseasTestCaseResultfromrobotlibcoreimportDynamicCore, keywordclassListenerExample(DynamicCore):
ROBOT_LIBRARY_SCOPE='GLOBAL'def__init__(self):
self.ROBOT_LIBRARY_LISTENER=selfcomponents= [KeywordsWithListener()]
super().__init__(components)
classKeywordsWithListener:
ROBOT_LISTENER_API_VERSION=3def__init__(self):
self.test=Nonedefstart_test(self, data: TestCase, result: TestCaseResult):
self.test=data.nameself.passed=result.passed@keyworddefkeyword_with_listener(self, name: str, status: bool):
assertname==self.test, f"Test case name {name} does not match expected {self.test}"assertstatus==self.passed, f"Test case status {status} does not match expected {self.passed}{type(self.passed)}"In the example, KeywordsWithListener acts as a listener and the start_test method is
called each time a test starts.
PLC supports translation of keywords names and documentation. Translations must be provided in
the translation argument in the HybridCore or DynamicCore__init__, either as a
dictionary or through a Path to a
JSON file. Providing translation data is optional, also it
is not mandatory to provide translation to all keyword.
The keys of the dictionary are the methods names, not the keyword names, which implements keyword.
Values are objects which contains two keys: name and doc. name key contains the keyword
translated name and doc contains keyword translated documentation. Providing
doc and name is optional, i.e. translations data can also provide translations only
to keyword names or only to documentation. But it is always recommended to provide translation to
both name and doc.
Library class documentation and instance documentation has special keys, __init__ key will
replace instance documentation and __intro__ will replace library class documentation.
Note
Arguments names, tags and types can not be currently translated.
If there is library like this:
fromrobotlibcoreimportDynamicCore, keywordclassSmallLibrary(DynamicCore):
"""Library documentation."""def__init__(self):
"""__init__ documentation."""DynamicCore.__init__(self, [])
@keyword(tags=["tag1", "tag2"])defnormal_keyword(self, arg: int, other: str) ->str:
"""I have doc Multiple lines. Other line. """data=f"{arg}{other}"print(data)
returndatadefnot_keyword(self, data: str) ->str:
print(data)
returndata@keyword(name="This Is New Name", tags=["tag1", "tag2"])defname_changed(self, some: int, other: int) ->int:
"""This one too"""print(f"{some}{type(some)}, {other}{type(other)}")
returnsome+otherAnd we want to translate it as follows:
- keyword
normal_keywordtoother_name- its documentation to
This is new doc
- its documentation to
- keyword
name_changedtoname_changed_again- its documentation to
This is also replaced.\n\nnew line..
- its documentation to
- the library constructor documentation to
Replaces init docs with this one. - the library documentation to
New __intro__ documentation is here.
To provide the translation as a file, simply pass the path to a JSON file containing the translations:
frompathlibimportPathclassSmallLibrary(DynamicCore):
"""Library documentation."""def__init__(self):
"""__init__ documentation."""DynamicCore.__init__(self, [], translation=Path("/path/to/my_translation.json"))
# ...Important
Translation files passed as paths must always be in JSON format.
You can also pass the translation data as a dictionary:
importjsonfrompathlibimportPathclassSmallLibrary(DynamicCore):
"""Library documentation."""def__init__(self):
"""__init__ documentation."""translation_data=json.loads(Path("/path/to/my_translation.json").read_text(encoding="utf-8"))
DynamicCore.__init__(self, [], translation=translation_data)
# ...This also allows you to use other data formats such as YAML:
normal_keyword:
name: other_namedoc: This is new docname_changed:
name: name_changed_againdoc: | This is also replaced. new line.__init__:
name: __init__doc: Replaces init docs with this one.__intro__:
name: __intro__doc: New __intro__ documentation is here.importyamlfrompathlibimportPathclassSmallLibrary(DynamicCore):
"""Library documentation."""def__init__(self, translation_file: Path):
"""__init__ documentation."""translation_data=yaml.safe_load(translation_file.read_text(encoding="utf-8"))
DynamicCore.__init__(self, [], translation=translation_data)
# ...