Extension Lab
Chapter lab: Extension Lab
This chapter works in a prepared project. Download it before the first task; it unpacks into its own folder.
Download ch22-extension-lab.zip
Or fetch and unpack it from a terminal in VS Code:
curl -fsSLO https://manykarim.github.io/robotframework-essentials-web/labs/ch22-extension-lab.zip && unzip -q ch22-extension-lab.zip && rm ch22-extension-lab.zipInvoke-WebRequest https://manykarim.github.io/robotframework-essentials-web/labs/ch22-extension-lab.zip -OutFile ch22-extension-lab.zip; Expand-Archive ch22-extension-lab.zip -DestinationPath .; Remove-Item ch22-extension-lab.zipOpen the folder in VS Code with code rfe-ch22-extension-lab (or File → Open Folder…) and accept the recommended RobotCode and Python extensions. Then set it up:
uv syncpython3 -m venv .venvsource .venv/bin/activatepip install -r requirements.txtpython -m venv .venv.venv\Scripts\Activate.ps1pip install -r requirements.txtKeep the environment active in every terminal where you run lesson commands.
poetry install --no-rootPython 3.13 or newer is required; uv installs it when it is missing.
Open in browserNo setup? Try the lab right here: its files, an editor and a terminal, running in your browser.
What's inside
Directoryrfe-ch22-extension-lab/
Directory.vscode/
- extensions.json
Directorychecks/
- orders.robot
Directorydata/
- orders.csv
Directoryext/
- README.md
- .gitignore
- .python-version
- LAB.md
- pyproject.toml
- README.md
- requirements.txt
- uv.lock
Bring the two parts together into one small extension package — the shape a real internal Robot Framework extension takes:
- a typed library the suite uses, documented with Libdoc, and
- a listener that reports the run.
As you build, keep the design question in mind: a library adds keywords; a listener observes; a modifier reshapes what runs; a result visitor reshapes results; a parser reads new formats; a dynamic library is for keywords known only at run time. Reach for the simplest one that fits.
Task 1 — Build the shipping library
Section titled “Task 1 — Build the shipping library”Goal: create libraries/shipping.py with a typed keyword
shipping_fee(weight) returning a base fee plus a per-kilogram charge, rounded
to two decimals.
Here is the same shape over a different fee. Adapt it to a base of 4.99 plus
0.50 per kilogram of weight.
Worked example — adapt it, don’t copy it
def handling_fee(items: int) -> float: return round(1.50 + 0.25 * items, 2)Done when
libraries/shipping.py defines shipping_fee.
Hint 1
Annotate the parameter so Robot Framework converts it:
def shipping_fee(weight: float) -> float:.
Hint 2
Return the rounded total: return round(4.99 + 0.50 * weight, 2).
Solution
def shipping_fee(weight: float) -> float: return round(4.99 + 0.50 * weight, 2)Task 2 — Build the run-report listener
Section titled “Task 2 — Build the run-report listener”Goal: create ext/RunReport.py — a v3 listener that writes
results/run_report.md as a table of the run’s tests.
Here is a status-count variant. Adapt it to collect rows and write a table.
Worked example — adapt it, don’t copy it
import pathlib
class StatusCount: ROBOT_LISTENER_API_VERSION = 3
def __init__(self): self.counts = {}
def end_test(self, data, result): self.counts[result.status] = self.counts.get(result.status, 0) + 1
def close(self): summary = ", ".join(f"{n} {status}" for status, n in self.counts.items()) pathlib.Path("results").mkdir(exist_ok=True) pathlib.Path("results/counts.txt").write_text(summary + "\n", encoding="utf-8")Done when
ext/RunReport.py is a v3 listener.
Hint 1
Collect (data.name, result.status) per test in end_test, keeping
ROBOT_LISTENER_API_VERSION = 3.
Hint 2
In close(), write a | Test | Status | table to results/run_report.md.
Solution
import pathlib
class RunReport: ROBOT_LISTENER_API_VERSION = 3
def __init__(self): self.rows = []
def end_test(self, data, result): self.rows.append((data.name, result.status))
def close(self): lines = ["| Test | Status |", "| --- | --- |"] lines += [f"| {name} | {status} |" for name, status in self.rows] pathlib.Path("results").mkdir(exist_ok=True) pathlib.Path("results/run_report.md").write_text("\n".join(lines) + "\n", encoding="utf-8")Task 3 — Wire it together, run, and document
Section titled “Task 3 — Wire it together, run, and document”Create checks/shipping.robot that imports your library and uses the keyword:
*** Settings ***Library ../libraries/shipping.py
*** Test Cases ***Shipping Fee Is Charged ${fee}= Shipping Fee 3 Should Be Equal As Numbers ${fee} 6.49Run it with your listener, then generate the library’s docs:
uv run robot --listener ext/RunReport.py checks/shipping.robotuv run libdoc libraries/shipping.py results/shipping.htmlrobot --listener ext/RunReport.py checks/shipping.robotlibdoc libraries/shipping.py results/shipping.htmlpoetry run robot --listener ext/RunReport.py checks/shipping.robotpoetry run libdoc libraries/shipping.py results/shipping.htmlDone when
the suite passes, and both results/run_report.md and
results/shipping.html were generated.
Check your understanding
Quick recall. Your first answer stays in this browser; the quiz never blocks your progress.
For keywords known only at run time (generated from data), you reach for…
A dynamic library (
get_keyword_names/run_keyword, or PythonLibCore) fits runtime-defined keywords.Show the answer
Answer: a dynamic library — A dynamic library (
get_keyword_names/run_keyword, or PythonLibCore) fits runtime-defined keywords.To reshape which tests run before execution, you use…
A pre-run modifier edits the running model; a result visitor works after the run; a parser reads new formats.
Show the answer
Answer: a pre-run modifier — A pre-run modifier edits the running model; a result visitor works after the run; a parser reads new formats.
Key terms
- extension packageconcept
- A library (+Libdoc) plus a listener — a small internal RF extension.
- choosing a mechanismconcept
- Library adds keywords · listener observes · modifier reshapes · visitor post-processes · parser reads formats.