End-to-end test automation · MIT · local-first

Tests you read.
Docs you run.

ronsel turns every cross-system test into a Markdown notebook. Read it, review it in a PR, and run it against your real websites, APIs, databases and message brokers — locally or in CI.

$ npm install -g ronsel

For your manager: every test case is a document anyone can audit, and the pipeline executes it.

An example flow

One document. Three systems. One answer.

This is a real flow. A delivery fails and the parcel is rescheduled; it checks that the tracking API, the warehouse database and the picking screens still agree. Press Run — the executions are pre-recorded, nothing leaves this page.

flows/logistics/failed-delivery-retry.mdMARKDOWNSTANDBY

A failed delivery is rescheduled

When the courier reports the recipient absent, the parcel is queued for a second attempt and the warehouse is told.

owneranapriority8tagslogistics last-mile smokereviewed✓ truedue2026-09-30xray.testKeyWMS-482latent appsmqtt · client warehouse · wms/shipments/+

A parcel leaves the warehouse and the courier cannot hand it over: nobody is home. Three systems have to agree on what happens next. The tracking API must accept the failed attempt, the warehouse database must schedule a second one, and the pickers must hear about it over MQTT. This is the flow that proves all three still agree. It runs against the real warehouse, with the courier faked.

The parcel ships

A new shipment for a random order. The courier's label service is mimicked, so this step is the same on a laptop with no VPN as it is in staging.

step 1wms · createShipmentstandby
application: wms
method: createShipment
parameters:
  body:
    orderRef: "ORD-{{ randomInt0_9999 }}"
    recipient: "{{ randomName }}"
    postalCode: "{{ randomPostalCode }}"
    service: next-day
mimic:
  - application: carrier
    url: "/labels"
test:
  status: 201
  body:
    status: "in_transit"
    trackingRef: "$expr: typeof value === 'string' && value.length > 0"

The warehouse agrees

The API said in_transit. The database is where the pickers actually look, so ask it directly. {{ shipmentId }} is what the previous step wrote to flow memory.

step 2warehouse-db · querystandby
application: warehouse-db
method: query
parameters:
  body:
    sql: "select status, attempts from shipments where id = $1"
    values:
      - "{{ shipmentId }}"
test:
  status: 200
  body:
    rows:
      0:
        status: "in_transit"
        attempts: 1

The courier finds nobody home

The failure we came for. The response is immediate, but the interesting part is asynchronous: an event on wms/shipments/{{ shipmentId }} that the picking screens subscribe to. The MQTT client has been subscribed since before the flow started, so nothing is missed while the HTTP call is in flight.

step 3wms · reportDeliveryAttemptstandby
application: wms
method: reportDeliveryAttempt
parameters:
  params:
    shipmentId: "{{ shipmentId }}"
  body:
    outcome: RECIPIENT_ABSENT
    courierId: "{{ randomPostmanId }}"
test:
  status: 202
  latentApplications:
    - application: mqtt
      client: warehouse
      test:
        - topic: "wms/shipments/{{ shipmentId }}"
          message:
            status: "reattempt_scheduled"
      retry:
        attempts: 3
        delay: 2

And tomorrow it goes out again

Second attempt booked, and booked for a future date — not for the morning that has already gone.

step 4warehouse-db · querystandby
application: warehouse-db
method: query
parameters:
  body:
    sql: "select status, attempts, next_attempt_at from shipments where id = $1"
    values:
      - "{{ shipmentId }}"
test:
  status: 200
  body:
    rows:
      0:
        status: "reattempt_scheduled"
        attempts: 2
        next_attempt_at: "$expr: new Date(value) > new Date()"

The problem

Test knowledge shouldn't live in one engineer's code

Locked in code

Your best engineer wrote the suite. Only they can read it. When they move on, your coverage becomes a black box.

For your manager: our tests stop depending on one person.

Parked in Jira

Your QA team wrote the test cases. They are precise, reviewed and current. None of them has ever executed.

For your manager: the cases we already wrote start running in CI.

Split across systems

The real flow crosses a web UI, an API, a database and a message broker. Each tool tests one piece alone. The bug lives in between.

For your manager: one test follows the whole flow, end to end.

One artifact does both jobs. A document people read. A test the pipeline runs.

How it works

Results land where the question was asked

A flow is a Markdown file: frontmatter, prose, and step blocks. You read a step, you run it, and the evidence appears below it. The same document runs headless in your pipeline. One command, one exit code.

