Skip to main content
Version: 1.x

Browser Bind & Screencast Fixtures

Praman ships two opt-in fixtures that unlock agentic workflows — where a running test exposes its live browser to external Playwright CLI agents, and video frames stream to AI vision pipelines in real time.

Overview

The agentic workflow follows three phases:

┌──────────────────────────────────────────────────────────┐
│ 1. TEST STARTS 2. AGENT CONNECTS 3. TEST ENDS │
│ │
│ Test runs with CLI agent attaches browser. │
│ PRAMAN_BIND=1 via endpoint URL unbind() │
│ → browser.bind() → inspects live UI called in │
│ → logs endpoint → discovers controls finally block│
│ │
│ screencast records frame handlers screencast │
│ chapters + actions feed AI vision .stop() │
└──────────────────────────────────────────────────────────┘
FixtureScopeOpt-in viaPlaywright version
browserBindTestPRAMAN_BIND=1 env1.59+
screencastTestAlways available1.59+ (degrades)

Prerequisites

  • Playwright 1.59 or laterbrowser.bind() and page.screencast were introduced in 1.59.
  • PRAMAN_BIND environment variable — set to 1 or true to enable browser binding.
  • Praman installednpm install playwright-praman.

:::warning Playwright version Both fixtures require Playwright 1.59+. On older versions, browserBind throws ERR_BIND_NOT_SUPPORTED when enabled, while screencast methods silently no-op (except onFrame, which throws ERR_SCREENCAST_NOT_STARTED). :::


Quick Start

import { browserBindTest } from 'playwright-praman';

// Run with: PRAMAN_BIND=1 npx playwright test
browserBindTest('agent-assisted SAP test', async ({ browserBind }) => {
if (browserBind.bound) {
// A Playwright CLI agent can now connect at browserBind.endpointUrl
// and inspect the live browser while the test runs.
}
});

:::tip Zero overhead when disabled When PRAMAN_BIND is not set (or is '0'), the fixture returns { bound: false, endpointUrl: undefined, bindTitle: undefined } with no runtime cost. :::


Configuration

Environment Variables

VariableRequiredDefaultDescription
PRAMAN_BINDNo(unset)Set to 1 or true to enable browser binding
PRAMAN_BIND_TITLENo'praman-agent'Override the bind title passed to browser.bind()
# Enable binding with a custom title
PRAMAN_BIND=1 PRAMAN_BIND_TITLE=my-sap-agent npx playwright test

BrowserBindResult

The browserBind fixture provides a BrowserBindResult to the test body:

interface BrowserBindResult {
/** Whether the browser is currently bound. */
readonly bound: boolean;
/** The endpoint URL for Playwright CLI. undefined when bound is false. */
readonly endpointUrl: string | undefined;
/** The bind title used. undefined when bound is false. */
readonly bindTitle: string | undefined;
}

How It Works with CLI Agents

When PRAMAN_BIND=1, the fixture calls browser.bind(title) at the start of the test, which opens an ephemeral endpoint. The endpoint URL is logged at info level so that a Playwright CLI agent can connect without polling:

INFO browser-bind: Browser bound — Playwright CLI agent can connect
endpointUrl: "ws://127.0.0.1:43657/xyz"
title: "praman-agent"

A CLI agent attaches to this endpoint to inspect the live UI, discover controls, or take snapshots — while the test continues running. On teardown, browser.unbind() is called in a finally block regardless of test outcome.

# Terminal 1: Run the test with binding enabled
PRAMAN_BIND=1 npx playwright test tests/e2e/purchase-order.spec.ts

# Terminal 2: CLI agent connects to the bound browser
playwright-cli attach praman-agent
playwright-cli snapshot --filename=live-state.yml

Screencast Fixture

The screencast fixture wraps page.screencast (Playwright 1.59+) to provide structured video recording with chapter markers, action annotations, and real-time frame streaming.

Chapter Markers

Use showChapter(title) inside test.step() to label logical sections of the video:

import { screencastTest } from 'playwright-praman';

screencastTest('purchase order workflow', async ({ page, screencast }) => {
await screencast.showChapter('Navigate to PO list');
await page.goto('/fiori#PurchaseOrder-manage');

await screencast.showChapter('Create new PO');
await page.getByRole('button', { name: 'Create' }).click();
});

Action Annotations

showActions(options?) enables DOM-level overlays for click, fill, and keyboard events:

await screencast.showActions({
position: 'bottom-right',
duration: 800,
fontSize: 20,
});

// Subsequent actions are annotated in the video
await page.getByRole('button', { name: 'Save' }).click();
OptionTypeDefaultDescription
durationnumber500How long each annotation displays (ms)
fontSizenumber24Font size of the action title (px)
positionstring'top-right'Overlay position on the video frame

UI5 Control Tree Overlay

Toggle a visual overlay of the current UI5 control tree on video frames:

screencast.showUI5ControlTree(true);
// Frame handlers can now use the enabled flag for annotation

:::info Phase 1 The UI5 overlay is currently state-only — it sets a flag that downstream frame handlers or a bridge extension can use to annotate each frame. Visual overlay injection will be added in a future release. :::

Frame Streaming

Register handlers for every raw video frame with onFrame(handler). Frames are JPEG buffers with a monotonic timestamp. The screencast starts lazily when the first handler is registered.

