Live Viewer and Manual Takeover

Updated July 2026
Headless automation usually runs blind. WebBrowserBot ships a live viewer instead: a single-page UI that streams frames from every open session, lets you switch between tabs and sessions, browse saved screenshots, and take manual control of a running session while your automation waits.

How the Viewer Works

The viewer server is one async process that owns everything. It holds the session manager, so the same browser sessions your automation drives are the ones the UI displays. It runs three things concurrently: the command socket your automation calls, an HTTP server for the single-page UI, and a WebSocket per connected viewer.

Frames are pushed over the WebSocket as the page changes, so watching a session costs almost nothing when the page is idle. Each viewer subscribes to the session it wants to watch, and can switch which tab of that session it is viewing without disturbing the automation.

Because the server owns the sessions rather than mirroring them, anything you do in the UI is real. Clicking in the viewer clicks in the actual browser, and the automation sees the resulting page state on its next read.

Running the Viewer

Start the server, and it listens on both ports:

python3 viewer_server.py

The UI and its WebSocket are on 127.0.0.1:8770, and the automation command socket is on 127.0.0.1:8771. Open the UI in a browser and enter the password from config.json (or the VIEWER_PASSWORD environment variable). Set your own password before you put this anywhere reachable.

Once you are in, the Sessions rail lists every open session. Any session your automation opens on the command socket appears there automatically, with no extra step. The UI also has a screenshots browser that lists saved PNGs from the screenshots directory, pruned to the retention window you configure.

Manual Takeover

Every session in the viewer has a Take Control button. While a session is taken over, your mouse and keyboard drive the real browser through the WebSocket, so you can log in by hand, clear a CAPTCHA, accept a consent banner, or nudge a stuck flow back on track.

Takeover does not kill the automation. It pauses it. While a session is taken over, the bot refuses drive commands from your code and answers read-only ones normally, which means your automation can keep observing the page while you work. Click the button again to release control, and your code resumes driving.

Handling Takeover in Your Automation

When a human has control, drive commands come back refused:

{"success": false, "error": "paused for manual takeover", "data": {"paused_for_takeover": true}}

The refused commands are the ones that change the page: goto, back, forward, refresh, click, type, press, select, hover, scroll, execute_js, new_tab, switch_tab, close_tab, click_frame, and mouse_click.

These keep working the whole time: read, read_full, text, screenshot, list_sessions, list_tabs, list_frames, get_profile, save_profile, wait, and sleep.

The right response in your code is simple: when you see paused_for_takeover, back off, sleep, and retry. Do not treat it as a failure and do not tear the session down, because the human is fixing something for you.

while True:
    resp = cmd({"command": "click", "session_id": sid, "selector": "#submit"})
    if resp["success"]:
        break
    if resp.get("data", {}).get("paused_for_takeover"):
        cmd({"command": "sleep", "ms": 3000})
        continue
    raise RuntimeError(resp["error"])

Exposing the Viewer Safely

Both listeners bind to localhost only, so out of the box nothing is reachable from the network. To watch sessions from another machine, put a TLS reverse proxy in front of the viewer and keep the command socket private.

With Apache, that means proxying the WebSocket path and the page itself to port 8770, and leaving port 8771 alone entirely. Add HTTP basic auth in front of it so the viewer password is not your only layer. The project ships an example vhost that covers both the standalone setup and the variant where the UI is served as static files under an existing site.

Never expose the command socket

Port 8771 accepts unauthenticated commands from anything that can reach it, by design, because it is meant to be localhost only. Proxy the viewer, never the command socket.