Skip to content

Browsing

BrowsingModule

Module for managing browsing contexts, navigation and screenshots.

Source code in bidiwave/modules/browsing.py
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
class BrowsingModule:
    """Module for managing browsing contexts, navigation and screenshots."""

    def __init__(
        self,
        connection: Connection,
        script_module: ScriptModule | None = None,
    ) -> None:
        self._connection = connection
        self._script_module = script_module

    async def create_context(
        self,
        type: Literal["tab", "window"] = "tab",
        user_context: str | None = None,
        reference_context: str | None = None,
        background: bool | None = None,
    ) -> BrowsingContext:
        params: dict[str, Any] = {"type": type}
        if user_context is not None:
            params["userContext"] = user_context
        if reference_context is not None:
            params["referenceContext"] = reference_context
        if background is not None:
            params["background"] = background
        result = await self._connection.send_command(
            BROWSING_CREATE_CONTEXT, params
        )
        return BrowsingContext(
            id=result.get("context", ""),
            url=result.get("url", ""),
            user_context=result.get("userContext"),
            _module=self,
        )

    async def create_user_context(
        self,
        accept_insecure_certs: bool | None = None,
    ) -> UserContextInfo:
        """Creates an isolated user context (profile with its own cookies).

        Args:
            accept_insecure_certs: Whether the context accepts insecure
                TLS certificates. Defaults to the session value if omitted.

        Returns:
            UserContextInfo with the ID of the created user context.
        """
        params: dict[str, Any] = {}
        if accept_insecure_certs is not None:
            params["acceptInsecureCerts"] = accept_insecure_certs
        result = await self._connection.send_command(
            BROWSER_CREATE_USER_CONTEXT, params
        )
        return UserContextInfo.model_validate(result)

    async def close_browser(self) -> None:
        """Closes the browser, ending the session and cleaning up resources."""
        await self._connection.send_command(BROWSER_CLOSE, {})

    async def get_client_windows(self) -> list[ClientWindowInfo]:
        """Lists all open client windows.

        Returns:
            List of ClientWindowInfo with window state, dimensions, and position.
        """
        result = await self._connection.send_command(
            BROWSER_GET_CLIENT_WINDOWS, {}
        )
        parsed = GetClientWindowsResult.model_validate(result)
        return parsed.client_windows

    async def set_client_window_state(
        self,
        client_window: str,
        state: Literal["normal", "minimized", "maximized", "fullscreen"],
    ) -> None:
        """Sets the state of a client window.

        Args:
            client_window: ID of the client window.
            state: Target state — "normal", "minimized", "maximized", or "fullscreen".
        """
        await self._connection.send_command(
            BROWSER_SET_CLIENT_WINDOW_STATE,
            {"clientWindow": client_window, "state": state},
        )

    async def remove_user_context(self, user_context: str) -> None:
        """Removes a user context and all its browsing contexts.

        Args:
            user_context: ID of the user context to remove.
        """
        await self._connection.send_command(
            BROWSER_REMOVE_USER_CONTEXT, {"userContext": user_context}
        )

    async def get_user_contexts(self) -> list[UserContextInfo]:
        """Lists all available user contexts.

        Returns:
            List of UserContextInfo.
        """
        result = await self._connection.send_command(
            BROWSER_GET_USER_CONTEXTS, {}
        )
        parsed = GetUserContextsResult.model_validate(result)
        return parsed.user_contexts

    async def navigate(
        self,
        context: BrowsingContext | str,
        url: str,
        wait: Literal["none", "interactive", "complete"] = "complete",
    ) -> Navigation:
        ctx_id = context.id if hasattr(context, "id") else context
        result = await self._connection.send_command(
            BROWSING_NAVIGATE, {"context": ctx_id, "url": url, "wait": wait}
        )
        nav = Navigation.model_validate(result)
        if isinstance(context, BrowsingContext):
            context.url = nav.url
        return nav

    async def close(self, context: BrowsingContext | str) -> None:
        ctx_id = context.id if hasattr(context, "id") else context
        await self._connection.send_command(BROWSING_CLOSE, {"context": ctx_id})

    async def screenshot(
        self,
        context: BrowsingContext | str,
        format: Literal["png", "jpeg"] = "png",
        quality: int | None = None,
        clip: dict[str, Any] | None = None,
        origin: Literal["viewport", "document"] | None = None,
    ) -> Screenshot:
        ctx_id = context.id if hasattr(context, "id") else context
        params: dict[str, Any] = {"context": ctx_id}
        if format != "png":
            params["format"] = format
        if quality is not None and format == "jpeg":
            params["quality"] = quality
        if clip is not None:
            params["clip"] = clip
        if origin is not None:
            params["origin"] = origin
        result = await self._connection.send_command(BROWSING_CAPTURE_SCREENSHOT, params)
        return Screenshot.model_validate(result)

    async def get_tree(
        self,
        root: str | None = None,
        max_depth: int | None = None,
    ) -> GetTreeResult:
        params: dict[str, Any] = {}
        if root is not None:
            params["root"] = root
        if max_depth is not None:
            params["maxDepth"] = max_depth
        result = await self._connection.send_command(BROWSING_GET_TREE, params)
        return GetTreeResult.model_validate(result)

    async def wait_for_selector(
        self,
        context: BrowsingContext | str,
        selector: str,
        timeout: float = 10.0,
    ) -> bool:
        """Waits for an element to exist in the DOM."""
        ctx_id = context.id if hasattr(context, "id") else context
        # Use evaluate instead of callFunction (driver bug with primitive args)
        # json.dumps produces a JS-safe string literal; repr() is Python syntax
        # and is not guaranteed to be valid/safe JavaScript for all inputs.
        js_selector = json.dumps(selector)
        expression = (
            f"new Promise(resolve => {{"
            f" const el = document.querySelector({js_selector});"
            f" if (el) return resolve(true);"
            f" const target = document.body || document.documentElement;"
            f" if (!target) return resolve(false);"
            f" new MutationObserver((_, obs) => {{"
            f" if (document.querySelector({js_selector})) {{"
            f" obs.disconnect();"
            f" resolve(true);"
            f" }}"
            f" }}).observe(target, {{childList: true, subtree: true}});"
            f"}})"
        )
        result = await asyncio.wait_for(
            self._connection.send_command(
                "script.evaluate",
                {
                    "target": {"context": ctx_id},
                    "expression": expression,
                    "awaitPromise": True,
                },
            ),
            timeout=timeout,
        )
        if result.get("type") == "exception":
            details = result.get("exceptionDetails", {})
            raise JavaScriptError(
                "javascript error",
                details.get("text", "Unknown JS error in wait_for_selector"),
            )
        # Unwrap script success wrapper
        inner = result.get("result", result)
        return inner.get("value") is True

    async def wait_for_function(
        self,
        context: BrowsingContext | str,
        expression: str,
        timeout: float = 10.0,
    ) -> Any:
        """Waits for a JS expression to return truthy.

        Wraps the expression in a Promise that polls internally and resolves
        when the condition is truthy, using ``awaitPromise=True`` instead of
        client-side polling.
        """
        ctx_id = context.id if hasattr(context, "id") else context
        wrapped = (
            f"new Promise(resolve => {{"
            f" const check = () => {{"
            f"  try {{ const r = ({expression});"
            f"   if (r) return resolve(r);"
            f"  }} catch(e) {{ return resolve(undefined); }}"
            f"  setTimeout(check, 100);"
            f" }};"
            f" check();"
            f"}})"
        )
        result = await asyncio.wait_for(
            self._connection.send_command(
                "script.evaluate",
                {
                    "target": {"context": ctx_id},
                    "expression": wrapped,
                    "awaitPromise": True,
                },
            ),
            timeout=timeout,
        )
        if result.get("type") == "exception":
            details = result.get("exceptionDetails", {})
            raise JavaScriptError(
                "javascript error",
                details.get("text", "Unknown JS error in wait_for_function"),
            )
        inner = result.get("result", result)
        return inner.get("value")

    async def reload(
        self,
        context: BrowsingContext | str,
        wait: Literal["none", "interactive", "complete"] = "complete",
        ignore_cache: bool | None = None,
    ) -> Navigation:
        """Reloads the current context."""
        ctx_id = context.id if hasattr(context, "id") else context
        params: dict[str, Any] = {"context": ctx_id, "wait": wait}
        if ignore_cache is not None:
            params["ignoreCache"] = ignore_cache
        result = await self._connection.send_command(
            BROWSING_RELOAD, params
        )
        nav = Navigation.model_validate(result)
        if isinstance(context, BrowsingContext):
            context.url = nav.url
        return nav

    async def traverse_history(
        self,
        context: BrowsingContext | str,
        direction: Literal["back", "forward"],
    ) -> Navigation:
        """Navigates back or forward in history."""
        ctx_id = context.id if hasattr(context, "id") else context
        result = await self._connection.send_command(
            BROWSING_TRAVERSE_HISTORY,
            {"context": ctx_id, "direction": direction},
        )
        nav = Navigation.model_validate(result)
        if isinstance(context, BrowsingContext):
            context.url = nav.url
        return nav

    async def handle_user_prompt(
        self,
        context: BrowsingContext | str,
        accept: bool | None = None,
        user_text: str | None = None,
    ) -> None:
        """Accepts or dismisses a dialog (alert, confirm, prompt)."""
        ctx_id = context.id if hasattr(context, "id") else context
        params: dict[str, Any] = {"context": ctx_id}
        if accept is not None:
            params["accept"] = accept
        if user_text is not None:
            params["userText"] = user_text
        await self._connection.send_command(
            BROWSING_HANDLE_USER_PROMPT, params
        )

    async def print(
        self,
        context: BrowsingContext | str,
        background: bool = False,
        margin: dict[str, Any] | None = None,
        orientation: Literal["portrait", "landscape"] = "portrait",
        page: dict[str, Any] | None = None,
        page_ranges: list[int | str] | None = None,
        scale: float = 1.0,
        shrink_to_fit: bool = True,
    ) -> PrintResult:
        """Exports the context to PDF and returns base64 data.

        Args:
            background: Whether to print background graphics.
                Also accepted as ``printBackground`` per spec.
            margin: Page margins dict.
            orientation: Page orientation.
            page: Page size dict.
            page_ranges: Page ranges to print.
            scale: Scale factor (0.1 to 2.0).
            shrink_to_fit: Whether to shrink content to fit page.
        """
        ctx_id = context.id if hasattr(context, "id") else context
        params: dict[str, Any] = {
            "context": ctx_id,
            "printBackground": background,
            "orientation": orientation,
            "scale": scale,
            "shrinkToFit": shrink_to_fit,
        }
        if margin is not None:
            params["margin"] = margin
        if page is not None:
            params["page"] = page
        if page_ranges is not None:
            params["pageRanges"] = page_ranges
        result = await self._connection.send_command(BROWSING_PRINT, params)
        return PrintResult.model_validate(result)

    async def locate_nodes(
        self,
        context: BrowsingContext | str,
        locator: dict[str, Any],
        max_node_count: int | None = None,
        start_nodes: list[dict[str, Any]] | None = None,
        serialization_options: dict[str, Any] | None = None,
    ) -> LocateNodesResult:
        """Locates elements in the DOM using a locator.

        Args:
            context: BrowsingContext or context ID.
            locator: Dict with the locator type, e.g.:
                {"type": "css", "value": "div.product"}
                {"type": "xpath", "value": "//div[@id='foo']"}
                {"type": "innerText", "value": "Click me"}
            max_node_count: Maximum number of nodes to return.
            start_nodes: Nodes to search from (e.g. iframes).
            serialization_options: Controls how node values are serialized.
                e.g. {"maxDomDepth": 1, "includeShadowTree": "all"}.
        """
        ctx_id = context.id if hasattr(context, "id") else context
        params: dict[str, Any] = {"context": ctx_id, "locator": locator}
        if max_node_count is not None:
            params["maxNodeCount"] = max_node_count
        if start_nodes is not None:
            params["startNodes"] = start_nodes
        if serialization_options is not None:
            params["serializationOptions"] = serialization_options
        result = await self._connection.send_command(
            BROWSING_LOCATE_NODES, params
        )
        return LocateNodesResult.model_validate(result)

    async def activate(self, context: BrowsingContext | str) -> None:
        """Activates a browsing context (brings it to front)."""
        ctx_id = context.id if hasattr(context, "id") else context
        await self._connection.send_command(
            BROWSING_ACTIVATE, {"context": ctx_id}
        )

    async def set_viewport(
        self,
        context: BrowsingContext | str,
        viewport: ViewportSize | dict[str, int] | None = None,
        device_pixel_ratio: float | None = None,
    ) -> None:
        """Sets the viewport and device pixel ratio of a context.

        Args:
            context: BrowsingContext or context ID.
            viewport: ViewportSize or dict with "width" and "height"
                in CSS pixels. Pass None to reset to the original viewport.
            device_pixel_ratio: Ratio of physical pixels to CSS pixels.
        """
        ctx_id = context.id if hasattr(context, "id") else context
        params: dict[str, Any] = {"context": ctx_id}
        if viewport is not None:
            if isinstance(viewport, ViewportSize):
                params["viewport"] = viewport.model_dump()
            else:
                params["viewport"] = viewport
        if device_pixel_ratio is not None:
            params["devicePixelRatio"] = device_pixel_ratio
        await self._connection.send_command(
            BROWSING_SET_VIEWPORT, params
        )

    async def get_viewport(
        self,
        context: BrowsingContext | str,
    ) -> Viewport:
        """Gets the current viewport and device pixel ratio of a context.

        Args:
            context: BrowsingContext or context ID.

        Returns:
            Viewport with width, height, and device pixel ratio.
        """
        ctx_id = context.id if hasattr(context, "id") else context
        result = await self._connection.send_command(
            BROWSING_GET_VIEWPORT, {"context": ctx_id}
        )
        parsed = GetViewportResult.model_validate(result)
        return parsed.viewport

    async def open(
        self,
        url: str,
        wait: Literal["none", "interactive", "complete"] = "complete",
    ) -> Page:
        """Creates a context, navigates to the URL and returns a Page object."""
        from bidiwave.convenience.page import Page

        ctx = await self.create_context()
        try:
            await self.navigate(ctx, url, wait)
        except Exception:
            await self.close(ctx)
            raise
        return Page(self, self._script_module, ctx)

