Chapter 29Lesson 02220–300 min

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.

Robot Framework 7.4.2--dryrun--debugfileDisposable labUnicode

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?

Why is the timing repair better than Sleep 1 s?

What evidence proves the scope incident is deterministic rather than flaky?

What should be preserved before fixing the encoding incident?

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.

Next lesson

Troubleshooting Imports, Scope, Timing, Encoding, and Flaky Automation: Configuration, Design Patterns, and Trade-Offs

Continue with Troubleshooting Imports, Scope, Timing, Encoding, and Flaky Automation: Configuration, Design Patterns, and Trade-Offs. 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

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.

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