Chapter 12Lesson 03190–260 min

Resource Files, Variable Files, Library Imports, and Project Layout: Configuration, Design Patterns, and Trade-Offs

Choose project-layout and import strategies deliberately: source-relative paths, project-root search paths, resource granularity, variable-file formats, Python packaging, dependency direction, and output placement.

Design patternsTrade-offsImport graphPackagingPortability

Learning objectives

  • Compare source-relative imports with one explicit project-root module-search contract.
  • Choose resource-file granularity without creating a dependency maze.
  • Choose among Robot resource variables, Python variable files, JSON, and optional YAML based on behavior and dependencies.
  • Explain when a custom library should be packaged instead of injected with --pythonpath.
  • Keep generated results outside source and avoid cycles/collisions as architecture rules.

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. Configuration is architecture when it determines import identity

A path is not merely syntax. It decides who owns a dependency and what must be true in every environment. Resource ../resources/common.resource says the suite and resource move together in one source tree. Library company.robot.Common says a Python package must be installed or discoverable. --pythonpath project-root says the runner establishes a source root. Choose the statement that matches the actual ownership contract.

2. Decision table: path/import strategies

Choice Strength Risk Good default
Source-relative Resource/Variables path Portable within one repository tree; independent of cwd Long ../../ chains after deep nesting Use for nearby project-owned resource/data relationships
Project-root --pythonpath Uniform module/resource lookup from many suites Runner must define the root explicitly Use for local project libraries or package-like resources
Installed Python package Best reuse/versioning; no repo-specific path injection Packaging/release overhead Use for mature shared libraries
Absolute source path in test data Unambiguous on one machine Not portable across workstations/CI Avoid except generated/local diagnostics
cwd-relative shell path Convenient interactively Changes with launcher/IDE/CI Avoid as an implicit import contract

3. Few large resources versus many tiny resources

A resource should group cohesive domain behavior, not every keyword with a similar implementation type. One giant common.resource creates broad coupling and name collisions. Hundreds of one-keyword resources create import overhead and navigation noise. Prefer a small graph whose direction is easy to draw: suites → domain resources → shared utility resources → libraries. A lower-level utility resource should not import a higher-level domain resource back.

4. Choose the simplest variable source that preserves provenance

Mechanism Use when Avoid when
*** Variables *** in resource Small static values belong with the reusable keyword abstraction Environment-specific or large structured configuration
Python variable file Values need Python types, controlled computation, or arguments Import-time I/O/random/network side effects are not essential
JSON variable file Static structured data should be dependency-free Humans need comments or advanced YAML features
YAML variable file Human-friendly structured configuration is useful You cannot explicitly depend on/pin PyYAML
CLI/environment variable Deployment/CI supplies a run-specific value The value should be committed as stable test data

Robot variable precedence still applies. If multiple variable files define the same name, the earliest imported variable file wins, while variables from the suite Variable section and command-line variables have higher priority. Avoid “priority as configuration logic”; use distinct names or one authoritative source when possible.

5. Variable files should describe values, not orchestrate the world

A Python variable file can execute arbitrary Python during import. That makes it tempting to create directories, fetch tokens, contact APIs, or generate random test data there. The result is hidden setup whose failure appears as an import error and whose side effects happen before the test lifecycle is visible. Keep variable-file import deterministic and low-side-effect. Put external actions in explicit setup/user/library keywords where logs and cleanup ownership are visible.

6. --pythonpath versus installing a library package

--pythonpath is appropriate for a repository-local library while it evolves with the tests. Once multiple repositories depend on the same library, normal Python packaging gives you versioned installation, dependency metadata, and consistent discovery. Do not “publish a library” by asking every CI job to append a developer-specific source directory to PYTHONPATH.

7. Generated results belong outside source boundaries

# Good repository intent
results/
*.log
# keep tests/, resources/, variables/, libraries/, data/ as source/fixtures

Generated output.xml, log.html, report.html, Libdoc HTML, screenshots, and debug files are execution evidence. Put them under an explicit output root and archive them according to CI retention policy. Writing them beside labels.robot makes source trees dirty, risks accidental commits, and complicates parallel workers.

8. Dependency cycles are an architecture smell even when imports appear to work

Preferred acyclic dependency direction
flowchart TD
T[tests/] --> D[domain resources/]
D --> U[utility resources/]
D --> V[variables/]
U --> L[libraries/]
D --> F[data/]
T --> O[results/]
O -. generated evidence only .-> T

Keep the solid arrows one-way. If utility.resource imports domain.resource while the domain already imports the utility, responsibilities are inverted. Break the shared behavior into a lower-level resource/library that both can depend on rather than hiding the cycle with dynamic imports.

9. Duplicate keyword names: qualify intentionally, then improve architecture

Robot's keyword scope priority resolves some conflicts, but two imported resource files with the same keyword name remain ambiguous. Full names such as orders.Normalize ID and users.Normalize ID make the current behavior explicit. If many calls require qualification, the project likely needs clearer domain naming rather than a global search-order tweak.

Do not use Set Library Search Order as a blanket architecture fix. It can be useful for controlled runtime polymorphism, but global priority changes make the source harder to read. Prefer explicit names and cohesive resources.

10. Keep other configuration layers separate

Layer Example Owner
Robot core execution --pythonpath, --outputdir Robot runner contract
Python environment venv, pinned packages Project dependency management
External library config Browser/API/database library arguments Library/integration layer
System under test App endpoint, fixture database Test environment
Editor/RobotCode Language server/project settings Developer tooling, not execution truth
CI/container Workspace mount, environment variables, artifact upload Pipeline/runtime platform

11. Worked scenario: choose a portable import contract

Three suites in tests/api/, tests/ui/, and tests/ops/ need one repository-local Python library and two domain resources. A reasonable design is:

  1. Put the Python library under libraries/ and add the project root once with --pythonpath <root>.
  2. Import domain resources with stable source-relative paths or package-like paths through the same root—pick one convention and document it.
  3. Keep fixture paths anchored to the resource that owns them via ${CURDIR}.
  4. Write all results to an explicit root outside tests/.
  5. If the library becomes shared across repos, package and install it instead of expanding PYTHONPATH rules.

12. Knowledge check

When is ${CURDIR} usually stronger than ${EXECDIR}?

What is the main trade-off of --pythonpath project-root?

Why can Python variable files make CI failures hard to diagnose?

Two resources import each other. What is the production design response?

13. Summary and next step

Project layout decisions are dependency contracts. Prefer source-relative relationships for nearby project files, an explicit module-search contract for repository-local Python code, installed packages for mature shared libraries, deterministic variable sources, and a separate output root. Lesson 4 diagnoses what happens when those rules fail: cycles, collisions, cwd dependence, module shadowing, optional dependency gaps, import-time secrets, and brittle path chains.

Next lesson

Resource Files, Variable Files, Library Imports, and Project Layout: Diagnostics, Failure Modes, and Production Practices

Continue with Resource Files, Variable Files, Library Imports, and Project Layout: Diagnostics, Failure Modes, and Production Practices. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

Further reading

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.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.