create_user_context async

create_user_context(accept_insecure_certs: bool | None = None) -> UserContextInfo

Creates an isolated user context (profile with its own cookies).

Parameters:

Name Type Description Default
accept_insecure_certs bool | None

Whether the context accepts insecure TLS certificates. Defaults to the session value if omitted.

None

Returns:

Type Description
UserContextInfo

UserContextInfo with the ID of the created user context.

Source code in bidiwave/modules/browsing.py
async def create_user_context(
    self,
    accept_insecure_certs: bool | None = None,
) -> UserContextInfo:
    """Creates an isolated user context (profile with its own cookies).

    Args:
        accept_insecure_certs: Whether the context accepts insecure
            TLS certificates. Defaults to the session value if omitted.

    Returns:
        UserContextInfo with the ID of the created user context.
    """
    params: dict[str, Any] = {}
    if accept_insecure_certs is not None:
        params["acceptInsecureCerts"] = accept_insecure_certs
    result = await self._connection.send_command(
        BROWSER_CREATE_USER_CONTEXT, params
    )
    return UserContextInfo.model_validate(result)

close_browser async

close_browser() -> None

Closes the browser, ending the session and cleaning up resources.

Source code in bidiwave/modules/browsing.py
async def close_browser(self) -> None:
    """Closes the browser, ending the session and cleaning up resources."""
    await self._connection.send_command(BROWSER_CLOSE, {})

