Resource Files, Variable Files, Library Imports, and Project Layout: Guided Hands-On Workflow
Refactor a disposable monolithic suite into explicit tests, resources, variables, libraries, data, and results boundaries; then verify path behavior with dry-run, Libdoc, and two launch locations.
Learning objectives
- Split a monolithic suite into tests, resources, variable files, a tiny Python library, data, and results.
- Use ${CURDIR} for source-relative fixture paths and an explicit project-root --pythonpath for library imports.
- Inspect ${CURDIR}/${EXECDIR} without depending on either accidentally.
- Verify resource/library imports with --dryrun and Libdoc.
- Run the same project from two working directories using one documented command contract.
Current compatibility baseline. Verified
2026-08-31: Robot Framework 7.4.2 is the current stable release and
requires Python 3.8+; 7.5b1 is a pre-release and is not required in
this chapter. In 7.4.2, .resource is the recommended
resource-file extension. Resource and Settings-level variable-file
paths are resolved relative to the importing data file first and
then via Python's module search path. ${CURDIR} is the
absolute directory of the current data file;
${EXECDIR} is the absolute directory where execution
started. JSON variable files require no optional package; YAML
variable files require PyYAML.
1. Disposable scenario: normalize and validate synthetic build labels
The lab operates only on local synthetic strings and a small text fixture. There is no browser, API, database, SSH service, container, or production target. The custom Python library performs a pure string normalization; the resource file expresses the domain-level validation; variable files supply non-sensitive configuration.
2. Preflight and version evidence
python --version
python -m robot --version
python -c "import os, sys; print(os.getcwd()); print(sys.executable)"
Use an isolated environment from Chapter 02 and pin Robot Framework 7.4.2 for this lab. Record the actual Python version and working directory. Do not install PyYAML: the mandatory non-Python variable-file example uses JSON.
3. Start from a deliberately monolithic suite
*** Variables ***
${PREFIX} build-
${EXPECTED} BUILD-ALPHA
*** Test Cases ***
Normalize label
${raw}= Set Variable ${PREFIX}alpha
${normalized}= Convert To Upper Case ${raw}
Should Be Equal ${normalized} ${EXPECTED}
This is small enough to work, but it hides future boundaries. If ten suites need the same normalization contract or environment-independent values, copying these definitions multiplies drift.
4. Target project layout
rf12-layout-lab/
├── tests/
│ └── labels.robot
├── resources/
│ └── labels.resource
├── variables/
│ ├── demo.py
│ └── demo.json
├── libraries/
│ ├── __init__.py
│ └── text_tools.py
├── data/
│ └── sample-label.txt
└── results/
The folders communicate ownership. tests/ expresses
scenarios. resources/ contains Robot-level
abstractions. variables/ contains imported
configuration values. libraries/ is Python capability
code. data/ is ordinary fixture content.
results/ is generated evidence and should normally be
ignored by source control.
5. Add one tiny Python library — and keep it pure
# libraries/text_tools.py
def normalize_label(value: str) -> str:
"""Normalize a synthetic build label without external side effects."""
return value.strip().upper()
This module owns a low-level transformation only. It does not read environment variables, files, credentials, or network endpoints during import. Chapter 20 will teach full custom-library architecture; here the purpose is to understand import placement.
6. Add Python and JSON variable files with different responsibilities
# variables/demo.py
PREFIX = "build-"
EXPECTED = "BUILD-ALPHA"
{
"fixture_name": "sample-label.txt",
"expected_file_value": "build-beta"
}
The Python file demonstrates normal Python variable-file import. The JSON file demonstrates a supported data-only format without optional dependencies. If you choose YAML later, pin/install PyYAML explicitly and record that dependency.
7. Move the domain contract into a .resource file
*** Settings ***
Library libraries.text_tools
Variables ../variables/demo.py
Variables ../variables/demo.json
Library OperatingSystem
Library String
*** Keywords ***
Label Should Normalize
[Arguments] ${raw} ${expected}
${actual}= Normalize Label ${raw}
Should Be Equal ${actual} ${expected}
Fixture Label Should Match
${fixture_path}= Set Variable ${CURDIR}/../data/${fixture_name}
${raw}= Get File ${fixture_path}
${raw}= Strip String ${raw}
Should Be Equal ${raw} ${expected_file_value}
${CURDIR} here means the directory containing
labels.resource, so the fixture path remains tied to
that source file no matter where the process is launched. The Python
library import by module name requires the project root on the
module search path; that is a runner responsibility.
8. Keep the suite focused on intent
*** Settings ***
Resource ../resources/labels.resource
*** Test Cases ***
Normalize configured label
Label Should Normalize ${PREFIX}alpha ${EXPECTED}
Read source-relative fixture
Fixture Label Should Match
Observe path identities
Log CURDIR=${CURDIR}
Log EXECDIR=${EXECDIR}
The suite imports one domain resource. It does not need to know
where the Python library or JSON file lives because that is owned by
the resource boundary. The final test logs path identities as
evidence; it should not make correctness depend on a particular
${EXECDIR}.
9. Create the synthetic data fixture
build-beta
Save this exact text as data/sample-label.txt. The
fixture is ordinary data, not a Robot variable file. The resource
reads it explicitly with OperatingSystem.Get File.
10. Define one launch contract that works from different cwd values
Set RF12_ROOT once to the absolute path of
rf12-layout-lab. The command contract is then “all
project paths are derived from that root; cwd is irrelevant.”
Bash / zsh:
export RF12_ROOT="/absolute/path/to/rf12-layout-lab"
python -m robot --pythonpath "$RF12_ROOT" --outputdir "$RF12_ROOT/results/from-root" "$RF12_ROOT/tests"
PowerShell:
$env:RF12_ROOT = "C:\absolute\path\to\rf12-layout-lab"
python -m robot `
--pythonpath $env:RF12_ROOT `
--outputdir "$env:RF12_ROOT\results\from-root" `
"$env:RF12_ROOT\tests"
Now change to any unrelated directory and run the same command
contract with a different output subdirectory such as
results/from-other-cwd. ${EXECDIR} will
differ; imports and fixture lookup should not.
11. Verify imports before executing behavior
python -m robot --pythonpath "$RF12_ROOT" --dryrun --outputdir "$RF12_ROOT/results/dryrun" "$RF12_ROOT/tests"
python -m robot.libdoc --pythonpath "$RF12_ROOT" libraries.text_tools "$RF12_ROOT/results/text_tools.html"
python -m robot.libdoc --pythonpath "$RF12_ROOT" "$RF12_ROOT/resources/labels.resource" "$RF12_ROOT/results/labels-resource.html"
Dry-run validates parsing, imports, keyword resolution, and argument contracts without running normal library keywords. Libdoc proves that Robot can resolve the Python module/resource and exposes the available keyword signatures/documentation. These checks complement—not replace—a real test run.
12. Expected evidence
| Evidence | Expected observation |
|---|---|
output.xml/report.html |
Three tests PASS for the clean lab |
log.html |
CURDIR points to tests/ in the
suite and to resources/ when evaluated in the
resource; EXECDIR matches launch cwd
|
| Libdoc library HTML |
Normalize Label is discoverable from
libraries.text_tools
|
| Libdoc resource HTML |
Label Should Normalize and
Fixture Label Should Match are documented
|
| Result tree |
Generated files stay under results/, not
tests/ or resources/
|
13. Challenge: choose the correct path layer
You add data/more/sample.txt used only by
resources/labels.resource. Should you (a) rely on cwd,
(b) add data/ to --pythonpath, or (c)
anchor the fixture to the resource with ${CURDIR}?
Choose and justify the owner. Then explain why a Python library
module belongs on the module search path instead.
14. Knowledge check
Why does the Python library import use --pythonpath but the fixture file does not?
The library is imported as a Python module by name, so its project root must be on the module search path. The fixture is ordinary data and is addressed explicitly with a source-relative ${CURDIR} path.
What changes when the same command contract is launched from another directory?
${EXECDIR} and process cwd change. Source-relative CURDIR paths and module imports remain deterministic because the command supplies an explicit project root.
Does a successful Libdoc run prove the business test passes?
No. It proves the library/resource can be resolved and documented. Runtime behavior still needs Robot execution.
Why use JSON instead of YAML in the mandatory lab?
JSON is supported directly and needs no optional dependency. YAML is valid but requires PyYAML, which must be pinned and documented if chosen.
15. Summary and next step
You now have a small project whose source boundaries and runner contract are explicit. Resources own Robot-level abstractions, variable files own values, Python libraries own capability code, data stays ordinary data, and results stay generated evidence. Lesson 3 turns these mechanics into design decisions about search paths, resource granularity, variable-file formats, packaging, and dependency direction.
Further reading
- Robot Framework 7.4.2 User Guide — Resource and variable files — resource structure, import rules, Python/YAML/JSON variable files, and precedence.
- Robot Framework 7.4.2 User Guide — Using test libraries — importing libraries by name or path and library aliases.
-
Robot Framework 7.4.2 User Guide — Module search path
— installed packages, PYTHONPATH, and
--pythonpath. -
Robot Framework 7.4.2 User Guide — Built-in variables
—
${CURDIR},${EXECDIR}, path separators, and temporary directory. - Robot Framework 7.4.2 User Guide — Handling keywords with same names — scope priority, explicit qualification, and search order.
- Robot Framework 7.4.2 User Guide — Libdoc — library/resource documentation and import verification.
- Robot Framework 7.4.2 on PyPI — pinned stable package metadata and Python requirement.
- Robot Framework releases — re-check stable/pre-release status when updating the chapter.
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.