Skip to content

A listener that writes a report

Chapter lab: Listening to executionch20-listening-to-execution.zip · 4 KB

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:

Terminal window
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.zip

Open 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:

Terminal window
uv sync
Terminal window
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

Keep the environment active in every terminal where you run lesson commands.

Terminal window
poetry install --no-root

Python 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_LISTENER attribute, but a standalone listener passed on the command line is the common case and the one we use here.

A v3 listener's events during a runsequence diagram. Declare ROBOT_LISTENER_API_VERSION = 3 and implement what you needend_test — test 1fires after each test1end_test — test 2...once more per test2close()fires once at the very end3Declare ROBOT_LISTENER_API_VERSION = 3 and implement what you need

Task 1 — Collect each result, then write a table

Section titled “Task 1 — Collect each result, then write a table”
Edit

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”
Run

The bundled checks/orders.robot is the run you observe. Attach your listener:

Terminal window
uv run robot --listener ext/MarkdownReport.py checks/orders.robot
Terminal window
robot --listener ext/MarkdownReport.py checks/orders.robot
Terminal window
poetry run robot --listener ext/MarkdownReport.py checks/orders.robot

Open 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.

  1. What does a listener do that a library does not?

    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.

  2. How do you attach a standalone listener?

    Show the answer

    Answer: --listener File.py on the command line — Pass --listener path/to/File.py to robot; a library can also self-register via ROBOT_LIBRARY_LISTENER.

Key terms

listener v3concept
Declare ROBOT_LISTENER_API_VERSION = 3; hook end_test, end_suite, close.
--listenercommand
Attach a listener to a run from the command line.

All terms in the glossary →