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.1"
".": "3.5.2"
}
2 changes: 1 addition & 1 deletion .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: 48b4dfac35a842d7fb0d228caf87544e
openapi_spec_hash: 8a36f79075102c63234ed06107deb8c9
config_hash: 7386d24e2f03a3b2a89b3f6881446348
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 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)

### Bug Fixes

* **client:** loosen json header parsing ([ea4142c](https://github.com/browserbase/stagehand-ruby/commit/ea4142cc5973c9207a81c7336c8425c9dbe63a30))


### Chores

* **docs:** remove www prefix ([8498bdd](https://github.com/browserbase/stagehand-ruby/commit/8498bdd34e0288b8a1c28e789d35dcecf91cd8b7))

## 3.5.1 (2026-02-03)

Full Changelog: [v3.5.0...v3.5.1](https://github.com/browserbase/stagehand-ruby/compare/v3.5.0...v3.5.1)
Expand Down
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ If you’d like to use the repository from source, you can either install from g
To install via git in your `Gemfile`:

```ruby
gem "stagehand", git: "https://www.github.com/browserbase/stagehand-ruby"
gem "stagehand", git: "https://github.com/browserbase/stagehand-ruby"
```

Alternatively, reference local copy of the repo:

```bash
$ git clone -- 'https://www.github.com/browserbase/stagehand-ruby' '<path-to-repo>'
$ git clone -- 'https://github.com/browserbase/stagehand-ruby' '<path-to-repo>'
```

```ruby
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.1)
stagehand (3.5.2)
cgi
connection_pool

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,14 +149,16 @@ export MODEL_API_KEY="your-openai-api-key"
bundle exec ruby examples/local_browser_example.rb
```

Playwright local example:
Playwright local example (SSE streaming):

```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

bundle exec ruby examples/local_browser_playwright_example.rb
```

Playwright remote example:
Expand All @@ -168,7 +170,7 @@ npm install playwright
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_playwright_example.rb
bundle exec ruby examples/remote_browser_playwright_example.rb
```

Watir local example:
Expand Down
207 changes: 207 additions & 0 deletions examples/local_browser_playwright_example.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#!/usr/bin/env ruby
# typed: ignore
# frozen_string_literal: true

require "bundler/setup"
require "stagehand"

# Example: Using Playwright with Stagehand local mode (local browser).
#
# Prerequisites:
# - Set MODEL_API_KEY or OPENAI_API_KEY environment variable
# - Set BROWSERBASE_API_KEY and BROWSERBASE_PROJECT_ID (can be any value in local mode)
# - Install Playwright (outside this gem):
# gem install playwright-ruby-client
# npm install playwright
# ./node_modules/.bin/playwright install chromium
#
# Run:
# bundle exec ruby examples/local_browser_playwright_example.rb

begin
require("playwright")
rescue LoadError
warn("Playwright is not installed. Run: gem install playwright-ruby-client")
exit(1)
end

model_key = ENV["MODEL_API_KEY"] || ENV["OPENAI_API_KEY"]
browserbase_api_key = ENV["BROWSERBASE_API_KEY"].to_s
browserbase_project_id = ENV["BROWSERBASE_PROJECT_ID"].to_s

missing = []
missing << "MODEL_API_KEY" if model_key.to_s.empty?
missing << "BROWSERBASE_API_KEY" if browserbase_api_key.empty?
missing << "BROWSERBASE_PROJECT_ID" if browserbase_project_id.empty?

unless missing.empty?
warn("Set #{missing.join(', ')} to run the local Playwright example.")
exit(1)
end

def print_stream_event(label, event)
case event.type
when :log
puts("[#{label}] log: #{event.data.message}")
when :system
status = event.data.status
if event.data.respond_to?(:error) && event.data.error
puts("[#{label}] system #{status}: #{event.data.error}")
elsif event.data.respond_to?(:result) && !event.data.result.nil?
puts("[#{label}] system #{status}: #{event.data.result}")
else
puts("[#{label}] system #{status}")
end
else
puts("[#{label}] event: #{event.inspect}")
end
end

def stream_with_result(label, stream)
puts("#{label} stream:")
result = nil
stream.each do |event|
print_stream_event(label, event)
if event.type == :system && event.data.respond_to?(:result) && !event.data.result.nil?
result = event.data.result
end
end
result
end

client = Stagehand::Client.new(
browserbase_api_key: browserbase_api_key,
browserbase_project_id: browserbase_project_id,
model_api_key: model_key,
server: "local"
)

session_id = nil

begin
# rubocop:disable Metrics/BlockLength
Playwright.create(playwright_cli_executable_path: "./node_modules/.bin/playwright") do |playwright|
browser_server = playwright.chromium.launch_server(headless: true)
cdp_url = browser_server.ws_endpoint

start_response = client.sessions.start(
model_name: "openai/gpt-5-nano",
browser: {
type: :local,
launch_options: {
cdp_url: cdp_url
}
}
)
session_id = start_response.data.session_id

puts("Session started: #{session_id}")
puts("Connecting Playwright over CDP...")

browser = playwright.chromium.connect_over_cdp(cdp_url)
begin
context = browser.contexts.first || browser.new_context
page = context.pages.first || context.new_page
page.goto("https://example.com")
page.wait_for_load_state(state: "domcontentloaded")

observe_stream = client.sessions.observe_streaming(
session_id,
instruction: "Find all clickable links on this page"
)
observe_result = stream_with_result("Observe", observe_stream)
if observe_result.nil?
observe_response = client.sessions.observe(
session_id,
instruction: "Find all clickable links on this page"
)
observe_result = observe_response.data.result
end
puts("Found #{observe_result.length} possible actions")

act_input = "Click the 'Learn more' link"
act_stream = client.sessions.act_streaming(
session_id,
input: act_input
)
act_result = stream_with_result("Act", act_stream)
if act_result.nil?
act_response = client.sessions.act(
session_id,
input: act_input
)
act_result = act_response.data.result
end
act_message = act_result.is_a?(Hash) ? (act_result[:message] || act_result["message"]) : act_result
puts("Act completed: #{act_message}")

extract_stream = client.sessions.extract_streaming(
session_id,
instruction: "Extract the main heading and any links on this page",
schema: {
type: "object",
properties: {
heading: {type: "string"},
links: {type: "array", items: {type: "string"}}
}
}
)
extract_result = stream_with_result("Extract", extract_stream)
if extract_result.nil?
extract_response = client.sessions.extract(
session_id,
instruction: "Extract the main heading and any links on this page",
schema: {
type: "object",
properties: {
heading: {type: "string"},
links: {type: "array", items: {type: "string"}}
}
}
)
extract_result = extract_response.data.result
end
puts("Extracted: #{extract_result}")

execute_stream = client.sessions.execute_streaming(
session_id,
execute_options: {
instruction: "Click the 'Learn more' link if available and summarize the destination.",
max_steps: 5
},
agent_config: {
model: {
model_name: "openai/gpt-5-nano",
api_key: model_key
},
cua: false
}
)
execute_result = stream_with_result("Execute", execute_stream)
if execute_result.nil?
execute_response = client.sessions.execute(
session_id,
execute_options: {
instruction: "Click the 'Learn more' link if available and summarize the destination.",
max_steps: 5
},
agent_config: {
model: {
model_name: "openai/gpt-5-nano",
api_key: model_key
},
cua: false
}
)
execute_result = execute_response.data.result
end
puts("Execute result: #{execute_result}")
ensure
browser.close
browser_server.close
end
end
# rubocop:enable Metrics/BlockLength
ensure
client.sessions.end(session_id) if session_id
end
Loading