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.
@@ -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)
@@ -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,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.