get_client_windows async

get_client_windows() -> list[ClientWindowInfo]

Lists all open client windows.

Returns:

Type Description
list[ClientWindowInfo]

List of ClientWindowInfo with window state, dimensions, and position.

Source code in bidiwave/modules/browsing.py
async def get_client_windows(self) -> list[ClientWindowInfo]:
    """Lists all open client windows.

    Returns:
        List of ClientWindowInfo with window state, dimensions, and position.
    """
    result = await self._connection.send_command(
        BROWSER_GET_CLIENT_WINDOWS, {}
    )
    parsed = GetClientWindowsResult.model_validate(result)
    return parsed.client_windows

set_client_window_state async

set_client_window_state(client_window: str, state: Literal['normal', 'minimized', 'maximized', 'fullscreen']) -> None

Sets the state of a client window.

Parameters:

Name Type Description Default
client_window str

ID of the client window.

required
state Literal['normal', 'minimized', 'maximized', 'fullscreen']

Target state — "normal", "minimized", "maximized", or "fullscreen".

required
Source code in bidiwave/modules/browsing.py
async def set_client_window_state(
    self,
    client_window: str,
    state: Literal["normal", "minimized", "maximized", "fullscreen"],
) -> None:
    """Sets the state of a client window.

    Args:
        client_window: ID of the client window.
        state: Target state — "normal", "minimized", "maximized", or "fullscreen".
    """
    await self._connection.send_command(
        BROWSER_SET_CLIENT_WINDOW_STATE,
        {"clientWindow": client_window, "state": state},
    )

