Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
5 changes: 5 additions & 0 deletions .changeset/ambrosial-anaconda-of-blizzard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"stagehand": patch
---

Enable access to iframes on api
15 changes: 12 additions & 3 deletions stagehand/page.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,14 @@ async def act(
elif isinstance(action_or_result, ActOptions):
payload = action_or_result.model_dump(exclude_none=True, by_alias=True)
elif isinstance(action_or_result, dict):
payload = ObserveResult(**action_or_result).model_dump(
exclude_none=True, by_alias=True
)
if "description" in action_or_result:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this related to the PR? seems like Claude Code / Cursor added unless intentional to have actFromObserve distinction when sending the payload to the API?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added, it's so that people can pass sth like

await page.act({
  "action": "click the button"
  "iframes": True
})

Previously it was only parsing the dict as an observe result

await page.act({
   "description": "A brief description of the component",
   "method": 'click',
   "arguments": [],
   "selector": 'xpath=/html/body[1]/div[1]/main[1]/button[1]'
})

Makes it easy to copy-paste code from TS directly

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome!

payload = ObserveResult(**action_or_result).model_dump(
exclude_none=True, by_alias=True
)
else:
payload = ActOptions(**action_or_result).model_dump(
exclude_none=True, by_alias=True
)
else:
raise TypeError(
"Invalid arguments for 'act'. Expected str, ObserveResult, or ActOptions."
Expand All @@ -156,6 +161,10 @@ async def act(
self, self._stagehand, "", self._stagehand.self_heal
)
self._stagehand.logger.debug("act", category="act", auxiliary=payload)
if payload.get("iframes"):
raise ValueError(
"iframes is not yet supported without API (to enable make sure you set env=BROWSERBASE and use_api=true)"
)
result = await self._act_handler.act(payload)
return result

Expand Down
3 changes: 3 additions & 0 deletions stagehand/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ class ActOptions(StagehandBaseModel):
dom_settle_timeout_ms: Optional[int] = None
timeout_ms: Optional[int] = None
model_client_options: Optional[dict[str, Any]] = None
iframes: Optional[bool] = None


class ActResult(StagehandBaseModel):
Expand Down Expand Up @@ -98,6 +99,7 @@ class ExtractOptions(StagehandBaseModel):
use_text_extract: Optional[bool] = None
dom_settle_timeout_ms: Optional[int] = None
model_client_options: Optional[dict[Any, Any]] = None
iframes: Optional[bool] = None

@field_serializer("schema_definition")
def serialize_schema_definition(
Expand Down Expand Up @@ -196,6 +198,7 @@ class ObserveOptions(StagehandBaseModel):
draw_overlay: Optional[bool] = None
dom_settle_timeout_ms: Optional[int] = None
model_client_options: Optional[dict[str, Any]] = None
iframes: Optional[bool] = None


class ObserveResult(StagehandBaseModel):
Expand Down