Troubleshooting Imports, Scope, Timing, Encoding, and Flaky Automation: Guided Hands-On Workflow
This guided lab seeds four different failures in a disposable local project and demonstrates why each needs different evidence. You will use dry-run, targeted execution, variable provenance, bounded readiness polling, and explicit UTF-8 decoding.
Learning objectives
- Build a disposable incident lab with four controlled failures: resource import, variable provenance, a local timing race, and Unicode decoding.
- Use dry-run only where its semantics fit and switch to targeted execution when runtime state must be observed.
- Capture exact commands, versions, paths, variable provenance, timestamps, bytes/text evidence, and result/debug artifacts.
- Apply the least invasive repair to each seeded incident and rerun only the smallest affected slice first.
- Produce one-sentence root-cause statements that identify the failed layer, triggering condition, evidence, and repair.
Current compatibility baseline — verified 2026-09-01. Robot Framework 7.4.2 is the current stable release and requires Python 3.8+. Robot Framework 7.5b1 is a pre-release and is not required here. Pabot 5.2.2 is the stable parallel-runner baseline; parallel-only diagnostics are optional in this chapter. Mandatory labs use local files, local Python processes, synthetic data, loopback/private state only, and disposable result directories.
1. Scenario and safety boundary
You are on call for a fictional Robot project named
incident-lab. Four tests have been seeded with
failures. Everything runs against local files and child processes;
no browser, cloud account, database, SSH host, or production service
is used.
| Incident | Seeded symptom | Actual layer | Prohibited shortcut |
|---|---|---|---|
| A — import | Resource file cannot be resolved. | Suite/import graph | Adding broad PYTHONPATH entries. |
| B — scope |
Expected MODE=resource, observed another value.
|
Variable priority/provenance | Setting a global variable to force the expected text. |
| C — timing | Ready marker is intermittently absent. | External-process readiness race | Increasing Sleep. |
| D — encoding | Reading UTF-8 fixture fails under an ASCII decoder. | Text/bytes codec boundary | errors="ignore". |
Guardrail. Create the lab only inside a directory
you own. Cleanup commands in this lesson remove only the named
incident-lab/results and
incident-lab/state directories. Never generalize them
to an unverified path.
2. Preflight and reproducible environment
python -m venv .venv
# Windows PowerShell: .\.venv\Scripts\Activate.ps1
# macOS/Linux: source .venv/bin/activate
python -m pip install "robotframework==7.4.2"
python --version
python -m robot --version
Record the actual Python version printed by your environment. Robot Framework 7.4.2 requires Python 3.8 or newer; the lab does not depend on a particular minor Python version. Create this structure:
incident-lab/
├── config/
│ └── runtime_vars.py
├── libraries/
│ └── IncidentLab.py
├── resources/
│ └── common.resource
├── state/
│ └── unicode.txt
├── tools/
│ └── delayed_writer.py
├── tests/
│ ├── incident_import.robot
│ ├── incident_scope.robot
│ ├── incident_timing.robot
│ └── incident_encoding.robot
└── results/
3. Build the deterministic local fixtures
3.1 Resource and variable file
*** Variables ***
${MODE} resource
*** Keywords ***
Resource Identity
RETURN common.resource
Save that as resources/common.resource. Save
config/runtime_vars.py as:
import sys
PYTHON = sys.executable
3.2 Local Python library
from pathlib import Path
import time
class IncidentLab:
ROBOT_LIBRARY_SCOPE = "TEST"
def wait_for_file(self, path, timeout=2.0, interval=0.05):
target = Path(path)
deadline = time.monotonic() + float(timeout)
while time.monotonic() < deadline:
if target.exists():
return str(target)
time.sleep(float(interval))
raise AssertionError(f"File was not ready within {timeout}s: {target}")
def read_ascii(self, path):
return Path(path).read_text(encoding="ascii")
def read_utf8(self, path):
return Path(path).read_text(encoding="utf-8")
TEST scope keeps each test-library instance isolated.
The timing helper uses a monotonic deadline and checks the actual
condition. The two read methods intentionally create a wrong and
correct encoding boundary.
3.3 Delayed writer
from pathlib import Path
import sys, time
delay = float(sys.argv[1])
path = Path(sys.argv[2])
text = sys.argv[3]
time.sleep(delay)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
Finally create state/unicode.txt as UTF-8 with this
exact content:
café – تهران – ✓
4. Incident A — broken resource import
Seed tests/incident_import.robot:
*** Settings ***
Resource ../resources/missing.resource
*** Test Cases ***
Import incident
Should Be Equal ${MODE} resource
4.1 Observe without executing library keywords
cd incident-lab
python -m robot --dryrun -d results/import tests/incident_import.robot
Expected evidence: Robot reports that the resource
import cannot be resolved. The failure occurs before any external
action. Preserve console output and
results/import/output.xml. Do not add a search path
yet.
4.2 Explain the resolution rule
The importing file is tests/incident_import.robot, so
../resources/missing.resource points deterministically
to resources/missing.resource. That file does not
exist. The intended file is resources/common.resource.
4.3 Least-invasive repair
Resource ../resources/common.resource
Rerun the same dry-run command. The import error should disappear. If another structural error remains, diagnose that new evidence rather than declaring the entire suite fixed.
Root cause statement: “The suite import graph
referenced resources/missing.resource, which did not
exist; dry-run showed the unresolved resource before test execution;
correcting the project-relative resource filename restored import
resolution.”
5. Incident B — variable priority mistaken for a collision
Seed tests/incident_scope.robot:
*** Settings ***
Resource ../resources/common.resource
*** Variables ***
${MODE} suite-file
*** Test Cases ***
Scope incident
Log Observed MODE = ${MODE}
Should Be Equal ${MODE} resource
Dry-run can report keyword/signature/import issues here, but it does not validate the final variable value. Execute only this test:
python -m robot --test "Scope incident" -d results/scope tests/incident_scope.robot
Expected evidence: the assertion reports
suite-file != resource. The suite file's Variable
section has higher priority than the imported resource variable with
the same name. Nothing is “random.”
Now run the same test with a command-line value:
python -m robot --variable MODE:cli --test "Scope incident" -d results/scope-cli tests/incident_scope.robot
The observed value becomes cli, demonstrating another
higher-priority source. The correct repair is architectural: either
remove the duplicate variable and keep one owner, or change the
assertion to test the intended source. For this lab, remove the
suite-level ${MODE} so the resource owns the default.
Root cause statement: “The test expected the imported resource default, but a same-named suite Variable-section value had higher priority; targeted execution exposed the winning value; removing the duplicate suite definition restored single-source ownership.”
6. Incident C — readiness race against a local child process
Seed tests/incident_timing.robot:
*** Settings ***
Library Process
Library OperatingSystem
Library ../libraries/IncidentLab.py
Variables ../config/runtime_vars.py
*** Variables ***
${READY_FILE} ${CURDIR}/../state/ready.txt
*** Test Cases ***
Timing incident
Remove File ${READY_FILE}
Start Process ${PYTHON} ${CURDIR}/../tools/delayed_writer.py 0.35 ${READY_FILE} ready=✓
File Should Exist ${READY_FILE}
The child process writes after roughly 350 ms, but the assertion runs immediately. Depending on process scheduling, the symptom may appear intermittent on different machines. Run it several times only to collect evidence, not to “average away” failure:
python -m robot --test "Timing incident" -d results/timing tests/incident_timing.robot
Expected evidence: the marker may be absent at assertion time. The source, imports, and variables are otherwise valid. Add a timestamp or inspect the file creation time if needed; the condition is readiness, not “350 ms has elapsed.”
6.1 Repair by observing readiness
Timing incident
Remove File ${READY_FILE}
Start Process ${PYTHON} ${CURDIR}/../tools/delayed_writer.py 0.35 ${READY_FILE} ready=✓
Wait For File ${READY_FILE} timeout=2.0 interval=0.05
${content}= Get File ${READY_FILE}
Should Be Equal ${content} ready=✓
The repaired test waits on the external condition and then asserts content. It does not increase a blind sleep. In a production browser/API/database library, prefer that library's native explicit wait or polling feature where available.
Root cause statement: “The test asserted a marker before the asynchronous writer had created it; repeated targeted runs showed the file state lagged the assertion; replacing the immediate check with bounded condition polling removed the race while retaining the content assertion.”
7. Incident D — UTF-8 data decoded as ASCII
Seed tests/incident_encoding.robot:
*** Settings ***
Library ../libraries/IncidentLab.py
*** Variables ***
${UNICODE_FILE} ${CURDIR}/../state/unicode.txt
*** Test Cases ***
Encoding incident
${text}= Read Ascii ${UNICODE_FILE}
Should Contain ${text} café
Execute only this test. The custom library is explicitly decoding a UTF-8 file using ASCII, so the failure is deterministic for non-ASCII bytes:
python -m robot --test "Encoding incident" --loglevel DEBUG --debugfile debug.txt -d results/encoding tests/incident_encoding.robot
Use the debug file only in this synthetic lab. Preserve the original UTF-8 fixture. The repair is to make the codec boundary correct:
${text}= Read Utf8 ${UNICODE_FILE}
Should Be Equal ${text} café – تهران – ✓
Do not replace the decoder with errors="ignore". That
may remove precisely the characters the test is meant to preserve.
Root cause statement: “A UTF-8 fixture containing non-ASCII characters was decoded by the custom library as ASCII; targeted execution preserved the decode exception and source bytes; switching the library call to explicit UTF-8 restored the intended Unicode string.”
8. Build the incident evidence ledger
| Incident | First failing layer | Before evidence | Repair | After proof |
|---|---|---|---|---|
| A | Import graph | Dry-run unresolved resource path | Correct resource filename | Same dry-run passes. |
| B | Variable provenance |
Assertion shows suite-file; CLI run shows
cli
|
Remove duplicate suite owner | Targeted execution sees resource default. |
| C | External readiness/timing | Marker absent before delayed writer completes | Bounded condition wait + content assertion | Repeated targeted runs pass without fixed sleep. |
| D | Encoding boundary | Decode error with UTF-8 source bytes | Explicit UTF-8 decode | Unicode equality passes. |
Keep raw first-failure results separate from repaired-run results. Never overwrite the only evidence with the passing rerun.
9. Challenge — choose the layer before the tool
Change the scope test by adding --variable MODE:cli and
change the timing writer delay from 0.35 to
0.75. Before running anything, predict which test
changes, which diagnostic tool is useful, and which is irrelevant.
Your answer should recognize that dry-run still cannot prove the
variable's final value, while the timing test's bounded wait should
remain correct as long as the delay is below its timeout.
10. Verification and cleanup
- All four incidents have a preserved failing result directory and a distinct repaired-run directory.
- No repair added global variables, blanket retries, fixed sleeps, broad Python paths, or silent decoding.
- The timing fixture is local and the child process exits on its own.
- Any debug file contains only synthetic data.
# From the parent of incident-lab, after verifying the path:
python -c "from pathlib import Path; p=Path('incident-lab/results'); print(p.resolve())"
# Then remove only incident-lab/results and incident-lab/state/ready.txt using your OS file manager
# or a guarded project cleanup script.
Knowledge check
Why did dry-run solve Incident A but not prove Incident B?
Incident A was an import-graph problem that dry-run exposes. Incident B depends on runtime variable value/provenance, and dry-run does not validate variables.
Why is the timing repair better than
Sleep 1 s?
It waits for the actual readiness condition with a bounded timeout, avoids unconditional delay, and still fails with a meaningful readiness error if the condition never becomes true.
What evidence proves the scope incident is deterministic rather than flaky?
The documented variable-priority rules plus targeted runs showing suite-file by default and cli when injected explain the result consistently.
What should be preserved before fixing the encoding incident?
The original UTF-8 fixture or bytes, exact decoder/codec assumption, exception/result evidence, and library/version context.
Summary and bridge
You diagnosed four failures with different owners using different evidence, while keeping the lab local and disposable. Lesson 3 converts these tactics into design choices and trade-offs so teams know when to use dry-run, TRACE/debug files, explicit waits, path controls, UTF-8 normalization, local reproduction, and CI-only instrumentation.
Further reading
- Robot Framework User Guide — current syntax, imports, variable priority/scope, dry-run, log levels, debug file, search paths, output artifacts, and execution semantics.
- BuiltIn library, OperatingSystem library, and Process library — assertions, variable inspection, filesystem checks, and local process control used by the labs.
- Robot Framework releases and Robot Framework on PyPI — verify the stable/pre-release boundary before reproducing incident behavior.
- Pabot documentation and Pabot releases — parallel worker behavior and evidence when diagnosing contention or collisions.
- Python codecs documentation — text/bytes conversion and explicit encoding behavior at custom-library boundaries.
Troubleshooting commands are intentionally conservative and local. Re-check primary documentation when Robot Framework, Python, external libraries, Pabot, containers, or CI runners change.
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.