remove_user_context async

remove_user_context(user_context: str) -> None

Removes a user context and all its browsing contexts.

Parameters:

Name Type Description Default
user_context str

ID of the user context to remove.

required
Source code in bidiwave/modules/browsing.py
async def remove_user_context(self, user_context: str) -> None:
    """Removes a user context and all its browsing contexts.

    Args:
        user_context: ID of the user context to remove.
    """
    await self._connection.send_command(
        BROWSER_REMOVE_USER_CONTEXT, {"userContext": user_context}
    )

get_user_contexts async

get_user_contexts() -> list[UserContextInfo]

Lists all available user contexts.

Returns:

Type Description
list[UserContextInfo]

List of UserContextInfo.

Source code in bidiwave/modules/browsing.py
async def get_user_contexts(self) -> list[UserContextInfo]:
    """Lists all available user contexts.

    Returns:
        List of UserContextInfo.
    """
    result = await self._connection.send_command(
        BROWSER_GET_USER_CONTEXTS, {}
    )
    parsed = GetUserContextsResult.model_validate(result)
    return parsed.user_contexts

wait_for_selector async

wait_for_selector(context: BrowsingContext | str, selector: str, timeout: float = 10.0) -> bool

Waits for an element to exist in the DOM.

Source code in bidiwave/modules/browsing.py
async def wait_for_selector(
    self,
    context: BrowsingContext | str,
    selector: str,
    timeout: float = 10.0,
) -> bool:
    """Waits for an element to exist in the DOM."""
    ctx_id = context.id if hasattr(context, "id") else context
    # Use evaluate instead of callFunction (driver bug with primitive args)
    # json.dumps produces a JS-safe string literal; repr() is Python syntax
    # and is not guaranteed to be valid/safe JavaScript for all inputs.
    js_selector = json.dumps(selector)
    expression = (
        f"new Promise(resolve => {{"
        f" const el = document.querySelector({js_selector});"
        f" if (el) return resolve(true);"
        f" const target = document.body || document.documentElement;"
        f" if (!target) return resolve(false);"
        f" new MutationObserver((_, obs) => {{"
        f" if (document.querySelector({js_selector})) {{"
        f" obs.disconnect();"
        f" resolve(true);"
        f" }}"
        f" }}).observe(target, {{childList: true, subtree: true}});"
        f"}})"
    )
    result = await asyncio.wait_for(
        self._connection.send_command(
            "script.evaluate",
            {
                "target": {"context": ctx_id},
                "expression": expression,
                "awaitPromise": True,
            },
        ),
        timeout=timeout,
    )
    if result.get("type") == "exception":
        details = result.get("exceptionDetails", {})
        raise JavaScriptError(
            "javascript error",
            details.get("text", "Unknown JS error in wait_for_selector"),
        )
    # Unwrap script success wrapper
    inner = result.get("result", result)
    return inner.get("value") is True

