Skip to main content

What is act()?

page.act("click on add to cart")
act enables Stagehand to perform individual actions on a web page. Use it to build self-healing and deterministic automations that adapt to website changes.

Why use act()?

Natural Language Instructions

Write automation in plain English. No selectors or complex syntax.

Precise Control

Build automations step by step. Define exactly what happens at every moment.

Self-Healing

Actions automatically adapt when websites change.

Caching

Cache actions to avoid LLM calls and ensure consistent execution across runs.

Using act()

Use act to perform single actions in your automation. Here’s how to click a button:
await page.goto("https://example-store.com");
await page.act("click the add to cart button");
await page.goto("https://example-store.com")
await page.act("click the add to cart button")
With act, breaking complex actions into small, single-step actions works best. If you need to orchestrate multi-step flows, use multiple act commands or agent.
ActionExample instruction
Clickclick the button
Fillfill the field with <value>
Typetype <text> into the search box
Presspress <key> in the search field
Scrollscroll to <position>
Select from dropdownselect <value> from the dropdown
Break your task into single-step actions.
// Break it into single-step actions
await page.act("open the filters panel");
await page.act("choose 4-star rating");
await page.act("click the apply button");
# Break it into single-step actions
await page.act("open the filters panel")
await page.act("choose 4-star rating")
await page.act("click the apply button")

Advanced Configuration

For advanced scenarios, you can configure additional options:
// Dynamic food search with advanced options
const foodItem = "organic quinoa";

await page.act({
  action: "Type %foodItem% in the search box and press enter",
  variables: {
    foodItem: foodItem
  },
  modelName: "google/gemini-2.5-pro",
  modelClientOptions: {
    modelApiKey: process.env.GOOGLE_API_KEY,
  },
  iframes: true, // Search within iframes if needed
  domSettleTimeoutMs: 45000, // Wait longer for dynamic content
  timeoutMs: 60000 // Extended timeout for slow-loading forms
});
# Dynamic food search with advanced options
food_item = "organic quinoa"

await page.act({
  "action": "Type %foodItem% in the search box and press enter",
  "variables": {
    "foodItem": food_item
  },
  "modelName": "google/gemini-2.5-pro",
  "modelClientOptions": {
    "modelApiKey": os.environ.get("GOOGLE_API_KEY")
  },
  "iframes": True, # Search within iframes if needed
  "domSettleTimeoutMs": 45000, # Wait longer for dynamic content
  "timeoutMs": 60000 # Extended timeout for slow-loading forms
})
Shadow DOM support is now available! Set experimental: true in your Stagehand configuration to enable it. See the configuration guide for more details.

Best practices

Ensure reliable actions

Use observe() to discover candidate actions on the current page and plan reliably. It returns a list of suggested actions (with selector, description, method, and arguments). You can pass an observed action directly to act to execute it.
const [action] = await page.observe("click the login button");

if (action) {
  await page.act(action);
}
results = await page.observe("click the login button")

if results:
    await page.act(results[0])

Analyze pages with observe()

Plan actions with observe() before executing with act.

Reduce model costs

Cache observed actions to avoid repeated LLM calls and ensure consistent execution.
// Cost-optimized actions with caching
const actionCache = new Map();

const getCachedAction = async (instruction: string) => {
  if (actionCache.has(instruction)) {
    return actionCache.get(instruction);
  }
  
  const [action] = await page.observe(instruction);
  actionCache.set(instruction, action);
  return action;
};

// Reuse cached actions
const loginAction = await getCachedAction("click the login button");
await page.act(loginAction);
# Cost-optimized actions with caching
action_cache = {}

async def get_cached_action(instruction: str):
    if instruction in action_cache:
        return action_cache[instruction]
    
    results = await page.observe(instruction)
    if results:
        action = results[0]
        action_cache[instruction] = action
        return action
    
    return None

# Reuse cached actions
login_action = await get_cached_action("click the login button")
if login_action:
    await page.act(login_action)

Complete caching guide

Learn advanced caching techniques and patterns for optimal performance.

Secure your automations

Variables will not be shared with LLM providers. Use them for passwords, API keys, and other sensitive data.
Load sensitive data from environment variables using .env files. Never hardcode API keys, passwords, or other secrets directly in your code.
await page.act({
  action: "enter %username% in the email field",
  variables: {
    username: "user@example.com"
  }
});

await page.act({
  action: "enter %password% in the password field",
  variables: {
    password: process.env.USER_PASSWORD
  }
});
# If using Python, set `use_api: true` in your Stagehand configuration

await page.act(
  "enter %username% in the email field",
  variables={
      "username": "user@example.com"
  }
)

await page.act(
  "enter %password% in the password field",
  variables={
      "password": os.environ.get("USER_PASSWORD")
  }
)
When handling sensitive data, set verbose: 0 in your Stagehand configuration to prevent secrets from appearing in logs. See the configuration guide for more details.

