Skip to content

[BUG] Browser tool context_options silently ignored during session creation #414

@ryanopp

Description

@ryanopp

Checks

  • I have updated to the lastest minor and patch version of Strands
  • I have checked the documentation and this is not expected behavior
  • I have searched ./issues and there are no duplicates of my issue

Strands Version

1.30.0

Tools Package Version

0.2.22

Tools used

Browser (LocalChromiumBrowser)

Python Version

3.13.6

Operating System

macOS

Installation Method

pip

Steps to Reproduce

from strands_tools.browser import LocalChromiumBrowser

# Pass custom context_options to the constructor
browser = LocalChromiumBrowser(
    context_options={
        "viewport": {"width": 1920, "height": 1080},
        "user_agent": "MyCustomAgent/1.0",
    }
)

# Start the browser and create a session
browser.browser({
    "action": {
        "type": "init_session",
        "session_name": "test-session",
        "description": "test"
    }
})

# Navigate to any page and check the viewport
browser.browser({
    "action": {
        "type": "navigate",
        "url": "https://example.com",
        "session_name": "test-session"
    }
})

# Take a screenshot — viewport will be 1280x720 (Playwright default),
# NOT 1920x1080 as specified in context_options

Expected Behavior

The browser context should use the context_options passed to the constructor.
The viewport should be 1920x1080 and the user agent should be "MyCustomAgent/1.0".

All Playwright BrowserContext options passed via context_options should be
applied when creating new browser contexts — this includes viewport,
user_agent, storage_state, locale, timezone_id, permissions,
geolocation, color_scheme, etc.

Actual Behavior

All context_options are silently ignored. The viewport is 1280x720 (Playwright
default) regardless of what was passed to the constructor.

The constructor accepts and stores the options correctly:

  • LocalChromiumBrowser.__init__ stores them in self._context_options
  • start_platform() merges them into self._default_context_options

But the base class Browser._setup_session_from_browser creates contexts with
no arguments:

# browser.py line ~241
session_context = await session_browser.new_context()  # ← no arguments

self._default_context_options is never passed through.

Additional Context

This is particularly impactful for storage_state, which is the standard
Playwright mechanism for reusing authentication across browser contexts. There
is no workaround using the public API — the only option is to subclass and
override _setup_session_from_browser.

Full reproduction script (runs 4 tests confirming the bug):

reproduce_context_options_bug.py (click to expand)
"""
Requires: pip install playwright strands-agents-tools && playwright install chromium
"""
import asyncio
import inspect

async def test_context_options_ignored():
    from strands_tools.browser import LocalChromiumBrowser
    from playwright.async_api import async_playwright

    # [1] Constructor accepts context_options
    sig = inspect.signature(LocalChromiumBrowser.__init__)
    assert "context_options" in sig.parameters
    print("[1] ✅ context_options IS accepted by constructor")

    # [2] Options are stored in _default_context_options
    custom_viewport = {"width": 1920, "height": 1080}
    custom_user_agent = "TestBot/1.0"
    browser_tool = LocalChromiumBrowser(
        context_options={"viewport": custom_viewport, "user_agent": custom_user_agent}
    )
    browser_tool.start_platform()
    stored = browser_tool._default_context_options
    assert stored.get("viewport") == custom_viewport
    assert stored.get("user_agent") == custom_user_agent
    print("[2] ✅ Options stored correctly in _default_context_options")

    # [3] _setup_session_from_browser ignores stored options
    source = inspect.getsource(
        LocalChromiumBrowser.__mro__[1]._setup_session_from_browser
    )
    if "new_context()" in source and "_default_context_options" not in source:
        print("[3] ❌ BUG: new_context() called with NO arguments")
    else:
        print("[3] ✅ Bug may be fixed")

    # [4] Proof: viewport is Playwright default despite custom options
    async with async_playwright() as p:
        chromium = await p.chromium.launch(headless=True)
        ctx_no_opts = await chromium.new_context()
        page = await ctx_no_opts.new_page()
        size_default = page.viewport_size
        await page.close()
        await ctx_no_opts.close()

        ctx_with_opts = await chromium.new_context(**stored)
        page = await ctx_with_opts.new_page()
        size_fixed = page.viewport_size
        await page.close()
        await ctx_with_opts.close()
        await chromium.close()

    print(f"[4] new_context()         → {size_default}")
    print(f"    new_context(**opts)    → {size_fixed}")
    print(f"    Expected              → {custom_viewport}")
    if size_default != custom_viewport:
        print("    ❌ Without options: viewport does NOT match constructor value")
    if size_fixed == custom_viewport:
        print("    ✅ With options: viewport matches — proves fix works")

asyncio.run(test_context_options_ignored())

Affected code path:

  • LocalChromiumBrowser.__init__ (stores options) → start_platform() (merges
    into _default_context_options) → Browser._setup_session_from_browser
    (ignores them)

Possible Solution

Two changes in browser.py:

1. Add _default_context_options to Browser.__init__:

def __init__(self):
    # ... existing attributes ...
    self._default_context_options: Dict[str, Any] = {}

This makes context options part of the base class contract. The default {}
means no behavioral change for existing code.

2. Pass the options in _setup_session_from_browser:

session_context = await session_browser.new_context(**self._default_context_options)

This follows the existing pattern where LocalChromiumBrowser.create_browser_session
passes **self._default_launch_options to chromium.launch().

I'm happy to submit a PR for this fix if you'd like.

Related Issues

#287 (networkidle timeout) — separate bug in the same base class. Both affect Browser methods in browser.py. Could be addressed in the same PR or separately.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions