diff --git a/components/docskit/components.tsx b/components/docskit/components.tsx
index 256e20ab..3b2e50af 100644
--- a/components/docskit/components.tsx
+++ b/components/docskit/components.tsx
@@ -1,16 +1,16 @@
-import { Block, CodeBlock } from 'codehike/blocks';
-import type { RawCode } from 'codehike/code';
-import Link from 'fumadocs-core/link';
-import { z } from 'zod';
-import { Code } from './code';
-import { SimpleCode } from './code-simple';
-import { InlineCode } from './inline-code';
-import { WithNotes } from './notes';
-import { NoteTooltip } from './notes.tooltip';
-import ScrollyCoding from './scrollycoding';
-import Slideshow from './slideshow';
-import Spotlight from './spotlight';
-import { Terminal } from './terminal';
+import { Block, CodeBlock } from "codehike/blocks";
+import type { RawCode } from "codehike/code";
+import Link from "fumadocs-core/link";
+import { z } from "zod";
+import { Code } from "./code";
+import { SimpleCode } from "./code-simple";
+import { InlineCode } from "./inline-code";
+import { WithNotes } from "./notes";
+import { NoteTooltip } from "./notes.tooltip";
+import ScrollyCoding from "./scrollycoding";
+import Slideshow from "./slideshow";
+import Spotlight from "./spotlight";
+import { Terminal } from "./terminal";
// Export RawCode type for external use
export type { RawCode };
@@ -38,13 +38,13 @@ export const docskit = {
function DocsKitCode(props: { codeblock: RawCode }) {
const { codeblock, ...rest } = props;
- if (codeblock.lang === 'package-install') {
+ if (codeblock.lang === "package-install") {
return ;
}
- if (codeblock.lang === 'terminal') {
+ if (codeblock.lang === "terminal") {
// Parse flags from meta string (e.g., "terminal -o" -> hideOutput: true)
- const hideOutput = codeblock.meta?.includes('-o') || false;
+ const hideOutput = codeblock.meta?.includes("-o") || false;
return ;
}
@@ -60,7 +60,7 @@ function CodeTabs(props: unknown) {
}).safeParse(props);
if (error) {
- throw betterError(error, 'CodeTabs');
+ throw betterError(error, "CodeTabs");
}
const { code, flags, storage } = data;
@@ -70,50 +70,114 @@ function CodeTabs(props: unknown) {
function betterError(error: z.ZodError, componentName: string) {
const { issues } = error;
- if (issues.length === 1 && issues[0].path[0] === 'code') {
- return new Error(`<${componentName}> should contain at least one codeblock marked with \`!!\``);
+ if (issues.length === 1 && issues[0].path[0] === "code") {
+ return new Error(
+ `<${componentName}> should contain at least one codeblock marked with \`!!\``,
+ );
} else {
return error;
}
}
function DocsKitLink(props: any) {
- if (props.href === 'tooltip') {
+ if (props.href === "tooltip") {
return {props.children};
}
return ;
}
function PackageInstall({ codeblock }: { codeblock: RawCode }) {
- return (
-
);
}
@@ -127,10 +191,12 @@ function TerminalPicker(props: unknown) {
}).safeParse(props);
if (error) {
- throw betterError(error, 'TerminalPicker');
+ throw betterError(error, "TerminalPicker");
}
const { code, storage, flags } = data;
- const hideOutput = flags?.includes('-o') || false;
- return ;
+ const hideOutput = flags?.includes("-o") || false;
+ return (
+
+ );
}
diff --git a/components/docskit/terminal.tsx b/components/docskit/terminal.tsx
index 2cd5b39b..701b4b76 100644
--- a/components/docskit/terminal.tsx
+++ b/components/docskit/terminal.tsx
@@ -4,12 +4,12 @@ import {
highlight,
Pre,
type RawCode,
-} from 'codehike/code';
-import { CommandBlock } from './annotations/terminal-command';
-import { OutputBlock } from './annotations/terminal-output';
-import { wordWrap } from './annotations/word-wrap';
-import { TerminalClient } from './terminal.client';
-import theme from './theme.mjs';
+} from "codehike/code";
+import { CommandBlock } from "./annotations/terminal-command";
+import { OutputBlock } from "./annotations/terminal-output";
+import { wordWrap } from "./annotations/word-wrap";
+import { TerminalClient } from "./terminal.client";
+import theme from "./theme.mjs";
export async function Terminal(props: {
codeblocks: RawCode[];
@@ -25,7 +25,11 @@ export async function Terminal(props: {
@@ -35,11 +39,17 @@ export async function Terminal(props: {
}),
);
- return ;
+ return (
+
+ );
}
const createOutputHandler = (hideOutput?: boolean): AnnotationHandler => ({
- name: 'output',
+ name: "output",
Block: (props) => {
const Component = OutputBlock as any;
return ;
@@ -47,7 +57,7 @@ const createOutputHandler = (hideOutput?: boolean): AnnotationHandler => ({
});
const command: AnnotationHandler = {
- name: 'command',
+ name: "command",
Block: CommandBlock,
};
@@ -65,30 +75,32 @@ function extractAnnotations(code: string) {
const lines = code.split(/\r?\n/);
const annotations = [] as BlockAnnotation[];
lines.forEach((line, index) => {
- if (line.startsWith('$ ')) {
+ if (line.startsWith("$ ")) {
annotations.push({
- name: 'command',
+ name: "command",
query: line.slice(2),
fromLineNumber: index + 1,
toLineNumber: index + 1,
});
} else {
const last = annotations[annotations.length - 1];
- if (last.name === 'command' && last.query.endsWith('\\')) {
+ if (last.name === "command" && last.query.endsWith("\\")) {
last.query = `${last.query}\n${line}`;
last.toLineNumber = index + 1;
- } else if (!last || last.name !== 'output') {
+ } else if (!last || last.name !== "output") {
annotations.push({
- name: 'output',
- query: '',
+ name: "output",
+ query: "",
fromLineNumber: index + 1,
toLineNumber: index + 1,
});
- } else if ('toLineNumber' in last) {
+ } else if ("toLineNumber" in last) {
last.toLineNumber = index + 1;
}
}
});
- const codeWithoutPrompt = lines.map((line) => line.replace(/^\$ /, '')).join('\n');
+ const codeWithoutPrompt = lines
+ .map((line) => line.replace(/^\$ /, ""))
+ .join("\n");
return { annotations, value: codeWithoutPrompt };
}
diff --git a/content/docs/integrations/agno/quickstart.mdx b/content/docs/integrations/agno/quickstart.mdx
index 3dfd6772..2d9c9927 100644
--- a/content/docs/integrations/agno/quickstart.mdx
+++ b/content/docs/integrations/agno/quickstart.mdx
@@ -20,23 +20,10 @@ Make sure you have:
Create and activate a virtual environment, then install dependencies:
-```bash Terminal -wc
-# Create project
-mkdir steel-agno-starter
-cd steel-agno-starter
-
-# (Recommended) Create & activate a virtual environment
-python3 -m venv .venv
-source .venv/bin/activate # On Windows: .venv\Scripts\activate
-
-# Create files
-touch main.py .env
-
-# Install dependencies
-pip install agno steel-sdk python-dotenv playwright
+```package-install python
+agno steel-sdk python-dotenv playwright
```
-
Create a `.env` file with your keys and a default task:
```env ENV -wcn -f .env
diff --git a/content/docs/integrations/browser-use/quickstart.mdx b/content/docs/integrations/browser-use/quickstart.mdx
index fc266cb2..a1837f8d 100644
--- a/content/docs/integrations/browser-use/quickstart.mdx
+++ b/content/docs/integrations/browser-use/quickstart.mdx
@@ -20,22 +20,12 @@ Ensure you have the following:
#### Step 1: Set up your environment
-First, create a project directory, set up a virtual environment, and install the required packages:
+First, set up a virtual environment, and install the required packages:
-```bash Terminal -wc
-# Create a project directory
-mkdir steel-browser-use-agent
-cd steel-browser-use-agent
-
-# Recommended: Create and activate a virtual environment
-uv venv
-source .venv/bin/activate # On Windows, use: .venv\Scripts\activate
-
-# Install required packages
-pip install steel-sdk browser-use python-dotenv
+```package-install python
+steel-sdk browser-use python-dotenv
```
-
Create a `.env` file with your API keys:
```env ENV -wcn -f .env
diff --git a/content/docs/integrations/claude-computer-use/quickstart-py.mdx b/content/docs/integrations/claude-computer-use/quickstart-py.mdx
index 0fa24c62..f0da5ee9 100644
--- a/content/docs/integrations/claude-computer-use/quickstart-py.mdx
+++ b/content/docs/integrations/claude-computer-use/quickstart-py.mdx
@@ -20,19 +20,10 @@ We'll build a Claude Computer Use loop that enables autonomous web task executio
#### Step 1: Setup and Dependencies
-First, create a project directory, set up a virtual environment, and install the required packages:
+First, set up a virtual environment, and install the required packages:
-```bash Terminal -wc
-# Create a project directory
-mkdir steel-claude-computer-use
-cd steel-claude-computer-use
-
-# Recommended: Create and activate a virtual environment
-python -m venv venv
-source venv/bin/activate # On Windows, use: venv\Scripts\activate
-
-# Install required packages
-pip install steel-sdk anthropic playwright python-dotenv pillow
+```package-install python
+steel-sdk anthropic playwright python-dotenv pillow
```
diff --git a/content/docs/integrations/crewai/quickstart.mdx b/content/docs/integrations/crewai/quickstart.mdx
index 606aaa22..315c5199 100644
--- a/content/docs/integrations/crewai/quickstart.mdx
+++ b/content/docs/integrations/crewai/quickstart.mdx
@@ -20,20 +20,8 @@ Make sure you have:
Create and activate a virtual environment, then install dependencies:
-```bash Terminal -wc
-# Create project
-mkdir steel-crewai-starter
-cd steel-crewai-starter
-
-# (Recommended) Create & activate a virtual environment
-python3 -m venv .venv
-source .venv/bin/activate # On Windows: .venv\Scripts\activate
-
-# Create files
-touch main.py .env
-
-# Install dependencies
-pip install crewai[tools] steel-sdk python-dotenv pydantic
+```package-install python
+crewai[tools] steel-sdk python-dotenv pydantic
```
diff --git a/content/docs/integrations/notte/quickstart.mdx b/content/docs/integrations/notte/quickstart.mdx
index a17bf3c1..715f2c92 100644
--- a/content/docs/integrations/notte/quickstart.mdx
+++ b/content/docs/integrations/notte/quickstart.mdx
@@ -14,26 +14,14 @@ llm: true
* **Python 3.11+**
:::
-### Step 1: Project Setup
+### Step 1: Project Setup and Install Dependencies
-Create a virtual environment and a minimal project:
-
-```bash Terminal -wc
-python3 -m venv .venv && \
-source .venv/bin/activate && \
-mkdir notte-steel && cd notte-steel && \
-touch main.py .env
-```
-
-
-### Step 2: Install Dependencies
-
-```bash Terminal -wc
-pip install steel-sdk notte python-dotenv
+```package-install python
+steel-sdk notte python-dotenv
```
-### Step 3: Environment Variables
+### Step 2: Environment Variables
Create a `.env` file with your API keys and a default task:
@@ -44,7 +32,7 @@ TASK="Go to Wikipedia and search for machine learning"
```
-### Step 4: Initialize Steel & Notte, then Connect via CDP
+### Step 3: Initialize Steel & Notte, then Connect via CDP
Set up Steel, load env vars, and prepare to start the Notte agent.
@@ -67,7 +55,7 @@ TASK = os.getenv("TASK") or "Go to Wikipedia and search for machine learning"
```
-### Step 5: Run a Notte Agent Task
+### Step 4: Run a Notte Agent Task
Create a Steel session, connect Notte via **CDP**, run your task, and print the result.
diff --git a/content/docs/integrations/stagehand/quickstart-py.mdx b/content/docs/integrations/stagehand/quickstart-py.mdx
index 01fc820a..74b8c860 100644
--- a/content/docs/integrations/stagehand/quickstart-py.mdx
+++ b/content/docs/integrations/stagehand/quickstart-py.mdx
@@ -20,18 +20,12 @@ Ensure you have the following:
### Step 1: Set up your environment
-First, create a project directory and install the required packages:
+First install the required packages:
-```bash Terminal -wc
-# Create a project directory
-mkdir steel-stagehand-starter
-cd steel-stagehand-starter
-
-# Install required packages
-pip install steel-sdk stagehand pydantic python-dotenv
+```package-install python
+steel-sdk stagehand pydantic python-dotenv
```
-
Create a `.env` file with your API keys:
```env ENV -wcn -f .env
diff --git a/content/docs/overview/files-api/overview.mdx b/content/docs/overview/files-api/overview.mdx
index eaccc239..c242ec95 100644
--- a/content/docs/overview/files-api/overview.mdx
+++ b/content/docs/overview/files-api/overview.mdx
@@ -322,6 +322,57 @@ await page.setInputFiles("#file-input", [uploadedSessionFile.path]);
```
+#### Browser-Use Example
+
+Browser-use needs some setup before it can be used. This includes setting up the browser profile with the correct downloads path and adding in a step hook to extract downloaded files to your local machine if necessary.
+
+```python Python -wcn -f main.py
+# Before agent main loop...
+
+# Hook to extract downloaded files to local machine if necessary
+async def step_hook_start(agent):
+ if os.environ.get("BROWSER_PROVIDER") == "steel":
+ await agent._check_and_update_downloads()
+ if agent.available_file_paths and len(agent.available_file_paths) > 0:
+ has_new_files = False
+ for file_path in agent.available_file_paths:
+ if file_path not in downloaded_files:
+ downloaded_files.append(file_path)
+ has_new_files = True
+ if has_new_files:
+ try:
+ extracted_files = await browser_service.extract_downloaded_files(DOWNLOAD_PATH)
+ logger.info(f"Extracted files: {extracted_files}")
+ except Exception as e:
+ logger.error(f"Failed to extract downloaded files: {e}")
+
+async def main():
+ try:
+ browser_session = Browser(cdp_url=cdp_url, downloads_path="/files")
+ await browser_session.connect()
+ await browser_session.cdp_client.send.Target.createBrowserContext()
+ browser_context_ids_return = await browser_session.cdp_client.send.Target.getBrowserContexts()
+ browser_context_ids = browser_context_ids_return['browserContextIds']
+ browser_context_id = browser_context_ids[0]
+ await browser_session.cdp_client.send.Browser.setDownloadBehavior(params={"behavior": "allow", "downloadPath": "/files", "eventsEnabled": True, "browserContextId": browser_context_id})
+ agent = Agent(task=TASK, llm=model, browser_session=browser_session)
+ agent.browser_session.browser_profile.downloads_path = LOCAL_DOWNLOAD_PATH
+ agent_results = await agent.run(
+ on_step_start=step_hook_start,
+ max_steps=5
+ )
+ except Exception as e:
+ print(f"Error: {e}")
+ finally:
+ # Clean up resources
+ if session:
+ client.sessions.release(session.id)
+ print("Session released")
+ print("Done!")
+# Rest of code...
+```
+
+
#### Complete Example
End-to-end workflow demonstrating global file management and session file operations.
diff --git a/content/docs/overview/guides/meta.json b/content/docs/overview/guides/meta.json
new file mode 100644
index 00000000..9e2294ad
--- /dev/null
+++ b/content/docs/overview/guides/meta.json
@@ -0,0 +1,12 @@
+{
+ "title": "Guides",
+ "root": true,
+ "pages": [
+ "---Guides---",
+ "human-in-the-loop",
+ "playwright-node",
+ "playwright-python",
+ "puppeteer",
+ "selenium"
+ ]
+}
diff --git a/content/docs/overview/guides/playwright-node.mdx b/content/docs/overview/guides/playwright-node.mdx
new file mode 100644
index 00000000..e764acf9
--- /dev/null
+++ b/content/docs/overview/guides/playwright-node.mdx
@@ -0,0 +1,150 @@
+---
+title: Connect with Playwright (Node)
+description: Drive a Steel session with Playwright via WebSocket connection
+sidebarTitle: Connect with Playwright (Node)
+llm: true
+---
+
+This guide shows you how to drive Steel's cloud browser sessions using Playwright with Node.js/TypeScript. Looking for Python? Check out our [Playwright Python guide](link-to-python-guide).
+
+Steel sessions are designed to be easily driven by Playwright. There are two main methods for connecting to & driving a Steel session with Playwright.
+
+
+
+**Quick Start:** Want to jump right in? [Skip to example project](https://docs.steel.dev/overview/guides/connect-with-playwright-node#example-project-scraping-hacker-news).
+
+Method #1: One-line change (_easiest)_
+--------------------------------------
+
+Most Playwright scripts start with `chromium.launch()` function to launch your browser with desired args that looks something like this:
+```typescript Typescript -wcn
+const browser = await chromium.launch({...});
+```
+
+
+
+Simply change this line to the following (replacing `MY_STEEL_API_KEY` with your api key):
+```typescript Typescript -wcn
+const browser = await chromium.connectOverCDP(
+ 'wss://connect.steel.dev?apiKey=MY_STEEL_API_KEY'
+);
+```
+
+
+
+**_and voila!_** This will automatically start and connect to a Steel session for you with all default parameters set. Your subsequent calls will work as they did previously.
+
+When you're done, the session automatically releases when your script calls `browser.close()`, `browser.disconnect()`, or ends the connection.
+
+
+
+#### **Advanced: Custom Session IDs**
+
+This doesn’t support other UTM parameters to add args (that is what Method #2 is for) other than one - `sessionId`. This allows you to set a custom session id (UUIDv4 format) for the session.
+
+This is helpful because you don’t get any data returned from connecting like this but by setting your own session ID, you can use the API/SDKs to retrieve data or taking actions on the session like manually releasing it.
+
+Example:
+```typescript Typescript -wcn
+import { v4 as uuidv4 } from 'uuid';
+import Steel from 'steel-sdk';
+
+const sessionId = uuidv4(); // '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
+
+const browser = await chromium.connectOverCDP(
+ `wss://connect.steel.dev?apiKey=${process.env.STEEL_API_KEY}&sessionId=${sessionId}`
+);
+
+// Get session details
+const client = new Steel();
+const session = await client.sessions.retrieve(sessionId);
+console.log(`View session live at: ${session.sessionViewerUrl}`);
+```
+
+
+
+Method #2: Create and connect
+-----------------------------
+
+Use this method when you need to drive a session with non-default features like proxy support or CAPTCHA solving. The main difference is that you'll:
+
+* Start a session via API
+
+* Connect to it via chromium.connectOverCDP()
+
+* Release the session when finished
+
+
+If you want your session to be recorded in the live viewer make sure to use the existing browser context from the session when controlling a page as opposed to creating a new context.
+```typescript Typescript -wcn
+import Steel from 'steel-sdk';
+import { chromium } from 'playwright';
+import dotenv from 'dotenv';
+
+dotenv.config();
+
+const client = new Steel({
+ steelAPIKey: process.env.STEEL_API_KEY,
+});
+
+async function main() {
+ // Create a session with additional features
+ const session = await client.sessions.create({
+ useProxy: true,
+ solveCaptcha: true,
+ });
+
+ // Connect with Playwright
+ const browser = await chromium.connectOverCDP(
+ `wss://connect.steel.dev?apiKey=${process.env.STEEL_API_KEY}&sessionId=${session.id}`
+ );
+
+ // Create page at existing context to ensure session is recorded. This is crucial!
+ const currentContext = browser.contexts()[0];
+ const page = await currentContext.pages()[0];
+
+ // Run your automation
+ await page.goto('https://example.com');
+
+ // Always clean up when done
+ await browser.close();
+ await client.sessions.release(session.id);
+}
+
+main();
+```
+
+
+
+**Important**: With Method #2, sessions remain active until explicitly released or timed out. It’s best practise to call `client.sessions.release()` when finished instead of waiting for the session to timeout to be released.
+
+
+
+Example Project: Scraping Hacker News
+-------------------------------------
+
+Here's a working example that scrapes Hacker News with proper error handling and session management:
+
+Starter code that scrapes Hacker News for top 5 stories using Steel's Node SDK and Playwright.
+
+
+
+Run by entering following commands in the terminal:
+
+* `export STEEL_API_KEY=your_api_key`
+
+* `npm start`
+
+
+The example includes:
+
+* Complete session configuration options
+
+* Error handling best practices
+
+* A working Hacker News scraper example
+
+* TypeScript support
+
+
+You can also clone it on [Github](https://github.com/steel-dev/steel-cookbook/blob/main/examples/steel-playwright-starter), [StackBlitz](https://stackblitz.com/edit/steel-playwright-starter?file=README.md), or [Replit](https://replit.com/@steel-dev/steel-playwright-starter?v=1) to start editing it yourself!
diff --git a/content/docs/overview/guides/playwright-python.mdx b/content/docs/overview/guides/playwright-python.mdx
new file mode 100644
index 00000000..a072525b
--- /dev/null
+++ b/content/docs/overview/guides/playwright-python.mdx
@@ -0,0 +1,146 @@
+---
+title: Connect with Playwright (Python)
+description: Drive a Steel session with Playwright-python via WebSocket connection
+sidebarTitle: Connect with Playwright (Python)
+llm: true
+---
+
+
+This guide shows you how to drive Steel's cloud browser sessions using Playwright with Python. Looking for Node.js/TypeScript? Check out our [Playwright Node.js guide](link-to-node-guide).
+
+Steel sessions are designed to be easily driven by Playwright. There are two main methods for connecting to & driving a Steel session with Playwright.
+
+
+
+Quick Start: Want to jump right in? [Skip to example project](https://docs.steel.dev/overview/guides/connect-with-playwright-python#example-project-scraping-hacker-news).
+
+Method #1: One-line change (_easiest)_
+--------------------------------------
+
+Most Playwright scripts start with `chromium.launch()` function to launch your browser with desired args that looks something like this:
+```python Python -wcn
+browser = chromium.launch()
+```
+
+
+
+Simply change this line to the following (replacing `MY_STEEL_API_KEY` with your api key):
+```python Python -wcn
+browser = chromium.connect_over_cdp(
+ 'wss://connect.steel.dev?apiKey=MY_STEEL_API_KEY'
+)
+```
+
+
+
+**_and voila!_** This will automatically start and connect to a Steel session for you with all default parameters set. Your subsequent calls will work as they did previously.
+
+When you're done, the session automatically releases when your script calls `browser.close()`, `browser.disconnect()`, or ends the connection.
+
+
+
+#### **Advanced: Custom Session IDs**
+
+This doesn’t support other UTM parameters to add args (that is what Method #2 is for) other than one - `sessionId`. This allows you to set a custom session id (UUIDv4 format) for the session.
+
+This is helpful because you don’t get any data returned from connecting like this but by setting your own session ID, you can use the API/SDKs to retrieve data or taking actions on the session like manually releasing it.
+
+Example:
+```python Python -wcn
+from uuid import uuid4
+from playwright.sync_api import sync_playwright
+
+session_id = str(uuid4()) # '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
+
+playwright = sync_playwright().start()
+browser = playwright.chromium.connect_over_cdp(
+ f'wss://connect.steel.dev?apiKey={os.getenv("STEEL_API_KEY")}&sessionId={session_id}'
+)
+```
+
+
+
+Method #2: Create and connect
+-----------------------------
+
+Use this method when you need to drive a session with non-default features like proxy support or CAPTCHA solving. The main difference is that you'll:
+
+* Start a session via API
+
+* Connect to it via chromium.connect\_over\_cdp()
+
+* Release the session when finished
+
+
+If you want your session to be recorded in the live viewer make sure to use the existing browser context from the session when controlling a page as opposed to creating a new context.
+```python Python -wcn
+import os
+from dotenv import load_dotenv
+from playwright.sync_api import sync_playwright
+from steel import Steel
+
+load_dotenv()
+
+client = Steel(
+ steel_api_key=os.getenv('STEEL_API_KEY'),
+)
+
+def main():
+ # Create a session with additional features
+ session = client.sessions.create(
+ use_proxy=True,
+ solve_captcha=True,
+ )
+
+ # Connect with Playwright
+ playwright = sync_playwright().start()
+ browser = playwright.chromium.connect_over_cdp(
+ f'wss://connect.steel.dev?apiKey={os.getenv("STEEL_API_KEY")}&sessionId={session.id}'
+ )
+
+ # Create page at existing context to ensure session is recorded.
+ currentContext = browser.contexts[0]
+ page = currentContext.new_page()
+
+ # Run your automation
+ page.goto('https://example.com')
+
+ # Always clean up when done
+ browser.close()
+ client.sessions.release(session.id)
+
+if __name__ == "__main__":
+ main()
+```
+
+
+**Important**: With Method #2, sessions remain active until explicitly released or timed out. It’s best practise to call `client.sessions.release()` when finished instead of waiting for the session to timeout to be released.
+
+
+
+Example Project: Scraping Hacker News
+-------------------------------------
+
+Here's a working example that scrapes Hacker News with proper error handling and session management:
+
+Starter code that scrapes Hacker News for top 5 stories using Steel's Python SDK and Playwright.
+
+
+
+To run it:
+
+* Add your `STEEL_API_KEY` to the secrets pane. It's located under "Tools" on the left hand pane.
+
+* Hit Run
+
+
+The example includes:
+
+* Complete session configuration options
+
+* Error handling best practices
+
+* A working Hacker News scraper example
+
+
+You can also clone it on [Github](https://github.com/steel-dev/steel-cookbook/tree/main/examples/steel-playwright-python-starter) [](https://github.com/steel-dev/steel-puppeteer-starter)or [Replit](https://replit.com/@steel-dev/steel-playwright-python-starter?v=1) to start editing it yourself!
diff --git a/content/docs/overview/guides/puppeteer.mdx b/content/docs/overview/guides/puppeteer.mdx
new file mode 100644
index 00000000..7fd5f096
--- /dev/null
+++ b/content/docs/overview/guides/puppeteer.mdx
@@ -0,0 +1,144 @@
+---
+title: Connect with Puppeteer
+description: Drive a Steel session with Puppeteer via WebSocket connection
+sidebarTitle: Connect with Puppeteer
+llm: true
+---
+
+This guide shows you how to drive Steel's cloud browser sessions using Puppeteer.
+
+Steel sessions are designed to be easily driven by Puppeteer. There are two main methods for connecting to & driving a Steel session with Puppeteer.
+
+
+
+**Quick Start**: Want to jump right in? [Skip to example project.](#example-project-scraping-hacker-news)
+
+Method #1: One-line change (_easiest)_
+--------------------------------------
+
+Most Puppeteer scripts start with a `puppeteer.launch()` function to launch your browser with desired args that looks something like this:
+```typescript Typescript -wcn
+const browser = await puppeteer.launch({...});
+```
+
+
+
+Simply change this line to the following (replacing `MY_STEEL_API_KEY` with your api key):
+```typescript Typescript -wcn
+const browser = await puppeteer.connect({
+ browserWSEndpoint: 'wss://connect.steel.dev?apiKey=MY_STEEL_API_KEY',
+});
+```
+
+
+
+**_and voila!_** This will automatically start and connect to a Steel session for you with all default parameters set. Your subsequent calls will work as they did previously.
+
+When you're done, the session automatically releases when your script calls `browser.close()`, `browser.disconnect()`, or ends the connection.
+
+
+
+**Advanced: Custom Session IDs**
+
+This doesn’t support other UTM parameters to add args (that is what Method #2 is for) other than one - `sessionId`. This allows you to set a custom session id (UUIDv4 format) for the session.
+
+This is helpful because you don’t get any data returned from connecting like this but by setting your own session ID, you can use the API/SDKs to retrieve data or taking actions on the session like manually releasing it.
+
+Example:
+```typescript Typescript -wcn
+import { v4 as uuidv4 } from 'uuid';
+import Steel from 'steel-sdk';
+
+const sessionId = uuidv4(); // '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
+
+const browser = await puppeteer.connect({
+ browserWSEndpoint: `wss://connect.steel.dev?apiKey=${process.env.STEEL_API_KEY}&sessionId=${sessionId}`,
+});
+
+// Get session details
+const client = new Steel();
+const session = await client.sessions.retrieve(sessionId);
+console.log(`View session live at: ${session.sessionViewerUrl}`);
+```
+
+
+
+Method #2: Create and connect
+-----------------------------
+
+Use this method when you need to drive a session with non-default features like proxy support or CAPTCHA solving. The main difference is that you'll:
+
+* Start a session via API
+
+* Connect to it via puppeteer.connect()
+
+* Release the session when finished
+```typescript Typescript -wcn
+import Steel from 'steel-sdk';
+import puppeteer from 'puppeteer';
+import dotenv from 'dotenv';
+
+dotenv.config();
+
+const client = new Steel({
+ steelAPIKey: process.env.STEEL_API_KEY, // Optional
+});
+
+async function main() {
+ // Create a session with additional features
+ const session = await client.sessions.create({
+ useProxy: true,
+ solveCaptcha: true,
+ });
+
+ // Connect with Puppeteer
+ const browser = await puppeteer.connect({
+ browserWSEndpoint: `wss://connect.steel.dev?apiKey=${process.env.STEEL_API_KEY}&sessionId=${session.id}`,
+ });
+
+ // Run your automation
+ const page = await browser.newPage();
+ await page.goto('https://example.com');
+
+ // Always clean up when done
+ await browser.close();
+ await client.sessions.release(session.id);
+}
+
+main();
+```
+
+
+
+**Important**: With Method #2, sessions remain active until explicitly released or timed out. It’s best practise to call `client.sessions.release()` when finished instead of waiting for the session to timeout to be released.
+
+
+
+Example Project: Scraping Hacker News
+-------------------------------------
+
+Here's a working example that scrapes Hacker News with proper error handling and session management:
+
+Starter code that scrapes Hacker News for top 5 stories using Steel's Node SDK and Puppeteer.
+
+
+
+Run by entering following commands in the terminal:
+
+* `export STEEL_API_KEY=your_api_key`
+
+* `npm start`
+
+
+The example includes:
+
+* Complete session configuration options
+
+* Error handling best practices
+
+* A working Hacker News scraper example
+
+* TypeScript support
+
+
+You can also clone it on [Github](https://github.com/steel-dev/steel-cookbook/tree/main/examples/steel-puppeteer-starter), [Val.town](https://www.val.town/v/stevekrouse/steel_puppeteer_starter), [StackBlitz](https://stackblitz.com/edit/steel-puppeteer-starter?file=README.md), or [Replit](https://replit.com/@steel-dev/steel-puppeteer-starter?v=1) to start editing it yourself!
diff --git a/content/docs/overview/guides/selenium.mdx b/content/docs/overview/guides/selenium.mdx
new file mode 100644
index 00000000..9cd596b1
--- /dev/null
+++ b/content/docs/overview/guides/selenium.mdx
@@ -0,0 +1,147 @@
+---
+title: Connect with Selenium
+description: How to drive and connect to Steel browser sessions with Selenium
+sidebarTitle: Connect with Selenium
+llm: true
+---
+
+
+Our Selenium integration is in its early stages and is not at feature parity with our Puppeteer and Playwright integrations. Some features like CAPTCHA solving and proxy support are currently unavailable. More details are provided below.
+
+Steel sessions are designed to be easily driven by Selenium, allowing you to run your existing Selenium scripts in the cloud with minimal changes.
+
+This guide shows you how to drive Steel's cloud browser sessions using Selenium with Python.
+
+Quick Start: Want to jump right in? [Skip to example project.](https://docs.steel.dev/overview/guides/connect-with-selenium#example-project-scraping-hacker-news)
+
+Limitations
+-----------
+
+Before we begin, please note that the following features are not yet supported in our Selenium integration:
+
+* **CAPTCHA Solving:** Automatic CAPTCHA solving is not available.
+
+* **Proxy Support:** Custom proxy configurations are currently unsupported.
+
+* **Advanced Session Management:** Features like session cloning and cookie manipulation are limited.
+
+* **Live Session Viewer:** While sessions are logged in the Steel Cloud app, we don’t currently have support for the live session viewer.
+
+
+
+
+Connecting to Steel with Selenium
+---------------------------------
+
+Most Selenium scripts start with a simple WebDriver setup that looks something like this:
+```python Python -wcn
+from selenium import webdriver
+
+driver = webdriver.Chrome() # or Firefox(), Safari(), etc.
+driver.get('https://example.com')
+```
+
+To run your script with Steel, you'll need to:
+
+* Create a session with Selenium support enabled
+
+* Set up custom header handling (required for authentication)
+
+* Connect using Steel's dedicated Selenium URL
+
+
+#### Here's what that looks like:
+
+First, create a custom connection handler for Steel-specific headers:
+
+```python Python -wcn
+from selenium.webdriver.remote.remote_connection import RemoteConnection
+
+class CustomRemoteConnection(RemoteConnection):
+ def __init__(self, remote_server_addr: str, session_id: str):
+ super().__init__(remote_server_addr)
+ self._session_id = session_id
+
+ def get_remote_connection_headers(self, parsed_url, keep_alive=False):
+ headers = super().get_remote_connection_headers(parsed_url, keep_alive)
+ headers.update({
+ 'steel-api-key': os.environ.get("STEEL_API_KEY"),
+ 'session-id': self._session_id
+ })
+ return headers
+
+```
+
+
+
+
+Then use it to connect to Steel:
+
+```python Python -wcn
+from steel import Steel
+from selenium import webdriver
+import os
+
+client = Steel(
+ steel_api_key=os.getenv('STEEL_API_KEY'),
+)
+
+def main():
+ # Create a session with Selenium support
+ session = client.sessions.create(
+ is_selenium=True, # Required for Selenium sessions
+ )
+
+ # Connect using the custom connection handler
+ driver = webdriver.Remote(
+ command_executor=CustomRemoteConnection(
+ remote_server_addr='http://connect.steelbrowser.com/selenium',
+ session_id=session.id
+ ),
+ options=webdriver.ChromeOptions()
+ )
+
+ # Run your automation
+ driver.get('https://example.com')
+
+ # Clean up when done
+ driver.quit()
+ client.sessions.release(session.id)
+
+if __name__ == "__main__":
+ main()
+```
+
+**Important**: Sessions remain active until explicitly released or timed out. It’s best practise to call `client.sessions.release()` when finished instead of relying on timeout.
+
+Why Custom Headers?
+-------------------
+
+Unlike Puppeteer and Playwright, Selenium doesn't natively support adding the headers required by Steel (session-id and steel-api-key). That's why we need to create a custom connection handler to include these headers with each request.
+
+Example Project: Scraping Hacker News
+-------------------------------------
+
+Here's a working example that scrapes Hacker News with proper error handling and session management:
+
+Starter code that scrapes Hacker News for top 5 stories using Steel's Python SDK and Selenium.
+
+
+
+To run it:
+
+* Add your `STEEL_API_KEY` to the secrets pane. It's located under "Tools" on the left hand pane.
+
+* Hit Run
+
+
+The example includes:
+
+* Complete session configuration options
+
+* Error handling best practices
+
+* A working Hacker News scraper example
+
+
+You can also clone it on [Github](https://github.com/steel-dev/steel-cookbook/tree/main/examples/steel-selenium-starter) or [Replit](https://replit.com/@steel-dev/steel-selenium-starter?v=1#README.md) to start editing it yourself!
diff --git a/content/docs/overview/meta.json b/content/docs/overview/meta.json
index 6e932450..60dc8658 100644
--- a/content/docs/overview/meta.json
+++ b/content/docs/overview/meta.json
@@ -7,6 +7,7 @@
"steel-cli",
"need-help",
"pricinglimits",
+ "...guides",
"...sessions-api",
"...credentials-api",
"...files-api",
diff --git a/content/docs/overview/sessions-api/human-in-the-loop.mdx b/content/docs/overview/sessions-api/human-in-the-loop.mdx
new file mode 100644
index 00000000..382da4c7
--- /dev/null
+++ b/content/docs/overview/sessions-api/human-in-the-loop.mdx
@@ -0,0 +1,143 @@
+---
+title: Implement Human-in-the-Loop Controls
+description: How to let users take control of Steel browser sessions
+sidebarTitle: Implement Human-in-the-Loop Controls
+llm: true
+---
+
+Steel's debug URL feature allows you to implement human-in-the-loop workflows where users can directly interact with and control browser sessions. This is particularly useful when you need users to take temporary control of automated browser sessions.
+
+### Prerequisites
+
+* Basic familiarity with [Steel sessions](https://docs.steel.dev/overview/sessions-api/overview)
+
+* Understanding of [debug URLs](https://docs.steel.dev/overview/guides/view-and-embed-live-sessions)
+
+* A Steel API key
+
+
+
+
+### Making Sessions Interactive
+
+To enable human interaction with a session, you'll need to configure two key parameters when embedding the session viewer:
+
+* `interactive=true`: Enables users to interact with the page through clicks, scrolling, and form inputs
+
+* `showControls=true`: Shows the navigation bar where users can enter URLs and use forward/back controls
+```typescript Typescript -wcn
+
+```
+
+When both parameters are enabled, users can:
+
+* Click and interact with elements on the page
+
+* Scroll the page
+
+* Enter new URLs in the navigation bar
+
+* Use browser-style forward/back navigation
+
+* Fill out forms and input fields
+
+* Navigate through websites naturally
+
+
+
+
+If you’re building user facing agents, this is particularly useful when you need users to:
+
+* Take control of an automated session that needs assistance
+
+* Enter sensitive information like login credentials
+
+* Solve CAPTCHAs
+
+* Verify or correct automated actions
+
+* Demonstrate actions that will be automated
+
+
+
+
+### Implementation Examples
+
+#### React Implementation
+
+Here's how to embed an interactive session viewer into a React Application:
+```typescript Typescript -wcn
+// SessionViewer.tsx
+import React from 'react';
+
+type SessionViewerProps = {
+ debugURL: string;
+};
+
+const SessionViewer: React.FC = ({ debugURL }) => {
+ return (
+
+
+ Automated session - Click inside to take control
+
+ );
+};
+
+export default App;
+```
+
+### Best Practices
+
+* Ensure your iframe container is large enough for comfortable interaction (recommended minimum height: 600px)
+
+* Make it clear to users when they can interact with the session
+
+* Remember that any actions taken in an interactive session affect the actual browser session & state
+
+
+
+
+### What's Next
+
+Learn about session timeouts for managing interactive sessions:
+
+Session Lifecycle
+
+Learn how to start and release browser sessions programatically.
diff --git a/content/docs/overview/sessions-api/meta.json b/content/docs/overview/sessions-api/meta.json
index b6f07b14..ea9fa109 100644
--- a/content/docs/overview/sessions-api/meta.json
+++ b/content/docs/overview/sessions-api/meta.json
@@ -1,5 +1,15 @@
{
"title": "Sessions API",
"root": false,
- "pages": ["---Sessions API---", "overview", "quickstart", "session-lifecycle", "mobile-mode","multi-region","embed-sessions"]
+ "pages": [
+ "---Sessions API---",
+ "overview",
+ "quickstart",
+ "session-lifecycle",
+ "mobile-mode",
+ "multi-region",
+ "embed-sessions",
+ "human-in-the-loop",
+ "reusing-auth-context"
+ ]
}
diff --git a/content/docs/overview/sessions-api/reusing-auth-context.mdx b/content/docs/overview/sessions-api/reusing-auth-context.mdx
new file mode 100644
index 00000000..f498dfb6
--- /dev/null
+++ b/content/docs/overview/sessions-api/reusing-auth-context.mdx
@@ -0,0 +1,183 @@
+---
+title: Reusing Context & Auth
+description: How to Reuse Authentication Across Steel Sessions
+sidebarTitle: Reusing Context & Auth
+llm: true
+---
+
+The Steel Sessions API provides a `contexts` endpoint that allows you to capture and transfer browser state (including cookies and local storage) between sessions. This is particularly useful for maintaining authenticated states across multiple sessions, helping your AI agents access protected resources efficiently without repeatedly handling login processes or exposing credentials at all.
+
+In this guide, you'll learn how to use the Steel Sessions API to reuse authentication between browser sessions.
+
+:::callout
+For an easier way to reuse authentication, context, cookies, extensions etc. consider using Steel's new [Profiles API](/overview/profiles-api/overview). It utilizes auth context alongside a complete browser profile to automatically reuse all your auth, not just context or cookies.
+:::
+
+For additional practical examples and recipes, check out the [Steel Cookbook](https://github.com/steel-dev/steel-cookbook).
+
+
+
+### Prerequisites
+
+* Steel API Key
+
+* [Steel SDK](https://github.com/steel-dev/steel-python) installed.
+```package-install
+steel-sdk
+```
+
+* Familiarity with [Steel sessions](https://docs.steel.dev/overview/sessions-api/overview)
+
+
+
+
+### Overview of the Process
+
+Reusing authentication across sessions involves a straightforward workflow:
+
+* **Create and authenticate an initial session.**
+ Create a Steel session, navigate to target websites, and authenticate (log-in, etc).
+
+* **Capture the session context.**
+ Extract browser state data through the `GET /v1/sessions/{id}/context` endpoint. This endpoint returns a context object containing browser state information such as cookies and local storage.
+ **Example:**
+
+ ```typescript Typescript -wcn
+ const initialSessionContext = await client.sessions.context(initialSession.id);
+ ```
+
+* **Reuse session context in new sessions.**
+ Create new sessions using the captured context object by passing it directly to the `sessionContext` parameter.
+ **Example:**
+
+ ```typescript Typescript -wcn
+ const session = await client.sessions.create({ sessionContext: initialSessionContext });
+ ```
+
+ Now your new session will begin with the same authenticated state as your previous session without having to manually authenticate again.
+
+
+
+
+### Complete Example (Playwright, Node.js)
+
+**Note**: While this example uses TypeScript, Node.js, and Playwright, the same logic applies regardless of your programming language or automation framework. The Steel API handles the context management - you just need to capture and reuse it using your preferred tools.
+
+The following script demonstrates the entire authentication reuse process. It:
+
+* Creates an initial session and authenticates with a webesite by logging in
+
+* Captures the authenticated session context
+
+* Creates a new session using the captured context
+
+* Verifies the authentication was successfully transferred to the new session
+
+
+```typescript Typescript -wcn
+import { chromium, Page } from "playwright";
+import Steel from "steel-sdk";
+import dotenv from "dotenv";
+
+dotenv.config();
+
+const client = new Steel({
+ steelAPIKey: process.env.STEEL_API_KEY,
+});
+
+// Helper function to perform login
+async function login(page: Page) {
+ await page.goto("https://practice.expandtesting.com/login");
+ await page.fill('input[name="username"]', "practice");
+ await page.fill('input[name="password"]', "SuperSecretPassword!");
+ await page.click('button[type="submit"]');
+}
+
+// Helper function to verify authentication
+async function verifyAuth(page: Page): Promise {
+ await page.goto("https://practice.expandtesting.com/secure");
+ const welcomeText = await page.textContent("#username");
+ return welcomeText?.includes("Hi, practice!") ?? false;
+}
+
+async function main() {
+ let session;
+ let browser;
+
+ try {
+ // Step 1: Create and authenticate initial session
+ console.log("Creating initial Steel session...");
+ session = await client.sessions.create();
+ console.log(
+ `\x1b[1;93mSteel Session #1 created!\x1b[0m\n` +
+ `View session at \x1b[1;37m${session.sessionViewerUrl}\x1b[0m`
+ );
+
+ // Connect Playwright to the session
+ browser = await chromium.connectOverCDP(
+ `wss://connect.steel.dev?apiKey=${process.env.STEEL_API_KEY}&sessionId=${session.id}`
+ );
+
+ const page = await browser.contexts()[0].pages()[0];
+ await login(page);
+
+ if (await verifyAuth(page)) {
+ console.log("✓ Authentication successful");
+ }
+
+ // Step 2: Capture and transfer authentication
+ const sessionContext = await client.sessions.context(session.id);
+
+ // Clean up first session
+ await browser.close();
+ await client.sessions.release(session.id);
+ console.log("Session #1 released");
+
+ // Step 3: Create new authenticated session
+
+ session = await client.sessions.create({ sessionContext });
+ console.log(
+ `\x1b[1;93mSteel Session #2 created!\x1b[0m\n` +
+ `View session at \x1b[1;37m${session.sessionViewerUrl}\x1b[0m`
+ );
+
+ // Connect to new session
+ browser = await chromium.connectOverCDP(
+ `wss://connect.steel.dev?apiKey=${process.env.STEEL_API_KEY}&sessionId=${session.id}`
+ );
+
+ // Verify authentication transfer
+ const newPage = await browser.contexts()[0].pages()[0];
+ if (await verifyAuth(newPage)) {
+ console.log("\x1b[32m✓ Authentication successfully transferred!\x1b[0m");
+ }
+ } catch (error) {
+ console.error("Error:", error);
+ } finally {
+ // Cleanup
+ await browser?.close();
+ if (session) {
+ await client.sessions.release(session.id);
+ console.log("Session #2 released");
+ }
+ }
+}
+
+main().catch(console.error);
+```
+
+Check out the full example
+
+### Important Considerations
+
+* **Cookie and JWT Based Authentication Only:**
+ This method works exclusively with websites that utilize cookie-based or JWT-based authentication (saved onto Local Storage).
+
+* **Enhancing Continuity:**
+ A useful practice is to save the URL of the last visited page along with the session context. This allows you to restore the browsing context, providing continuity for users.
+
+* **Session Security:**
+ Treat captured contexts as sensitive data. Ensure proper security and regularly refresh your sessions to maintain account integrity.
+
+* **Available for Live Sessions:**
+ Context can only be captures from live sessions. So if you wish to re-use a context, make sure to grab the object _before_ releasing the session.