diff --git a/.gitignore b/.gitignore
index 860eff42..46152338 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,4 @@ dist
codegen.log
Brewfile.lock.json
screenshot.png
-openapi.v1.yaml
+**/.DS_Store
diff --git a/examples/e2e/test_playwright.py b/examples/e2e/test_playwright.py
new file mode 100644
index 00000000..725d47b1
--- /dev/null
+++ b/examples/e2e/test_playwright.py
@@ -0,0 +1,78 @@
+import os
+from typing import Generator
+
+import pytest
+from dotenv import load_dotenv
+from playwright.sync_api import Playwright, sync_playwright
+
+from browserbase import Browserbase
+
+from .. import (
+ BROWSERBASE_API_KEY,
+ playwright_basic,
+ playwright_proxy,
+ playwright_upload,
+ playwright_captcha,
+ playwright_contexts,
+ playwright_downloads,
+)
+
+bb = Browserbase(api_key=BROWSERBASE_API_KEY)
+load_dotenv()
+
+CI = os.getenv("CI", "false").lower() == "true"
+
+
+@pytest.fixture(scope="session")
+def playwright() -> Generator[Playwright, None, None]:
+ with sync_playwright() as p:
+ yield p
+
+
+def test_playwright_basic(playwright: Playwright) -> None:
+ playwright_basic.run(playwright)
+
+
+@pytest.mark.skipif(True, reason="Flaky and fails often")
+def test_playwright_captcha(playwright: Playwright) -> None:
+ playwright_captcha.run(playwright)
+
+
+def test_playwright_contexts(playwright: Playwright) -> None:
+ playwright_contexts.run(playwright)
+
+
+def test_playwright_downloads(playwright: Playwright) -> None:
+ playwright_downloads.run(playwright)
+
+
+def test_playwright_proxy_enable_via_create_session(playwright: Playwright) -> None:
+ playwright_proxy.run_enable_via_create_session(playwright)
+
+
+def test_playwright_proxy_enable_via_querystring(playwright: Playwright) -> None:
+ playwright_proxy.run_enable_via_querystring_with_created_session(playwright)
+
+
+@pytest.mark.skipif(CI, reason="Flaky and fails on CI")
+def test_playwright_proxy_geolocation_country(playwright: Playwright) -> None:
+ playwright_proxy.run_geolocation_country(playwright)
+
+
+@pytest.mark.skipif(CI, reason="Flaky and fails on CI")
+def test_playwright_proxy_geolocation_state(playwright: Playwright) -> None:
+ playwright_proxy.run_geolocation_state(playwright)
+
+
+@pytest.mark.skipif(CI, reason="Flaky and fails on CI")
+def test_playwright_proxy_geolocation_american_city(playwright: Playwright) -> None:
+ playwright_proxy.run_geolocation_american_city(playwright)
+
+
+@pytest.mark.skipif(CI, reason="Flaky and fails on CI")
+def test_playwright_proxy_geolocation_non_american_city(playwright: Playwright) -> None:
+ playwright_proxy.run_geolocation_non_american_city(playwright)
+
+
+def test_playwright_upload(playwright: Playwright) -> None:
+ playwright_upload.run(playwright)
diff --git a/examples/e2e/test_playwright_basic.py b/examples/e2e/test_playwright_basic.py
deleted file mode 100644
index aba94d4c..00000000
--- a/examples/e2e/test_playwright_basic.py
+++ /dev/null
@@ -1,21 +0,0 @@
-import pytest
-from playwright.sync_api import Playwright, sync_playwright
-
-from browserbase import Browserbase
-
-from .. import (
- BROWSERBASE_API_KEY,
- playwright_basic,
-)
-
-bb = Browserbase(api_key=BROWSERBASE_API_KEY)
-
-
-@pytest.fixture(scope="session")
-def playwright():
- with sync_playwright() as p:
- yield p
-
-
-def test_playwright_basic(playwright: Playwright):
- playwright_basic.run(playwright)
diff --git a/examples/packages/extensions/.gitignore b/examples/packages/extensions/.gitignore
new file mode 100644
index 00000000..6f66c74b
--- /dev/null
+++ b/examples/packages/extensions/.gitignore
@@ -0,0 +1 @@
+*.zip
\ No newline at end of file
diff --git a/examples/packages/extensions/browserbase-test/hello.html b/examples/packages/extensions/browserbase-test/hello.html
new file mode 100644
index 00000000..2a0b7102
--- /dev/null
+++ b/examples/packages/extensions/browserbase-test/hello.html
@@ -0,0 +1,5 @@
+
+
+ Hello Extensions
+
+
diff --git a/examples/packages/extensions/browserbase-test/images/logo.png b/examples/packages/extensions/browserbase-test/images/logo.png
new file mode 100644
index 00000000..583360a1
Binary files /dev/null and b/examples/packages/extensions/browserbase-test/images/logo.png differ
diff --git a/examples/packages/extensions/browserbase-test/manifest.json b/examples/packages/extensions/browserbase-test/manifest.json
new file mode 100644
index 00000000..a3d41e7c
--- /dev/null
+++ b/examples/packages/extensions/browserbase-test/manifest.json
@@ -0,0 +1,21 @@
+{
+ "manifest_version": 3,
+ "name": "Browserbase Extension Test",
+ "description": "Test extension for browserbase",
+ "version": "1.0",
+ "action": {
+ "default_popup": "hello.html"
+ },
+ "content_scripts": [
+ {
+ "matches": ["https://www.browserbase.com/*"],
+ "js": ["scripts/content.js"]
+ }
+ ],
+ "web_accessible_resources": [
+ {
+ "resources": ["images/logo.png"],
+ "matches": ["https://www.browserbase.com/*"]
+ }
+ ]
+}
diff --git a/examples/packages/extensions/browserbase-test/scripts/content.js b/examples/packages/extensions/browserbase-test/scripts/content.js
new file mode 100644
index 00000000..abd195a7
--- /dev/null
+++ b/examples/packages/extensions/browserbase-test/scripts/content.js
@@ -0,0 +1,11 @@
+const imageUrl = chrome.runtime.getURL("images/logo.png");
+window
+ .fetch(imageUrl)
+ .then((response) => {
+ if (response.ok) {
+ console.log("browserbase test extension image loaded");
+ }
+ })
+ .catch((error) => {
+ console.log(error);
+ });
diff --git a/examples/packages/logo.png b/examples/packages/logo.png
new file mode 100644
index 00000000..583360a1
Binary files /dev/null and b/examples/packages/logo.png differ
diff --git a/examples/playwright_basic.py b/examples/playwright_basic.py
index e909f9db..ac059171 100644
--- a/examples/playwright_basic.py
+++ b/examples/playwright_basic.py
@@ -1,25 +1,20 @@
from playwright.sync_api import Playwright, sync_playwright
from examples import (
- BROWSERBASE_API_KEY,
BROWSERBASE_PROJECT_ID,
- BROWSERBASE_CONNECT_URL,
bb,
)
-def run(playwright: Playwright):
+def run(playwright: Playwright) -> None:
# Create a session on Browserbase
session = bb.sessions.create(project_id=BROWSERBASE_PROJECT_ID)
assert session.id is not None
assert session.status == "RUNNING", f"Session status is {session.status}"
# Connect to the remote session
- connect_url = (
- f"{BROWSERBASE_CONNECT_URL}?sessionId={session.id}&apiKey={BROWSERBASE_API_KEY}"
- )
chromium = playwright.chromium
- browser = chromium.connect_over_cdp(connect_url)
+ browser = chromium.connect_over_cdp(session.connect_url)
context = browser.contexts[0]
page = context.pages[0]
diff --git a/examples/playwright_captcha.py b/examples/playwright_captcha.py
new file mode 100644
index 00000000..a5f0b431
--- /dev/null
+++ b/examples/playwright_captcha.py
@@ -0,0 +1,60 @@
+from playwright.sync_api import Playwright, ConsoleMessage, sync_playwright
+
+from examples import (
+ BROWSERBASE_API_KEY,
+ BROWSERBASE_PROJECT_ID,
+ BROWSERBASE_CONNECT_URL,
+ bb,
+)
+
+DEFAULT_CAPTCHA_URL = "https://www.google.com/recaptcha/api2/demo"
+OVERRIDE_TIMEOUT = 60000 # 60 seconds, adjust as needed
+
+
+def run(playwright: Playwright) -> None:
+ # Create a session on Browserbase
+ session = bb.sessions.create(project_id=BROWSERBASE_PROJECT_ID)
+ assert session.id is not None
+ assert session.status == "RUNNING", f"Session status is {session.status}"
+
+ # Connect to the remote session
+ connect_url = (
+ f"{BROWSERBASE_CONNECT_URL}?sessionId={session.id}&apiKey={BROWSERBASE_API_KEY}"
+ )
+ chromium = playwright.chromium
+ browser = chromium.connect_over_cdp(connect_url)
+ context = browser.contexts[0]
+ page = context.pages[0]
+
+ captcha_solving_started = False
+ captcha_solving_finished = False
+
+ # Browserbase logs messages to the console to indicate when captcha solving has started and finished
+ # We can track these messages to know when the captcha solving has started and finished
+ def handle_console(msg: ConsoleMessage) -> None:
+ nonlocal captcha_solving_started, captcha_solving_finished
+ if msg.text == "browserbase-solving-started":
+ captcha_solving_started = True
+ page.evaluate("window.captchaSolvingFinished = false;")
+ elif msg.text == "browserbase-solving-finished":
+ captcha_solving_finished = True
+ page.evaluate("window.captchaSolvingFinished = true;")
+
+ page.on("console", handle_console)
+
+ page.goto(DEFAULT_CAPTCHA_URL, wait_until="networkidle")
+ page.wait_for_function(
+ "() => window.captchaSolvingFinished === true", timeout=OVERRIDE_TIMEOUT
+ )
+
+ assert captcha_solving_started, "Captcha solving did not start"
+ assert captcha_solving_finished, "Captcha solving did not finish"
+
+ page.close()
+ browser.close()
+ print("Done!")
+
+
+if __name__ == "__main__":
+ with sync_playwright() as playwright:
+ run(playwright)
diff --git a/examples/playwright_contexts.py b/examples/playwright_contexts.py
new file mode 100644
index 00000000..6776a088
--- /dev/null
+++ b/examples/playwright_contexts.py
@@ -0,0 +1,128 @@
+import time
+from typing import Optional
+
+from playwright.sync_api import Cookie, Browser, Playwright, sync_playwright
+
+from examples import (
+ BROWSERBASE_API_KEY,
+ BROWSERBASE_PROJECT_ID,
+ BROWSERBASE_CONNECT_URL,
+ bb,
+)
+from browserbase.types.session_create_params import (
+ BrowserSettings,
+ BrowserSettingsContext,
+)
+
+CONTEXT_TEST_URL = "https://www.browserbase.com"
+SECOND = 1000
+
+
+def add_hour(date: float) -> int:
+ return int((date + 3600) * 1000) // SECOND
+
+
+def find_cookie(browser: Browser, name: str) -> Optional[Cookie]:
+ default_context = browser.contexts[0]
+ cookies = default_context.cookies()
+ return next((cookie for cookie in cookies if cookie.get("name") == name), None)
+
+
+def run(playwright: Playwright) -> None:
+ context_id = None
+ session_id = None
+ test_cookie_name = None
+ test_cookie_value = None
+
+ # Step 1: Creates a context
+ context = bb.contexts.create(project_id=BROWSERBASE_PROJECT_ID)
+ assert context.id is not None
+ context_id = context.id
+
+ uploaded_context = bb.contexts.retrieve(id=context_id)
+ assert uploaded_context.id == context_id
+
+ # Step 2: Creates a session with the context
+ session = bb.sessions.create(
+ project_id=BROWSERBASE_PROJECT_ID,
+ browser_settings=BrowserSettings(
+ context=BrowserSettingsContext(id=context_id, persist=True),
+ ),
+ )
+
+ assert (
+ session.context_id == context_id
+ ), f"Session context_id is {session.context_id}, expected {context_id}"
+ session_id = session.id
+
+ # Step 3: Populates and persists the context
+ print(f"Populating context {context_id} during session {session_id}")
+ connect_url = (
+ f"{BROWSERBASE_CONNECT_URL}?sessionId={session_id}&apiKey={BROWSERBASE_API_KEY}"
+ )
+ browser = playwright.chromium.connect_over_cdp(connect_url)
+ page = browser.contexts[0].pages[0]
+
+ page.goto(CONTEXT_TEST_URL, wait_until="domcontentloaded")
+
+ now = time.time()
+ test_cookie_name = f"bb_{int(now * 1000)}"
+ test_cookie_value = time.strftime("%Y-%m-%dT%H:%M:%S.000Z", time.gmtime(now))
+ browser.contexts[0].add_cookies(
+ [
+ {
+ "domain": ".browserbase.com",
+ "expires": add_hour(now),
+ "name": test_cookie_name,
+ "path": "/",
+ "value": test_cookie_value,
+ }
+ ]
+ )
+
+ assert find_cookie(browser, test_cookie_name) is not None
+
+ page.goto("https://www.google.com", wait_until="domcontentloaded")
+ page.go_back()
+
+ assert find_cookie(browser, test_cookie_name) is not None
+
+ page.close()
+ browser.close()
+
+ time.sleep(5)
+
+ # Step 4: Creates another session with the same context
+ session = bb.sessions.create(
+ project_id=BROWSERBASE_PROJECT_ID,
+ browser_settings=BrowserSettings(
+ context=BrowserSettingsContext(id=context_id, persist=True)
+ ),
+ )
+ assert (
+ session.context_id == context_id
+ ), f"Session context_id is {session.context_id}, expected {context_id}"
+ session_id = session.id
+
+ # Step 5: Uses context to find previous state
+ print(f"Reusing context {context_id} during session {session_id}")
+ connect_url = (
+ f"{BROWSERBASE_CONNECT_URL}?sessionId={session_id}&apiKey={BROWSERBASE_API_KEY}"
+ )
+ browser = playwright.chromium.connect_over_cdp(connect_url)
+ page = browser.contexts[0].pages[0]
+
+ page.goto(CONTEXT_TEST_URL, wait_until="domcontentloaded")
+
+ found_cookie = find_cookie(browser, test_cookie_name)
+ print(found_cookie)
+ assert found_cookie is not None
+ assert found_cookie.get("value") == test_cookie_value
+
+ page.close()
+ browser.close()
+
+
+if __name__ == "__main__":
+ with sync_playwright() as playwright:
+ run(playwright)
diff --git a/examples/playwright_downloads.py b/examples/playwright_downloads.py
new file mode 100644
index 00000000..bded0cff
--- /dev/null
+++ b/examples/playwright_downloads.py
@@ -0,0 +1,88 @@
+import io
+import re
+import zipfile
+
+from playwright.sync_api import Playwright, sync_playwright
+
+from examples import (
+ BROWSERBASE_API_KEY,
+ BROWSERBASE_PROJECT_ID,
+ BROWSERBASE_CONNECT_URL,
+ bb,
+)
+
+download_re = re.compile(r"sandstorm-(\d{13})+\.mp3")
+
+
+def get_download(session_id: str) -> bytes:
+ response = bb.sessions.downloads.list(id=session_id)
+ return response.read()
+
+
+def run(playwright: Playwright) -> None:
+ # Create a session on Browserbase
+ session = bb.sessions.create(project_id=BROWSERBASE_PROJECT_ID)
+ assert session.id is not None
+ assert session.status == "RUNNING", f"Session status is {session.status}"
+
+ # Connect to the remote session
+ connect_url = (
+ f"{BROWSERBASE_CONNECT_URL}?sessionId={session.id}&apiKey={BROWSERBASE_API_KEY}"
+ )
+ browser = playwright.chromium.connect_over_cdp(connect_url)
+ context = browser.contexts[0]
+ page = context.pages[0]
+
+ # Set up CDP session for download behavior
+ client = context.new_cdp_session(page)
+ client.send( # pyright: ignore
+ "Browser.setDownloadBehavior",
+ {
+ "behavior": "allow",
+ "downloadPath": "downloads",
+ "eventsEnabled": True,
+ },
+ )
+
+ # Navigate to the download test page
+ page.goto("https://browser-tests-alpha.vercel.app/api/download-test")
+
+ # Start download and wait for it to complete
+ with page.expect_download() as download_info:
+ page.locator("#download").click()
+ download = download_info.value
+
+ # Check for download errors
+ download_error = download.failure()
+ if download_error:
+ raise Exception(f"Download for session {session.id} failed: {download_error}")
+
+ page.close()
+ browser.close()
+
+ # Verify the download
+ zip_buffer = get_download(session.id)
+ if len(zip_buffer) == 0:
+ raise Exception(f"Download buffer is empty for session {session.id}")
+
+ zip_file = zipfile.ZipFile(io.BytesIO(zip_buffer))
+ zip_entries = zip_file.namelist()
+ mp3_entry = next((entry for entry in zip_entries if download_re.match(entry)), None)
+
+ if not mp3_entry:
+ raise Exception(
+ f"Session {session.id} is missing a file matching '{download_re.pattern}' in its zip entries: {zip_entries}"
+ )
+
+ expected_file_size = 6137541
+ actual_file_size = zip_file.getinfo(mp3_entry).file_size
+ assert (
+ actual_file_size == expected_file_size
+ ), f"Expected file size {expected_file_size}, but got {actual_file_size}"
+
+ print("Download test passed successfully!")
+
+
+if __name__ == "__main__":
+ with sync_playwright() as playwright:
+ run(playwright)
diff --git a/examples/playwright_extensions.py b/examples/playwright_extensions.py
new file mode 100644
index 00000000..d12f47a0
--- /dev/null
+++ b/examples/playwright_extensions.py
@@ -0,0 +1,159 @@
+import os
+import time
+import zipfile
+from io import BytesIO
+from pathlib import Path
+
+from playwright.sync_api import Page, Playwright, sync_playwright
+
+from examples import (
+ BROWSERBASE_API_KEY,
+ BROWSERBASE_PROJECT_ID,
+ BROWSERBASE_CONNECT_URL,
+ bb,
+)
+from browserbase.types import Extension, SessionCreateResponse
+
+PATH_TO_EXTENSION = (
+ Path.cwd() / "examples" / "packages" / "extensions" / "browserbase-test"
+)
+
+
+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.
+ Mark save_local=True to save the zip file to a local file.
+ """
+ # Ensure we're looking at an extension
+ assert "manifest.json" in os.listdir(
+ path
+ ), "No manifest.json found in the extension folder."
+
+ # Create a BytesIO object to hold the zip file in memory
+ memory_zip = BytesIO()
+
+ # Create a ZipFile object
+ with zipfile.ZipFile(memory_zip, "w", zipfile.ZIP_DEFLATED) as zf:
+ # Recursively walk through the directory
+ for root, _, files in os.walk(path):
+ for file in files:
+ # Create the full file path
+ file_path = os.path.join(root, file)
+ # Calculate the archive name (path relative to the root directory)
+ archive_name = os.path.relpath(file_path, path)
+ # Add the file to the zip
+ zf.write(file_path, archive_name)
+
+ if save_local:
+ with open(f"{path}.zip", "wb") as f:
+ f.write(memory_zip.getvalue())
+
+ return memory_zip
+
+
+def create_extension() -> str:
+ zip_data = zip_extension(save_local=True)
+ extension: Extension = bb.extensions.create(
+ file=("extension.zip", zip_data.getvalue())
+ )
+ return extension.id
+
+
+def get_extension(id: str) -> Extension:
+ return bb.extensions.retrieve(id)
+
+
+def delete_extension(id: str) -> None:
+ bb.extensions.delete(id)
+
+
+def check_for_message(page: Page, message: str) -> None:
+ # Wait for the extension to load and log a message
+ console_messages: list[str] = []
+ page.on("console", lambda msg: console_messages.append(msg.text))
+ page.goto("https://www.browserbase.com/")
+
+ start = time.time()
+ while time.time() - start < 10:
+ if message in console_messages:
+ break
+ assert (
+ message in console_messages
+ ), f"Expected message not found in console logs. Messages: {console_messages}"
+
+
+def run(playwright: Playwright) -> None:
+ expected_message = "browserbase test extension image loaded"
+ extension_id = None
+
+ # Create extension
+ extension_id = create_extension()
+ print(f"Created extension with ID: {extension_id}")
+
+ # Get extension
+ extension = get_extension(extension_id)
+ print(f"Retrieved extension: {extension}")
+
+ # Use extension
+ session: SessionCreateResponse = bb.sessions.create(
+ project_id=BROWSERBASE_PROJECT_ID,
+ extension_id=extension.id,
+ )
+
+ browser = playwright.chromium.connect_over_cdp(
+ f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session.id}"
+ )
+ context = browser.contexts[0]
+ page = context.pages[0]
+ check_for_message(page, expected_message)
+ page.close()
+ browser.close()
+
+ # Use extension with proxies
+ session_with_proxy: SessionCreateResponse = bb.sessions.create(
+ project_id=BROWSERBASE_PROJECT_ID,
+ extension_id=extension_id,
+ proxies=True,
+ )
+
+ browser = playwright.chromium.connect_over_cdp(
+ f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session_with_proxy.id}"
+ )
+ context = browser.contexts[0]
+ page = context.pages[0]
+
+ console_messages: list[str] = []
+ page.on("console", lambda msg: console_messages.append(msg.text))
+
+ page.goto("https://www.browserbase.com/")
+
+ check_for_message(page, expected_message)
+ page.close()
+ browser.close()
+
+ # Delete extension
+ delete_extension(extension_id)
+ 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)}")
+
+ try:
+ 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)}")
+
+
+if __name__ == "__main__":
+ with sync_playwright() as playwright:
+ run(playwright)
diff --git a/examples/playwright_proxy.py b/examples/playwright_proxy.py
new file mode 100644
index 00000000..4ea6677b
--- /dev/null
+++ b/examples/playwright_proxy.py
@@ -0,0 +1,198 @@
+import time
+
+from playwright.sync_api import Page, Playwright, sync_playwright
+
+from examples import (
+ BROWSERBASE_API_KEY,
+ BROWSERBASE_PROJECT_ID,
+ BROWSERBASE_CONNECT_URL,
+ bb,
+)
+
+GRACEFUL_SHUTDOWN_TIMEOUT = 30000 # Assuming 30 seconds, adjust as needed
+
+
+def check_proxy_bytes(session_id: str) -> None:
+ bb.sessions.update(
+ id=session_id, project_id=BROWSERBASE_PROJECT_ID, status="REQUEST_RELEASE"
+ )
+ time.sleep(GRACEFUL_SHUTDOWN_TIMEOUT / 1000)
+ updated_session = bb.sessions.retrieve(id=session_id)
+ assert (
+ updated_session.proxy_bytes is not None and updated_session.proxy_bytes > 0
+ ), f"Proxy bytes: {updated_session.proxy_bytes}"
+
+
+def run_enable_via_create_session(playwright: Playwright) -> None:
+ session = bb.sessions.create(project_id=BROWSERBASE_PROJECT_ID, proxies=True)
+
+ browser = playwright.chromium.connect_over_cdp(
+ f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session.id}"
+ )
+
+ context = browser.contexts[0]
+ page = context.pages[0]
+ page.goto("https://www.google.com")
+ page_title = page.title()
+
+ page.close()
+ browser.close()
+
+ assert page_title == "Google"
+ check_proxy_bytes(session.id)
+
+
+def run_enable_via_querystring_with_created_session(playwright: Playwright) -> None:
+ session = bb.sessions.create(project_id=BROWSERBASE_PROJECT_ID, proxies=True)
+
+ browser = playwright.chromium.connect_over_cdp(
+ f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session.id}&enableProxy=true"
+ )
+
+ context = browser.contexts[0]
+ page = context.pages[0]
+ page.goto("https://www.google.com/")
+ page_title = page.title()
+
+ page.close()
+ browser.close()
+
+ assert page_title == "Google"
+ check_proxy_bytes(session.id)
+
+
+def extract_from_table(page: Page, cell: str) -> str:
+ page.goto("https://www.showmyip.com/")
+ page.wait_for_selector("table.iptab")
+
+ td = page.locator(f"table.iptab tr:has-text('{cell}') td:last-child")
+
+ text = td.text_content()
+ if not text:
+ raise Exception(f"Failed to extract {cell}")
+ return text.strip()
+
+
+def run_geolocation_country(playwright: Playwright) -> None:
+ session = bb.sessions.create(
+ project_id=BROWSERBASE_PROJECT_ID,
+ proxies=[
+ {
+ "geolocation": {"country": "CA"},
+ "type": "browserbase",
+ }
+ ],
+ )
+
+ browser = playwright.chromium.connect_over_cdp(
+ f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session.id}"
+ )
+
+ context = browser.contexts[0]
+ page = context.pages[0]
+
+ country = extract_from_table(page, "Country")
+
+ page.close()
+ browser.close()
+
+ assert country == "Canada"
+
+
+def run_geolocation_state(playwright: Playwright) -> None:
+ session = bb.sessions.create(
+ project_id=BROWSERBASE_PROJECT_ID,
+ proxies=[
+ {
+ "geolocation": {
+ "country": "US",
+ "state": "NY",
+ },
+ "type": "browserbase",
+ }
+ ],
+ )
+
+ browser = playwright.chromium.connect_over_cdp(
+ f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session.id}"
+ )
+
+ context = browser.contexts[0]
+ page = context.pages[0]
+
+ state = extract_from_table(page, "Region")
+
+ page.close()
+ browser.close()
+
+ assert state == "New York"
+
+
+def run_geolocation_american_city(playwright: Playwright) -> None:
+ session = bb.sessions.create(
+ project_id=BROWSERBASE_PROJECT_ID,
+ proxies=[
+ {
+ "geolocation": {
+ "city": "Los Angeles",
+ "country": "US",
+ "state": "CA",
+ },
+ "type": "browserbase",
+ }
+ ],
+ )
+
+ browser = playwright.chromium.connect_over_cdp(
+ f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session.id}"
+ )
+
+ context = browser.contexts[0]
+ page = context.pages[0]
+
+ city = extract_from_table(page, "City")
+
+ page.close()
+ browser.close()
+
+ assert city == "Los Angeles"
+
+
+def run_geolocation_non_american_city(playwright: Playwright) -> None:
+ session = bb.sessions.create(
+ project_id=BROWSERBASE_PROJECT_ID,
+ proxies=[
+ {
+ "geolocation": {
+ "city": "London",
+ "country": "GB",
+ },
+ "type": "browserbase",
+ }
+ ],
+ )
+
+ browser = playwright.chromium.connect_over_cdp(
+ f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session.id}"
+ )
+
+ context = browser.contexts[0]
+ page = context.pages[0]
+
+ city = extract_from_table(page, "City")
+
+ page.close()
+ browser.close()
+
+ assert city == "London"
+
+
+if __name__ == "__main__":
+ with sync_playwright() as playwright:
+ # You can run any of these tests by uncommenting them
+ run_enable_via_create_session(playwright)
+ # run_enable_via_querystring_with_created_session(playwright)
+ # run_geolocation_country(playwright)
+ # run_geolocation_state(playwright)
+ # run_geolocation_american_city(playwright)
+ # run_geolocation_non_american_city(playwright)
diff --git a/examples/playwright_upload.py b/examples/playwright_upload.py
new file mode 100644
index 00000000..c1a2237c
--- /dev/null
+++ b/examples/playwright_upload.py
@@ -0,0 +1,63 @@
+from pathlib import Path
+
+from playwright.sync_api import Playwright, sync_playwright
+
+from examples import (
+ BROWSERBASE_API_KEY,
+ BROWSERBASE_PROJECT_ID,
+ BROWSERBASE_CONNECT_URL,
+ bb,
+)
+
+PATH_TO_UPLOAD = Path.cwd() / "examples" / "packages" / "logo.png"
+
+
+def run(playwright: Playwright) -> None:
+ # Create a session
+ session = bb.sessions.create(project_id=BROWSERBASE_PROJECT_ID)
+
+ # Construct the URL
+ url = (
+ f"{BROWSERBASE_CONNECT_URL}?apiKey={BROWSERBASE_API_KEY}&sessionId={session.id}"
+ )
+
+ # Connect to the browser
+ browser = playwright.chromium.connect_over_cdp(url)
+ context = browser.contexts[0]
+ page = context.pages[0]
+
+ try:
+ # Navigate to the upload test page
+ page.goto("https://browser-tests-alpha.vercel.app/api/upload-test")
+
+ # Locate the file input element
+ file_input = page.locator("#fileUpload")
+ file_input.set_input_files(str(PATH_TO_UPLOAD))
+
+ # Get the uploaded file name
+ file_name_span = page.locator("#fileName")
+ file_name = file_name_span.inner_text()
+
+ # Get the uploaded file size
+ file_size_span = page.locator("#fileSize")
+ file_size = int(file_size_span.inner_text())
+
+ # Assert the file name and size
+ assert (
+ file_name == "logo.png"
+ ), f"Expected file name to be 'logo.png', but got '{file_name}'"
+ assert (
+ file_size > 0
+ ), f"Expected file size to be greater than 0, but got {file_size}"
+
+ print("File upload test passed successfully!")
+
+ finally:
+ # Clean up
+ page.close()
+ browser.close()
+
+
+if __name__ == "__main__":
+ with sync_playwright() as playwright:
+ run(playwright)
diff --git a/openapi.v1.yaml b/openapi.v1.yaml
new file mode 100644
index 00000000..7bb296d0
--- /dev/null
+++ b/openapi.v1.yaml
@@ -0,0 +1,1142 @@
+openapi: 3.0.0
+info:
+ title: Browserbase API
+ description: Browserbase API for 3rd party developers
+ version: v1
+tags: []
+paths:
+ /v1/contexts:
+ post:
+ operationId: Contexts_create
+ summary: Create a Context
+ parameters: []
+ responses:
+ "201":
+ description: The request has succeeded and a new resource has been created as a result.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreateContextResponse"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreateContext"
+ /v1/contexts/{id}:
+ get:
+ operationId: Contexts_get
+ summary: Context
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ $ref: "#/components/schemas/uuid"
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Context"
+ put:
+ operationId: Contexts_update
+ summary: Update Context
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreateContextResponse"
+ /v1/extensions:
+ post:
+ operationId: Extensions_upload
+ summary: Upload an Extension
+ parameters: []
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Extension"
+ "415":
+ description: Client error
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Error"
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ properties:
+ file:
+ type: string
+ format: binary
+ required:
+ - file
+ /v1/extensions/{id}:
+ get:
+ operationId: Extensions_get
+ summary: Extension
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ $ref: "#/components/schemas/uuid"
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Extension"
+ delete:
+ operationId: Extensions_delete
+ summary: Delete Extension
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ $ref: "#/components/schemas/uuid"
+ responses:
+ "204":
+ description: "There is no content to send for this request, but the headers may be useful. "
+ /v1/projects:
+ get:
+ operationId: Projects_list
+ summary: List all projects
+ parameters: []
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Project"
+ /v1/projects/{id}:
+ get:
+ operationId: Projects_get
+ summary: Project
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ $ref: "#/components/schemas/uuid"
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Project"
+ /v1/projects/{id}/usage:
+ get:
+ operationId: Projects_usage
+ summary: Project Usage
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ $ref: "#/components/schemas/uuid"
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ProjectUsage"
+ /v1/sessions:
+ get:
+ operationId: Sessions_list
+ summary: List Sessions
+ parameters:
+ - name: status
+ in: query
+ required: false
+ schema:
+ $ref: "#/components/schemas/SessionStatus"
+ explode: false
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/Session"
+ post:
+ operationId: Sessions_create
+ summary: Create a Session
+ parameters: []
+ responses:
+ "201":
+ description: The request has succeeded and a new resource has been created as a result.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ projectId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: The Project ID linked to the Session.
+ startedAt:
+ type: string
+ format: date-time
+ endedAt:
+ type: string
+ format: date-time
+ expiresAt:
+ type: string
+ format: date-time
+ status:
+ $ref: "#/components/schemas/SessionStatus"
+ proxyBytes:
+ type: integer
+ description: Bytes used via the [Proxy](/features/stealth-mode#proxies-and-residential-ips)
+ avgCpuUsage:
+ type: integer
+ description: CPU used by the Session
+ memoryUsage:
+ type: integer
+ description: Memory used by the Session
+ keepAlive:
+ type: boolean
+ description: Indicates if the Session was created to be kept alive upon disconnections
+ contextId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: Optional. The Context linked to the Session.
+ region:
+ allOf:
+ - $ref: "#/components/schemas/Region"
+ description: The region where the Session is running.
+ id:
+ $ref: "#/components/schemas/uuid"
+ createdAt:
+ type: string
+ format: date-time
+ updatedAt:
+ type: string
+ format: date-time
+ connectUrl:
+ type: string
+ format: uri
+ description: WebSocket URL to connect to the Session.
+ seleniumRemoteUrl:
+ type: string
+ format: uri
+ description: HTTP URL to connect to the Session.
+ signingKey:
+ type: string
+ description: Signing key to use when connecting to the Session via HTTP.
+ required:
+ - projectId
+ - startedAt
+ - expiresAt
+ - status
+ - proxyBytes
+ - keepAlive
+ - region
+ - id
+ - createdAt
+ - updatedAt
+ - connectUrl
+ - seleniumRemoteUrl
+ - signingKey
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/CreateSession"
+ x-codeSamples:
+ - lang: cURL
+ source: |-
+ curl --request POST \
+ --url https://www.browserbase.com/v1/sessions \
+ --header 'Content-Type: application/json' \
+ --header 'X-BB-API-Key: ' \
+ --data '{"projectId": ""}'
+ - lang: JavaScript
+ source: |-
+ fetch('https://www.browserbase.com/v1/sessions', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-BB-API-Key': ''
+ },
+ body: JSON.stringify({
+ "projectId": "",
+ })
+ })
+ - lang: Python
+ source: |-
+ import requests
+
+ url = "https://www.browserbase.com/v1/sessions"
+
+ payload = {
+ "projectId": "",
+ }
+ headers = {
+ "X-BB-API-Key": "",
+ "Content-Type": "application/json"
+ }
+
+ response = requests.request("POST", url, json=payload, headers=headers)
+
+ print(response.text)
+ - lang: PHP
+ source: |-
+ "https://www.browserbase.com/v1/sessions",
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_ENCODING => "",
+ CURLOPT_MAXREDIRS => 10,
+ CURLOPT_TIMEOUT => 30,
+ CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
+ CURLOPT_CUSTOMREQUEST => "POST",
+ CURLOPT_POSTFIELDS => "{"projectId": ""}",
+ CURLOPT_HTTPHEADER => [
+ "Content-Type: application/json",
+ "X-BB-API-Key: "
+ ],
+ ]);
+
+ $response = curl_exec($curl);
+ $err = curl_error($curl);
+
+ curl_close($curl);
+
+ if ($err) {
+ echo "cURL Error #:" . $err;
+ } else {
+ echo $response;
+ }
+ - lang: Go
+ source: |-
+ package main
+
+ import (
+ "fmt"
+ "strings"
+ "net/http"
+ "io/ioutil"
+ )
+
+ func main() {
+
+ url := "https://www.browserbase.com/v1/sessions"
+
+ payload := strings.NewReader("{"projectId": ""}")
+
+ req, _ := http.NewRequest("POST", url, payload)
+
+ req.Header.Add("X-BB-API-Key", "")
+ req.Header.Add("Content-Type", "application/json")
+
+ res, _ := http.DefaultClient.Do(req)
+
+ defer res.Body.Close()
+ body, _ := ioutil.ReadAll(res.Body)
+
+ fmt.Println(res)
+ fmt.Println(string(body))
+
+ }
+ - lang: Java
+ source: |-
+ HttpResponse response = Unirest.post("https://www.browserbase.com/v1/sessions")
+ .header("X-BB-API-Key", "")
+ .header("Content-Type", "application/json")
+ .body("{"projectId": ""}")
+ .asString();
+ /v1/sessions/{id}:
+ get:
+ operationId: Sessions_get
+ summary: Session
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ $ref: "#/components/schemas/uuid"
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Session"
+ post:
+ operationId: Sessions_update
+ summary: Update Session
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Session"
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SessionUpdate"
+ /v1/sessions/{id}/debug:
+ get:
+ operationId: Sessions_getDebug
+ summary: Session Live URLs
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ $ref: "#/components/schemas/uuid"
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/SessionLiveUrls"
+ /v1/sessions/{id}/downloads:
+ get:
+ operationId: Sessions_getDownloads
+ summary: Session Downloads
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ $ref: "#/components/schemas/uuid"
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/zip:
+ schema:
+ type: string
+ format: binary
+ /v1/sessions/{id}/logs:
+ get:
+ operationId: Sessions_getLogs
+ summary: Session Logs
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ $ref: "#/components/schemas/uuid"
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/SessionLog"
+ "422":
+ description: Client error
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Error"
+ /v1/sessions/{id}/recording:
+ get:
+ operationId: Sessions_getRecording
+ summary: Session Recording
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ $ref: "#/components/schemas/uuid"
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: "#/components/schemas/SessionRecording"
+ "422":
+ description: Client error
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Error"
+ /v1/sessions/{id}/uploads:
+ post:
+ operationId: Sessions_uploadFile
+ summary: Create Session Uploads
+ parameters:
+ - name: id
+ in: path
+ required: true
+ schema:
+ type: string
+ responses:
+ "200":
+ description: The request has succeeded.
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ message:
+ type: string
+ required:
+ - message
+ "415":
+ description: Client error
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Error"
+ requestBody:
+ required: true
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ properties:
+ file:
+ type: string
+ format: binary
+ required:
+ - file
+security:
+ - BrowserbaseAuth: []
+components:
+ schemas:
+ BrowserbaseAuth:
+ type: object
+ required:
+ - type
+ - in
+ - name
+ properties:
+ type:
+ type: string
+ enum:
+ - apiKey
+ description: API key authentication
+ in:
+ type: string
+ enum:
+ - header
+ description: location of the API key
+ name:
+ type: string
+ enum:
+ - X-BB-API-Key
+ description: name of the API key
+ description: Your [Browserbase API Key](https://www.browserbase.com/settings).
+ BrowserbaseProxyConfig:
+ type: object
+ required:
+ - type
+ properties:
+ type:
+ type: string
+ enum:
+ - browserbase
+ description: Type of proxy. Always use 'browserbase' for the Browserbase managed proxy network.
+ geolocation:
+ allOf:
+ - $ref: "#/components/schemas/GeolocationConfig"
+ description: Geographic location for the proxy. Optional.
+ domainPattern:
+ type: string
+ description: Domain pattern for which this proxy should be used. If omitted, defaults to all domains. Optional.
+ Context:
+ type: object
+ required:
+ - id
+ - createdAt
+ - updatedAt
+ - projectId
+ properties:
+ id:
+ $ref: "#/components/schemas/uuid"
+ createdAt:
+ type: string
+ format: date-time
+ updatedAt:
+ type: string
+ format: date-time
+ projectId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: The Project ID linked to the uploaded Context.
+ ContextSetting:
+ type: object
+ required:
+ - id
+ properties:
+ id:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: The Context ID.
+ persist:
+ type: boolean
+ description: Whether or not to persist the context after browsing. Defaults to `false`.
+ CreateContext:
+ type: object
+ required:
+ - projectId
+ properties:
+ projectId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: The Project ID. Can be found in [Settings](https://www.browserbase.com/settings).
+ CreateContextResponse:
+ type: object
+ required:
+ - id
+ - uploadUrl
+ - publicKey
+ - cipherAlgorithm
+ - initializationVectorSize
+ properties:
+ id:
+ $ref: "#/components/schemas/uuid"
+ uploadUrl:
+ type: string
+ minLength: 1
+ description: An upload URL to upload a custom user-data-directory.
+ publicKey:
+ type: string
+ description: The public key to encrypt the user-data-directory.
+ cipherAlgorithm:
+ type: string
+ description: The cipher algorithm used to encrypt the user-data-directory. AES-256-CBC is currently the only supported algorithm.
+ initializationVectorSize:
+ type: integer
+ format: uint8
+ description: The initialization vector size used to encrypt the user-data-directory. [Read more about how to use it](/features/contexts).
+ CreateSession:
+ type: object
+ required:
+ - projectId
+ properties:
+ projectId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: The Project ID. Can be found in [Settings](https://www.browserbase.com/settings).
+ extensionId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: The uploaded Extension ID. See [Upload Extension](/reference/api/upload-an-extension).
+ browserSettings:
+ $ref: "#/components/schemas/SessionBrowserSettings"
+ timeout:
+ type: integer
+ minimum: 60
+ maximum: 21600
+ description: Duration in seconds after which the session will automatically end. Defaults to the Project's `defaultTimeout`.
+ keepAlive:
+ type: boolean
+ description: Set to true to keep the session alive even after disconnections. This is available on the Startup plan only.
+ proxies:
+ description: Proxy configuration. Can be true for default proxy, or an array of proxy configurations.
+ region:
+ allOf:
+ - $ref: "#/components/schemas/Region"
+ description: The region where the Session should run.
+ CreateSessionConnectDetails:
+ type: object
+ required:
+ - connectUrl
+ - seleniumRemoteUrl
+ - signingKey
+ properties:
+ connectUrl:
+ type: string
+ format: uri
+ description: WebSocket URL to connect to the Session.
+ seleniumRemoteUrl:
+ type: string
+ format: uri
+ description: HTTP URL to connect to the Session.
+ signingKey:
+ type: string
+ description: Signing key to use when connecting to the Session via HTTP.
+ Entity:
+ type: object
+ required:
+ - id
+ - createdAt
+ - updatedAt
+ properties:
+ id:
+ $ref: "#/components/schemas/uuid"
+ createdAt:
+ type: string
+ format: date-time
+ updatedAt:
+ type: string
+ format: date-time
+ Error:
+ type: object
+ required:
+ - code
+ - message
+ properties:
+ code:
+ type: integer
+ message:
+ type: string
+ Extension:
+ type: object
+ required:
+ - id
+ - createdAt
+ - updatedAt
+ - fileName
+ - projectId
+ properties:
+ id:
+ $ref: "#/components/schemas/uuid"
+ createdAt:
+ type: string
+ format: date-time
+ updatedAt:
+ type: string
+ format: date-time
+ fileName:
+ type: string
+ minLength: 1
+ projectId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: The Project ID linked to the uploaded Extension.
+ ExternalProxyConfig:
+ type: object
+ required:
+ - type
+ - server
+ properties:
+ type:
+ type: string
+ enum:
+ - external
+ description: Type of proxy. Always 'external' for this config.
+ server:
+ type: string
+ description: Server URL for external proxy. Required.
+ domainPattern:
+ type: string
+ description: Domain pattern for which this proxy should be used. If omitted, defaults to all domains. Optional.
+ username:
+ type: string
+ description: Username for external proxy authentication. Optional.
+ password:
+ type: string
+ description: Password for external proxy authentication. Optional.
+ Fingerprint:
+ type: object
+ properties:
+ httpVersion:
+ type: number
+ enum:
+ - 1
+ - 2
+ browsers:
+ type: array
+ items:
+ type: string
+ enum:
+ - chrome
+ - edge
+ - firefox
+ - safari
+ devices:
+ type: array
+ items:
+ type: string
+ enum:
+ - desktop
+ - mobile
+ locales:
+ type: array
+ items:
+ type: string
+ description: Full list of locales is available [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language).
+ operatingSystems:
+ type: array
+ items:
+ type: string
+ enum:
+ - android
+ - ios
+ - linux
+ - macos
+ - windows
+ description: 'Note: `operatingSystems` set to `ios` or `android` requires `devices` to include `"mobile"`.'
+ screen:
+ $ref: "#/components/schemas/FingerprintScreen"
+ FingerprintScreen:
+ type: object
+ properties:
+ maxHeight:
+ type: integer
+ maxWidth:
+ type: integer
+ minHeight:
+ type: integer
+ minWidth:
+ type: integer
+ GeolocationConfig:
+ type: object
+ required:
+ - country
+ properties:
+ city:
+ type: string
+ description: Name of the city. Use spaces for multi-word city names. Optional.
+ state:
+ type: string
+ minLength: 2
+ maxLength: 2
+ description: US state code (2 characters). Must also specify US as the country. Optional.
+ country:
+ type: string
+ minLength: 2
+ maxLength: 2
+ description: Country code in ISO 3166-1 alpha-2 format
+ description: Configuration for geolocation
+ Project:
+ type: object
+ required:
+ - id
+ - createdAt
+ - updatedAt
+ - name
+ - ownerId
+ - defaultTimeout
+ properties:
+ id:
+ $ref: "#/components/schemas/uuid"
+ createdAt:
+ type: string
+ format: date-time
+ updatedAt:
+ type: string
+ format: date-time
+ name:
+ type: string
+ minLength: 1
+ ownerId:
+ type: string
+ defaultTimeout:
+ type: integer
+ minimum: 60
+ maximum: 21600
+ ProjectUsage:
+ type: object
+ required:
+ - browserMinutes
+ - proxyBytes
+ properties:
+ browserMinutes:
+ type: integer
+ minimum: 0
+ proxyBytes:
+ type: integer
+ minimum: 0
+ Region:
+ type: string
+ enum:
+ - us-west-2
+ - us-east-1
+ - eu-central-1
+ - ap-southeast-1
+ Session:
+ type: object
+ required:
+ - id
+ - createdAt
+ - updatedAt
+ - projectId
+ - startedAt
+ - expiresAt
+ - status
+ - proxyBytes
+ - keepAlive
+ - region
+ properties:
+ id:
+ $ref: "#/components/schemas/uuid"
+ createdAt:
+ type: string
+ format: date-time
+ updatedAt:
+ type: string
+ format: date-time
+ projectId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: The Project ID linked to the Session.
+ startedAt:
+ type: string
+ format: date-time
+ endedAt:
+ type: string
+ format: date-time
+ expiresAt:
+ type: string
+ format: date-time
+ status:
+ $ref: "#/components/schemas/SessionStatus"
+ proxyBytes:
+ type: integer
+ description: Bytes used via the [Proxy](/features/stealth-mode#proxies-and-residential-ips)
+ avgCpuUsage:
+ type: integer
+ description: CPU used by the Session
+ memoryUsage:
+ type: integer
+ description: Memory used by the Session
+ keepAlive:
+ type: boolean
+ description: Indicates if the Session was created to be kept alive upon disconnections
+ contextId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: Optional. The Context linked to the Session.
+ region:
+ allOf:
+ - $ref: "#/components/schemas/Region"
+ description: The region where the Session is running.
+ SessionBrowserSettings:
+ type: object
+ properties:
+ context:
+ $ref: "#/components/schemas/ContextSetting"
+ extensionId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: The uploaded Extension ID. See [Upload Extension](/reference/api/upload-an-extension).
+ fingerprint:
+ allOf:
+ - $ref: "#/components/schemas/Fingerprint"
+ description: See usage examples [in the Stealth Mode page](/features/stealth-mode#fingerprinting).
+ viewport:
+ $ref: "#/components/schemas/SessionBrowserSettingsViewport"
+ blockAds:
+ type: boolean
+ description: Enable or disable ad blocking in the browser. Defaults to `false`.
+ solveCaptchas:
+ type: boolean
+ description: Enable or disable captcha solving in the browser. Defaults to `true`.
+ recordSession:
+ type: boolean
+ description: Enable or disable session recording. Defaults to `true`.
+ logSession:
+ type: boolean
+ description: Enable or disable session logging. Defaults to `true`.
+ SessionBrowserSettingsViewport:
+ type: object
+ properties:
+ width:
+ type: integer
+ height:
+ type: integer
+ SessionLiveUrls:
+ type: object
+ required:
+ - debuggerFullscreenUrl
+ - debuggerUrl
+ - pages
+ - wsUrl
+ properties:
+ debuggerFullscreenUrl:
+ type: string
+ format: uri
+ debuggerUrl:
+ type: string
+ format: uri
+ pages:
+ type: array
+ items:
+ type: object
+ properties:
+ id:
+ type: string
+ url:
+ type: string
+ format: uri
+ faviconUrl:
+ type: string
+ format: uri
+ title:
+ type: string
+ debuggerUrl:
+ type: string
+ format: uri
+ debuggerFullscreenUrl:
+ type: string
+ format: uri
+ required:
+ - id
+ - url
+ - faviconUrl
+ - title
+ - debuggerUrl
+ - debuggerFullscreenUrl
+ wsUrl:
+ type: string
+ format: uri
+ SessionLog:
+ type: object
+ required:
+ - eventId
+ - method
+ - pageId
+ - sessionId
+ - timestamp
+ properties:
+ eventId:
+ type: string
+ method:
+ type: string
+ pageId:
+ type: integer
+ request:
+ $ref: "#/components/schemas/SessionLogRequestBody"
+ response:
+ $ref: "#/components/schemas/SessionLogResponseBody"
+ sessionId:
+ type: string
+ timestamp:
+ type: integer
+ description: milliseconds that have elapsed since the UNIX epoch
+ frameId:
+ type: string
+ loaderId:
+ type: string
+ SessionLogRequestBody:
+ type: object
+ required:
+ - timestamp
+ - params
+ - rawBody
+ properties:
+ timestamp:
+ type: integer
+ description: milliseconds that have elapsed since the UNIX epoch
+ params:
+ type: object
+ additionalProperties: {}
+ rawBody:
+ type: string
+ SessionLogResponseBody:
+ type: object
+ required:
+ - timestamp
+ - result
+ - rawBody
+ properties:
+ timestamp:
+ type: integer
+ description: milliseconds that have elapsed since the UNIX epoch
+ result:
+ type: object
+ additionalProperties: {}
+ rawBody:
+ type: string
+ SessionRecording:
+ type: object
+ required:
+ - id
+ - data
+ - sessionId
+ - timestamp
+ - type
+ properties:
+ id:
+ type: string
+ data:
+ type: object
+ additionalProperties: {}
+ description: See [rrweb documentation](https://github.com/rrweb-io/rrweb/blob/master/docs/recipes/dive-into-event.md).
+ sessionId:
+ type: string
+ timestamp:
+ type: integer
+ description: milliseconds that have elapsed since the UNIX epoch
+ type:
+ type: integer
+ SessionStatus:
+ type: string
+ enum:
+ - RUNNING
+ - ERROR
+ - TIMED_OUT
+ - COMPLETED
+ SessionUpdate:
+ type: object
+ required:
+ - projectId
+ - status
+ properties:
+ projectId:
+ allOf:
+ - $ref: "#/components/schemas/uuid"
+ description: The Project ID. Can be found in [Settings](https://www.browserbase.com/settings).
+ status:
+ type: string
+ enum:
+ - REQUEST_RELEASE
+ description: Set to `REQUEST_RELEASE` to request that the session complete. Use before session's timeout to avoid additional charges.
+ Versions:
+ type: string
+ enum:
+ - v1
+ uuid:
+ type: string
+ securitySchemes:
+ BrowserbaseAuth:
+ type: apiKey
+ in: header
+ name: X-BB-API-Key
+ description: Your [Browserbase API Key](https://www.browserbase.com/settings).
+servers:
+ - url: https://www.browserbase.com
+ description: Public endpoint
+ variables: {}
diff --git a/src/browserbase/resources/sessions/sessions.py b/src/browserbase/resources/sessions/sessions.py
index d298c011..74a99bbb 100644
--- a/src/browserbase/resources/sessions/sessions.py
+++ b/src/browserbase/resources/sessions/sessions.py
@@ -714,4 +714,4 @@ def recording(self) -> AsyncRecordingResourceWithStreamingResponse:
@cached_property
def uploads(self) -> AsyncUploadsResourceWithStreamingResponse:
- return AsyncUploadsResourceWithStreamingResponse(self._sessions.uploads)
+ return AsyncUploadsResourceWithStreamingResponse(self._sessions.uploads)
\ No newline at end of file