electrobun-browser-tools
v1.0.1
Published
Incur-based inspection CLI for Electrobun app/window/view/layout state.
Readme
electrobun-browser-tools
Inspection-first bridge, CLI, and Playwright-style driver for Electrobun and Electron-backed desktop apps.
It models the runtime as:
app -> window -> view -> layout -> node
The package now has two complementary surfaces:
electrobun-browser-tools: programmatic JS client, inspection CLI, bridge mounting helpers, catalog metadata, and shared typeselectrobun-browser-tools/bridge: runtime-side bridge mounting helpers
Mount The Bridge
The CLI and driver only work against apps that expose the bridge.
For Electrobun apps, the standard setup is:
import { mountElectrobunToolBridge } from 'electrobun-browser-tools/bridge'
await mountElectrobunToolBridge({
mainWindow,
})That starts a local Bun endpoint, registers the app by appId, and exposes the inspection and driver commands over the same transport.
Common optional inputs include:
appId,appName,appVersionstateinstrumentationdescribeView,describeWindowgetActiveViewId,getActiveWindowIdwindows,browserView,buildConfig
Use mountToolBridge(...) when you want the built-in server with a custom adapter. Use createToolBridge(...) when you want the fetch handler and will mount it yourself.
Inspection Mode
Inspection mode is still the baseline capability set. It is read-heavy and centered on discovery, layout snapshots, live DOM reads, buffered events/logs/errors, state namespaces, and optional capture/network/perf helpers.
Start with machine-readable output:
electrobun-browser-tools catalog --json
electrobun-browser-tools doctor --app <app-id> --json
electrobun-browser-tools status --app <app-id> --json
electrobun-browser-tools tree --app <app-id> --json
electrobun-browser-tools layout snapshot --app <app-id> --summary --jsonThen drill into one target:
electrobun-browser-tools layout explain testid:checkout-submit --app <app-id> --json
electrobun-browser-tools dom html testid:checkout-submit --outer --app <app-id> --json
electrobun-browser-tools events tail --types dialog.opened --app <app-id> --json
electrobun-browser-tools logs summary --app <app-id> --json
electrobun-browser-tools state list --app <app-id> --jsonIf you already know the endpoint, use --url <bridge-url> instead of --app <app-id>.
The CLI resolves connections in this order:
--url--appthrough the local bridge registry- the only mounted bridge, when exactly one is registered
Useful environment variables:
EBT_APP,EBT_URL,EBT_TOKEN,EBT_WINDOW,EBT_VIEW,EBT_TIMEOUTELECTROBUN_BROWSER_TOOLS_APP,ELECTROBUN_BROWSER_TOOLS_URL,ELECTROBUN_BROWSER_TOOLS_TOKEN,ELECTROBUN_BROWSER_TOOLS_WINDOW,ELECTROBUN_BROWSER_TOOLS_VIEW,ELECTROBUN_BROWSER_TOOLS_TIMEOUT
To inspect the built-in mock fixture without a live app:
electrobun-browser-tools doctor --url mock://default --jsonDriver Mode
The JavaScript client now covers both inspection and live-driving from the root package entrypoint. Locator actions resolve against the current DOM on each attempt instead of operating on stale snapshot node ids.
import { connect } from 'electrobun-browser-tools'
const driver = await connect({
url: process.env.EBT_URL,
timeout: 15_000,
})
const page = driver.page('active')
await page.getByTestId('customer-name').fill('Ada Lovelace')
await page.getByTestId('order-notes').fill('Fragile')
await page.locator('select[data-testid="shipping-speed"]').selectOption('express')
await page.getByRole('radio', { name: /card/i }).check()
await page.getByTestId('accept-terms').check()
await page.getByTestId('reveal-secret').click()
await page.getByTestId('secret-offer').waitFor({ state: 'visible' })
await page.getByTestId('dismiss-promo').click()
await page.waitForRequest(/submit/)
await page.getByTestId('checkout-submit').click()
await page.waitForURL(/#complete$/)
console.log(await page.url())Current high-level JavaScript surface:
connect(options)createCatalogPayload()driver.doctor(),driver.status(),driver.tree()driver.windows(),driver.window(ref).info(),driver.window(ref).focus(),driver.window(ref).screenshot()driver.views(windowRef?)driver.eventsWait(),driver.eventsTail(),driver.eventsSummary()driver.logsTail(),driver.logsSearch(),driver.logsSummary()driver.errorsList(),driver.errorsGet(),driver.errorsWatch()driver.stateList(),driver.stateGet()driver.networkTail(),driver.networkSummary(),driver.networkWait()driver.perfSummary(),driver.perfMarks()driver.compareSnapshots(before, after),driver.exportSnapshot(snapshot)driver.page(ref?)driver.window(ref?)page.locator(selector)page.getByRole(role, { name? })page.getByText(text)page.getByTestId(testId)page.view(),page.devtools(action)page.url(),page.snapshot(),page.screenshot()page.node(),page.hitTest(),page.explain()page.ancestors(),page.descendants()page.query(),page.text(),page.attrs(),page.style(),page.html()page.waitForURL(url)page.waitForEvent(eventName, { match? })page.waitForRequest(url, { match? })page.waitForResponse(url, { match? })locator.filter({ has, hasText, visible })locator.first(),locator.nth(index)locator.resolve(),locator.count()locator.textContent(),locator.innerHTML(),locator.isVisible(),locator.boundingBox()locator.waitFor({ state })locator.click(),locator.fill(),locator.clear(),locator.press()locator.focus(),locator.blur(),locator.hover()locator.check(),locator.uncheck(),locator.selectOption()
Action helpers accept timeout, and mutating locator actions also accept force when you need to bypass visibility or occlusion checks.
CLI Driver Commands
The CLI now exposes the same live-driving capabilities in a bash-friendly form.
Page-scoped commands:
page viewpage snapshotpage screenshotpage urlpage resolve <targetRef>page count <targetRef>page text-content <targetRef>page inner-html <targetRef>page is-visible <targetRef>page bounding-box <targetRef>page click <targetRef>page fill <targetRef> <value>page clear <targetRef>page press <targetRef> <key>page focus <targetRef>page blur <targetRef>page hover <targetRef>page check <targetRef>page uncheck <targetRef>page select-option <targetRef> <value>or--values a,bpage wait-for <targetRef>page wait-for-url <matcher>page wait-for-event <eventName>page wait-for-request <matcher>page wait-for-response <matcher>
Window-scoped alias:
window screenshot
Example flow:
electrobun-browser-tools page fill testid:customer-name "Ada Lovelace" --app <app-id> --json
electrobun-browser-tools page select-option testid:shipping-speed express --app <app-id> --json
electrobun-browser-tools page check testid:accept-terms --app <app-id> --json
electrobun-browser-tools page click testid:reveal-secret --app <app-id> --json
electrobun-browser-tools page wait-for testid:secret-offer --state visible --app <app-id> --json
electrobun-browser-tools page wait-for-request '/customer=Ada/' --app <app-id> --json
electrobun-browser-tools page click testid:checkout-submit --app <app-id> --json
electrobun-browser-tools page wait-for-url '*#complete' --app <app-id> --json
electrobun-browser-tools page text-content testid:summary-output --app <app-id> --jsonCLI page commands use live locator refs:
css:button[data-testid="checkout-submit"]testid:checkout-submitrole:buttonrole:button:Place ordertext:Place order
node: refs are intentionally rejected for driver actions because they come from snapshots and are not stable live targets.
For more complex locator composition, target-based page commands also accept:
--scopeor--within--has--has-text--first--nth--visible true|false--locator-json '{"kind":"role","role":"button","filters":[{"visible":true}]}'
Action commands support --timeout, and mutating commands also support --force.
Common Refs
Window refs:
activeid:2title:Checkout
View refs:
activeid:21host:11url:*checkout*
Target refs for layout and DOM inspection:
node:n12css:button[data-testid="checkout-submit"]testid:checkout-submitrole:buttonrole:button:Place ordertext:Place order
Most layout and DOM commands can reuse a cached snapshot with --snapshot <snapshot-id>.
Capabilities And Limits
- Sandboxed views remain partial. They can expose events and metadata, but live DOM inspection and DOM-based driver actions require a DOM-capable view.
- Screenshot capture is runtime-specific. Built-in Electrobun screenshot support currently targets the visible on-screen region instead of full-page content.
- The built-in Electrobun screenshot path is currently tied to the default
electrobun/bunruntime on macOS. page.screenshot()andwindow.screenshot()route through the existing capture layer, so unsupported platforms and runtimes still fail explicitly rather than silently degrading.page.waitForRequest()andpage.waitForResponse()use the bridge's in-page network instrumentation, so they depend on mounted instrumentation rather than generic browser devtools parity.unsafe.evaluate()is intentionally not part of the first stable driver surface.
Run doctor --json to inspect both legacy capability booleans and the richer detailed capability flags exposed by the bridge.
Lower-Level APIs
If you need a custom adapter or want to mount the bridge yourself, the lower-level APIs are still available:
createToolBridge(options)mountToolBridge(options)mountElectrobunToolBridge(options)
mountElectrobunToolBridge(...) accepts the lower-level bridge options too, including host, port, path, buffer sizes, capabilities, and getRecent.
Apps still own domain-specific state. Use the bridge state option to expose that cleanly:
await mountElectrobunToolBridge({
mainWindow,
state: {
workspace: () => ({
cwd: process.cwd(),
branch: currentBranch,
}),
sessions: () => ({
activeSessionId,
total: sessions.length,
}),
},
})Testing
The repo now validates the driver and inspection surfaces with:
- protocol and capability registry tests
- subprocess CLI tests
- typed JavaScript client and driver API tests
- a live Bun-hosted Electrobun runtime fixture
- a live Electron fixture through the lower-level bridge API
Useful local checks:
bun run typecheck
bun run build
bun run test