Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.10.19, 3.14]
python-version: ["3.10", "3.14"]
rf-version: [6.1.1, 7.4.2]

steps:
Expand Down
58 changes: 53 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,63 @@ Then Library can be imported in Robot Framework side like this:
Library ${CURDIR}/PluginLib.py plugins=${CURDIR}/MyPlugin.py
```

# Listener

PLC supports
[library listeners](https://robotframework.org/robotframework/latest/RobotFrameworkUserGuide.html#libraries-as-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.
Comment on lines +161 to +165
Comment on lines +161 to +165

Example:
```python
from robot.running.model import TestCase
from robot.result.model import TestCase as TestCaseResult

from robotlibcore import DynamicCore, keyword


class ListenerExample(DynamicCore):

ROBOT_LIBRARY_SCOPE = 'GLOBAL'

def __init__(self):
self.ROBOT_LIBRARY_LISTENER = self
components = [KeywordsWithListener()]
super().__init__(components)



class KeywordsWithListener:
ROBOT_LISTENER_API_VERSION = 3

def __init__(self):
self.test = None


def start_test(self, data: TestCase, result: TestCaseResult):
self.test = data.name
self.passed = result.passed

@keyword
def keyword_with_listener(self, name: str, status: bool):
assert name == self.test, f"Test case name {name} does not match expected {self.test}"
assert status == 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.

# Translation

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](https://docs.python.org/3/library/pathlib.html) to a
[JSON](https://www.json.org/json-en.html) file. Providing translation data is optional, also it
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](https://docs.python.org/3/library/pathlib.html) to a
[JSON](https://www.json.org/json-en.html) 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.
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
Comment on lines 213 to 216
translated name and `doc` contains keyword translated documentation. Providing
`doc` and `name` is optional, i.e. translations data can also provide translations only
Expand Down
7 changes: 5 additions & 2 deletions atest/ListenerCore.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ def _start_suite(self, name, attrs):

@keyword
def first_component(self, arg: str):
assert arg == self.suite_name, f"Suite name '{self.suite_name}' should be detected by listener, but was not."
name = self.suite_name
assert name == arg, f"Test suite name {name} does not match expected {arg}."


class SecondComponent:
Expand All @@ -46,7 +47,9 @@ def __init__(self) -> None:

@keyword
def second_component(self, arg: str):
assert self.listener.test.name == arg, "Test case name should be detected by listener, but was not."
name = self.listener.test.name
assert name == arg, f"Test case name {name} does not match expected {arg}."



class ExternalListener:
Expand Down
33 changes: 33 additions & 0 deletions atest/ListenerExample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from robot.running.model import TestCase
from robot.result.model import TestCase as TestCaseResult

from robotlibcore import DynamicCore, keyword


class ListenerExample(DynamicCore):

ROBOT_LIBRARY_SCOPE = 'GLOBAL'
ROBOT_LISTENER_API_VERSION = 3

def __init__(self):
self.ROBOT_LIBRARY_LISTENER = self
components = [KeywordsWithListener()]
super().__init__(components)
Comment on lines +9 to +15



class KeywordsWithListener:
ROBOT_LISTENER_API_VERSION = 3

def __init__(self):
self.test = None

Comment on lines +22 to +24

def start_test(self, data: TestCase, result: TestCaseResult):
self.test = data.name
self.passed = result.passed

@keyword
def keyword_with_listener(self, name: str, status: bool):
assert name == self.test, f"Test case name {name} does not match expected {self.test}"
assert status == self.passed, f"Test case status {status} does not match expected {self.passed} {type(self.passed)}"
8 changes: 8 additions & 0 deletions atest/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
tests = join(curdir, "tests.robot")
tests_types = join(curdir, "tests_types.robot")
plugin_api = join(curdir, "plugin_api")
listener_api = join(curdir, "tests_listener.robot")
sys.path.insert(0, join(curdir, "..", "src"))
python_version = platform.python_version()
for variant in library_variants:
Expand Down Expand Up @@ -58,6 +59,13 @@
if rc > 250:
sys.exit(rc)
process_output(output)

output = join(outdir, f"lib-Listener-python-{python_version}-robot-{RF_VERSION}.xml")
rc = run(listener_api, name="Listener", output=output, report=None, log=None, loglevel="debug")
if rc > 250:
sys.exit(rc)
process_output(output)

print("\nCombining results.")
library_variants.append("DynamicTypesLibrary")
xml_files = [str(xml_file) for xml_file in Path(outdir).glob("*.xml")]
Expand Down
6 changes: 5 additions & 1 deletion atest/tests_listener.robot
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
*** Settings ***
Library ListenerCore.py
Library ListenerExample.py


*** Test Cases ***
Expand All @@ -22,4 +23,7 @@ Tests The Suite Name
... to the suite name from _start_suite.
...
... It uses an independent class as listener which is manually set.
First Component Tests Listener
First Component Listener

Test With Listener Example
Keyword With Listener Test With Listener Example False