01One .md file

Frontmatter for owner, priority, tags. Prose for people. YAML step blocks for the runner.

02Runs against real systems

HTTP APIs, PostgreSQL, MQTT and, through Playwright, the browser. Dependencies you can't touch are mimicked per step.

03Evidence under each step

Request, response, timings and every assertion, inline. No console, no report to cross-reference.

04Same file, headless in CI

The environment is a flag, not a rewrite. One command, one exit code.

$ ronsel --file flows/logistics/failed-delivery-retry.md --env staging

For your manager: the test report is the test itself.

The tool

What it looks like on your screen

The document, the folder it lives in, and the two ways AI writes one for you — all local, all yours.

a flow — run like a notebook, with the evidence under each step.
a folder — every property a column, sortable and saved as a view.
written by AI — describe the scenario, get a flow built from your applications.
rewritten by AI — the result stays unsaved until you have read it.

Capabilities

What a step can do

application: wmsHTTP APIs

Call any method of your applications. Assert on status, headers and body.

method: queryPostgreSQL

Ask the database directly when the API is not the only source of truth.

latentApplications:MQTT

Subscribe before the flow starts and assert on what arrives, whenever it arrives.

application: browserBrowser

Drive a website through Playwright in the same flow as your API and database steps. Experimental.

mimic:Mimic

Fake a dependency per step, failure scenarios included.

{{ randomName }}Random data

Fresh names, ids and dates on every attempt, from built-in replacers.

$expr: value > 0Memory & $expr

Steps share what they learn. Assertions go beyond equality.

retry: attempts: 3Retry & delay

Async systems get a second look, not a flake.

--env stagingEnvironments

Credentials live in env files per application, out of the repository.

xray.testKey: WMS-482Jira · Xray

Pull your Tests from Xray; each becomes a flow that runs in CI. Read-only, never writes to Jira.

ollama · gemini · claudeWritten with AI

Describe the scenario and get a flow built from your own applications. Local Ollama, Gemini or Claude.

Coverage

Coverage your whole team can see

Every property in a flow's frontmatter — owner, priority, due, anything you invent — becomes a column. Folders render as tables you can sort, filter and save as views, in one views.yaml your team keeps in git.

flows/last-mile · 4 flowsAll flowsRelease readinessCritical
nameownerpriority ↓tagsreviewedduecoverage*
proof-of-delivery.mdjose9pod, critical2026-08-25shallow
failed-delivery-retry.mdana8last-mile, smoke2026-09-30deep
cod-cash-collection.mdmarta7cod, billing2026-09-08deep
address-correction.mdlucia5edge-case2026-09-15shallow

* a formula: if(flow.steps > 3, "deep", "shallow") — computed columns, filters and saved views live in views.yaml

For your manager: release readiness is a saved view, not a meeting.

Security · local-first

Nothing leaves the house

Flows are files in a folder on your machine. No account, no signup, no new data processor.

For your manager: no new vendor, no new data processor, nothing leaves the house.

Your credentials stay put

Database passwords, Jira tokens and model keys live in your context folder. They are never sent to the browser.

Files in your git

Version flows in your own repository, review them in your own PRs.

Works offline

Seeded examples run with no network at all.

Local AI option

A local Ollama model keeps even flow writing on your machine.

Licensing

MIT. Free. Full stop.

Read the source, fork it, keep it forever. No per-seat licence, no vendor, no paid tier. Installing needs no account and no email.

LICENSE · MIT

Limits

What it is not

nota unit test framework. It tests behaviour across systems, not functions.

nota load testing tool. One flow, one run, one verdict.

betabrowser automation through Playwright is experimental. Expect rough edges.

readthe Xray integration never writes to Jira. Pull only.

Quick start

Your first flow runs in five minutes

The first run seeds example applications and flows that work offline. Open the notebook, press Run, and start replacing the examples with your systems. Node and npm are the only prerequisites.

terminal
$ npm install -g ronsel
$ ronsel --server
→ http://localhost:3001

Who builds it

lab34, Galicia, Spain

lab34 is an engineering company building AI for IT operations. ronsel is open source — published because working tools are better proof than slides. More at lab34.es.

“Working tools are better proof than slides. ronsel is the tool we wanted when a bug lived between three systems and nobody could read the test that should have caught it.”

Jose Constela · founder, lab34