wait_for_function async

wait_for_function(context: BrowsingContext | str, expression: str, timeout: float = 10.0) -> Any

Waits for a JS expression to return truthy.

Wraps the expression in a Promise that polls internally and resolves when the condition is truthy, using awaitPromise=True instead of client-side polling.

Source code in bidiwave/modules/browsing.py
async def wait_for_function(
    self,
    context: BrowsingContext | str,
    expression: str,
    timeout: float = 10.0,
) -> Any:
    """Waits for a JS expression to return truthy.

    Wraps the expression in a Promise that polls internally and resolves
    when the condition is truthy, using ``awaitPromise=True`` instead of
    client-side polling.
    """
    ctx_id = context.id if hasattr(context, "id") else context
    wrapped = (
        f"new Promise(resolve => {{"
        f" const check = () => {{"
        f"  try {{ const r = ({expression});"
        f"   if (r) return resolve(r);"
        f"  }} catch(e) {{ return resolve(undefined); }}"
        f"  setTimeout(check, 100);"
        f" }};"
        f" check();"
        f"}})"
    )
    result = await asyncio.wait_for(
        self._connection.send_command(
            "script.evaluate",
            {
                "target": {"context": ctx_id},
                "expression": wrapped,
                "awaitPromise": True,
            },
        ),
        timeout=timeout,
    )
    if result.get("type") == "exception":
        details = result.get("exceptionDetails", {})
        raise JavaScriptError(
            "javascript error",
            details.get("text", "Unknown JS error in wait_for_function"),
        )
    inner = result.get("result", result)
    return inner.get("value")