User Data Best Practices

Complete guide to securing your browser automations with best practices and configurations.

Troubleshooting

Problem: act fails with “method not supported” errorSolutions:
  • Use clear and detailed instructions for what you want to accomplish
  • Review our evals to find the best models for your use case
  • Use observe() and verify the resulting action is within a list of expected actions
Solution 1: Validate with observe
const prompt = "click the submit button";
const expectedMethod = "click";

try {
  await page.act(prompt);
} catch (error) {
  if (error.message.includes("method not supported")) {
    // Observe the same prompt to get the planned action
    const [action] = await page.observe(prompt);
    
    if (action && action.method === expectedMethod) {
      await page.act(action);
    } else {
      throw new Error(`Unsupported method: expected "${expectedMethod}", got "${action?.method}"`);
    }
  } else {
    throw error;
  }
}
prompt = "click the submit button"
expected_method = "click"

try:
    await page.act(prompt)
except Exception as error:
    if "method not supported" in str(error):
        # Observe the same prompt to get the planned action
        results = await page.observe(prompt)
        
        if results and results[0].method == expected_method:
            await page.act(results[0])
        else:
            method = results[0].method if results else "unknown"
            raise Exception(f'Unsupported method: expected "{expected_method}", got "{method}"')
    else:
        raise error
Solution 2: Retry with exponential backoff
// Retry with exponential backoff for intermittent issues
const prompt = "click the submit button";
const maxRetries = 3;

for (let attempt = 0; attempt <= maxRetries; attempt++) {
  try {
    await page.act(prompt, { timeoutMs: 10000 + (attempt * 5000) });
    break; // Success, exit retry loop
  } catch (error) {
    if (error.message.includes("method not supported") && attempt < maxRetries) {
      // Exponential backoff: wait 2^attempt seconds
      const delay = Math.pow(2, attempt) * 1000;
      console.log(`Retry ${attempt + 1}/${maxRetries} after ${delay}ms`);
      await new Promise(resolve => setTimeout(resolve, delay));
    } else {
      throw error;
    }
  }
}
# Retry with exponential backoff for intermittent issues
import asyncio

prompt = "click the submit button"
max_retries = 3

for attempt in range(max_retries + 1):
    try:
        timeout = 10000 + (attempt * 5000)
        await page.act(prompt, {"timeoutMs": timeout})
        break  # Success, exit retry loop
    except Exception as error:
        if "method not supported" in str(error) and attempt < max_retries:
            # Exponential backoff: wait 2^attempt seconds
            delay = 2 ** attempt
            print(f"Retry {attempt + 1}/{max_retries} after {delay}s")
            await asyncio.sleep(delay)
        else:
            raise error
Problem: act times out or fails to complete action (often due to element not found)Solutions:
// Handle timeout and element not found issues
try {
  await page.act("click the submit button", { timeout: 30000 });
} catch (error) {
  // Check if page is fully loaded
  await page.waitForLoadState('domcontentloaded');
  
  // Use observe to check element state
  const [element] = await page.observe("find the submit button");
  
  if (element) {
    console.log("Element found, trying more specific instruction");
    await page.act("click the submit button at the bottom of the form");
  } else {
    console.log("Element not found, trying alternative selector");
    await page.act("click the button with text 'Submit'");
  }
}
# Handle timeout and element not found issues
try:
    await page.act("click the submit button", {"timeout": 30000})
except Exception as error:
    # Check if page is fully loaded
    await page.wait_for_load_state('domcontentloaded')
    
    # Use observe to check element state
    results = await page.observe("find the submit button")
    
    if results:
        print("Element found, trying more specific instruction")
        await page.act("click the submit button at the bottom of the form")
    else:
        print("Element not found, trying alternative selector")
        await page.act("click the button with text 'Submit'")
Problem: act performs action on wrong elementSolutions:
  • Be more specific in instructions: include visual cues, position, or context
  • Use observe() to preview which element will be selected
  • Add contextual information: “the search button in the header”
  • Use unique identifiers when available
// More precise element targeting
// Instead of:
await page.act("click the button");

// Use specific context:
await page.act("click the red 'Delete' button next to the user John Smith");

// Or preview with observe first:
const [action] = await page.observe("click the submit button in the checkout form");
if (action.description.includes("checkout")) {
  await page.act(action);
}
# More precise element targeting
# Instead of:
await page.act("click the button")

# Use specific context:
await page.act("click the red 'Delete' button next to the user John Smith")

# Or preview with observe first:
results = await page.observe("click the submit button in the checkout form")
if results and "checkout" in results[0].description:
    await page.act(results[0])

Next steps

Orchestrate complex workflows with Agent

Use Agent to autonomously execute multi-step tasks and complex workflows.

Caching actions

Speed up repeated automations by caching actions.

Extract data with extract()

Use extract with a data schema to pull clean, typed data from any page.

Working with iframes

Learn best practices for interacting with elements inside iframes.