Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ export MODEL_API_KEY="sk-proj-your-llm-api-key"
uv run python examples/full_example.py
```

## Local mode example

If you want to run Stagehand locally, use the local example (`examples/local_example.py`). It shows how to configure the client for `server="local"`:

```bash
pip install stagehand-alpha
uv run python examples/local_example.py
```

<details>
<summary><strong>Local development</strong></summary>

Expand Down Expand Up @@ -73,7 +82,6 @@ async def main() -> None:
# Navigate to a webpage
await session.navigate(
url="https://news.ycombinator.com",
frame_id="", # empty string for the main frame
)
print("Navigated to Hacker News")

Expand Down
1 change: 0 additions & 1 deletion examples/act_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ async def main() -> None:
# Navigate to example.com
await session.navigate(
url="https://www.example.com",
frame_id="", # Empty string for main frame
)
print("Navigated to example.com")

Expand Down
1 change: 0 additions & 1 deletion examples/full_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ async def main() -> None:
# Navigate to Hacker News
await session.navigate(
url="https://news.ycombinator.com",
frame_id="", # Empty string for main frame
)
print("Navigated to Hacker News")

Expand Down
1 change: 0 additions & 1 deletion examples/local_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ def main() -> None:
client.sessions.navigate(
id=session_id,
url="https://www.example.com",
frame_id="",
)
print("✅ Navigation complete")

Expand Down
31 changes: 20 additions & 11 deletions src/stagehand/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from typing import TYPE_CHECKING, Union
from typing import TYPE_CHECKING, Any, Union, Mapping, TypeVar, cast
from datetime import datetime
from typing_extensions import Unpack, Literal

Expand All @@ -23,6 +23,15 @@
from .types.session_observe_response import SessionObserveResponse
from .types.session_navigate_response import SessionNavigateResponse

TSessionParams = TypeVar("TSessionParams", bound=Mapping[str, Any])


def _with_default_frame_id(params: TSessionParams) -> TSessionParams:
prepared = dict(params)
if "frame_id" not in prepared:
prepared["frame_id"] = ""
return cast(TSessionParams, prepared)

if TYPE_CHECKING:
from ._client import Stagehand, AsyncStagehand

Expand All @@ -49,7 +58,7 @@ def navigate(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
**params,
**_with_default_frame_id(params),
)

def act(
Expand All @@ -67,7 +76,7 @@ def act(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
**params,
**_with_default_frame_id(params),
)

def observe(
Expand All @@ -85,7 +94,7 @@ def observe(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
**params,
**_with_default_frame_id(params),
)

def extract(
Expand All @@ -103,7 +112,7 @@ def extract(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
**params,
**_with_default_frame_id(params),
)

def execute(
Expand All @@ -121,7 +130,7 @@ def execute(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
**params,
**_with_default_frame_id(params),
)

def end(
Expand Down Expand Up @@ -171,7 +180,7 @@ async def navigate(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
**params,
**_with_default_frame_id(params),
)

async def act(
Expand All @@ -189,7 +198,7 @@ async def act(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
**params,
**_with_default_frame_id(params),
)

async def observe(
Expand All @@ -207,7 +216,7 @@ async def observe(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
**params,
**_with_default_frame_id(params),
)

async def extract(
Expand All @@ -225,7 +234,7 @@ async def extract(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
**params,
**_with_default_frame_id(params),
)

async def execute(
Expand All @@ -243,7 +252,7 @@ async def execute(
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
**params,
**_with_default_frame_id(params),
)

async def end(
Expand Down
13 changes: 11 additions & 2 deletions tests/test_sessions_create_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
from __future__ import annotations

import os
import json
from typing import cast

import httpx
import pytest
from respx import MockRouter
from respx.models import Call

from stagehand import Stagehand, AsyncStagehand

Expand Down Expand Up @@ -37,8 +40,11 @@ def test_sessions_create_returns_bound_session(respx_mock: MockRouter, client: S
session = client.sessions.create(model_name="openai/gpt-5-nano")
assert session.id == session_id

session.navigate(url="https://example.com", frame_id="")
session.navigate(url="https://example.com")
assert navigate_route.called is True
first_call = cast(Call, navigate_route.calls[0])
request_body = json.loads(first_call.request.content)
assert request_body["frameId"] == ""


@pytest.mark.respx(base_url=base_url)
Expand Down Expand Up @@ -67,5 +73,8 @@ async def test_async_sessions_create_returns_bound_session(
session = await async_client.sessions.create(model_name="openai/gpt-5-nano")
assert session.id == session_id

await session.navigate(url="https://example.com", frame_id="")
await session.navigate(url="https://example.com")
assert navigate_route.called is True
first_call = cast(Call, navigate_route.calls[0])
request_body = json.loads(first_call.request.content)
assert request_body["frameId"] == ""
Loading