reload async

reload(context: BrowsingContext | str, wait: Literal['none', 'interactive', 'complete'] = 'complete', ignore_cache: bool | None = None) -> Navigation

Reloads the current context.

Source code in bidiwave/modules/browsing.py
async def reload(
    self,
    context: BrowsingContext | str,
    wait: Literal["none", "interactive", "complete"] = "complete",
    ignore_cache: bool | None = None,
) -> Navigation:
    """Reloads the current context."""
    ctx_id = context.id if hasattr(context, "id") else context
    params: dict[str, Any] = {"context": ctx_id, "wait": wait}
    if ignore_cache is not None:
        params["ignoreCache"] = ignore_cache
    result = await self._connection.send_command(
        BROWSING_RELOAD, params
    )
    nav = Navigation.model_validate(result)
    if isinstance(context, BrowsingContext):
        context.url = nav.url
    return nav

traverse_history async

traverse_history(context: BrowsingContext | str, direction: Literal['back', 'forward']) -> Navigation

Navigates back or forward in history.

Source code in bidiwave/modules/browsing.py
async def traverse_history(
    self,
    context: BrowsingContext | str,
    direction: Literal["back", "forward"],
) -> Navigation:
    """Navigates back or forward in history."""
    ctx_id = context.id if hasattr(context, "id") else context
    result = await self._connection.send_command(
        BROWSING_TRAVERSE_HISTORY,
        {"context": ctx_id, "direction": direction},
    )
    nav = Navigation.model_validate(result)
    if isinstance(context, BrowsingContext):
        context.url = nav.url
    return nav

handle_user_prompt async

handle_user_prompt(context: BrowsingContext | str, accept: bool | None = None, user_text: str | None = None) -> None

Accepts or dismisses a dialog (alert, confirm, prompt).

Source code in bidiwave/modules/browsing.py
async def handle_user_prompt(
    self,
    context: BrowsingContext | str,
    accept: bool | None = None,
    user_text: str | None = None,
) -> None:
    """Accepts or dismisses a dialog (alert, confirm, prompt)."""
    ctx_id = context.id if hasattr(context, "id") else context
    params: dict[str, Any] = {"context": ctx_id}
    if accept is not None:
        params["accept"] = accept
    if user_text is not None:
        params["userText"] = user_text
    await self._connection.send_command(
        BROWSING_HANDLE_USER_PROMPT, params
    )

print async

print(context: BrowsingContext | str, background: bool = False, margin: dict[str, Any] | None = None, orientation: Literal['portrait', 'landscape'] = 'portrait', page: dict[str, Any] | None = None, page_ranges: list[int | str] | None = None, scale: float = 1.0, shrink_to_fit: bool = True) -> PrintResult

Exports the context to PDF and returns base64 data.

Parameters:

Name Type Description Default
background bool