screencast.onFrame(async ({ buffer, timestamp }) => {
// Send frame to an AI vision pipeline for anomaly detection
const analysis = await aiVision.analyze(buffer);
if (analysis.anomaly) {
throw new Error(analysis.reason);
}
});

:::tip Multiple handlers You can register multiple frame handlers. They are called in registration order. Errors from handlers are caught and logged at debug level (non-fatal to the test). :::

Control Highlighting

:::note Added in v1.3.0 :::

highlightControls(enabled, style?) draws a visible overlay on UI5 controls as they are interacted with — useful for video recordings, demos, and AI vision pipelines that need to see which control is active.

import { screencastTest } from 'playwright-praman';

screencastTest('highlighted workflow', async ({ screencast, ui5 }) => {
// Enable highlighting with the default Praman accent outline
screencast.highlightControls(true);

// Every press(), enterText(), and select() now highlights the control
const saveBtn = await ui5.control({ controlType: 'sap.m.Button', properties: { text: 'Save' } });
await saveBtn.press(); // Control is highlighted before the press

// Disable when done
screencast.highlightControls(false);
});

Custom Highlight Styles

Pass a CSS string or property map as the second argument:

// CSS string
screencast.highlightControls(true, 'outline: 3px solid blue; background: rgba(0,0,255,0.1)');

// CSS property map
screencast.highlightControls(true, {
outline: '3px solid #e8482b',
outlineOffset: '2px',
background: 'rgba(232, 72, 43, 0.05)',
});

The default style is outline: 3px solid #e8482b; outline-offset: 2px (Praman accent red outline). Each interaction clears the previous highlight before applying the new one.

How It Works

When highlighting is enabled, Praman's control proxy calls locator.highlight({ style }) before every press(), enterText(), and select() interaction. The highlight is:

  • Page-scoped — each page tracks its own highlight state independently
  • Best-effort — highlighting never breaks an interaction; failures are silently caught
  • Automatic cleanuppage.hideHighlight() is called before each new highlight

:::warning Playwright 1.60+ required highlightControls() requires Playwright 1.60+ (which introduced locator.highlight({ style })). On older versions, the method is a silent no-op. :::


Fixture Composition with mergeTests

Both fixtures can be composed with coreTest and each other using Playwright's mergeTests():

import { mergeTests } from '@playwright/test';
import { coreTest } from 'playwright-praman';
import { browserBindTest } from 'playwright-praman';
import { screencastTest } from 'playwright-praman';

const test = mergeTests(coreTest, browserBindTest, screencastTest);

test('full agentic workflow', async ({ browserBind, screencast, page }) => {
// browserBind: CLI agent connects to the live browser
if (browserBind.bound) {
console.info('Agent endpoint:', browserBind.endpointUrl);
}

// screencast: structured video with chapter markers
await screencast.showChapter('Navigate to app');
await page.goto('/fiori#PurchaseOrder-manage');

await screencast.showActions({ position: 'bottom' });

screencast.onFrame(async ({ buffer }) => {
// Real-time AI vision analysis
});
});

:::info Cross-fixture dependencies The screencast fixture declares rootLogger as a { option: true } placeholder (PW-MERGE-1 pattern). When composed with coreTest via mergeTests(), it receives the shared pino logger. Without coreTest, it falls back to its own logger instance. :::


Error Handling

Both fixtures throw structured PramanError instances with error codes, suggestions, and retry information:

browserBind Errors

Error CodeWhenRetryable
ERR_BIND_NOT_SUPPORTEDbrowser.bind() missing (Playwright < 1.59)No
ERR_BIND_FAILEDbrowser.bind() rejects at runtimeNo
// Example error shape
PramanError {
code: 'ERR_BIND_NOT_SUPPORTED',
message: 'browser.bind() is not available — requires Playwright 1.59 or later',
attempted: 'Bind browser for Playwright CLI agent access (PRAMAN_BIND=1)',
retryable: false,
suggestions: [
'Upgrade Playwright to 1.59 or later: npm install -D @playwright/test@latest',
'Remove PRAMAN_BIND=1 to disable browser binding',
],
}

screencast Errors

Error CodeWhenRetryable
ERR_SCREENCAST_CHAPTER_FAILEDshowChapter() rejectsNo
ERR_SCREENCAST_NOT_STARTEDonFrame() called on Playwright < 1.59No
ERR_SCREENCAST_FRAME_HANDLERpage.screencast.start() rejectsNo

Troubleshooting

ProblemSolution
ERR_BIND_NOT_SUPPORTED thrownUpgrade Playwright to 1.59+: npm install -D @playwright/test@latest
ERR_BIND_FAILED thrownCheck that no other process has already bound this browser instance
browserBind.bound is always falseSet PRAMAN_BIND=1 in the environment before running the test
Endpoint URL not visible in logsSet log level to info or lower in your Praman config
showChapter() has no effectVerify Playwright 1.59+ and that video recording is enabled in config
onFrame() throws on registrationpage.screencast is unavailable — upgrade Playwright to 1.59+
Frame handlers never fireEnsure the screencast has time to start — avoid immediate test teardown
unbind() warning in teardown logsNon-fatal — the browser may have already been closed by the test
Custom bind title not appliedSet PRAMAN_BIND_TITLE env var before the test process starts

Next Steps