From 63bcc439c11cdab1caef8f7676cdead1cb8f9a0c Mon Sep 17 00:00:00 2001 From: Abhinav Kumar Singh Date: Sat, 15 Aug 2026 15:04:44 +0530 Subject: [PATCH] fix: surface unexpected extension API success --- examples/playwright_extensions.py | 38 ++++++++++++++------ tests/test_playwright_extensions.py | 55 +++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 11 deletions(-) create mode 100644 tests/test_playwright_extensions.py diff --git a/examples/playwright_extensions.py b/examples/playwright_extensions.py index b7f6332c..aa510e25 100644 --- a/examples/playwright_extensions.py +++ b/examples/playwright_extensions.py @@ -2,6 +2,7 @@ import time import zipfile from io import BytesIO +from typing import Callable from pathlib import Path from playwright.sync_api import Page, Playwright, sync_playwright @@ -10,11 +11,26 @@ BROWSERBASE_PROJECT_ID, bb, ) +from browserbase import APIStatusError from browserbase.types import Extension, SessionCreateResponse PATH_TO_EXTENSION = Path.cwd() / "examples" / "packages" / "extensions" / "browserbase-test" +def expect_api_status_error( + operation: Callable[[], object], + *, + failure_message: str, + success_message: str, +) -> None: + try: + operation() + except APIStatusError as error: + print(f"{success_message}: {error}") + else: + raise AssertionError(failure_message) + + def zip_extension(path: Path = PATH_TO_EXTENSION, save_local: bool = False) -> BytesIO: """ Create an in-memory zip file from the contents of the given folder. @@ -122,20 +138,20 @@ def run(playwright: Playwright) -> None: print(f"Deleted extension with ID: {extension_id}") # Verify deleted extension is unusable - try: - get_extension(extension_id) - raise AssertionError("Expected to fail when retrieving deleted extension") - except Exception as e: - print(f"Failed to get deleted extension as expected: {str(e)}") + expect_api_status_error( + lambda: get_extension(extension_id), + failure_message="Expected to fail when retrieving deleted extension", + success_message="Failed to get deleted extension as expected", + ) - try: - bb.sessions.create( + expect_api_status_error( + lambda: bb.sessions.create( project_id=BROWSERBASE_PROJECT_ID, extension_id=extension_id, - ) - raise AssertionError("Expected to fail when creating session with deleted extension") - except Exception as e: - print(f"Failed to create session with deleted extension as expected: {str(e)}") + ), + failure_message="Expected to fail when creating session with deleted extension", + success_message="Failed to create session with deleted extension as expected", + ) if __name__ == "__main__": diff --git a/tests/test_playwright_extensions.py b/tests/test_playwright_extensions.py new file mode 100644 index 00000000..9206c6dc --- /dev/null +++ b/tests/test_playwright_extensions.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +import importlib +from types import ModuleType + +import httpx +import pytest + +from browserbase import APIStatusError + + +@pytest.fixture +def extension_example(monkeypatch: pytest.MonkeyPatch) -> ModuleType: + monkeypatch.setenv("BROWSERBASE_API_KEY", "test-api-key") + monkeypatch.setenv("BROWSERBASE_PROJECT_ID", "test-project-id") + return importlib.import_module("examples.playwright_extensions") + + +def test_expect_api_status_error_accepts_api_failure( + extension_example: ModuleType, capsys: pytest.CaptureFixture[str] +) -> None: + request = httpx.Request("GET", "https://example.com/extensions/id") + response = httpx.Response(404, request=request) + + def operation() -> object: + raise APIStatusError("not found", response=response, body=None) + + extension_example.expect_api_status_error( + operation, + failure_message="operation unexpectedly succeeded", + success_message="operation failed as expected", + ) + + assert "operation failed as expected" in capsys.readouterr().out + + +def test_expect_api_status_error_rejects_unexpected_success(extension_example: ModuleType) -> None: + with pytest.raises(AssertionError, match="operation unexpectedly succeeded"): + extension_example.expect_api_status_error( + lambda: object(), + failure_message="operation unexpectedly succeeded", + success_message="operation failed as expected", + ) + + +def test_expect_api_status_error_does_not_hide_other_errors(extension_example: ModuleType) -> None: + def operation() -> object: + raise RuntimeError("unexpected failure") + + with pytest.raises(RuntimeError, match="unexpected failure"): + extension_example.expect_api_status_error( + operation, + failure_message="operation unexpectedly succeeded", + success_message="operation failed as expected", + )