Whether to print background graphics. Also accepted as printBackground per spec.

False
margin dict[str, Any] | None

Page margins dict.

None
orientation Literal['portrait', 'landscape']

Page orientation.

'portrait'
page dict[str, Any] | None

Page size dict.

None
page_ranges list[int | str] | None

Page ranges to print.

None
scale float

Scale factor (0.1 to 2.0).

1.0
shrink_to_fit bool

Whether to shrink content to fit page.

True
Source code in bidiwave/modules/browsing.py
async def print(
    self,
    context: BrowsingContext | str,
    background: bool = False,
    margin: dict[str, Any] | None = None,
    orientation: Literal["portrait", "landscape"] = "portrait",
    page: dict[str, Any] | None = None,
    page_ranges: list[int | str] | None = None,
    scale: float = 1.0,
    shrink_to_fit: bool = True,
) -> PrintResult:
    """Exports the context to PDF and returns base64 data.

    Args:
        background: Whether to print background graphics.
            Also accepted as ``printBackground`` per spec.
        margin: Page margins dict.
        orientation: Page orientation.
        page: Page size dict.
        page_ranges: Page ranges to print.
        scale: Scale factor (0.1 to 2.0).
        shrink_to_fit: Whether to shrink content to fit page.
    """
    ctx_id = context.id if hasattr(context, "id") else context
    params: dict[str, Any] = {
        "context": ctx_id,
        "printBackground": background,
        "orientation": orientation,
        "scale": scale,
        "shrinkToFit": shrink_to_fit,
    }
    if margin is not None:
        params["margin"] = margin
    if page is not None:
        params["page"] = page
    if page_ranges is not None:
        params["pageRanges"] = page_ranges
    result = await self._connection.send_command(BROWSING_PRINT, params)
    return PrintResult.model_validate(result)

locate_nodes async

locate_nodes(context: BrowsingContext | str, locator: dict[str, Any], max_node_count: int | None = None, start_nodes: list[dict[str, Any]] | None = None, serialization_options: dict[str, Any] | None = None) -> LocateNodesResult

Locates elements in the DOM using a locator.

Parameters:

Name Type Description Default
context BrowsingContext | str

BrowsingContext or context ID.

required
locator dict[str, Any]

Dict with the locator type, e.g.: {"type": "css", "value": "div.product"} {"type": "xpath", "value": "//div[@id='foo']"} {"type": "innerText", "value": "Click me"}

required
max_node_count int | None

Maximum number of nodes to return.

None
start_nodes list[dict[str, Any]] | None

Nodes to search from (e.g. iframes).

None
serialization_options dict[str, Any] | None

Controls how node values are serialized. e.g. {"maxDomDepth": 1, "includeShadowTree": "all"}.

None
Source code in bidiwave/modules/browsing.py
async def locate_nodes(
    self,
    context: BrowsingContext | str,
    locator: dict[str, Any],
    max_node_count: int | None = None,
    start_nodes: list[dict[str, Any]] | None = None,
    serialization_options: dict[str, Any] | None = None,
) -> LocateNodesResult:
    """Locates elements in the DOM using a locator.

    Args:
        context: BrowsingContext or context ID.
        locator: Dict with the locator type, e.g.:
            {"type": "css", "value": "div.product"}
            {"type": "xpath", "value": "//div[@id='foo']"}
            {"type": "innerText", "value": "Click me"}
        max_node_count: Maximum number of nodes to return.
        start_nodes: Nodes to search from (e.g. iframes).
        serialization_options: Controls how node values are serialized.
            e.g. {"maxDomDepth": 1, "includeShadowTree": "all"}.
    """
    ctx_id = context.id if hasattr(context, "id") else context
    params: dict[str, Any] = {"context": ctx_id, "locator": locator}
    if max_node_count is not None:
        params["maxNodeCount"] = max_node_count
    if start_nodes is not None:
        params["startNodes"] = start_nodes
    if serialization_options is not None:
        params["serializationOptions"] = serialization_options
    result = await self._connection.send_command(
        BROWSING_LOCATE_NODES, params
    )
    return LocateNodesResult.model_validate(result)

activate async

activate(context: BrowsingContext | str) -> None

