Problem
client.screenshot() relies on html2canvas running inside a connected browser client. This means any automated/headless workflow that needs screenshots must spin up a full browser (e.g. Puppeteer) just to give html2canvas a DOM to render in.
In our case we have a virtual user agent that drives a bwserve app via the action dispatch API (/bw/return/action/{clientId}). The agent needs to see what the app looks like after each action. The /api/screenshot endpoint calls client.screenshot('body') which requires a real browser client connected via SSE.
We had to add Puppeteer as a dependency solely to provide that browser. The Puppeteer browser navigates to the page, establishes the SSE connection, and then html2canvas can run inside it. It works, but it's a heavyweight solution for what should be a server-side capability.
What would help
A server-side screenshot path in bwserve that doesn't require a browser client. Some options:
-
bwserve tracks the DOM state it has pushed — since the server knows every render(), patch(), mount() call it has sent, it could maintain a virtual DOM and render it to an image server-side (e.g. via a lightweight renderer or by serializing to HTML and using something like playwright's screenshot internally).
-
A built-in headless client option — app.createHeadlessClient() that bwserve manages internally, so consumers don't have to bring their own Puppeteer.
-
Expose the last-rendered HTML — even just a client.getRenderedHTML() that returns the full HTML string would let consumers snapshot it themselves without needing a live browser connection.
Option 3 is probably the lightest lift and would unblock a lot of automation use cases.
Context
This came up building a virtual user testing system for a bwserve app. The agent sends actions, waits for the server to process them, then needs to see the result. The text view (/api/view via client.inspect()) works fine without a browser, but screenshots require one.
Problem
client.screenshot()relies on html2canvas running inside a connected browser client. This means any automated/headless workflow that needs screenshots must spin up a full browser (e.g. Puppeteer) just to give html2canvas a DOM to render in.In our case we have a virtual user agent that drives a bwserve app via the action dispatch API (
/bw/return/action/{clientId}). The agent needs to see what the app looks like after each action. The/api/screenshotendpoint callsclient.screenshot('body')which requires a real browser client connected via SSE.We had to add Puppeteer as a dependency solely to provide that browser. The Puppeteer browser navigates to the page, establishes the SSE connection, and then html2canvas can run inside it. It works, but it's a heavyweight solution for what should be a server-side capability.
What would help
A server-side screenshot path in bwserve that doesn't require a browser client. Some options:
bwserve tracks the DOM state it has pushed — since the server knows every
render(),patch(),mount()call it has sent, it could maintain a virtual DOM and render it to an image server-side (e.g. via a lightweight renderer or by serializing to HTML and using something likeplaywright's screenshot internally).A built-in headless client option —
app.createHeadlessClient()that bwserve manages internally, so consumers don't have to bring their own Puppeteer.Expose the last-rendered HTML — even just a
client.getRenderedHTML()that returns the full HTML string would let consumers snapshot it themselves without needing a live browser connection.Option 3 is probably the lightest lift and would unblock a lot of automation use cases.
Context
This came up building a virtual user testing system for a bwserve app. The agent sends actions, waits for the server to process them, then needs to see the result. The text view (
/api/viewviaclient.inspect()) works fine without a browser, but screenshots require one.