> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stagehand.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Act

> Interact with a web page

## What is `act()`?

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    await stagehand.act("click on add to cart");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    await stagehand.act("click on add to cart")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    if _, err := client.Act(ctx, stagehand.ActInstruction("click on add to cart"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

`act` performs one action on a web page. Chain single-step calls to build automations that survive website changes.

`act` accepts either a natural language instruction or an `Action` returned by [`observe()`](/v4/basics/observe). Passing an instruction runs model inference to find the target; passing an `Action` replays it deterministically with no inference at all.

## Why use `act()`?

<CardGroup cols={2}>
  <Card title="Natural language instructions" icon="wand-magic-sparkles" href="#using-act">
    Write automation in plain English. No selectors or complex syntax.
  </Card>

  <Card title="Precise control" icon="crosshairs" href="#best-practices">
    Build automations step by step. Define exactly what happens at every moment.
  </Card>

  <Card title="Self-healing" icon="bandage" href="#ensure-reliable-actions">
    Turn on `selfHeal` to re-infer an action when its recorded selector breaks.
  </Card>

  <Card title="Caching" icon="repeat" href="#reduce-model-costs">
    Cache actions to avoid LLM calls and ensure consistent execution across runs.
  </Card>
</CardGroup>

## Using `act()`

Use `act` to perform single actions in your automation. Here's how to click a button:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    await page.goto("https://example-store.com");
    await stagehand.act("click the add to cart button");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    await page.goto("https://example-store.com")
    await stagehand.act("click the add to cart button")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    if _, err := page.Goto(ctx, "https://example-store.com", nil); err != nil {
    	return err
    }
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the add to cart button"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

<Note>
  **iFrame and Shadow DOM support** Stagehand automatically handles iFrame traversal and shadow DOM elements without requiring additional configuration. Page snapshots merge every frame's accessibility tree by default.
</Note>

With `act`, breaking complex actions into small, single-step actions works best. If you need to orchestrate multi-step flows, use multiple `act` commands.

<Accordion title="Suggested actions">
  | Action               | `method`                   | Example instruction                  |
  | -------------------- | -------------------------- | ------------------------------------ |
  | Click                | `click`                    | `click the button`                   |
  | Double click         | `doubleClick`              | `double click the file name`         |
  | Fill                 | `fill`                     | `fill the field with <value>`        |
  | Type                 | `type`                     | `type <text> into the search box`    |
  | Press                | `press`                    | `press <key> in the search field`    |
  | Hover                | `hover`                    | `hover over the account menu`        |
  | Scroll               | `scrollTo`                 | `scroll to <position>`               |
  | Scroll one screen    | `nextChunk` / `prevChunk`  | `scroll down one page`               |
  | Select from dropdown | `selectOptionFromDropdown` | `select <value> from the dropdown`   |
  | Drag and drop        | `dragAndDrop`              | `drag the card onto the Done column` |

  The `method` column is the value you will see on an `Action` returned by [`observe()`](/v4/basics/observe).
</Accordion>

### Return value of `act()`

When you use `act()`, Stagehand returns a result with two fields: `data` holds the action payload, and `metadata` carries the action ID and server-side cache status.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    {
      data: {
        success: true,
        message: 'Action [click] performed successfully on selector: xpath=/html[1]/body[1]/div[1]/span[1]',
        actionDescription: 'Favorite Colour',
        actions: [
          {
            selector: 'xpath=/html[1]/body[1]/div[1]/span[1]',
            description: 'Favorite Colour',
            method: 'click',
            arguments: []
          },
          {
            selector: 'xpath=/html[1]/body[1]/div[2]/div[1]/section[1]/div[1]/div[1]/div[25]',
            description: 'Peach',
            method: 'click',
            arguments: []
          }
        ]
      },
      metadata: {
        actionId: 'act_01HZY...',
        cache: { status: 'MISS', missReason: 'not_found' }
      }
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    ActResult(
        data=ActResultData(
            success=True,
            message="Action [click] performed successfully on selector: xpath=/html[1]/body[1]/div[1]/span[1]",
            action_description="Favorite Colour",
            actions=[
                Action(
                    selector="xpath=/html[1]/body[1]/div[1]/span[1]",
                    description="Favorite Colour",
                    method="click",
                    arguments=[],
                ),
                Action(
                    selector="xpath=/html[1]/body[1]/div[2]/div[1]/section[1]/div[1]/div[1]/div[25]",
                    description="Peach",
                    method="click",
                    arguments=[],
                ),
            ],
        ),
        metadata=StagehandResultMetadata(
            action_id="act_01HZY...",
            cache={"status": "MISS", "miss_reason": "not_found"},
        ),
    )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    clickMethod := "click"
    actionID := "act_01HZY..."
    cacheMetadata := stagehand.CacheMetadata{Status: stagehand.CacheStatusMISS}

    result := stagehand.ActResult{
    	Data: stagehand.ActResultData{
    		Success:           true,
    		Message:           "Action [click] performed successfully on selector: xpath=/html[1]/body[1]/div[1]/span[1]",
    		ActionDescription: "Favorite Colour",
    		Actions: []stagehand.Action{
    			{
    				Selector:    "xpath=/html[1]/body[1]/div[1]/span[1]",
    				Description: "Favorite Colour",
    				Method:      &clickMethod,
    				Arguments:   []string{},
    			},
    			{
    				Selector:    "xpath=/html[1]/body[1]/div[2]/div[1]/section[1]/div[1]/div[1]/div[25]",
    				Description: "Peach",
    				Method:      &clickMethod,
    				Arguments:   []string{},
    			},
    		},
    	},
    	Metadata: stagehand.StagehandResultMetadata{
    		ActionID:    &actionID,
    		Cache: &cacheMetadata,
    	},
    }

    // Read the fields you care about off the result
    fmt.Println(result.Data.Message, result.Metadata.Cache.Status)
    ```
  </Tab>
</Tabs>

<Tabs>
  <Tab title="Do this" icon="check">
    Break your task into single-step actions.

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        // Break it into single-step actions
        await stagehand.act("open the filters panel");
        await stagehand.act("choose 4-star rating");
        await stagehand.act("click the apply button");
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        # Break it into single-step actions
        await stagehand.act("open the filters panel")
        await stagehand.act("choose 4-star rating")
        await stagehand.act("click the apply button")
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        // Break it into single-step actions
        if _, err := client.Act(ctx, stagehand.ActInstruction("open the filters panel"), nil); err != nil {
        	return err
        }
        if _, err := client.Act(ctx, stagehand.ActInstruction("choose 4-star rating"), nil); err != nil {
        	return err
        }
        if _, err := client.Act(ctx, stagehand.ActInstruction("click the apply button"), nil); err != nil {
        	return err
        }
        ```
      </Tab>
    </Tabs>
  </Tab>

  <Tab title="Don't do this" icon="xmark">
    Multi-step instructions are unreliable. Sequence them yourself instead.

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        // Too complex - trying to do multiple things at once
        await stagehand.act("open the filters panel, choose 4-star rating, and click apply");
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        # Too complex - trying to do multiple things at once
        await stagehand.act("open the filters panel, choose 4-star rating, and click apply")
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        // Too complex - trying to do multiple things at once
        if _, err := client.Act(ctx, stagehand.ActInstruction("open the filters panel, choose 4-star rating, and click apply"), nil); err != nil {
        	return err
        }
        ```
      </Tab>
    </Tabs>
  </Tab>
</Tabs>

## Advanced configuration

You can pass additional options to configure the model, timeout, variables, target page, and target locator:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Custom model configuration
    await stagehand.act("choose 'Peach' from the favorite color dropdown", {
      model: {
        modelName: "google/gemini-2.5-flash",
        apiKey: process.env.GOOGLE_API_KEY,
      },
      timeout: 10000,
      locator: page.locator("form"),
      ignoreLocators: [page.locator(".promo-modal")],
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Custom model configuration
    import os

    from stagehand import ModelConfig

    await stagehand.act(
        "choose 'Peach' from the favorite color dropdown",
        model=ModelConfig(
            model_name="google/gemini-2.5-flash",
            api_key=os.environ["GOOGLE_API_KEY"],
        ),
        timeout=10000,
        locator=page.locator("form"),
        ignore_locators=[page.locator(".promo-modal")],
    )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Custom model configuration
    modelAPIKey := os.Getenv("GOOGLE_API_KEY")
    model := stagehand.ModelConfig{
    	ModelName: "google/gemini-2.5-flash",
    	APIKey:    &modelAPIKey,
    }
    timeout := 10000.0

    if _, err := client.Act(ctx, stagehand.ActInstruction("choose 'Peach' from the favorite color dropdown"), &stagehand.StagehandClientActOptions{
    	Page:    page,
    	Model:   &model,
    	Timeout: &timeout,
    	Locator: page.Locator("form"),
    	IgnoreLocators: []*stagehand.PageLocator{
    		page.Locator(".promo-modal"),
    	},
    }); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

Target locators only affect instruction-based `act()` calls. When you pass an `Action` returned by `observe()`, Stagehand replays that action's selector directly.

### Server-side caching

<Note>
  `cache` requires a Browserbase browser and a Browserbase API key. It has no effect on local browsers.
</Note>

When running on Browserbase, Stagehand can cache `act()` results server-side. Repeated calls with the same inputs return instantly without consuming LLM tokens. Enable caching on the constructor and override it per call:

Instruction-based `act()` calls with a target locator or ignored locators bypass the server-side cache and report `metadata.cache.status` as `DISABLED`.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { browserbase, Stagehand } from "@browserbasehq/stagehand";

    // Enable server-side caching for the entire instance
    const browser = await browserbase.launch({
      apiKey: process.env.BROWSERBASE_API_KEY,
    });
    const stagehand = await Stagehand.create({
      browser,
      cache: true,
    });

    // Or disable it for a single call
    await stagehand.act("click the login button", { cache: false });

    // Or lower the hit-count threshold for a single call
    await stagehand.act("click the login button", { cache: { threshold: 1 } });

    // Check whether a result was served from cache
    const result = await stagehand.act("click the login button");
    console.log(result.metadata.cache.status); // "HIT", "MISS", or "DISABLED"
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os

    from stagehand import CacheOptions, Stagehand, browserbase

    # Enable server-side caching for the entire instance
    browser = await browserbase.launch(
        api_key=os.environ["BROWSERBASE_API_KEY"],
    )
    stagehand = await Stagehand.create(
        browser=browser,
        cache=True,
    )

    # Or disable it for a single call
    await stagehand.act("click the login button", cache=False)

    # Or lower the hit-count threshold for a single call
    await stagehand.act("click the login button", cache=CacheOptions(threshold=1))

    # Check whether a result was served from cache
    result = await stagehand.act("click the login button")
    print(result.metadata.cache.status)  # "HIT", "MISS", or "DISABLED"
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Enable server-side caching for the entire instance
    apiKey := os.Getenv("BROWSERBASE_API_KEY")
    instanceCache := stagehand.CacheEnabled(true)
    browser, err := stagehand.LaunchBrowserbase(ctx, stagehand.BrowserbaseLaunchOptions{APIKey: apiKey})
    if err != nil {
    	return err
    }
    client, err := stagehand.Create(ctx, stagehand.CreateOptions{
    	Browser: browser,
    	Cache:   &instanceCache,
    })

    // Or disable it for a single call
    off := stagehand.CacheEnabled(false)
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the login button"), &stagehand.StagehandClientActOptions{
    	Cache: &off,
    }); err != nil {
    	return err
    }

    // Or lower the hit-count threshold for a single call
    eager := stagehand.CacheWithThreshold(1)
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the login button"), &stagehand.StagehandClientActOptions{
    	Cache: &eager,
    }); err != nil {
    	return err
    }

    // Check whether a result was served from cache
    result, err := client.Act(ctx, stagehand.ActInstruction("click the login button"), nil)
    if err != nil {
    	return err
    }
    fmt.Println(result.Metadata.Cache.Status) // "HIT", "MISS", or "DISABLED"
    ```
  </Tab>
</Tabs>

<Card title="Complete caching guide" icon="database" iconType="sharp-solid" href="/v4/best-practices/caching">
  Learn how the cache key is built, what the threshold does, and when results are invalidated.
</Card>

### Using with custom pages

Stagehand v4 drives the browser directly over the Chrome DevTools Protocol, so there is no Puppeteer, Playwright, or Patchright page interop. Instead, target any page Stagehand manages by passing the `page` option:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    const browser = await browserbase.launch({
      apiKey: process.env.BROWSERBASE_API_KEY,
    });
    const stagehand = await Stagehand.create({ browser });

    // Open a second page in the same browser context
    const blogPage = await browser.context.newPage("https://www.example.com/blog");

    // Use act with that specific page
    await stagehand.act("click the next page button", {
      page: blogPage,
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    browser = await browserbase.launch(
        api_key=os.environ["BROWSERBASE_API_KEY"],
    )
    stagehand = await Stagehand.create(browser=browser)

    # Open a second page in the same browser context
    blog_page = await browser.context.new_page("https://www.example.com/blog")

    # Use act with that specific page
    await stagehand.act("click the next page button", page=blog_page)
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    apiKey := os.Getenv("BROWSERBASE_API_KEY")
    browser, err := stagehand.LaunchBrowserbase(ctx, stagehand.BrowserbaseLaunchOptions{APIKey: apiKey})
    if err != nil {
    	return err
    }
    client, err := stagehand.Create(ctx, stagehand.CreateOptions{Browser: browser})
    if err != nil {
    	return err
    }

    browserContext, err := browser.Context()
    if err != nil {
    	return err
    }

    // Open a second page in the same browser context
    blogPage, err := browserContext.NewPage(ctx, "https://www.example.com/blog")
    if err != nil {
    	return err
    }

    // Use Act with that specific page
    _, err = client.Act(ctx, stagehand.ActInstruction("click the next page button"), &stagehand.StagehandClientActOptions{
    	Page: blogPage,
    })
    ```
  </Tab>
</Tabs>

This works with:

* **Active page:** omit `page` and Stagehand uses `context.activePage()` (default)
* **Existing pages:** receive the list from `context.pages()` first (await it in TypeScript and Python), then index into it
* **New pages:** create one with `context.newPage()`
* **Existing browsers:** attach to a browser you already run with `localBrowser.connect()`

<Card title="Complete API reference" icon="book" href="/v4/reference/stagehand">
  See the full `Stagehand` reference for detailed parameter documentation, return values, and advanced examples.
</Card>

## 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). Inspect the action before you commit to it, then hand it straight back to `act`:

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    const { data: actions } = await stagehand.observe("click the login button");
    const [action] = actions;

    if (action?.method === "click") {
      // No inference: Stagehand replays the observed action
      await stagehand.act(action);
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    result = await stagehand.observe("click the login button")

    if result.data and result.data[0].method == "click":
        # No inference: Stagehand replays the observed action
        await stagehand.act(result.data[0])
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    instruction := "click the login button"
    observed, err := client.Observe(ctx, &instruction, nil)
    if err != nil {
    	return err
    }

    if len(observed.Data) > 0 && observed.Data[0].Method != nil && *observed.Data[0].Method == "click" {
    	// No inference: Stagehand replays the observed action
    	if _, err := client.Act(ctx, stagehand.ObservedAction(observed.Data[0]), nil); err != nil {
    		return err
    	}
    }
    ```
  </Tab>
</Tabs>

<Note>
  Replaying an `Action` skips model inference, the page snapshot, and the DOM-settle wait, and it does not consult the server-side cache. If the recorded selector no longer resolves and `selfHeal` is on, Stagehand re-infers the action and retries once.
</Note>

<Card title="Analyze pages with observe()" icon="magnifying-glass" iconType="sharp-solid" href="/v4/basics/observe">
  Plan actions with `observe()` before executing with `act`.
</Card>

### Reduce model costs

Enable server-side caching with `cache` when running on Browserbase. Stagehand records the action on the first run; once the hit count meets the threshold, the cache serves identical calls without an LLM call.

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    import { browserbase, Stagehand } from "@browserbasehq/stagehand";

    const browser = await browserbase.launch({
      apiKey: process.env.BROWSERBASE_API_KEY,
    });
    const stagehand = await Stagehand.create({
      browser,
      cache: { threshold: 1 }, // Actions are cached after one identical result
    });

    // First run makes an LLM call and records the action
    await stagehand.act("click the login button");

    // Second run is served from the cache
    await stagehand.act("click the login button");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os

    from stagehand import CacheOptions, Stagehand, browserbase

    browser = await browserbase.launch(
        api_key=os.environ["BROWSERBASE_API_KEY"],
    )
    stagehand = await Stagehand.create(
        browser=browser,
        cache=CacheOptions(threshold=1),  # Actions are cached after one identical result
    )

    # First run makes an LLM call and records the action
    await stagehand.act("click the login button")

    # Second run is served from the cache
    await stagehand.act("click the login button")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    apiKey := os.Getenv("BROWSERBASE_API_KEY")
    cache := stagehand.CacheWithThreshold(1) // Actions are cached after one identical result

    browser, err := stagehand.LaunchBrowserbase(ctx, stagehand.BrowserbaseLaunchOptions{APIKey: apiKey})
    if err != nil {
    	return err
    }
    client, err := stagehand.Create(ctx, stagehand.CreateOptions{Browser: browser, Cache: &cache})
    if err != nil {
    	return err
    }

    // First run makes an LLM call and records the action
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the login button"), nil); err != nil {
    	return err
    }

    // Second run is served from the cache
    if _, err := client.Act(ctx, stagehand.ActInstruction("click the login button"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

<Note>
  The cache lives on Browserbase, keyed on the instruction, page content, and call options, so it persists across script executions and across machines. The model configuration is deliberately excluded from the key, so switching models does not invalidate your cache.
</Note>

<Card title="Complete caching guide" icon="database" iconType="sharp-solid" href="/v4/best-practices/caching">
  Learn advanced caching techniques and patterns for optimal performance.
</Card>

### Secure your automations

Variables are **not shared with LLM providers**. Use them for passwords, API keys, and other sensitive data. Stagehand exposes only the variable names to the model and substitutes the real values locally, so results record the placeholder rather than the secret. One exception: with [server-side caching](/v4/best-practices/caching) enabled, variable values travel to the cache service as part of the request, so turn the `cache` option off for calls that carry credentials.

<Note>
  Load sensitive data from environment variables. Never hardcode API keys, passwords, or other secrets directly in your code.
</Note>

<Tabs>
  <Tab title="TypeScript">
    ```typescript theme={null}
    // Variables use %variableName% syntax in the instruction
    await stagehand.act("type %username% into the email field", {
      variables: { username: "user@example.com" },
    });

    await stagehand.act("type %password% into the password field", {
      variables: { password: process.env.USER_PASSWORD },
    });

    await stagehand.act("click the login button");
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Variables use %variable_name% syntax in the instruction
    await stagehand.act(
        "type %username% into the email field",
        variables={"username": "user@example.com"},
    )

    await stagehand.act(
        "type %password% into the password field",
        variables={"password": os.environ["USER_PASSWORD"]},
    )

    await stagehand.act("click the login button")
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // Variables use %variableName% syntax in the instruction
    if _, err := client.Act(ctx, stagehand.ActInstruction("type %username% into the email field"), &stagehand.StagehandClientActOptions{
    	Variables: stagehand.Variables{
    		"username": stagehand.PrimitiveVariable(stagehand.StringVariable("user@example.com")),
    	},
    }); err != nil {
    	return err
    }

    userPassword := os.Getenv("USER_PASSWORD")
    if _, err := client.Act(ctx, stagehand.ActInstruction("type %password% into the password field"), &stagehand.StagehandClientActOptions{
    	Variables: stagehand.Variables{
    		"password": stagehand.PrimitiveVariable(stagehand.StringVariable(userPassword)),
    	},
    }); err != nil {
    	return err
    }

    if _, err := client.Act(ctx, stagehand.ActInstruction("click the login button"), nil); err != nil {
    	return err
    }
    ```
  </Tab>
</Tabs>

<Warning>
  When handling sensitive data, turn logging off in your Stagehand configuration to prevent secrets from appearing in logs. See the [logging guide](/v4/configuration/logging) for more details.
</Warning>

<Card title="User data best practices" icon="shield-check" iconType="sharp-solid" href="/v4/best-practices/user-data">
  Complete guide to persisting and securing browser state across sessions.
</Card>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Method not supported">
    **Problem**: `act` fails with "method not supported" error

    **Solutions**:

    * Use clear and detailed instructions for what you want to accomplish
    * Review the [Stagehand evals](https://stagehand.dev/evals) to find the best models for your use case
    * Use [`observe()`](/v4/basics/observe) and verify the resulting action is within a list of expected actions

    **Solution 1: Validate with observe**

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        const prompt = "click the submit button";
        const expectedMethod = "click";

        try {
          await stagehand.act(prompt);
        } catch (error) {
          const message = error instanceof Error ? error.message : String(error);

          if (message.includes("method not supported")) {
            // Observe the same prompt to get the planned action
            const { data: actions } = await stagehand.observe(prompt);
            const [action] = actions;

            if (action && action.method === expectedMethod) {
              await stagehand.act(action);
            } else {
              throw new Error(`Unsupported method: expected "${expectedMethod}", got "${action?.method}"`);
            }
          } else {
            throw error;
          }
        }
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        prompt = "click the submit button"
        expected_method = "click"

        try:
            await stagehand.act(prompt)
        except Exception as error:
            if "method not supported" not in str(error):
                raise

            # Observe the same prompt to get the planned action
            observed = await stagehand.observe(prompt)
            action = observed.data[0] if observed.data else None

            if action is not None and action.method == expected_method:
                await stagehand.act(action)
            else:
                method = action.method if action else None
                raise RuntimeError(f'Unsupported method: expected "{expected_method}", got "{method}"')
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        prompt := "click the submit button"
        expectedMethod := "click"

        if _, err := client.Act(ctx, stagehand.ActInstruction(prompt), nil); err != nil {
        	if !strings.Contains(err.Error(), "method not supported") {
        		return err
        	}

        	// Observe the same prompt to get the planned action
        	observed, observeErr := client.Observe(ctx, &prompt, nil)
        	if observeErr != nil {
        		return observeErr
        	}
        	if len(observed.Data) == 0 {
        		return fmt.Errorf("no action found for %q", prompt)
        	}

        	action := observed.Data[0]
        	if action.Method == nil || *action.Method != expectedMethod {
        		return fmt.Errorf("unsupported method: expected %q, got %v", expectedMethod, action.Method)
        	}
        	if _, err := client.Act(ctx, stagehand.ObservedAction(action), nil); err != nil {
        		return err
        	}
        }
        ```
      </Tab>
    </Tabs>

    **Solution 2: Retry with exponential backoff**

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        // 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 stagehand.act(prompt, { timeout: 10000 + (attempt * 5000) });
            break; // Success, exit retry loop
          } catch (error) {
            const message = error instanceof Error ? error.message : String(error);

            if (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;
            }
          }
        }
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        # 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:
                await stagehand.act(prompt, timeout=10000 + (attempt * 5000))
                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 * 1000}ms")
                    await asyncio.sleep(delay)
                else:
                    raise
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        // Retry with exponential backoff for intermittent issues
        prompt := "click the submit button"
        const maxRetries = 3

        for attempt := 0; attempt <= maxRetries; attempt++ {
        	timeout := float64(10000 + attempt*5000)
        	_, err := client.Act(ctx, stagehand.ActInstruction(prompt), &stagehand.StagehandClientActOptions{
        		Timeout: &timeout,
        	})
        	if err == nil {
        		break // Success, exit retry loop
        	}
        	if !strings.Contains(err.Error(), "method not supported") || attempt == maxRetries {
        		return err
        	}
        	// Exponential backoff: wait 2^attempt seconds
        	delay := time.Duration(1<<attempt) * time.Second
        	fmt.Printf("Retry %d/%d after %v\n", attempt+1, maxRetries, delay)
        	select {
        	case <-ctx.Done():
        		return ctx.Err()
        	case <-time.After(delay):
        	}
        }
        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="Action failed or timed out">
    **Problem**: `act` times out or fails to complete action (often due to element not found)

    **Solutions**:

    * Ensure page has fully loaded
    * Check if content is in iframes: Stagehand traverses them automatically, but a target locator can help
    * Increase action timeout
    * Use `observe()` first to verify element exists
    * Raise `domSettleTimeoutMs` on the constructor if the page keeps mutating after load

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        // Handle timeout and element not found issues
        try {
          await stagehand.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 { data: elements } = await stagehand.observe("find the submit button");

          if (elements.length > 0) {
            console.log("Element found, trying more specific instruction");
            await stagehand.act("click the submit button at the bottom of the form");
          } else {
            console.log("Element not found, trying alternative selector");
            await stagehand.act("click the button with text 'Submit'");
          }
        }
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        # Handle timeout and element not found issues
        try:
            await stagehand.act("click the submit button", timeout=30000)
        except Exception:
            # Check if page is fully loaded
            await page.wait_for_load_state("domcontentloaded")

            # Use observe to check element state
            observed = await stagehand.observe("find the submit button")

            if observed.data:
                print("Element found, trying more specific instruction")
                await stagehand.act("click the submit button at the bottom of the form")
            else:
                print("Element not found, trying alternative selector")
                await stagehand.act("click the button with text 'Submit'")
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        // Handle timeout and element not found issues
        timeout := 30000.0
        if _, err := client.Act(ctx, stagehand.ActInstruction("click the submit button"), &stagehand.StagehandClientActOptions{
        	Timeout: &timeout,
        }); err != nil {
        	// Check if page is fully loaded
        	if err := page.WaitForLoadState(ctx, stagehand.LoadStateDOMContentLoaded, nil); err != nil {
        		return err
        	}

        	// Use Observe to check element state
        	instruction := "find the submit button"
        	observed, observeErr := client.Observe(ctx, &instruction, nil)
        	if observeErr != nil {
        		return observeErr
        	}

        	if len(observed.Data) > 0 {
        		fmt.Println("Element found, trying more specific instruction")
        		_, err = client.Act(ctx, stagehand.ActInstruction("click the submit button at the bottom of the form"), nil)
        	} else {
        		fmt.Println("Element not found, trying alternative selector")
        		_, err = client.Act(ctx, stagehand.ActInstruction("click the button with text 'Submit'"), nil)
        	}
        	if err != nil {
        		return err
        	}
        }
        ```
      </Tab>
    </Tabs>
  </Accordion>

  <Accordion title="Incorrect element selected">
    **Problem**: `act` performs action on wrong element

    **Solutions**:

    * 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

    <Tabs>
      <Tab title="TypeScript">
        ```typescript theme={null}
        // More precise element targeting
        // Instead of:
        await stagehand.act("click the button");

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

        // Or preview with observe first:
        const { data: actions } = await stagehand.observe("click the submit button in the checkout form");
        const [action] = actions;
        if (action?.description.includes("checkout")) {
          await stagehand.act(action);
        }
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        # More precise element targeting
        # Instead of:
        await stagehand.act("click the button")

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

        # Or preview with observe first:
        observed = await stagehand.observe(
            "click the submit button in the checkout form"
        )
        if observed.data and "checkout" in observed.data[0].description:
            await stagehand.act(observed.data[0])
        ```
      </Tab>

      <Tab title="Go">
        ```go theme={null}
        // More precise element targeting
        // Instead of:
        _, err := client.Act(ctx, stagehand.ActInstruction("click the button"), nil)

        // Use specific context:
        _, err = client.Act(ctx, stagehand.ActInstruction("click the red 'Delete' button next to the user John Smith"), nil)

        // Or preview with Observe first:
        instruction := "click the submit button in the checkout form"
        observed, err := client.Observe(ctx, &instruction, nil)
        if err != nil {
        	return err
        }
        if len(observed.Data) > 0 && strings.Contains(observed.Data[0].Description, "checkout") {
        	if _, err := client.Act(ctx, stagehand.ObservedAction(observed.Data[0]), nil); err != nil {
        		return err
        	}
        }
        ```
      </Tab>
    </Tabs>
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="Discover actions with observe()" icon="magnifying-glass" iconType="sharp-solid" href="/v4/basics/observe">
    Use `observe()` to plan actions before executing them.
  </Card>

  <Card title="Caching actions" icon="bolt" iconType="sharp-solid" href="/v4/best-practices/caching">
    Speed up repeated automations by caching actions.
  </Card>

  <Card title="Extract data with extract()" icon="table" iconType="sharp-solid" href="/v4/basics/extract">
    Use `extract` with a data schema to pull clean, typed data from any page.
  </Card>

  <Card title="Work across multiple tabs" icon="clone" iconType="sharp-solid" href="/v4/best-practices/using-multiple-tabs">
    Target a specific page with the `page` option.
  </Card>
</CardGroup>
