robotframework-robotlibrary 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- RobotLibrary/__init__.py +297 -0
- RobotLibrary/_listener.py +227 -0
- RobotLibrary/_version.py +1 -0
- robotframework_robotlibrary-0.1.0.dist-info/METADATA +238 -0
- robotframework_robotlibrary-0.1.0.dist-info/RECORD +7 -0
- robotframework_robotlibrary-0.1.0.dist-info/WHEEL +4 -0
- robotframework_robotlibrary-0.1.0.dist-info/licenses/LICENSE +191 -0
RobotLibrary/__init__.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""RobotLibrary - Test Robot Framework tests and tasks within Robot Framework.
|
|
2
|
+
|
|
3
|
+
RobotLibrary enables testing Robot Framework test and task files from within
|
|
4
|
+
Robot Framework itself, without subprocess execution. It uses the Listener v3
|
|
5
|
+
API to dynamically inject test steps at runtime.
|
|
6
|
+
|
|
7
|
+
Usage in Robot Framework::
|
|
8
|
+
|
|
9
|
+
*** Settings ***
|
|
10
|
+
Library RobotLibrary
|
|
11
|
+
|
|
12
|
+
*** Test Cases ***
|
|
13
|
+
Test With Default Variables
|
|
14
|
+
Run Robot Test target.robot Login Test
|
|
15
|
+
|
|
16
|
+
Test With Custom Variables
|
|
17
|
+
Run Robot Test target.robot Login Test
|
|
18
|
+
... USERNAME=testuser
|
|
19
|
+
... PASSWORD=secret123
|
|
20
|
+
|
|
21
|
+
For RPA task suites (``*** Tasks ***``), the ``Run Robot Task`` alias can
|
|
22
|
+
be used::
|
|
23
|
+
|
|
24
|
+
*** Settings ***
|
|
25
|
+
Library RobotLibrary
|
|
26
|
+
|
|
27
|
+
*** Tasks ***
|
|
28
|
+
Process With Defaults
|
|
29
|
+
Run Robot Task tasks.robot Process Invoice
|
|
30
|
+
|
|
31
|
+
Process With Overrides
|
|
32
|
+
Run Robot Task tasks.robot Process Invoice
|
|
33
|
+
... SOURCE=file.csv
|
|
34
|
+
|
|
35
|
+
See the ``Run Robot Test`` and ``Run Robot Task`` keyword documentation for
|
|
36
|
+
full details.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from RobotLibrary._listener import RobotLibraryListener
|
|
40
|
+
from RobotLibrary._version import __version__
|
|
41
|
+
|
|
42
|
+
from pathlib import Path
|
|
43
|
+
from robot.api import logger, TestSuite
|
|
44
|
+
from robot.api.deco import keyword, library
|
|
45
|
+
from robot.running.model import Keyword as RunningKeyword
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@library(scope="GLOBAL", listener="SELF")
|
|
49
|
+
class RobotLibrary(RobotLibraryListener):
|
|
50
|
+
"""Robot Framework library for testing RF tests and tasks within RF.
|
|
51
|
+
|
|
52
|
+
This library provides the ``Run Robot Test`` and ``Run Robot Task``
|
|
53
|
+
keywords that enable running tests or tasks from external ``.robot``
|
|
54
|
+
files inside the current test/task execution. Steps from the target
|
|
55
|
+
test/task are injected into the calling test's body at runtime.
|
|
56
|
+
|
|
57
|
+
``Run Robot Task`` is a convenience alias for ``Run Robot Test`` that
|
|
58
|
+
improves readability when working with RPA task suites (files using
|
|
59
|
+
``*** Tasks ***`` instead of ``*** Test Cases ***).
|
|
60
|
+
|
|
61
|
+
== How it works ==
|
|
62
|
+
|
|
63
|
+
1. A test/task calls ``Run Robot Test`` (or ``Run Robot Task``).
|
|
64
|
+
2. The library's Listener v3 ``start_test`` method fires before execution.
|
|
65
|
+
3. The target ``.robot`` file is parsed and the named test/task is located.
|
|
66
|
+
4. Resource imports from the target suite are loaded via ``Import Resource``.
|
|
67
|
+
5. Variables (scalar, list, dict) from the target suite are injected as
|
|
68
|
+
``Set Test Variable`` calls.
|
|
69
|
+
6. ``[Setup]`` and ``[Teardown]`` from the target test/task are injected as
|
|
70
|
+
body steps.
|
|
71
|
+
7. All test/task body steps (including control structures) are deep-copied
|
|
72
|
+
and injected into the calling test's body.
|
|
73
|
+
8. The original marker keyword is removed, resulting in clean logs.
|
|
74
|
+
|
|
75
|
+
== Features ==
|
|
76
|
+
|
|
77
|
+
- *No subprocess execution* — tests run in the same Python process.
|
|
78
|
+
- *Control structures* — FOR, IF, WHILE, TRY/EXCEPT, BREAK, CONTINUE work.
|
|
79
|
+
- *Setup/teardown* — ``[Setup]`` and ``[Teardown]`` from target tests/tasks
|
|
80
|
+
are injected.
|
|
81
|
+
- *Resource imports* — resource files imported by the target suite are
|
|
82
|
+
auto-imported.
|
|
83
|
+
- *Variable override* — scalar, list, and dict variables can be overridden.
|
|
84
|
+
- *Clean logs* — injected steps appear directly in the test body.
|
|
85
|
+
- *Suite caching* — parsed suites are cached for performance.
|
|
86
|
+
- *Works with both tests and tasks* — ``Run Robot Test`` and
|
|
87
|
+
``Run Robot Task`` both work with ``*** Test Cases ***`` and
|
|
88
|
+
``*** Tasks ***`` suites.
|
|
89
|
+
- *Task-level settings* — ``Task Setup``, ``Task Teardown``,
|
|
90
|
+
``Task Template`` and ``Task Timeout`` in target suites are honoured.
|
|
91
|
+
|
|
92
|
+
== Example with Test Cases ==
|
|
93
|
+
|
|
94
|
+
Target suite (``login.robot``):
|
|
95
|
+
| *** Variables ***
|
|
96
|
+
| ${USERNAME} default_user
|
|
97
|
+
| ${PASSWORD} default_pass
|
|
98
|
+
|
|
|
99
|
+
| *** Test Cases ***
|
|
100
|
+
| Login Test
|
|
101
|
+
| Log Logging in as ${USERNAME}
|
|
102
|
+
| Should Not Be Empty ${USERNAME}
|
|
103
|
+
| Should Not Be Empty ${PASSWORD}
|
|
104
|
+
|
|
105
|
+
Meta-test suite (``test_login.robot``):
|
|
106
|
+
| *** Settings ***
|
|
107
|
+
| Library RobotLibrary
|
|
108
|
+
|
|
|
109
|
+
| *** Test Cases ***
|
|
110
|
+
| Test Login With Defaults
|
|
111
|
+
| Run Robot Test login.robot Login Test
|
|
112
|
+
|
|
|
113
|
+
| Test Login With Custom Credentials
|
|
114
|
+
| Run Robot Test login.robot Login Test
|
|
115
|
+
| ... USERNAME=admin PASSWORD=secret
|
|
116
|
+
|
|
117
|
+
== Example with Tasks (RPA) ==
|
|
118
|
+
|
|
119
|
+
Target task suite (``process.robot``):
|
|
120
|
+
| *** Variables ***
|
|
121
|
+
| ${SOURCE} default.csv
|
|
122
|
+
|
|
|
123
|
+
| *** Tasks ***
|
|
124
|
+
| Process Invoice
|
|
125
|
+
| Log Processing from ${SOURCE}
|
|
126
|
+
| Should Not Be Empty ${SOURCE}
|
|
127
|
+
|
|
128
|
+
Meta-test suite (``test_process.robot``):
|
|
129
|
+
| *** Settings ***
|
|
130
|
+
| Library RobotLibrary
|
|
131
|
+
|
|
|
132
|
+
| *** Test Cases ***
|
|
133
|
+
| Test Process With Defaults
|
|
134
|
+
| Run Robot Task process.robot Process Invoice
|
|
135
|
+
|
|
|
136
|
+
| Test Process With Custom Source
|
|
137
|
+
| Run Robot Task process.robot Process Invoice
|
|
138
|
+
| ... SOURCE=invoices.csv
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
ROBOT_LIBRARY_VERSION = __version__
|
|
142
|
+
ROBOT_LISTENER_API_VERSION = 3
|
|
143
|
+
|
|
144
|
+
def __init__(self):
|
|
145
|
+
"""Initialize the library.
|
|
146
|
+
|
|
147
|
+
Creates a suite cache for performance (avoids re-parsing the same
|
|
148
|
+
``.robot`` files) and registers the library as a Listener v3.
|
|
149
|
+
"""
|
|
150
|
+
self._suite_cache: dict[Path, TestSuite] = {}
|
|
151
|
+
|
|
152
|
+
@keyword("Run Robot Test")
|
|
153
|
+
def run_robot_test(self, suite_path: str, test_name: str, **variables: str) -> str:
|
|
154
|
+
"""Run a test or task from another Robot Framework suite file.
|
|
155
|
+
|
|
156
|
+
This keyword acts as a *marker* that is replaced at runtime by the
|
|
157
|
+
library's listener. The actual steps from the target test/task are
|
|
158
|
+
injected into the calling test's body before execution begins.
|
|
159
|
+
|
|
160
|
+
Arguments:
|
|
161
|
+
- ``suite_path``: Path to the ``.robot`` file (absolute, or relative
|
|
162
|
+
to the directory of the calling suite).
|
|
163
|
+
- ``test_name``: Name of the test case or task to run.
|
|
164
|
+
- ``**variables``: Variable overrides as ``NAME=value`` pairs. These
|
|
165
|
+
override variables defined in the target suite's
|
|
166
|
+
``*** Variables ***`` section.
|
|
167
|
+
|
|
168
|
+
Returns an error message if injection fails (i.e., the listener did
|
|
169
|
+
not replace this marker).
|
|
170
|
+
|
|
171
|
+
Examples:
|
|
172
|
+
| Run Robot Test | tests/login.robot | Valid Login |
|
|
173
|
+
| Run Robot Test | ${CURDIR}/api.robot | GET Request | API_URL=http://localhost |
|
|
174
|
+
|
|
175
|
+
The keyword also works with ``Test Template`` for data-driven testing:
|
|
176
|
+
| *** Settings ***
|
|
177
|
+
| Library RobotLibrary
|
|
178
|
+
| Test Template Run Robot Test
|
|
179
|
+
|
|
|
180
|
+
| *** Test Cases *** suite_path test_name NAME
|
|
181
|
+
| Log John ${CURDIR}/tasks.robot Log name John
|
|
182
|
+
| Log Jane ${CURDIR}/tasks.robot Log name Jane
|
|
183
|
+
"""
|
|
184
|
+
return f"ERROR: Injection failed for test '{test_name}'"
|
|
185
|
+
|
|
186
|
+
@keyword("Run Robot Task")
|
|
187
|
+
def run_robot_task(self, suite_path: str, task_name: str, **variables: str) -> str:
|
|
188
|
+
"""Run a task from another Robot Framework suite file.
|
|
189
|
+
|
|
190
|
+
This is an alias for ``Run Robot Test`` intended for use with RPA
|
|
191
|
+
task suites (files using ``*** Tasks ***`` instead of
|
|
192
|
+
``*** Test Cases ***``). It works identically — the target task's
|
|
193
|
+
steps are injected into the calling test or task at runtime.
|
|
194
|
+
|
|
195
|
+
Arguments:
|
|
196
|
+
- ``suite_path``: Path to the ``.robot`` file containing tasks.
|
|
197
|
+
- ``task_name``: Name of the task to run.
|
|
198
|
+
- ``**variables``: Variable overrides as ``NAME=value`` pairs.
|
|
199
|
+
|
|
200
|
+
Examples:
|
|
201
|
+
| Run Robot Task | tasks/process_invoice.robot | Process Invoice |
|
|
202
|
+
| Run Robot Task | ${CURDIR}/rpa.robot | Data Entry | SOURCE=file.csv |
|
|
203
|
+
|
|
204
|
+
The keyword works with both ``Test Template`` and ``Task Template``:
|
|
205
|
+
| *** Settings ***
|
|
206
|
+
| Library RobotLibrary
|
|
207
|
+
| Task Template Run Robot Task
|
|
208
|
+
|
|
|
209
|
+
| *** Tasks *** suite_path task_name NAME
|
|
210
|
+
| Process John ${CURDIR}/tasks.robot Log name John
|
|
211
|
+
| Process Jane ${CURDIR}/tasks.robot Log name Jane
|
|
212
|
+
"""
|
|
213
|
+
return f"ERROR: Injection failed for task '{task_name}'"
|
|
214
|
+
|
|
215
|
+
# -- Suite loading and test lookup ----------------------------------------
|
|
216
|
+
|
|
217
|
+
def _load_suite(self, suite_path: str) -> TestSuite:
|
|
218
|
+
"""Load and cache a test suite from a ``.robot`` file.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
suite_path: Resolved absolute path to the ``.robot`` file.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
Parsed ``TestSuite`` object.
|
|
225
|
+
"""
|
|
226
|
+
resolved = Path(suite_path).resolve()
|
|
227
|
+
|
|
228
|
+
if resolved not in self._suite_cache:
|
|
229
|
+
logger.debug(f"Parsing suite: {resolved}")
|
|
230
|
+
self._suite_cache[resolved] = TestSuite.from_file_system(resolved)
|
|
231
|
+
|
|
232
|
+
return self._suite_cache[resolved]
|
|
233
|
+
|
|
234
|
+
def _find_test(self, suite: TestSuite, test_name: str):
|
|
235
|
+
"""Find a test case or task by name in a suite.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
suite: Parsed ``TestSuite``.
|
|
239
|
+
test_name: Exact name of the test or task.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
The matching test/task model object, or ``None``.
|
|
243
|
+
"""
|
|
244
|
+
for test in suite.tests:
|
|
245
|
+
if test.name == test_name:
|
|
246
|
+
return test
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
# -- Variable helpers -----------------------------------------------------
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def _parse_variable_overrides(args) -> dict[str, str]:
|
|
253
|
+
"""Parse ``KEY=value`` variable overrides from keyword arguments.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
args: Iterable of argument strings in ``KEY=value`` format.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Dictionary mapping variable names to values.
|
|
260
|
+
"""
|
|
261
|
+
variables: dict[str, str] = {}
|
|
262
|
+
for arg in args:
|
|
263
|
+
arg_str = str(arg)
|
|
264
|
+
if "=" in arg_str:
|
|
265
|
+
var_name, var_value = arg_str.split("=", 1)
|
|
266
|
+
variables[var_name.strip()] = var_value
|
|
267
|
+
return variables
|
|
268
|
+
|
|
269
|
+
@staticmethod
|
|
270
|
+
def _create_set_variable_keyword(name: str, value) -> RunningKeyword:
|
|
271
|
+
"""Create a ``Set Test Variable`` keyword call.
|
|
272
|
+
|
|
273
|
+
Handles scalar (``${}``) , list (``@{}``) and dictionary (``&{}``)
|
|
274
|
+
variables. For list and dict variables the individual items are
|
|
275
|
+
passed as separate arguments so that ``Set Test Variable`` receives
|
|
276
|
+
them correctly.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
name: Variable name including its ``${}``, ``@{}`` or ``&{}``
|
|
280
|
+
wrapper.
|
|
281
|
+
value: Variable value – a single string for scalars, or a tuple /
|
|
282
|
+
list of strings for list and dict variables.
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
A ``RunningKeyword`` model object.
|
|
286
|
+
"""
|
|
287
|
+
if isinstance(value, (list, tuple)) and (
|
|
288
|
+
name.startswith("@{") or name.startswith("&{")
|
|
289
|
+
):
|
|
290
|
+
return RunningKeyword(
|
|
291
|
+
name="BuiltIn.Set Test Variable",
|
|
292
|
+
args=[name, *value],
|
|
293
|
+
)
|
|
294
|
+
return RunningKeyword(
|
|
295
|
+
name="BuiltIn.Set Test Variable",
|
|
296
|
+
args=[name, value],
|
|
297
|
+
)
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Listener v3 mixin for RobotLibrary.
|
|
2
|
+
|
|
3
|
+
Implements the Listener v3 ``start_test`` and ``end_test`` hooks that perform
|
|
4
|
+
the runtime injection of test/task steps from target suites into the calling
|
|
5
|
+
test or task. The ``start_test`` hook fires for both tests and tasks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from robot.api import logger
|
|
9
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RobotLibraryListener:
|
|
13
|
+
"""Mixin providing Listener v3 hooks for test/task step injection.
|
|
14
|
+
|
|
15
|
+
This class is designed to be used as a base for ``RobotLibrary``. It
|
|
16
|
+
expects the subclass to provide:
|
|
17
|
+
|
|
18
|
+
- ``_load_suite(suite_path)``
|
|
19
|
+
- ``_find_test(suite, test_name)``
|
|
20
|
+
- ``_parse_variable_overrides(args)``
|
|
21
|
+
- ``_create_set_variable_keyword(name, value)``
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def start_test(self, data, result):
|
|
25
|
+
"""Listener v3 hook — inject test steps before test execution.
|
|
26
|
+
|
|
27
|
+
Called by Robot Framework before each test starts. Finds all
|
|
28
|
+
``Run Robot Test`` markers in the test body and replaces them with
|
|
29
|
+
actual test steps from target test files.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
data: Running test model (mutable).
|
|
33
|
+
result: Result object (read-only at this point).
|
|
34
|
+
"""
|
|
35
|
+
self._inject_test_steps(data)
|
|
36
|
+
|
|
37
|
+
def end_test(self, data, result):
|
|
38
|
+
"""Listener v3 hook — called after test execution completes.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
data: Running test model.
|
|
42
|
+
result: Result object with test outcome.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
# -- Injection pipeline ---------------------------------------------------
|
|
46
|
+
|
|
47
|
+
_MARKER_KEYWORDS = frozenset({"Run Robot Test", "Run Robot Task"})
|
|
48
|
+
|
|
49
|
+
def _inject_test_steps(self, test_data):
|
|
50
|
+
"""Find and replace all ``Run Robot Test`` / ``Run Robot Task`` markers.
|
|
51
|
+
|
|
52
|
+
Processes markers in reverse order to maintain correct indices after
|
|
53
|
+
each insertion/removal.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
test_data: Running test model to modify.
|
|
57
|
+
"""
|
|
58
|
+
markers = [
|
|
59
|
+
(idx, item)
|
|
60
|
+
for idx, item in enumerate(test_data.body)
|
|
61
|
+
if hasattr(item, "name") and item.name in self._MARKER_KEYWORDS
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
if not markers:
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
logger.info(
|
|
68
|
+
f"Found {len(markers)} Run Robot Test/Task marker(s) in '{test_data.name}'"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
for marker_idx, marker in reversed(markers):
|
|
72
|
+
try:
|
|
73
|
+
self._process_marker(test_data, marker_idx, marker)
|
|
74
|
+
except Exception as e:
|
|
75
|
+
logger.error(f"Failed to process marker at index {marker_idx}: {e}")
|
|
76
|
+
|
|
77
|
+
def _process_marker(self, test_data, marker_idx, marker):
|
|
78
|
+
"""Process a single ``Run Robot Test`` marker.
|
|
79
|
+
|
|
80
|
+
Extracts arguments, loads the target test suite, and injects the
|
|
81
|
+
steps into the calling test.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
test_data: Running test model to modify.
|
|
85
|
+
marker_idx: Index of the marker in the test body.
|
|
86
|
+
marker: The marker keyword model object.
|
|
87
|
+
"""
|
|
88
|
+
if not hasattr(marker, "args") or len(marker.args) < 2:
|
|
89
|
+
logger.warn(
|
|
90
|
+
"Run Robot Test requires at least 2 arguments: suite_path and test_name"
|
|
91
|
+
)
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# Resolve Robot Framework variables in arguments
|
|
95
|
+
try:
|
|
96
|
+
builtin = BuiltIn()
|
|
97
|
+
suite_path = builtin.replace_variables(str(marker.args[0]))
|
|
98
|
+
test_name = builtin.replace_variables(str(marker.args[1]))
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logger.debug(f"Could not resolve variables in marker arguments: {e}")
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
# Parse variable overrides from remaining arguments
|
|
104
|
+
variables = self._parse_variable_overrides(marker.args[2:])
|
|
105
|
+
|
|
106
|
+
logger.info(f"Injecting: {suite_path} / {test_name}")
|
|
107
|
+
|
|
108
|
+
# Load the target suite and locate the test/task
|
|
109
|
+
suite = self._load_suite(suite_path)
|
|
110
|
+
target_test = self._find_test(suite, test_name)
|
|
111
|
+
|
|
112
|
+
if not target_test:
|
|
113
|
+
logger.error(f"Test or task '{test_name}' not found in {suite_path}")
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
# Perform the injection
|
|
117
|
+
self._inject_variables_and_steps(
|
|
118
|
+
test_data, marker_idx, suite, target_test, variables
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
logger.info(
|
|
122
|
+
f"Successfully injected {len(target_test.body)} step(s) from '{test_name}'"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def _inject_variables_and_steps(
|
|
126
|
+
self, test_data, marker_idx, suite, target_test, overrides
|
|
127
|
+
):
|
|
128
|
+
"""Replace a marker with variable setup keywords and test steps.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
test_data: Running test model to modify.
|
|
132
|
+
marker_idx: Index of the marker to replace.
|
|
133
|
+
suite: Target ``TestSuite`` (provides the variable table).
|
|
134
|
+
target_test: Target test/task (provides the steps to inject).
|
|
135
|
+
overrides: Variable overrides from keyword arguments.
|
|
136
|
+
"""
|
|
137
|
+
variable_steps = []
|
|
138
|
+
|
|
139
|
+
# Inject variables from the target suite's *** Variables *** section
|
|
140
|
+
if hasattr(suite, "resource") and hasattr(suite.resource, "variables"):
|
|
141
|
+
for var in suite.resource.variables:
|
|
142
|
+
if hasattr(var, "name") and hasattr(var, "value"):
|
|
143
|
+
if var.name.startswith(("@{", "&{")):
|
|
144
|
+
# List / dict variables: keep the full tuple
|
|
145
|
+
var_value = var.value
|
|
146
|
+
elif isinstance(var.value, (list, tuple)) and var.value:
|
|
147
|
+
# Scalar variables: unwrap the single-element tuple
|
|
148
|
+
var_value = var.value[0]
|
|
149
|
+
else:
|
|
150
|
+
var_value = var.value
|
|
151
|
+
variable_steps.append(
|
|
152
|
+
self._create_set_variable_keyword(var.name, var_value)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Apply overrides (these take precedence)
|
|
156
|
+
if overrides:
|
|
157
|
+
logger.info(f"Applying variable overrides: {list(overrides.keys())}")
|
|
158
|
+
for var_name, var_value in overrides.items():
|
|
159
|
+
if not var_name.startswith("${"):
|
|
160
|
+
var_name = f"${{{var_name}}}"
|
|
161
|
+
variable_steps.append(
|
|
162
|
+
self._create_set_variable_keyword(var_name, var_value)
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# Build resource import steps so that keywords from the target
|
|
166
|
+
# suite's resource files are available in the calling test's scope.
|
|
167
|
+
import_steps = []
|
|
168
|
+
if hasattr(suite, "resource") and hasattr(suite.resource, "imports"):
|
|
169
|
+
from pathlib import Path
|
|
170
|
+
from robot.running.model import Keyword as RunningKeyword
|
|
171
|
+
|
|
172
|
+
suite_dir = Path(str(suite.source)).parent if suite.source else None
|
|
173
|
+
for imp in suite.resource.imports:
|
|
174
|
+
if imp.type == "RESOURCE":
|
|
175
|
+
imp_path = imp.name
|
|
176
|
+
# Resolve relative paths against the target suite dir
|
|
177
|
+
if suite_dir and not Path(imp_path).is_absolute():
|
|
178
|
+
imp_path = str(suite_dir / imp_path)
|
|
179
|
+
import_steps.append(
|
|
180
|
+
RunningKeyword(
|
|
181
|
+
name="BuiltIn.Import Resource",
|
|
182
|
+
args=[imp_path],
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Collect setup / teardown steps from the target test.
|
|
187
|
+
# These are injected as regular body steps (first / last) rather
|
|
188
|
+
# than using test_data.setup / .teardown, which keeps the logic
|
|
189
|
+
# simple and avoids RF-internal fixture resolution quirks.
|
|
190
|
+
setup_steps = []
|
|
191
|
+
if target_test.setup and target_test.setup.name:
|
|
192
|
+
from robot.running.model import Keyword as RunningKeyword
|
|
193
|
+
|
|
194
|
+
setup_steps.append(
|
|
195
|
+
RunningKeyword(
|
|
196
|
+
name=target_test.setup.name,
|
|
197
|
+
args=list(target_test.setup.args),
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
teardown_steps = []
|
|
202
|
+
if target_test.teardown and target_test.teardown.name:
|
|
203
|
+
from robot.running.model import Keyword as RunningKeyword
|
|
204
|
+
|
|
205
|
+
teardown_steps.append(
|
|
206
|
+
RunningKeyword(
|
|
207
|
+
name=target_test.teardown.name,
|
|
208
|
+
args=list(target_test.teardown.args),
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# Remove the marker keyword
|
|
213
|
+
test_data.body.pop(marker_idx)
|
|
214
|
+
|
|
215
|
+
# Build the full list of steps to inject:
|
|
216
|
+
# 1. resource imports
|
|
217
|
+
# 2. variable setup
|
|
218
|
+
# 3. target test [Setup] (as a regular keyword call)
|
|
219
|
+
# 4. target test body
|
|
220
|
+
# 5. target test [Teardown] (as a regular keyword call)
|
|
221
|
+
injected = import_steps + variable_steps + setup_steps
|
|
222
|
+
for step in target_test.body:
|
|
223
|
+
injected.append(step.deepcopy())
|
|
224
|
+
injected.extend(teardown_steps)
|
|
225
|
+
|
|
226
|
+
for idx, step in enumerate(injected):
|
|
227
|
+
test_data.body.insert(marker_idx + idx, step)
|
RobotLibrary/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: robotframework-robotlibrary
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Robot Framework library for testing Robot Framework tests and tasks within Robot Framework itself.
|
|
5
|
+
Project-URL: Homepage, https://github.com/datakurre/robotframework-robotlibrary
|
|
6
|
+
Project-URL: Repository, https://github.com/datakurre/robotframework-robotlibrary
|
|
7
|
+
Project-URL: Issues, https://github.com/datakurre/robotframework-robotlibrary/issues
|
|
8
|
+
Author: robotframework-robotlibrary contributors
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: meta-testing,robotframework,testautomation,testing
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Framework :: Robot Framework
|
|
14
|
+
Classifier: Framework :: Robot Framework :: Library
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Requires-Python: >=3.9
|
|
25
|
+
Requires-Dist: robotframework>=5.0
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# RobotLibrary
|
|
29
|
+
|
|
30
|
+
> [!WARNING]
|
|
31
|
+
> This project is primarily developed with the assistance of AI coding agents.
|
|
32
|
+
> See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
33
|
+
|
|
34
|
+
A [Robot Framework](https://robotframework.org/) library for testing Robot
|
|
35
|
+
Framework tests and tasks *within* Robot Framework itself — without subprocess
|
|
36
|
+
execution.
|
|
37
|
+
|
|
38
|
+
## Overview
|
|
39
|
+
|
|
40
|
+
RobotLibrary enables meta-testing: you write Robot Framework tests that
|
|
41
|
+
exercise *other* `.robot` test/task files. The target suite's steps are
|
|
42
|
+
injected into the calling test at runtime using the Listener v3 API, so
|
|
43
|
+
everything runs in a single process with clean logs.
|
|
44
|
+
|
|
45
|
+
**Key features:**
|
|
46
|
+
|
|
47
|
+
- **No subprocess execution** — tests run in the same Python process.
|
|
48
|
+
- **Control structures** — FOR, IF, WHILE, TRY/EXCEPT, BREAK, CONTINUE all work.
|
|
49
|
+
- **Test setup/teardown** — `[Setup]` and `[Teardown]` from target tests are injected.
|
|
50
|
+
- **Suite-level fixtures** — default `Test Setup` / `Test Teardown` from target `*** Settings ***` are honoured.
|
|
51
|
+
- **Resource imports** — resource files imported by the target suite are auto-imported at runtime.
|
|
52
|
+
- **Variable override** — suite variables (scalar, list, and dict) can be overridden per test.
|
|
53
|
+
- **Clean logs** — injected steps appear directly in the test body.
|
|
54
|
+
- **Works with both tests and tasks.**
|
|
55
|
+
|
|
56
|
+
**Important limitations:**
|
|
57
|
+
|
|
58
|
+
- **Early development** — many Robot Framework edge cases are not yet handled.
|
|
59
|
+
- **No library imports** — only Resource imports from the target suite are handled; Library imports are not.
|
|
60
|
+
- **No suite setup/teardown** — `Suite Setup` / `Suite Teardown` from target files are ignored (only test-level fixtures are injected).
|
|
61
|
+
- **Setup/teardown semantics** — target `[Setup]` and `[Teardown]` are injected as regular body steps, so they lack the special RF semantics (e.g. teardown always running on failure).
|
|
62
|
+
- **Advanced variable scoping** — complex variable expressions or scoping beyond `Set Test Variable` may not work.
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install robotframework-robotlibrary
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or install from source for development:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install -e .
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Quick Start
|
|
77
|
+
|
|
78
|
+
Given a target test suite (`login.robot`):
|
|
79
|
+
|
|
80
|
+
```robotframework
|
|
81
|
+
*** Variables ***
|
|
82
|
+
${USERNAME} default_user
|
|
83
|
+
${PASSWORD} default_pass
|
|
84
|
+
|
|
85
|
+
*** Test Cases ***
|
|
86
|
+
Login Test
|
|
87
|
+
Log Logging in as ${USERNAME}
|
|
88
|
+
Should Not Be Empty ${USERNAME}
|
|
89
|
+
Should Not Be Empty ${PASSWORD}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Write a meta-test suite (`test_login.robot`):
|
|
93
|
+
|
|
94
|
+
```robotframework
|
|
95
|
+
*** Settings ***
|
|
96
|
+
Library RobotLibrary
|
|
97
|
+
|
|
98
|
+
*** Test Cases ***
|
|
99
|
+
Test Login With Defaults
|
|
100
|
+
Run Robot Test ${CURDIR}/login.robot Login Test
|
|
101
|
+
|
|
102
|
+
Test Login With Custom Credentials
|
|
103
|
+
Run Robot Test ${CURDIR}/login.robot Login Test
|
|
104
|
+
... USERNAME=admin
|
|
105
|
+
... PASSWORD=secret
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Run the meta-tests:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
robot test_login.robot
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Keyword Documentation
|
|
115
|
+
|
|
116
|
+
### Run Robot Test
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Run Robot Test suite_path test_name **variables
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| Argument | Description |
|
|
123
|
+
| -------------- | ------------------------------------------------------------------- |
|
|
124
|
+
| `suite_path` | Path to the `.robot` file (absolute, or relative to current suite). |
|
|
125
|
+
| `test_name` | Name of the test case or task to run. |
|
|
126
|
+
| `**variables` | Variable overrides as `NAME=value` pairs. |
|
|
127
|
+
|
|
128
|
+
The keyword also works with `Test Template` for data-driven testing:
|
|
129
|
+
|
|
130
|
+
```robotframework
|
|
131
|
+
*** Settings ***
|
|
132
|
+
Library RobotLibrary
|
|
133
|
+
Test Template Run task
|
|
134
|
+
|
|
135
|
+
*** Keywords ***
|
|
136
|
+
Run task
|
|
137
|
+
[Arguments] ${NAME}
|
|
138
|
+
Run Robot Test ${CURDIR}/tasks.robot Log name NAME=${NAME}
|
|
139
|
+
|
|
140
|
+
*** Test Cases *** NAME
|
|
141
|
+
Log John John
|
|
142
|
+
Log Jane Jane
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Run Robot Task
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
Run Robot Task suite_path task_name **variables
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
An alias for `Run Robot Test` designed for RPA task suites (files using
|
|
152
|
+
`*** Tasks ***` instead of `*** Test Cases ***`). It works identically —
|
|
153
|
+
the target task's steps are injected into the calling test or task at runtime.
|
|
154
|
+
|
|
155
|
+
| Argument | Description |
|
|
156
|
+
| -------------- | ------------------------------------------------------------------------ |
|
|
157
|
+
| `suite_path` | Path to the `.robot` file containing tasks. |
|
|
158
|
+
| `task_name` | Name of the task to run. |
|
|
159
|
+
| `**variables` | Variable overrides as `NAME=value` pairs. |
|
|
160
|
+
|
|
161
|
+
```robotframework
|
|
162
|
+
*** Settings ***
|
|
163
|
+
Library RobotLibrary
|
|
164
|
+
|
|
165
|
+
*** Test Cases ***
|
|
166
|
+
Test Invoice Processing
|
|
167
|
+
Run Robot Task ${CURDIR}/process.robot Process Invoice
|
|
168
|
+
... SOURCE=invoices.csv
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The keyword also works with `Task Template` in task suites:
|
|
172
|
+
|
|
173
|
+
```robotframework
|
|
174
|
+
*** Settings ***
|
|
175
|
+
Library RobotLibrary
|
|
176
|
+
Task Template Run Robot Task
|
|
177
|
+
|
|
178
|
+
*** Tasks *** SUITE_PATH TASK_NAME NAME
|
|
179
|
+
Process John ${CURDIR}/tasks.robot Log name John
|
|
180
|
+
Process Jane ${CURDIR}/tasks.robot Log name Jane
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## How It Works
|
|
184
|
+
|
|
185
|
+
1. `Run Robot Test` (or `Run Robot Task`) acts as a *marker* keyword (it does nothing by itself).
|
|
186
|
+
2. The library registers itself as a Listener v3 via `@library(listener='SELF')`.
|
|
187
|
+
3. In the `start_test` hook (which fires for both tests *and* tasks), the listener finds all `Run Robot Test` / `Run Robot Task` markers.
|
|
188
|
+
4. The target `.robot` file is parsed (and cached) using `TestSuite.from_file_system()`.
|
|
189
|
+
5. Resource files imported by the target suite are loaded via `Import Resource`.
|
|
190
|
+
6. Variables from the target suite's `*** Variables ***` section (scalar, list,
|
|
191
|
+
and dict) are injected as `Set Test Variable` calls.
|
|
192
|
+
7. `[Setup]` and `[Teardown]` from the target test/task are injected as regular body
|
|
193
|
+
steps (first and last, respectively).
|
|
194
|
+
8. Test/task body steps (including all control structures) are deep-copied and inserted.
|
|
195
|
+
9. The marker keyword is removed — logs show only the injected steps.
|
|
196
|
+
|
|
197
|
+
**Task support:** Target suites can use either `*** Test Cases ***` or
|
|
198
|
+
`*** Tasks ***`. The Robot Framework model treats both uniformly, so all
|
|
199
|
+
features (variable injection, control structures, setup/teardown,
|
|
200
|
+
`Task Setup`/`Task Teardown` from the Settings section) work transparently.
|
|
201
|
+
|
|
202
|
+
**Note:** This is a proof-of-concept implementation. The step injection mechanism
|
|
203
|
+
has been tested with control structures (FOR, WHILE, IF, TRY/EXCEPT, BREAK,
|
|
204
|
+
CONTINUE), setup/teardown, resource imports, and complex variable types, but has
|
|
205
|
+
not been tested with custom libraries with state, nested suite hierarchies, or
|
|
206
|
+
advanced variable scoping.
|
|
207
|
+
|
|
208
|
+
## Development
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
# Install in editable mode
|
|
212
|
+
pip install -e .
|
|
213
|
+
|
|
214
|
+
# Run acceptance tests (meta-tests)
|
|
215
|
+
make test
|
|
216
|
+
|
|
217
|
+
# Generate keyword documentation
|
|
218
|
+
make libdoc
|
|
219
|
+
|
|
220
|
+
# Clean build artifacts
|
|
221
|
+
make clean
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Test Organization
|
|
225
|
+
|
|
226
|
+
The repository includes comprehensive meta-tests that verify RobotLibrary's
|
|
227
|
+
injection mechanism:
|
|
228
|
+
|
|
229
|
+
- **`tests/`** — Meta-tests that test RobotLibrary itself
|
|
230
|
+
- **`tests/examples/`** — Example test suites that serve as injection targets
|
|
231
|
+
|
|
232
|
+
See [tests/README.md](tests/README.md) and
|
|
233
|
+
[tests/examples/README.md](tests/examples/README.md) for details on the test
|
|
234
|
+
organization and examples of testable test patterns.
|
|
235
|
+
|
|
236
|
+
## License
|
|
237
|
+
|
|
238
|
+
Apache License 2.0 — see [LICENSE](LICENSE) for details.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
RobotLibrary/__init__.py,sha256=rJ-QsOkXSlJh8nKxKhxNd-0mSqfpQr1PRxF9FRWG8Ao,11128
|
|
2
|
+
RobotLibrary/_listener.py,sha256=rq7cQAu92tf1cGLuqWUlYMHJNhEsg_q-CcXsYHSG4Us,8808
|
|
3
|
+
RobotLibrary/_version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
4
|
+
robotframework_robotlibrary-0.1.0.dist-info/METADATA,sha256=90FczKUWgwU7p0cOdBEoCEAn9L-l0ZN7c_qtA2QP_xQ,8713
|
|
5
|
+
robotframework_robotlibrary-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
robotframework_robotlibrary-0.1.0.dist-info/licenses/LICENSE,sha256=vtzuDqr85w6C4lzmHJftPHhy5k5CN4UHS1DrE8f3QzY,10792
|
|
7
|
+
robotframework_robotlibrary-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to the Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by the Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding any notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
Copyright 2025 robotframework-robotlibrary contributors
|
|
180
|
+
|
|
181
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
182
|
+
you may not use this file except in compliance with the License.
|
|
183
|
+
You may obtain a copy of the License at
|
|
184
|
+
|
|
185
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
186
|
+
|
|
187
|
+
Unless required by applicable law or agreed to in writing, software
|
|
188
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
189
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
190
|
+
See the License for the specific language governing permissions and
|
|
191
|
+
limitations under the License.
|