The experiment editor
An experiment is a test written as a graph: nodes that send (an HTTP request, an OSC message, an MQTT publish…), wait for an answer, check what came back, play the other side or break the network on cue, joined by wires that say what runs next. A run starts at Start, follows the wires and passes when it reaches End with every step passed. You build it on the Experiments screen, run it there, and run the same file from the command line or a server.
The editor holds one experiment at a time and saves it as you work. To keep several, export snapshots or open another from a file (see Saving and files).
Every kind of node, its fields and outputs are in the node reference. How values flow between nodes is Data and templates; parallel branches, loops, retries and repeats are Flow.
The screen
| Area | What it holds |
|---|---|
| Toolbar (top) | The experiment's name and save state, adding nodes, parameters, profiles, the panes, focus mode, fullscreen and Run |
| Canvas bar | Undo and redo, the node finder, Arrange and zoom |
| Canvas | The graph: nodes, wires, and the run's progress drawn on them |
| Properties (right) | The selected node's fields, its preview and Send now; or what several selected nodes or a selected wire allow |
| Run timeline (bottom) | The steps of the current or last run, its outcome, report and seed |
The properties pane and the timeline have a handle on their inner edge: drag it, or focus it with Tab and use the arrow keys (Shift for bigger steps); a double click or Enter gives the pane its default size back. Sizes are kept for the next time. The timeline stays folded until the first run opens it.
The toolbar
| Control | What it does |
|---|---|
| ☰ Experiments | Opens the templates, opening a JSON file and exporting (Saving and files) |
| Experiment name | The experiment's name; a run needs one. Run reports record it, and Compare finds earlier runs by it |
| Saved / Saving… / Save failed | Whether the last change is on disk |
| ⚠ Complete the graph | Shown while the experiment cannot run; its tooltip says why, a click selects the node it is about (Validation) |
| + Add node | Opens the add menu, after the selected node when there is one (A) |
| Profile | Which profile runs, the preview and Send now use; shown when the experiment has profiles. ⚠ marks a profile that would not run |
{ } Parameters | Parameters, profiles, the seed, cookies and secrets (Data and templates) |
| ☷ Properties | Shows or hides the properties pane |
| ▢ Focus editor | Hides the sidebar, the header and the bottom panel (Focus mode and fullscreen) |
| ⛶ Fullscreen | The window in fullscreen, with focus mode |
| Run experiment | Checks, saves and runs the experiment; while it runs, the button is Stop |
| ▾ Run with… | One run with another profile, other parameter values or a given seed, without changing the experiment |
Moving around the canvas
- Pan: drag the empty canvas, or scroll.
- Zoom: Ctrl and the mouse wheel zoom around the pointer; − and + in the canvas bar step by 10 %. Zoom goes from 15 % to 200 %; the percentage between the buttons shows where you are.
- 1:1 (Reset zoom, Ctrl+1) goes back to 100 %.
- ⊡ Fit graph (Ctrl+0) shows the whole graph, at 100 % at most.
- Find a node: Nodes in the canvas bar (it shows how many nodes there are) or Ctrl+F. Type words from the node's name, its summary (a URL, an address, a topic) or its id; ↑↓ choose, Enter selects the node and brings it into view (zoomed to at least 80 %), Esc closes.
There is no minimap: Fit graph and the finder do its work.
Nodes and wires
A node shows its kind, a one-line summary of what it does, and badges for its settings: ↻ and a number for Retry, × and a count (or a time) for Repeat, ⚡ for Load. Its input is on the left — every node but Start has one — and its outputs on the right:
| Output | On | Followed when |
|---|---|---|
| Output | Most nodes | The step passed |
| Yes / No | Status branch, Branch on value | The comparison held / did not |
| Branch 1 / Branch 2 | Parallel branch | Always, both at once |
| Matched / Timeout | Waits | A matching message arrived / none did in time |
| Body / Done / Limit | Loop | Another iteration / the loop is over / it ran out of iterations |
An output may have several wires. The first wire continues the branch; each further wire starts a parallel branch with a copy of the variables known at that point. A Join branches node waits for every wire that leads into it. Details are in Flow.
Timeout and Limit are optional: left unwired, a timeout or running out of iterations fails the step. Every other output must have a wire before the experiment can run.
The wire from the body of a Loop back to it is drawn as an arc over the body; it is the only wire that may lead backwards.
Adding nodes
The add menu
The add menu lists every kind of node by group — Actions, Observe, Emulate, Faults, Data, Checks, Flow — and then Saved signals: the signals of your library that can become a node (OSC, HTTP, UDP with a text payload, MQTT), with their fields filled in.
Its search field has the focus as it opens. Type a few letters: every word must appear in the node's name, its description or its type (http, wait_osc); a signal is found by its name, folder, transport or target. ↑ ↓ choose, Enter adds, Esc closes. The line at the top says where the node goes: after a node, or parallel to one.
A new node is selected, the properties pane opens, and its main field — the URL, the address, the topic, the delay — has the focus with its text selected, so you can type straight away. Esc in a field takes you back to the node on the canvas, ready for the next A.
After the selected node
A, + Add node in the toolbar and + Add next in the properties pane add the next step after the selected node:
- It attaches to the node's first output that has no wire yet (the free No of a Status branch, say), or else to its first output.
- If that output already has a wire, the new node is spliced into it (into the first, if it has several): the wire now runs through the new node, and everything after it moves right to make room.
- With End selected, the node goes before it, when one wire leads into it.
- With nothing selected, the node lands in the middle of the view, without wires.
A node with a single output that is put on the empty Body of a Loop — this way or on a new wire — is also wired back to it, so the body is complete at once.
Into a wire
Every wire has a + in its middle (Insert node). It opens the add menu, and the node you pick is spliced into that wire. A Loop spliced in this way continues the flow through its Done.
On a new wire
Drag a wire out of an output and let go on the empty canvas: the add menu opens there, and the new node goes on a new wire of that output — beside the wires it already has, so it runs in parallel with them. The same happens when you click an output and then click the empty canvas, or double-click it.
Anywhere
Double-click the empty canvas to add a node at that spot, without wires.
From other screens
- Add to experiment on the OSC and HTTP screens adds what you just tried as the next step — just before End, when one wire leads into it — and switches to the editor.
- Wait for this next to a message in the OSC monitor adds a Wait for OSC that recognises it: its address and its text, whole-number and true/false arguments (floats are measurements that change, so they are left out). Stop the monitor before running: the run listens on that port itself.
- Wait for this on a topic of the MQTT tree adds a Wait for MQTT on that topic, at that broker.
While a run is going, nodes cannot be added; the console says so.
Connecting nodes
- Drag from an output onto a node. Letting go within 24 pixels of a node is enough.
- Click an output (or focus it and press Enter or Space): the canvas bar says Choose an input port. Click a node or its input to connect; click the empty canvas to add a node there on a new wire; Cancel or Esc gives up.
A wire is refused when it would create a cycle (other than the way back of a Loop), when it leads into Start or out of End, or when it would connect a node to itself; the editor says so. Drawing a wire that exists already changes nothing.
To remove a wire, click it — the properties pane shows what it connects — and press Delete, or use Remove connection there. Hovering a wire also shows a × above its +. The properties of a node list its outgoing wires, each with × (Disconnect).
Selecting several nodes
| To | Do |
|---|---|
| Select a node | Click it, or move to it with Tab |
| Add a node to the selection, or take it out | Shift or Ctrl and a click |
| Select with a frame | Shift and drag on the empty canvas: every node the frame touches joins the selection |
| Select every node | Ctrl+A |
| Clear the selection | Click the empty canvas; Esc when several are selected |
| Move them | Drag one of them: they all move |
| Nudge them | With a node focused, the arrow keys move the selection 5 pixels, 20 with Shift |
The properties pane shows the last node picked; with several selected, it shows how many, with Copy, Duplicate and Delete nodes.
Copy, cut and paste
Ctrl+C copies the selected nodes and the wires between them as text; Ctrl+X also removes them; Ctrl+V pastes them — into this experiment, into another one opened later, or into another Signal Lab window. The text is JSON, so you can also keep it in a file or a message.
- Start and End are one of a kind: they are never copied.
- Wires between a copied node and the rest of the graph are not copied; wire the copy in yourself.
- Every pasted node gets a new id.
- A node that names another one — Change impairment, Emulator down/up, WebSocket send, Wait for WebSocket, WebSocket close — names the copy when that was copied too; otherwise it keeps naming the original if it is in this experiment, or the first node of that kind here.
- A pasted Emulator or Impairment whose port this experiment already listens on moves to the next free port. What sends to the original still does.
- The copy lands 32 pixels right of and one row below where it came from, further down until it covers no node, and is selected.
Ctrl+D (Duplicate) does the same without the clipboard.
Deleting
Delete or Backspace (Delete node, Delete nodes) removes the selected nodes and their wires. A node that had exactly one wire in and one wire out leaves a wire in its place, from the node before it to the node after it, so a chain stays connected. Start and End cannot be deleted.
Undo and redo
Ctrl+Z undoes; Ctrl+Shift+Z or Ctrl+Y redoes (↶ ↷ in the canvas bar). A drag, a series of arrow-key nudges or the typing in one field is one step. The last 100 steps are kept while the app is open, across screen switches; opening another experiment is one step too, so Ctrl+Z brings the previous one back. Undo and redo wait while a run is going.
Arrange
Arrange lays the graph out from left to right: each node in the column after the last node that leads to it, the body of a Loop in its row — and then fits the view. It is one step of the history. A draft with a cycle that is not a loop's is left as it is.
The properties pane
With one node selected, the pane shows, from top to bottom:
- The node's name (its tooltip says what it does) and, when the experiment cannot run because of this node, what is wrong.
- Its fields. Fields that take templates suggest parameters, variables, secrets and generators as you type
{{, or on Ctrl+Space. - Its settings: send under load on an HTTP request, repeat sending on nodes that send, retry on failure on nodes that send or listen, and wait for a reply on OSC and UDP messages (see Node settings). Load replaces Repeat and Retry while it is on.
- The last run's load result, on an HTTP node that ran under load.
- The preview — Will be sent, Will wait for or Will compare — when the node has templates: what it would send, wait for or compare, resolved with the current parameters and the values known so far (Send now and the preview).
- Send now or Listen now, and what it did last.
- Its outgoing wires, each with ×.
- ⚡ Route through impairment on an OSC or UDP message: an Impairment is put in front of the node — listening on a free loopback port from 9010 up, forwarding to the node's target — and the node is pointed at it, so the next run degrades what it sends (Faults).
- + Add next, Copy, Duplicate and Delete node.
A double click on a node opens the pane with the cursor in its main field. With several nodes selected the pane offers what can be done to all of them; with a wire selected, what it connects and Remove connection. While a run is going, the fields are locked.
Send now and the preview
Send now (Ctrl+Enter, also from inside the node's fields) sends the selected node on its own, without running the experiment, through the same code a run uses. It is offered on HTTP request, TCP message, OSC message, UDP datagram, MQTT publish, WebSocket connect and WebSocket send. On a wait it is Listen now: the wait listens from now until a message matches or its timeout ends.
- The node uses the active profile, the stored secrets and the variable values known so far — from the last run and from earlier Send now results. If a template names a value nobody has set yet, nothing is sent and the names are listed.
- It is sent once: Retry, Repeat and Load do not apply, no cookies are kept, and emulators and relays of the experiment are not started.
- A WebSocket send or Wait for WebSocket opens the connection its WebSocket connect describes, for that one test.
- On an HTTP request the response is shown — status, time, size, and the body, JSON formatted. The values the Extract value nodes after the request would take are filled in at once. Click a value in a JSON response to add an Extract value node for it right after the request, its variable named and its value known. Mock this turns the response into a route of an emulator.
- The result is also written to the console.
The preview is resolved by the engine as well, about a quarter of a second after a change. Secret values are never shown, there or anywhere.
Validation
The editor checks the experiment as you edit, and once more when you press Run experiment:
- An output that still needs a wire pulses amber.
- A node no wire from Start reaches is drawn dashed; its tooltip says so.
- The node a problem is about is outlined, and the problem is written above its fields.
- ⚠ Complete the graph in the toolbar names the problem; a click selects the node.
An experiment runs when, among other things:
- it has a name, exactly one Start and one End, and at most 64 nodes;
- every node is reachable from Start and every required output has a wire;
- the only cycles are the bodies of Loop nodes leading back to them;
- every check and Extract value has an HTTP request before it on every path (one under load does not count: it leaves no response);
- a WebSocket send, Wait for WebSocket or WebSocket close comes after the WebSocket connect it uses;
- every field is filled in and in range, and every template names something known at that point.
Unfinished drafts are saved all the same. A run is refused, before any traffic, when a secret it needs is not stored. Every message is listed in the error reference.
Running
Run experiment checks the experiment, saves it, and starts it. If it cannot run, the problem is shown and its node selected. Otherwise the timeline opens and nodes light up as the run reaches them: ● running, ✓ passed, ✕ failed, ↻ retrying, ⟳ repeating, ⚡ under load; wires that carried the flow are coloured too.
- Waits, emulators and impairment relays open their ports before the first step, so nothing that arrives early is missed. A port that cannot be opened (another program has it, say) keeps the run from starting, and the node that needs it is shown.
- Stop ends the run at once: pauses, waits and loads included. The run is also a job in the console's job strip, which can stop it too.
- A run that takes longer than 300 seconds is stopped and fails.
- While a run is going the experiment cannot be edited; panning, zooming and selecting still work.
▾ Run with… next to the button runs once with another profile, other parameter values or a given seed; the experiment itself is not changed, and the form keeps what you typed until the app closes (Data and templates).
The run timeline
The Run timeline lists one row per step: the time, the node and what happened — passed, failed and why, a retry, a repeat's progress, a load's numbers once a second. Click a row to select its node on the canvas.
Its title line holds:
- the outcome: Passed, Failed with the reason, or Stopped;
- Report saved — the run's report file (on a server, a download);
- Compare — this run beside an earlier one of the same experiment;
- the profile and changed values the run used, when it had any;
- the run's seed, with Pin to keep it in the experiment so the next runs draw the same random values, or Unpin once it is pinned.
When the Inspector was capturing, a wait (or an expected reply) that matched links to the frame it matched; a click opens it in the Inspector. Reports, seeds and comparing runs are in Runs and reports.
Focus mode and fullscreen
▢ Focus editor hides the sidebar, the header and the bottom panel (console and Inspector), leaving the screen to the editor. ⛶ Fullscreen puts the window in fullscreen and turns focus mode on; leaving fullscreen puts focus mode back the way it was. Switching to another screen leaves focus mode.
Esc on the canvas, with nothing else to close, leaves fullscreen and then focus mode.
Saving and files
The experiment is saved by itself, a moment after each change, to experiment.json in the data folder (Documents/SignalLab on a desktop; a server has its own — see Files and folders). Drafts that cannot run yet are saved too. A run saves first, so what ran is what is on disk.
If that file cannot be read — edited by hand into broken JSON, say — the editor says which file and why, and leaves it alone. Opening another experiment from ☰ Experiments then replaces it at the next save.
☰ Experiments opens the experiments dialog:
- Templates: pick one and press Open experiment. They all use loopback addresses.
- Open JSON… reads an experiment file — written by this version of Signal Lab or an earlier one, up to 4 MiB — and shows its name and how many nodes and connections it has before you open it. Files from earlier versions are brought up to date as they open. A file that does not parse, or would not be a valid experiment, is refused with the reason, and the current experiment stays.
- Export current JSON writes a snapshot of the current experiment — nodes, wires, positions, parameters and profiles, never secret values — to a new file in
exportsin the data folder and shows its path; on a server, with a Download link. - Open experiment replaces the current experiment with the template or file. It does not run it, and Ctrl+Z brings the previous one back.
| Template | What it does |
|---|---|
| Empty experiment | Start and End, for your own flow |
| HTTP check | A GET to http://127.0.0.1:8080/ and a check for status 200 — the experiment you start with |
| HTTP to OSC | The same request; on 200 an OSC message to 127.0.0.1:9000, otherwise a 500 ms delay |
| Parallel flows | Two branches at once — a request and a log line — joined before End |
| OSC ping → reply | Sends /ping with the run's id to 127.0.0.1:9000 and waits on 127.0.0.1:9001 for /pong carrying it back |
| Poll until ready | Asks a device for /status every 0.3 s until it answers ready, ten times at most |
| Retry a flaky API | An emulated API that fails twice before it answers, and a loop that asks until it does |
| Fault phases | Datagrams to an emulated device through an impairment relay while a parallel branch switches the network clean, lossy, offline and clean again |
| Dependency outage | An emulated API taken down for two seconds by a parallel branch, and a client that keeps asking until it answers again |
| WebSocket echo | Connects to an echo service at ws://127.0.0.1:9001/echo, sends a JSON ping, expects it back unchanged, and closes |
The same files run without the editor: signallab run experiment.json — see The command line.
Keyboard shortcuts
Single keys act on the canvas and are left alone while you type in a field. On a Mac, in a browser, Cmd works where Ctrl is written.
| Keys | Action |
|---|---|
| A | Add a node after the selected one, or mid-view when nothing is selected |
| Ctrl+Enter | Send now, or Listen now, on the selected node |
| Ctrl+Z | Undo |
| Ctrl+Shift+Z, Ctrl+Y | Redo |
| Ctrl+A | Select every node |
| Ctrl+C / Ctrl+X / Ctrl+V | Copy / cut / paste the selected nodes and the wires between them |
| Ctrl+D | Duplicate the selected nodes |
| Delete, Backspace | Remove the selected wire, or the selected nodes |
| Arrow keys (a node focused) | Move the selected nodes by 5 pixels; with Shift, 20 |
| Ctrl+F | Find a node |
| Ctrl+0 | Fit the graph |
| Ctrl+1 | Zoom to 100 % |
| Ctrl + mouse wheel | Zoom around the pointer |
| Shift + click, Ctrl + click | Add a node to the selection, or take it out |
| Shift + drag on the empty canvas | Select with a frame |
| Double-click a node | Edit its fields |
| Double-click the empty canvas | Add a node there |
| Enter, Space on a focused output | Start a wire from it |
{{ or Ctrl+Space in a field | Suggest parameters, variables, secrets and generators |
| Esc in a field | Back to the node on the canvas |
| Esc on the canvas | Close the add menu; else cancel the wire being drawn; else let go of the selected wire; else clear a selection of several; else leave fullscreen; else leave focus mode |
In the add menu and the finder, ↑ ↓ choose, Enter takes the choice and Esc closes. All of the app's shortcuts are in Keyboard shortcuts.