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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "3.5.2"
".": "3.6.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 8
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-8fbb3fa8f3a37c1c7408de427fe125aadec49f705e8e30d191601a9b69c4cc41.yml
openapi_spec_hash: 8a36f79075102c63234ed06107deb8c9
config_hash: 7386d24e2f03a3b2a89b3f6881446348
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/browserbase%2Fstagehand-5d0052068f044366d6d31570d9712922c9a80fdd6f9995af815e9afc075507ef.yml
openapi_spec_hash: c0cb787da075d8cd2d938c05b36b5efa
config_hash: 4252fc025e947bc0fd6b2abd91a0cc8e
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 3.6.0 (2026-02-18)

Full Changelog: [v3.5.2...v3.6.0](https://github.com/browserbase/stagehand-ruby/compare/v3.5.2...v3.6.0)

### Features

* randomize region used for evals, split out pnpm and turbo cache, veri… ([a68a9d2](https://github.com/browserbase/stagehand-ruby/commit/a68a9d2705b2c3b3b35dcaab4b5693184194774b))

## 3.5.2 (2026-02-07)

Full Changelog: [v3.5.1...v3.5.2](https://github.com/browserbase/stagehand-ruby/compare/v3.5.1...v3.5.2)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ GIT
PATH
remote: .
specs:
stagehand (3.5.2)
stagehand (3.6.0)
cgi
connection_pool

Expand Down
261 changes: 135 additions & 126 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
# Stagehand Ruby API library

<!-- x-stagehand-custom-start -->
<div id="toc" align="center" style="margin-bottom: 0;">
<ul style="list-style: none; margin: 0; padding: 0;">
<a href="https://stagehand.dev">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/browserbase/stagehand/main/media/dark_logo.png" />
<img alt="Stagehand" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/light_logo.png" width="200" style="margin-right: 30px;" />
</picture>
</a>
</ul>
</div>
<p align="center">
<strong>The AI Browser Automation Framework</strong><br>
<a href="https://docs.stagehand.dev/v3/sdk/ruby">Read the Docs</a>
</p>

<p align="center">
<a href="https://github.com/browserbase/stagehand/tree/main?tab=MIT-1-ov-file#MIT-1-ov-file">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/browserbase/stagehand/main/media/dark_license.svg" />
<img alt="MIT License" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/light_license.svg" />
</picture>
</a>
<a href="https://stagehand.dev/discord">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/browserbase/stagehand/main/media/dark_discord.svg" />
<img alt="Discord Community" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/light_discord.svg" />
</picture>
</a>
</p>

<p align="center">
<a href="https://trendshift.io/repositories/12122" target="_blank"><img src="https://trendshift.io/api/badge/repositories/12122" alt="browserbase%2Fstagehand | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>

<p align="center">
If you're looking for other languages, you can find them
<a href="https://docs.stagehand.dev/v3/first-steps/introduction"> here</a>
</p>

<div align="center" style="display: flex; align-items: center; justify-content: center; gap: 4px; margin-bottom: 0;">
<b>Vibe code</b>
<span style="font-size: 1.05em;"> Stagehand with </span>
<a href="https://director.ai" style="display: flex; align-items: center;">
<span>Director</span>
</a>
<span> </span>
<picture>
<img alt="Director" src="https://raw.githubusercontent.com/browserbase/stagehand/main/media/director_icon.svg" width="25" />
</picture>
</div>
<!-- x-stagehand-custom-end -->

The Stagehand Ruby library provides convenient access to the Stagehand REST API from any Ruby 3.2.0+ application. It ships with comprehensive types & docstrings in Yard, RBS, and RBI – [see below](https://github.com/browserbase/stagehand-ruby#Sorbet) for usage with Sorbet. The standard library's `net/http` is used as the HTTP transport, with connection pooling via the `connection_pool` gem.

It is generated with [Stainless](https://www.stainless.com/).
Expand All @@ -24,163 +77,119 @@ gem "stagehand", :git => "git://github.com/browserbase/stagehand-ruby.git"

## Usage

This mirrors `examples/remote_browser_playwright_example.rb`.

```ruby
require "bundler/setup"
require "stagehand"

# Create a new Stagehand client with your credentials
require_relative "examples/env"
ExampleEnv.load!

require "playwright"

client = Stagehand::Client.new(
browserbase_api_key: ENV["BROWSERBASE_API_KEY"], # defaults to ENV["BROWSERBASE_API_KEY"]
browserbase_project_id: ENV["BROWSERBASE_PROJECT_ID"], # defaults to ENV["BROWSERBASE_PROJECT_ID"]
model_api_key: ENV["MODEL_API_KEY"] # defaults to ENV["MODEL_API_KEY"]
browserbase_api_key: ENV["BROWSERBASE_API_KEY"],
browserbase_project_id: ENV["BROWSERBASE_PROJECT_ID"],
model_api_key: ENV["MODEL_API_KEY"],
server: "remote"
)

# Start a new browser session
start_response = client.sessions.start(
model_name: "openai/gpt-5-nano"
model_name: "anthropic/claude-sonnet-4-6",
browser: { type: :browserbase }
)
puts "Session started: #{start_response.data.session_id}"

session_id = start_response.data.session_id
cdp_url = start_response.data.cdp_url
raise "No CDP URL returned for this session." if cdp_url.to_s.empty?

# Navigate to a webpage
client.sessions.navigate(
session_id,
url: "https://news.ycombinator.com"
)
puts "Navigated to Hacker News"
Playwright.create(playwright_cli_executable_path: "./node_modules/.bin/playwright") do |playwright|
browser = playwright.chromium.connect_over_cdp(cdp_url)
context = browser.contexts.first || browser.new_context
page = context.pages.first || context.new_page

# Use Observe to find possible actions on the page
observe_response = client.sessions.observe(
session_id,
instruction: "find the link to view comments for the top post"
)

actions = observe_response.data.result
puts "Found #{actions.length} possible actions"
client.sessions.navigate(session_id, url: "https://news.ycombinator.com")
page.wait_for_load_state(state: "domcontentloaded")

# Take the first action returned by Observe
action = actions.first
puts "Acting on: #{action.description}"
observe_stream = client.sessions.observe_streaming(
session_id,
instruction: "find the link to view comments for the top post"
)
observe_stream.each { |_event| }

# Pass the structured action to Act
# Convert the observe result to a hash and ensure method is set to "click"
act_response = client.sessions.act(
session_id,
input: action.to_h.merge(method: "click")
)
puts "Act completed: #{act_response.data.result[:message]}"

# Extract data from the page
# We're now on the comments page, so extract the top comment text
extract_response = client.sessions.extract(
session_id,
instruction: "extract the text of the top comment on this page",
schema: {
type: "object",
properties: {
comment_text: {
type: "string",
description: "The text content of the top comment"
act_stream = client.sessions.act_streaming(
session_id,
input: "Click the comments link for the top post"
)
act_stream.each { |_event| }

extract_stream = client.sessions.extract_streaming(
session_id,
instruction: "extract the text of the top comment on this page",
schema: {
type: "object",
properties: {
commentText: {type: "string"},
author: {type: "string"}
},
author: {
type: "string",
description: "The username of the comment author"
}
required: ["commentText"]
}
)
extract_stream.each { |_event| }

execute_stream = client.sessions.execute_streaming(
session_id,
execute_options: {
instruction: "Click the 'Learn more' link if available",
max_steps: 3
},
required: ["comment_text"]
}
)
puts "Extracted data: #{extract_response.data.result}"

# Get the author from the extracted data
extracted_data = extract_response.data.result
author = extracted_data[:author]
puts "Looking up profile for author: #{author}"

# Use the Agent to find the author's profile
# Execute runs an autonomous agent that can navigate and interact with pages
execute_response = client.sessions.execute(
session_id,
execute_options: {
instruction: "Find any personal website, GitHub, LinkedIn, or other best profile URL for the Hacker News user '#{author}'. " \
"Click on their username to go to their profile page and look for any links they have shared.",
max_steps: 15
},
agent_config: {
model: Stagehand::ModelConfig::ModelConfigObject.new(
model_name: "openai/gpt-5-nano",
api_key: ENV["MODEL_API_KEY"]
),
cua: false
}
)
puts "Agent completed: #{execute_response.data.result[:message]}"
puts "Agent success: #{execute_response.data.result[:success]}"
puts "Agent actions taken: #{execute_response.data.result[:actions]&.length || 0}"
agent_config: {
model: Stagehand::ModelConfig.new(
model_name: "anthropic/claude-opus-4-6",
api_key: ENV["MODEL_API_KEY"]
),
cua: false
}
)
execute_stream.each { |_event| }
end

# End the session to cleanup browser resources
client.sessions.end_(session_id)
puts "Session ended"
```

### Running the Examples

Install dependencies, set credentials, and run the scripts below.

```bash
# Install the gem dependencies
bundle install
```

Remote browser example:
## Running the Example

```bash
export BROWSERBASE_API_KEY="your-browserbase-api-key"
export BROWSERBASE_PROJECT_ID="your-browserbase-project-id"
export MODEL_API_KEY="your-openai-api-key"
bundle exec ruby examples/remote_browser_example.rb
```
Set your environment variables (from `examples/.env.example`):

Local mode example (embedded server, local Chrome/Chromium):
- `STAGEHAND_API_URL`
- `MODEL_API_KEY`
- `BROWSERBASE_API_KEY`
- `BROWSERBASE_PROJECT_ID`

```bash
export MODEL_API_KEY="your-openai-api-key"
bundle exec ruby examples/local_browser_example.rb
cp examples/.env.example examples/.env
# Edit examples/.env with your credentials.
```

Playwright local example (SSE streaming):
The examples load `examples/.env` automatically.

```bash
gem install playwright-ruby-client
npm install playwright
./node_modules/.bin/playwright install chromium
export MODEL_API_KEY="your-openai-api-key"
bundle exec ruby examples/local_playwright_example.rb
Examples and dependencies:

bundle exec ruby examples/local_browser_playwright_example.rb
```
- `examples/remote_browser_example.rb`: stagehand only
- `examples/local_browser_example.rb`: stagehand only
- `examples/remote_browser_playwright_example.rb`: `playwright-ruby-client` + Playwright browsers
- `examples/local_browser_playwright_example.rb`: `playwright-ruby-client` + Playwright browsers
- `examples/local_playwright_example.rb`: `playwright-ruby-client` + Playwright browsers
- `examples/local_watir_example.rb`: `watir`

Playwright remote example:
Install dependencies for the example you want to run, then execute it:

```bash
gem install playwright-ruby-client
npm install playwright
./node_modules/.bin/playwright install chromium
export BROWSERBASE_API_KEY="your-browserbase-api-key"
export BROWSERBASE_PROJECT_ID="your-browserbase-project-id"
export MODEL_API_KEY="your-openai-api-key"
bundle install
bundle exec ruby examples/remote_browser_playwright_example.rb
```

Watir local example:

```bash
gem install watir
export MODEL_API_KEY="your-openai-api-key"
bundle exec ruby examples/local_watir_example.rb
```

### Streaming

We provide support for streaming responses using Server-Sent Events (SSE).
Expand All @@ -202,7 +211,7 @@ When the library is unable to connect to the API, or if the API returns a non-su

```ruby
begin
session = stagehand.sessions.start(model_name: "openai/gpt-5-nano")
session = stagehand.sessions.start(model_name: "anthropic/claude-sonnet-4-6")
rescue Stagehand::Errors::APIConnectionError => e
puts("The server could not be reached")
puts(e.cause) # an underlying Exception, likely raised within `net/http`
Expand Down Expand Up @@ -245,7 +254,7 @@ stagehand = Stagehand::Client.new(
)

# Or, configure per-request:
stagehand.sessions.start(model_name: "openai/gpt-5-nano", request_options: {max_retries: 5})
stagehand.sessions.start(model_name: "anthropic/claude-sonnet-4-6", request_options: {max_retries: 5})
```

### Timeouts
Expand All @@ -259,7 +268,7 @@ stagehand = Stagehand::Client.new(
)

# Or, configure per-request:
stagehand.sessions.start(model_name: "openai/gpt-5-nano", request_options: {timeout: 5})
stagehand.sessions.start(model_name: "anthropic/claude-sonnet-4-6", request_options: {timeout: 5})
```

On timeout, `Stagehand::Errors::APITimeoutError` is raised.
Expand Down Expand Up @@ -291,7 +300,7 @@ Note: the `extra_` parameters of the same name overrides the documented paramete
```ruby
response =
stagehand.sessions.start(
model_name: "openai/gpt-5-nano",
model_name: "anthropic/claude-sonnet-4-6",
request_options: {
extra_query: {my_query_parameter: value},
extra_body: {my_body_parameter: value},
Expand Down
4 changes: 4 additions & 0 deletions examples/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
STAGEHAND_API_URL=https://api.stagehand.browserbase.com
MODEL_API_KEY=sk-proj-your-llm-api-key-here
BROWSERBASE_API_KEY=bb_live_your_api_key_here
BROWSERBASE_PROJECT_ID=your-bb-project-uuid-here
Loading