Writing Python Test Libraries and the Library API: Core Concepts and Mental Model
Understand Robot Framework static Python libraries as a deliberate adapter boundary: import, object lifetime, keyword exposure, type conversion, logging, return values, exceptions, secrets, and result evidence.
Learning objectives
- Explain the static Python library call path from Robot import to Python instance, keyword metadata, converted arguments, return/error/logging, and Robot result.
- Separate Robot variable scope from Python library-instance scope and choose TEST, SUITE, or GLOBAL deliberately.
-
Use
@libraryand@keywordas an explicit public automation API instead of exposing every Python helper. - Distinguish runtime argument conversion from return-type documentation metadata.
-
Use public
robot.apilogging, exceptions, and Secret types without coupling domain logic to Robot internals.
Current compatibility baseline — verified 2026-08-31.
Robot Framework 7.4.2 is the stable course baseline
and requires Python 3.8+. The lab uses the documented static Python
library API, robot.api.deco.library,
robot.api.deco.keyword, robot.api.logger,
public exceptions from robot.api,
robot.api.types.Secret, and Libdoc. The
@library decorator disables automatic keyword discovery
by default; keyword methods are therefore exposed deliberately with
@keyword. Function argument annotations participate in
automatic argument conversion. Return annotations and
@keyword(types={"return": ...}) are documentation
metadata in current Robot Framework and do not convert
returned objects during execution. Secret arguments are masked in
Robot representations but are not encrypted and can still be
disclosed by code that accesses .value. The mandatory
path uses only Python, Robot Framework core, and Python's standard
unittest; no browser, API, database, SSH, Pabot, CI
provider, container runtime, paid platform, or production target is
required.
1. The problem: Robot keywords are not the right language for every kind of logic
Chapters 01–19 built increasingly capable Robot suites and tasks.
User keywords are excellent for readable orchestration, but they
become a poor fit when a workflow needs reusable algorithms, complex
validation, protocol adapters, data structures, careful exception
mapping, or code that should also be unit-tested outside Robot
Framework. At that point, adding deeper
IF/FOR/TRY trees can create a
second programming language inside the test suite.
A Python test library is the intended escape hatch. Robot remains the readable orchestration surface; Python owns implementation details that are naturally expressed, type-checked, and unit-tested as Python. The engineering goal is not “move everything to Python.” It is to define a small stable boundary between business-facing Robot keywords and domain code.
Boundary rule. A library method may be technically callable from Robot and still be a bad keyword. Expose operations that make sense to automation authors; keep parsing helpers, caches, raw client objects, and internal convenience methods private to Python.
2. Read-only inspection before writing a library
# Bash/zsh or PowerShell commands are identical here.
python --version
python -m robot --version
python -m robot.libdoc --help
# Show where Python would import the framework from.
python -c "import robot; print(robot.__version__); print(robot.__file__)"
# Optional source-level inspection from Python.
python -c "from robot.api import logger, Failure, SkipExecution; print(logger.__name__)"
These commands prove which interpreter owns Robot Framework, whether
Libdoc belongs to that same environment, and whether public
robot.api symbols resolve. A library that works only
because an editor uses a different Python interpreter is not
reproducible automation.
3. Mental model: Robot calls an adapter object, not “Python in the suite”
flowchart TD A[Library setting] --> B[Python module/class import] B --> C[Library object instance] C --> D[Keyword metadata / signature] D --> E[Robot argument conversion] E --> F[Python method] F --> G[Domain logic / external adapter] G --> H[Return value or exception] F --> I[robot.api.logger] H --> J[Robot keyword status/result] I --> J J --> K[output.xml / log.html / report.html] C -. lifetime .-> L[TEST / SUITE / GLOBAL scope] M[Robot variable scope] -. separate .-> E
The Library setting causes Python import and library discovery. For a class library, Robot creates an object according to its library scope. Robot inspects exposed keyword metadata and the Python signature, converts supported arguments, then invokes the method. That method can call pure domain logic or an external client. Return values flow back to Robot unchanged as Python objects; failures are represented by exceptions; log messages can be emitted through the public logging API. Robot records the resulting keyword/test evidence.
The dashed arrows show two lifetimes that must not be confused. Library scope controls how long the Python object lives. Robot variable scope controls where Robot variables are visible. A suite variable does not change a Python library from TEST to SUITE scope, and a GLOBAL library does not automatically create Robot global variables.
4. Define state and ownership before the first mutable method
| State store | Example | Lifetime / owner | Primary risk |
|---|---|---|---|
| Robot variable scope | ${ORDER_ID} |
Local/test/suite/global Robot execution scope | Assuming it controls Python object lifetime |
| Python library instance | self._quoted_skus |
TEST, SUITE, or GLOBAL library scope | Cross-test leakage when scope is too broad |
| Python module state | module-level cache | Process/global for a module library | Implicit shared mutable state |
| Domain object state | dataclass/client/cache | Owned by Python code | Leaking implementation objects into tests |
| External-system state | file/API/database/browser | Independent system lifecycle | Cleanup/idempotency/credentials |
| Result evidence | output.xml/log/report | Robot output directory | Sensitive arguments/logs or missing failure context |
| Secret object | Secret wrapper |
Python object accessible to code | Mistaking masking for encryption |
A maintainable library documents which of these it reads and mutates. If an object is stateful, scope is part of the public contract—not an implementation footnote.
5. Static library API: module or class maps Python callables to keywords
The static API is the simplest Robot extension model. A module library exposes functions; a class library exposes methods on instantiated objects. By default, public functions/methods can become keywords while names beginning with an underscore are excluded. That automatic discovery can be surprising when base classes or imported functions enter the namespace.
For new class libraries, the explicit pattern is clearer:
from robot.api.deco import keyword, library
@library(scope="TEST", version="1.0.0")
class ExampleLibrary:
@keyword("Business Action")
def business_action(self, value: str) -> str:
return value.upper()
def helper_not_exposed(self, value: str) -> str:
return value.strip()
@library sets ROBOT_AUTO_KEYWORDS=False by
default. That means the un-decorated public helper above is not a
Robot keyword. This makes the automation API reviewable: keyword
exposure is a deliberate decision. Since Robot Framework 7.2, a
module containing exactly one class decorated with
@library can also select that class as the library even
when the class name differs from the module name.
6. Library scope is Python object lifetime
| Scope | Object lifetime | Use when | Risk |
|---|---|---|---|
| TEST (default) | New instance for every test/task; setup/teardown use their own instance | State must not cross test boundaries | More initialization cost for expensive clients |
| SUITE | One instance per suite | A safe suite-owned resource must be shared | Tests can accidentally depend on earlier tests |
| GLOBAL | One instance for the whole Robot execution | Truly global/stateless/explicitly resettable service | Largest blast radius for mutable state |
| Module library | Always global | Mostly stateless function collection | Module globals become shared state by construction |
If a stateful library uses SUITE or GLOBAL scope, provide an explicit reset/close keyword and define who calls it. Parallel execution later makes broad mutable scope even more dangerous because worker processes and external resources add more lifetime boundaries.
7. Argument conversion is executable behavior; return annotations are documentation
Robot data cells are strings unless variables already hold Python
objects. For supported type hints, Robot converts arguments before
calling the Python method. A method such as
def quote(price: float, quantity: int) receives a
Python float and int when the cells can be
converted. Conversion failure stops the keyword before the body is
entered.
Types can come from annotations, @keyword(types=...),
supported default values, or library-level custom converters. Use
annotations first because Python tooling and Libdoc both understand
them.
Important 7.4.2 distinction.
def keyword() -> int and
@keyword(types={"return": int}) make the return type
visible to Libdoc, but Robot Framework does not convert or
validate the returned object from that metadata during execution.
The Python implementation owns the return-value contract.
8. Failures, errors, skips, and fatal stops are separate contracts
| Python behavior | Robot meaning | When appropriate |
|---|---|---|
AssertionError or
robot.api.Failure
|
Normal keyword/test failure | System/domain result violated an asserted contract |
robot.api.Error / RuntimeError
|
Execution/usage error | Library used incorrectly or cannot execute as requested |
robot.api.ContinuableFailure |
Failure but execution may continue | Rare cases where collecting more failures is intentional |
robot.api.SkipExecution |
Current test/task is skipped | Known condition makes execution inapplicable |
robot.api.FatalError |
Stops whole execution after failure | Environment is so invalid remaining tests are meaningless |
Do not use logger.error() as a substitute for raising a
failure. Logging and status are different channels. A library should
emit context with the logger and use an exception when the keyword
contract has failed.
9. Public logging API: informative, timestamped, and privacy-aware
from robot.api import logger is the stable logging
surface for libraries. It supports TRACE, DEBUG, INFO, WARN, and
ERROR plus console/HTML options. When used outside Robot execution,
messages route to Python's logging system under the
RobotFramework logger, which helps keep domain/unit
tests independent.
from robot.api import logger
def reserve(item_id: str) -> None:
logger.debug(f"Validating item_id={item_id}")
# Do not log passwords, tokens, raw Authorization headers, or PII.
Logging should report identity and state transitions without copying entire sensitive payloads. The result artifacts are durable operational evidence and therefore part of the security boundary.
10. Secret is a type boundary, not encryption
Robot Framework 7.4 exposes robot.api.types.Secret. A
parameter annotated as Secret accepts only Secret
objects, helping a library require that callers use Robot's
secret-variable mechanism. The string and repr representations hide
the underlying value, but Python code can still access
secret.value.
from robot.api import logger
from robot.api.types import Secret
def login(token: Secret):
logger.debug(f"Received token object: {token}") # representation is masked
real_token = token.value # sensitive from this point
# Never log real_token. Downstream clients can still leak it.
Secret values are not encrypted, and downstream libraries/processes
can disclose them. Chapter 23 will cover secret operations in depth;
here the library contract is to minimize exposure and never
stringify .value into Robot evidence.
11. DevOps connection: custom libraries create a governed automation boundary
A custom library can be versioned, unit-tested, documented with Libdoc, packaged, reviewed, and reused across suites. That enables architectural separation: Robot files state what operational behavior is required, while Python contains how a reusable capability works. CI can run fast Python unit tests before slower Robot integration tests and can archive Libdoc/spec output as an API contract.
The boundary also improves incident response. A failure can be classified as Robot syntax/selection, library import, conversion, domain logic, external-system state, or result/reporting instead of treating “Robot failed” as one undifferentiated layer.
Knowledge check
Does a Robot suite variable make a Python library instance suite-scoped?
No. Robot variable scope and Python library scope are separate. Library scope comes from the library configuration such as @library(scope="SUITE") or ROBOT_LIBRARY_SCOPE.
Why is @library useful even when no scope/version
option is needed?
It disables automatic keyword discovery by default, so only explicitly decorated methods are exposed. This prevents accidental public helpers or inherited methods becoming Robot keywords.
A method is annotated -> int but returns a
string. Will Robot 7.4.2 automatically convert it to
int?
No. Return type information is shown by Libdoc but is not used to convert or validate returned values during execution.
When should a library raise Failure instead of
only logging ERROR?
When the keyword contract itself failed and the Robot status must be FAIL. Logging provides context; exceptions communicate keyword status.
Why can a Secret-typed parameter still leak a credential?
Python code can access Secret.value and downstream clients/tools can log or transmit the real value. Secret masking is not encryption or a universal redaction boundary.
12. Summary and bridge
A Python test library is a typed adapter boundary around domain behavior. Its public contract includes keyword exposure, constructor/keyword signatures, argument conversion, return values, exception semantics, logging, library scope, and privacy behavior. Lesson 2 builds that contract from an empty local project and verifies it independently with Python unit tests, Libdoc, and Robot execution.
References and version anchors
- Robot Framework 7.4.2 User Guide — Creating test libraries — static API, scope, decorators, conversion and failures
- Robot Framework 7.4.2 public robot.api — stable public API surface
- Robot Framework 7.4.2 robot.api.deco — @library and @keyword
- Robot Framework 7.4.2 Libdoc — library documentation generation
- Robot Framework PyPI — stable/pre-release and Python requirement
Keep the academy open
Support free, practical DevOps education.
Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.