
Agentcore Browser
- 1 installs
- 177 repo stars
- Updated June 23, 2026
- aws-samples/sample-host-openclaw-on-amazon-bedrock-agentcore
agentcore-browser is a skill that lets an AgentCore-hosted agent drive a headless Chromium browser to navigate, screenshot and interact with web pages.
About
Gives an agent a headless Chromium browser running inside the AgentCore container. It exposes navigate, screenshot, and interact actions (click, type, wait, scroll) that the agent calls to visit URLs, capture screenshots to the user's S3 namespace, and manipulate page elements. It requires enable_browser=true in the CDK config and runs each session in an isolated per-user microVM.
- Headless Chromium browser tool inside the AgentCore container
- Actions: navigate, screenshot, and interact (click, type, wait, scroll)
- Runs in an isolated per-user microVM with screenshots scoped to the user's S3
Agentcore Browser by the numbers
- 1 all-time installs (skills.sh)
- Ranked #14,098 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
agentcore-browser capabilities & compatibility
Runs inside an AWS Bedrock AgentCore container; requires that hosting environment and enable_browser=true.
- Capabilities
- s3 user files
- Works with
- aws
- Use cases
- web scraping · web search · testing
- Runs
- Hosted SaaS
- Pricing
- Bring your own API key
What agentcore-browser says it does
Headless Chromium browser running inside the AgentCore container. Navigate to URLs, take screenshots, and interact with page elements.
Browser runs inside an isolated per-user microVM
npx skills add https://github.com/aws-samples/sample-host-openclaw-on-amazon-bedrock-agentcore --skill agentcore-browserAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 177 |
| Last updated | June 23, 2026 |
| Repository | aws-samples/sample-host-openclaw-on-amazon-bedrock-agentcore ↗ |
What it does
Give a hosted AgentCore agent a headless Chromium browser to navigate, screenshot and interact with web pages.
Who is it for?
Agents hosted on AgentCore that need to visit sites, screenshot pages or fill forms
Skip if: Deployments without enable_browser=true, where the tools return an error
When should I use this skill?
The user asks to visit a website, take a screenshot, fill a form, or interact with a web page
What you get
The agent can navigate, screenshot and interact with web pages from inside its container.
- Page title and text content
- Page screenshots
- Interaction results
By the numbers
- Content truncated to 8000 characters
- Navigation timeout 30s, interaction 10s, wait 15s
Files
AgentCore Browser
Headless Chromium browser running inside the AgentCore container. Navigate to URLs, take screenshots, and interact with page elements.
Important
This skill requires enable_browser=true in CDK configuration. If the browser is not available, the tools will return a clear error message.
Usage
browser_navigate
Navigate to a URL and return the page title and text content.
node {baseDir}/navigate.js '{"url": "https://example.com"}'url(required): The URL to navigate to
Returns JSON: {"url": "...", "title": "...", "content": "..."}
Content is truncated to 8000 characters to keep responses manageable.
browser_screenshot
Take a screenshot of the current browser page and send it to the user.
node {baseDir}/screenshot.js '{"description": "Homepage after login"}'description(optional): Caption for the screenshot
Returns text with [SCREENSHOT:{s3key}] marker that the proxy converts to an image.
browser_interact
Interact with the current page — click elements, type text, wait for elements, or scroll.
node {baseDir}/interact.js '{"action": "click", "selector": "#submit-btn"}'
node {baseDir}/interact.js '{"action": "type", "selector": "#search", "text": "hello"}'
node {baseDir}/interact.js '{"action": "wait", "selector": ".results"}'
node {baseDir}/interact.js '{"action": "scroll"}'action(required): One ofclick,type,wait,scrollselector(optional): CSS selector for the target element (required for click, type, wait)text(optional): Text to type (required for type action)
Returns JSON: {"success": true, "message": "..."}
From Agent Chat
- "Go to example.com" -> browser_navigate with url
- "Take a screenshot" -> browser_screenshot
- "Click the login button" -> browser_interact with action=click
- "Type my email in the form" -> browser_interact with action=type
- "Scroll down" -> browser_interact with action=scroll
- "Wait for the results to load" -> browser_interact with action=wait
Security Notes
- Browser runs inside an isolated per-user microVM
- Screenshots are uploaded to the user's S3 namespace (no cross-user access)
- The browser session file is stored at
/tmp/agentcore-browser-session.json - Navigation timeout: 30s, interaction timeout: 10s, wait timeout: 15s
"use strict";
const fs = require("fs");
const BROWSER_SESSION_FILE = "/tmp/agentcore-browser-session.json";
const CONTENT_TRUNCATE_CHARS = 8000;
const NAV_TIMEOUT_MS = 30000;
const INTERACT_TIMEOUT_MS = 10000;
const WAIT_TIMEOUT_MS = 15000;
function getBrowserSession() {
if (!fs.existsSync(BROWSER_SESSION_FILE)) {
throw new Error(
"Browser session not available. Ensure enable_browser=true in CDK config and that the session has been initialized."
);
}
const data = JSON.parse(fs.readFileSync(BROWSER_SESSION_FILE, "utf8"));
if (!data.endpoint) throw new Error("Browser session file missing endpoint");
return data;
}
async function connectBrowser() {
const session = getBrowserSession();
const { chromium } = require("playwright-core");
const browser = await chromium.connectOverCDP(session.endpoint, {
timeout: 15000,
headers: session.headers || {},
});
const contexts = browser.contexts();
const context = contexts.length > 0 ? contexts[0] : await browser.newContext();
const pages = context.pages();
const page = pages.length > 0 ? pages[0] : await context.newPage();
return {
browser,
page,
disconnect: async () => {
try { await browser.close(); } catch {}
},
};
}
async function uploadScreenshotToS3(imageBuffer) {
const bucket = process.env.S3_USER_FILES_BUCKET;
if (!bucket) {
throw new Error("S3_USER_FILES_BUCKET environment variable is not set — cannot upload screenshot");
}
const userId = process.env.USER_ID || "default-user";
const namespace = userId.replace(/:/g, "_");
const timestamp = Date.now();
const key = `${namespace}/_screenshots/screenshot_${timestamp}.png`;
const { S3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
const client = new S3Client({ region: process.env.AWS_REGION || "us-east-1" });
await client.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: imageBuffer,
ContentType: "image/png",
}));
return key;
}
function truncateContent(text, maxChars) {
if (text.length <= maxChars) return text;
return text.slice(0, maxChars) + `\n\n[Content truncated at ${maxChars} characters]`;
}
const STEALTH_USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36";
async function applyStealthHeaders(page) {
await page.addInitScript(() => {
Object.defineProperty(navigator, "webdriver", { get: () => undefined });
Object.defineProperty(navigator, "plugins", { get: () => [1, 2, 3, 4, 5] });
Object.defineProperty(navigator, "languages", { get: () => ["en-US", "en"] });
window.chrome = { runtime: {} };
});
await page.setExtraHTTPHeaders({
"User-Agent": STEALTH_USER_AGENT,
"Accept-Language": "en-US,en;q=0.9",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
});
}
module.exports = {
getBrowserSession,
connectBrowser,
applyStealthHeaders,
uploadScreenshotToS3,
truncateContent,
CONTENT_TRUNCATE_CHARS,
NAV_TIMEOUT_MS,
INTERACT_TIMEOUT_MS,
WAIT_TIMEOUT_MS,
};
"use strict";
const { connectBrowser, applyStealthHeaders, INTERACT_TIMEOUT_MS, WAIT_TIMEOUT_MS } = require("./common");
const VALID_ACTIONS = new Set(["click", "type", "wait", "scroll"]);
async function browserInteract(args) {
const { action, selector, text } = args || {};
if (!action) return JSON.stringify({ error: "action is required" });
if (!VALID_ACTIONS.has(action)) {
return JSON.stringify({ error: `Invalid action. Must be one of: ${[...VALID_ACTIONS].join(", ")}` });
}
// Validate required args before connecting to browser
if ((action === "click" || action === "wait") && !selector) {
return JSON.stringify({ error: `selector is required for ${action}` });
}
if (action === "type") {
if (!selector) return JSON.stringify({ error: "selector is required for type" });
if (!text) return JSON.stringify({ error: "text is required for type" });
}
const { page, disconnect } = await connectBrowser();
try {
await applyStealthHeaders(page);
switch (action) {
case "click":
await page.click(selector, { timeout: INTERACT_TIMEOUT_MS });
return JSON.stringify({ success: true, message: `Clicked: ${selector}` });
case "type":
await page.fill(selector, text);
return JSON.stringify({ success: true, message: `Typed into: ${selector}` });
case "wait":
await page.waitForSelector(selector, { timeout: WAIT_TIMEOUT_MS });
return JSON.stringify({ success: true, message: `Element appeared: ${selector}` });
case "scroll":
await page.evaluate(() => window.scrollBy(0, 500));
return JSON.stringify({ success: true, message: "Scrolled down 500px" });
default:
return JSON.stringify({ error: `Unknown action: ${action}` });
}
} catch (err) {
if (err.message.includes("Browser session not available")) {
return JSON.stringify({ error: "Browser is not available. The enable_browser feature must be enabled in CDK configuration." });
}
return JSON.stringify({ error: `Interaction failed: ${err.message}` });
} finally {
await disconnect();
}
}
const args = JSON.parse(process.argv[2] || "{}");
browserInteract(args).then(console.log).catch(err => console.log(JSON.stringify({ error: err.message })));
"use strict";
const { connectBrowser, applyStealthHeaders, truncateContent, CONTENT_TRUNCATE_CHARS, NAV_TIMEOUT_MS } = require("./common");
async function browserNavigate(args) {
const { url } = args;
if (!url) return JSON.stringify({ error: "url is required" });
const { page, disconnect } = await connectBrowser();
try {
await applyStealthHeaders(page);
await page.goto(url, { waitUntil: "domcontentloaded", timeout: NAV_TIMEOUT_MS });
const title = await page.title();
// Extract readable text — remove script/style noise
let content = "";
try {
content = await page.evaluate(() => {
const clone = document.cloneNode(true);
clone.querySelectorAll("script, style, noscript, iframe").forEach(el => el.remove());
return clone.body?.innerText || clone.body?.textContent || "";
});
} catch (_) {
// Execution context may be destroyed during navigation — title is enough as fallback
}
return JSON.stringify({
url: page.url(),
title,
content: truncateContent(content.replace(/\s+/g, " ").trim(), CONTENT_TRUNCATE_CHARS),
});
} catch (err) {
if (err.message.includes("Browser session not available")) {
return JSON.stringify({ error: "Browser is not available. The enable_browser feature must be enabled in CDK configuration." });
}
return JSON.stringify({ error: `Navigation failed: ${err.message}` });
} finally {
await disconnect();
}
}
// CLI entrypoint (called by OpenClaw)
const args = JSON.parse(process.argv[2] || "{}");
browserNavigate(args).then(console.log).catch(err => console.log(JSON.stringify({ error: err.message })));
"use strict";
const { connectBrowser, uploadScreenshotToS3 } = require("./common");
async function browserScreenshot(args) {
const { description } = args || {};
const { page, disconnect } = await connectBrowser();
try {
const imageBuffer = await page.screenshot({ type: "png", fullPage: false });
const s3Key = await uploadScreenshotToS3(imageBuffer);
const caption = description ? ` — ${description}` : "";
return `Screenshot taken${caption}: [SCREENSHOT:${s3Key}]`;
} catch (err) {
if (err.message.includes("Browser session not available")) {
return JSON.stringify({ error: "Browser is not available. The enable_browser feature must be enabled in CDK configuration." });
}
return JSON.stringify({ error: `Screenshot failed: ${err.message}` });
} finally {
await disconnect();
}
}
const args = JSON.parse(process.argv[2] || "{}");
browserScreenshot(args).then(console.log).catch(err => console.log(JSON.stringify({ error: err.message })));
Related skills
FAQ
What actions can it perform?
Navigate to a URL, take a screenshot, and interact via click, type, wait, or scroll.
What is required to enable it?
enable_browser=true in the CDK configuration; otherwise the tools return a clear error message.