Isolation per flow
Each test flow needs its own session bound to its own thread — its own port, its own remote. Don’t share a VNCScreen reference across threads; give each flow its own.
Most visual automation tools assume one screen, one session, one machine. OculiX is built to go further — a single host can drive many isolated VNC sessions, each on its own port, with no physical display required. That’s how OculiX reaches enterprise scale: testing dozens of point-of-sale terminals, kiosks, or application instances from one VM.
But it’s worth being honest about how that works today versus where it’s going.
Visual automation was born in a world of one physical screen: one machine, one desktop, one cursor. SikuliX — which OculiX continues — inherited that worldview, and for fifteen years it was the right one. The engine keeps a notion of the current screen in shared, global state. When there is only ever one screen, that’s not a flaw; it’s the correct design.
VNC breaks the assumption. A VNC session isn’t a screen — it’s a network framebuffer: no monitor, no DISPLAY, no desktop login. You can open many, each on its own port. Suddenly “the current screen” is no longer a singleton, and the single-screen assumption baked into the engine becomes visible: the default port and the live-session registry live in shared, static state. Two flows running at once can reach for the same global and quietly pollute each other.
This is exactly why, today, parallelism lives one layer up.
A single VNC session works perfectly out of the box. This is the engine doing what it does best.
import org.sikuli.vnc.VNCScreen;
VNCScreen screen = VNCScreen.start("127.0.0.1", 5901);
// Behaves like a normal Screen — find, click, typescreen.find("scan_button.png").click();screen.type("amount_field.png", "42.00");
screen.stop();With a password and explicit timeouts:
VNCScreen scr = VNCScreen.start( "10.0.0.12", // VNC server IP 5901, // port "secret", // VNC password (or null) 3, // connection timeout (s) 0 // operation timeout (s));If you need an SSH tunnel to reach a remote framebuffer, OculiX ships a pure-Java tunnel (no WSL, no external sshpass):
import com.sikulix.util.SSHTunnel;
try (SSHTunnel tunnel = SSHTunnel.open("192.168.1.100", "user", "password")) { VNCScreen scr = VNCScreen.start("127.0.0.1", tunnel.getLocalPort()); // ... drive the session scr.stop();}You can run many sessions in parallel right now — and teams do, in production. But because the core was built single-session, you are currently responsible for the four things that keep concurrent sessions from colliding:
Isolation per flow
Each test flow needs its own session bound to its own thread — its own port, its own remote. Don’t share a VNCScreen reference across threads; give each flow its own.
Atomic port allocation
Two flows must never claim the same port. “Probably free” isn’t enough when several start at once — you need an atomic claim (a lock no two flows can win simultaneously).
One tunnel per session
If you tunnel to remote machines, open one per flow — and close the old one before opening the next, or orphaned tunnels hold ports hostage.
Readiness, not a sleep
Wait for the VNC server’s RFB banner (proof it’s ready for pixels) rather than a fixed delay. A fixed sleep does not survive several sessions starting together.
A sketch of the pattern (you supply the port allocation and readiness yourself, today):
import java.util.concurrent.*;
int[] ports = {5901, 5902, 5903, 5904}; // one per entity, allocated atomically by YOUExecutorService pool = Executors.newFixedThreadPool(ports.length);
for (int port : ports) { pool.submit(() -> { // each flow: its own tunnel, its own session, its own thread VNCScreen scr = VNCScreen.start("127.0.0.1", port); runCheckoutScenario(scr); // a balanced slice of your suite scr.stop(); });}pool.shutdown();Everything you build on top today is the engine’s natural next form. The goal for the next major version is to make multi-session parallelism first-class:
Thread-scoped sessions
No more global “current screen.” A session belongs to the flow that opened it, by construction — so two flows can’t reach for the same state, because there’s no shared state to reach for.
Built-in port allocation
Ask for a session, get an isolated one on a port the engine claimed atomically. No range to reserve, no lock-files to babysit.
Native RFB readiness
A session reports ready when the server actually says it’s ready — the optimistic startup sleep replaced by a real probe.
Tunnels wired in
The pure-Java SSHTunnel already in the core, integrated into a documented parallel pattern instead of hand-assembled per project.
The target, stated simply: declare N sessions, get N isolated sessions — no harness, no lock-files, no ghost tunnels. The single-screen assumption retired gently, with full respect for the years it served exactly right.
However sessions become isolated, the cleanest way to provision N isolated entities is one container per session: each runs a VNC server on a unique port, with the app under test inside.
One container = one entity. Each runs a VNC server (e.g. on :5901, :5902, …) plus the app under test, fully isolated.
Map the ports. Expose each container’s VNC port to the host so OculiX can reach 127.0.0.1:590X.
OculiX connects to all of them. One session per port, on the orchestrating host.
Collect per-entity results. Each container is one entity; its session yields one clean result.
flowchart LR
C1["Container 1 — entity A<br/>VNC :5901"]
C2["Container 2 — entity B<br/>VNC :5902"]
Cd["…"]
CN["Container N — entity J<br/>VNC :590N"]
O["OculiX<br/>N parallel sessions"]
R["One result<br/>per entity"]
C1 --> O
C2 --> O
Cd --> O
CN --> O
O --> R
This model scales naturally with an orchestrator (Kubernetes Deployment/Job per batch). The same “grid of parallel sessions” used in mobile/Selenium farms applies directly to visual automation.
No physical display
The VNC server provides the framebuffer. OculiX reads pixels over the wire — no monitor, no X session on the OculiX host.
Virtual displays server-side
Inside each container/VM, the VNC server typically runs against a virtual display (e.g. Xvfb on Linux). That’s where the app actually renders.
Resource budgeting
Each session costs CPU (matching), memory (framebuffer), and a thread. A modest VM comfortably runs a handful; tens of sessions want real capacity planning.
Network locality
Keep VNC traffic local (loopback or same subnet). Framebuffer refreshes are bandwidth-sensitive; remote-WAN VNC will bottleneck matching.
| Scenario | Fit |
|---|---|
| Testing many point-of-sale terminals / kiosks | ✅ Ideal (orchestration today, native soon) |
| Running a large suite faster via parallelism | ✅ Ideal |
| Per-entity / per-configuration validation | ✅ Ideal |
| Regulated compliance windows (test everything, fast) | ✅ Ideal |
| A single local desktop automation | ❌ Use a normal Screen |