A listener that writes a report
Chapter lab: Listening to execution
This chapter works in a prepared project. Download it before the first task; it unpacks into its own folder.
Download ch20-listening-to-execution.zip
Or fetch and unpack it from a terminal in VS Code:
curl -fsSLO https://manykarim.github.io/robotframework-essentials-web/labs/ch20-listening-to-execution.zip && unzip -q ch20-listening-to-execution.zip && rm ch20-listening-to-execution.zipInvoke-WebRequest https://manykarim.github.io/robotframework-essentials-web/labs/ch20-listening-to-execution.zip -OutFile ch20-listening-to-execution.zip; Expand-Archive ch20-listening-to-execution.zip -DestinationPath .; Remove-Item ch20-listening-to-execution.zipOpen the folder in VS Code with code rfe-ch20-listening-to-execution (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-ch20-listening-to-execution/
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
A listener watches a run and reacts to its events. Where a library adds keywords, a listener observes execution — it is how you build custom reporting, notifications, or metrics without touching a single test.
The current listener API is version 3. You declare
ROBOT_LISTENER_API_VERSION = 3, then implement the event methods you care
about: end_test(data, result) fires after each test, close() fires once at
the very end. You attach a listener on the command line with --listener.
A library can also be its own listener via a
ROBOT_LIBRARY_LISTENERattribute, but a standalone listener passed on the command line is the common case and the one we use here.
Task 1 — Collect each result, then write a table
Section titled “Task 1 — Collect each result, then write a table”Goal: create ext/MarkdownReport.py — a v3 listener that records each test’s
name and status, and in close() writes results/report.md as a markdown table.
Here is the same listener shape with a different output — a one-line status count. Adapt it to collect rows and write a table instead.
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/summary.txt").write_text(summary + "\n", encoding="utf-8")Done when
ext/MarkdownReport.py defines a listener class declaring API version 3.
Hint 1
Declare the version and collect rows: ROBOT_LISTENER_API_VERSION = 3, an
__init__ with self.rows = [], and end_test appending (data.name, result.status).
Hint 2
In close(), build the table lines and write the file:
start with ["| Test | Status |", "| --- | --- |"], add f"| {name} | {status} |"
per row, and write_text them to results/report.md.
Solution
import pathlib
class MarkdownReport: 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/report.md").write_text("\n".join(lines) + "\n", encoding="utf-8")Task 2 — Run the suite with your listener
Section titled “Task 2 — Run the suite with your listener”The bundled checks/orders.robot is the run you observe. Attach your listener:
uv run robot --listener ext/MarkdownReport.py checks/orders.robotrobot --listener ext/MarkdownReport.py checks/orders.robotpoetry run robot --listener ext/MarkdownReport.py checks/orders.robotOpen results/report.md — a table of every test and its status.
Done when
results/report.md is a markdown table of the suite’s results.
Check your understanding
Quick recall. Your first answer stays in this browser; the quiz never blocks your progress.
What does a listener do that a library does not?
A listener watches a run (e.g.
end_test,close) — it observes rather than adding keywords.Show the answer
Answer: observes execution events and reacts — A listener watches a run (e.g.
end_test,close) — it observes rather than adding keywords.How do you attach a standalone listener?
Pass
--listener path/to/File.pytorobot; a library can also self-register viaROBOT_LIBRARY_LISTENER.Show the answer
Answer:
--listener File.pyon the command line — Pass--listener path/to/File.pytorobot; a library can also self-register viaROBOT_LIBRARY_LISTENER.
Key terms
- listener v3concept
- Declare
ROBOT_LISTENER_API_VERSION = 3; hookend_test,end_suite,close. - --listenercommand
- Attach a listener to a run from the command line.