ronsel: E2E Flows You Can Read, Run and Commit
A year ago we open-sourced flows, a small orchestrator that ran multi-step end-to-end tests from YAML files. It did its job, and we kept using it on client projects. Along the way it grew a web UI, a notebook, AI assistance, remote agents and a second protocol or three, until the name stopped describing the thing. Today we are releasing that thing as ronsel, version 2.0, at github.com/lab34-es/ronsel and ronsel.lab34.es.
Ronsel is Galician for the wake a boat leaves on the water: the trace of where something has been. That is what the tool produces. Every run leaves a record of what was called, what came back, and whether it was what you expected.
The Problem, Still
The systems we build are chains: a user registers, a checkout completes, an IoT device receives a command. Each link is a different service with its own protocol, its own quirks, and a dependency on the link before it. Unit tests cover the links. What breaks in production is the chain.
Testing the chain usually means one of two things. Either a folder of scripts nobody but the author can read, or a document that describes the scenario and is executed by hand, by whoever is on rotation, with the results pasted into a ticket. The first is executable but opaque. The second is readable but does not run.
A Flow Is a Markdown Document
ronsel’s answer is to stop choosing. A flow is a Markdown file. You write headings, prose, warnings and notes as you would in any document, and mark the executable parts as fenced code blocks tagged step:
---
title: Fraud detection
description: Fraud must be detected when the customer is flagged
owner: ana
tags: [smoke, payments]
---
# Fraud detection
The invoice endpoint must refuse to answer for a flagged customer.
> [!WARNING]
> This flow creates a real customer in the environment it runs against.
```step
application: "accounting"
method: "getInvoice"
parameters:
params:
customerId: "{{ randomInt0_100 }}"
mimic:
- application: "fraud"
url: "/fraud-detection"
test:
status: 404
body:
error:
code: "ACCOUNTING_FRAUD_DETECTED"
```Press Run and the steps execute in order. The request, response, assertions and timings of each one appear right below the block that produced them, the way a Python notebook shows a cell’s output under the cell. The same file is what a reviewer reads in a pull request, what QA opens to understand the scenario, and what the pipeline executes at two in the morning.
Three nouns carry the whole tool:
- A flow says what to test. It lives under
flows/, in whatever folder tree you like. - An application knows how to talk to a system: a folder with a TypeScript module exporting methods. A method receives the step’s parameters, does the work (an HTTP request, a SQL query, an MQTT publish, a browser automation) and returns the status and body the step asserts on.
- An environment says which instance of that system, with which credentials: one env file per application per environment, kept out of git.
Everything sits in one folder, the context, which is meant to be a git repository of yours. A colleague clones it, runs npm install && npm run ronsel, fills in the env files, and has the same flows against the same applications.
What Changed Since flows
| flows (2025) | ronsel 2.0 | |
|---|---|---|
| Flow format | YAML | Markdown with step blocks |
| Interface | CLI | Notebook-style web UI + CLI |
| Protocols | HTTP, MQTT | HTTP, MQTT, PostgreSQL, browser via Playwright |
| Writing flows | by hand | by hand, or described in plain words to Ollama, Gemini or Anthropic |
| Where it runs | your machine, CI | your machine, CI, or a remote agent inside a network you cannot reach |
| Results | terminal output | a test-run folder with a standalone HTML report, per run |
| Sharing credentials | manual | one YAML export a teammate imports in one paste |
What did not change: steps are declarative, every run gets fresh random data through replacers like {{ uuid }} and {{ randomEmail }}, a memory carries values from one step to the next, and a step can mimic a dependency so a failure scenario is reproduced locally without breaking anything for real. In the example above, accounting is called for real, and what it asks fraud is answered by a fake for the duration of the step.
Assertions Beyond Equality
The test section compares status and body key by key, as deep as you write it. For anything else, a value prefixed with $expr: is a JavaScript expression over the actual value:
test:
status: 200
body:
count: "$expr: value > 10"
items: "$expr: Array.isArray(value) && value.length >= 3"
createdAt: "$expr: new Date(value) > new Date('2026-01-01')"Some effects never come back in the response: an HTTP call triggers a job that eventually publishes an MQTT message. Latent applications subscribe before the flow starts and let a step assert that the message arrived, with a retry window, wildcards in the topic, and a subset match on the payload.
Written With AI, Read by You
Describe a scenario in plain words and ronsel writes the flow from your own applications. The model is given the format rules, the catalogue of your applications (built from the JSDoc of each index.ts), and your prompt. Nothing more. What comes back lands in the editor as ordinary Markdown for you to read before anything runs. With a local Ollama, nothing leaves your machine. The same works for edits: select a flow, say what should change, review the diff.
We built this because the catalogue was already there for the documentation, and it turned out to be exactly what a model needs to write a correct step on the first try.
Running Where the Systems Are
Sometimes the systems under test are only reachable from a machine you cannot open a port into. A ronsel agent runs there, with a clone of the same context, and both sides connect out to an MQTT broker. Nothing listens on either side.
ronsel --remote agent-ourense --file flows/checkout.md --env uatWhat travels is the commit your context is on and the env values the flow needs, encrypted to the agent’s key so the broker never sees them. What comes back is every event of the run as it happens, and the test-run folder written into your own test-runs/ as if it had run locally. The agent’s key is trusted on first sight and refused if it ever changes, the way SSH treats a host key.
Get Started
It needs Node.js 24 or newer, and nothing installed globally:
mkdir e2e && cd e2e
npx ronsel startThat furnishes the folder with example applications and flows, writes a package.json that pins the version, and opens the UI on http://localhost:3001. From then on, npm run ronsel. In a pipeline:
ronsel --file flows/checkout.md --env production
ronsel --view smoke-tests --env production- Website and documentation: ronsel.lab34.es
- Source: github.com/lab34-es/ronsel
- Package: npmjs.com/package/ronsel
We built ronsel to solve our own testing and orchestration problems, and it has been in daily use on client projects for a year under its old name. Try it on your next scenario, and tell us what breaks. Contributions, feedback and ideas are welcome.