Activates a browsing context (brings it to front).

Source code in bidiwave/modules/browsing.py
async def activate(self, context: BrowsingContext | str) -> None:
    """Activates a browsing context (brings it to front)."""
    ctx_id = context.id if hasattr(context, "id") else context
    await self._connection.send_command(
        BROWSING_ACTIVATE, {"context": ctx_id}
    )

set_viewport async

set_viewport(context: BrowsingContext | str, viewport: ViewportSize | dict[str, int] | None = None, device_pixel_ratio: float | None = None) -> None

Sets the viewport and device pixel ratio of a context.

Parameters:

Name Type Description Default
context BrowsingContext | str

BrowsingContext or context ID.

required
viewport ViewportSize | dict[str, int] | None

ViewportSize or dict with "width" and "height" in CSS pixels. Pass None to reset to the original viewport.

None
device_pixel_ratio float | None

Ratio of physical pixels to CSS pixels.

None
Source code in bidiwave/modules/browsing.py
async def set_viewport(
    self,
    context: BrowsingContext | str,
    viewport: ViewportSize | dict[str, int] | None = None,
    device_pixel_ratio: float | None = None,
) -> None:
    """Sets the viewport and device pixel ratio of a context.

    Args:
        context: BrowsingContext or context ID.
        viewport: ViewportSize or dict with "width" and "height"
            in CSS pixels. Pass None to reset to the original viewport.
        device_pixel_ratio: Ratio of physical pixels to CSS pixels.
    """
    ctx_id = context.id if hasattr(context, "id") else context
    params: dict[str, Any] = {"context": ctx_id}
    if viewport is not None:
        if isinstance(viewport, ViewportSize):
            params["viewport"] = viewport.model_dump()
        else:
            params["viewport"] = viewport
    if device_pixel_ratio is not None:
        params["devicePixelRatio"] = device_pixel_ratio
    await self._connection.send_command(
        BROWSING_SET_VIEWPORT, params
    )

get_viewport async

get_viewport(context: BrowsingContext | str) -> Viewport

Gets the current viewport and device pixel ratio of a context.

Parameters:

Name Type Description Default
context BrowsingContext | str

BrowsingContext or context ID.

required

Returns:

Type Description
Viewport

Viewport with width, height, and device pixel ratio.

Source code in bidiwave/modules/browsing.py
async def get_viewport(
    self,
    context: BrowsingContext | str,
) -> Viewport:
    """Gets the current viewport and device pixel ratio of a context.

    Args:
        context: BrowsingContext or context ID.

    Returns:
        Viewport with width, height, and device pixel ratio.
    """
    ctx_id = context.id if hasattr(context, "id") else context
    result = await self._connection.send_command(
        BROWSING_GET_VIEWPORT, {"context": ctx_id}
    )
    parsed = GetViewportResult.model_validate(result)
    return parsed.viewport

open async

open(url: str, wait: Literal['none', 'interactive', 'complete'] = 'complete') -> Page

Creates a context, navigates to the URL and returns a Page object.

Source code in bidiwave/modules/browsing.py
async def open(
    self,
    url: str,
    wait: Literal["none", "interactive", "complete"] = "complete",
) -> Page:
    """Creates a context, navigates to the URL and returns a Page object."""
    from bidiwave.convenience.page import Page

    ctx = await self.create_context()
    try:
        await self.navigate(ctx, url, wait)
    except Exception:
        await self.close(ctx)
        raise
    return Page(self, self._script_module, ctx)

BrowsingContext dataclass

Represents a browsing context (tab/window).

Source code in bidiwave/modules/browsing.py
@dataclass
class BrowsingContext:
    """Represents a browsing context (tab/window)."""

    id: str
    url: str = ""
    user_context: str | None = None
    _module: BrowsingModule | None = field(default=None, repr=False)

    async def __aenter__(self) -> BrowsingContext:
        return self

    async def __aexit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
        if self._module:
            try:
                await self._module.close(self.id)
            except Exception as close_err:
                if exc_type is None:
                    raise
                logger.warning(
                    "Suppressing close error during exception (original: %s, close error: %s)",
                    exc_val,
                    close_err,
                )