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

> ## Agent Instructions
> The Zenrows API (Fetch, Extract via extract=auto, and Batch) is described in OpenAPI 3.1 at https://www.zenrows.com/openapi.json and listed in the RFC 9727 API catalog at https://www.zenrows.com/.well-known/api-catalog. Use the spec for exact parameter names and types.

# Fill and submit multi-step forms

> Fill in and submit multi-step forms in Zenrows Browser: search, pick options, set quantities and confirm the result.

Run a full form flow in Zenrows Browser, the browser for agents: search a catalog, open the product, pick options from dropdowns, set a quantity, submit, and confirm the site accepted it. The same building blocks cover sign-up flows, filters, quote requests and checkout steps.

**Uses:** [Browser](/browser-sessions/introduction) with Playwright's `fill`, `select_option`, `press`, `click` and `wait_for`

## Ask your AI agent

With the [Zenrows MCP server](/mcp/overview) connected to Claude, Cursor or any MCP client, paste this prompt. Your agent picks the right Zenrows tools and settings on its own.

```text theme={"dark"}
Use the Zenrows browser to open https://www.scrapingcourse.com/ecommerce/, search for
"abominable hoodie", open the product, choose size M and color Blue, set the quantity to 2
and click "Add to cart". Tell me the confirmation message and the cart total, then close the session.
```

## Run the form flow

The flow has three steps, and each one waits for proof that the previous one worked:

1. Fill the search box and press Enter. Wait for the product form.
2. Pick the size and color with `select_option` and set the quantity with `fill`.
3. Click the submit button and wait for the confirmation message.

<Warning>
  Use test sites or accounts you own when you practice form automation, and stop before final steps that create real orders, payments or messages.
</Warning>

<CodeGroup>
  ```python Python (Playwright) theme={"dark"}
  import asyncio
  from playwright.async_api import async_playwright

  CONNECTION_URL = "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY"

  async def main():
      async with async_playwright() as p:
          browser = await p.chromium.connect_over_cdp(CONNECTION_URL)
          try:
              page = await browser.contexts[0].new_page()
              await page.goto("https://www.scrapingcourse.com/ecommerce/", wait_until="domcontentloaded")

              # Step 1: search the catalog
              await page.fill("#woocommerce-product-search-field-0", "abominable hoodie")
              await page.press("#woocommerce-product-search-field-0", "Enter")
              await page.wait_for_selector(".variations_form")  # single match opens the product page
              print("Product page:", page.url)

              # Step 2: choose options and quantity
              await page.select_option("#size", "M")
              await page.select_option("#color", "Blue")
              await page.fill("input[name='quantity']", "2")

              # Step 3: submit and verify the result
              await page.click(".single_add_to_cart_button")
              message = page.locator(".woocommerce-message")
              await message.wait_for(state="attached")
              print("Result:", " ".join((await message.text_content()).split()))
              print("Cart:", " ".join((await page.locator(".cart-contents").first.text_content()).split()))
          finally:
              await browser.close()

  asyncio.run(main())
  ```

  ```javascript Node.js (Playwright) theme={"dark"}
  import { chromium } from "playwright-core";

  const connectionURL = "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY";

  const browser = await chromium.connectOverCDP(connectionURL);
  try {
    const page = await browser.contexts()[0].newPage();
    await page.goto("https://www.scrapingcourse.com/ecommerce/", { waitUntil: "domcontentloaded" });

    // Step 1: search the catalog
    await page.fill("#woocommerce-product-search-field-0", "abominable hoodie");
    await page.press("#woocommerce-product-search-field-0", "Enter");
    await page.waitForSelector(".variations_form"); // single match opens the product page
    console.log("Product page:", page.url());

    // Step 2: choose options and quantity
    await page.selectOption("#size", "M");
    await page.selectOption("#color", "Blue");
    await page.fill("input[name='quantity']", "2");

    // Step 3: submit and verify the result
    await page.click(".single_add_to_cart_button");
    const message = page.locator(".woocommerce-message");
    await message.waitFor({ state: "attached" });
    console.log("Result:", (await message.textContent()).replace(/\s+/g, " ").trim());
    console.log("Cart:", (await page.locator(".cart-contents").first().textContent()).replace(/\s+/g, " ").trim());
  } finally {
    await browser.close();
  }
  ```
</CodeGroup>

`select_option` takes the option's `value` attribute. For a select menu with labels that differ from values, pass `label="Medium"` (Python) or `{ label: "Medium" }` (Node.js).

## Submit forms that redirect

Logins and many sign-up forms answer with a redirect. For those, click with `no_wait_after=True` (`noWaitAfter: true` in Node.js) and wait for an element that exists on the result page. This is the pattern [Log in once and reuse the session](/academy/browser/login-and-reuse-sessions) uses:

```python Python (Playwright) theme={"dark"}
await page.click("#submit-button", no_wait_after=True)
await page.get_by_text("Welcome, Scraper!").wait_for()
```

Checking for a result element also catches validation errors early: if the success element never appears, read the form's error message instead.

## Example output

Output of the form flow:

```text theme={"dark"}
Product page: https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/
Result: View cart 2 × “Abominable Hoodie” have been added to your cart.
Cart: $138.00 2 items
```

## Customize it

| Change | How |
| - | - |
| Tick or untick a checkbox | `await page.check("#terms")` / `await page.uncheck("#newsletter")` |
| Pick by visible label | `await page.select_option("#size", label="M")` |
| Type like a person, key by key | `await page.locator("#search").press_sequentially("hoodie", delay=100)` |
| Upload a file | `await page.set_input_files("input[type=file]", "invoice.pdf")` |
| Fill the form from a specific country | Add `&proxy_country=us` to the connection URL, see [Country targeting](/browser-sessions/features/country) |
| Run the same flow from the terminal | `zenrows browser fill`, `select`, `click` with `--session`, see [Let your AI assistant browse](/academy/browser/ai-assistant-browsing) |

## Troubleshooting

* **`select_option` finds no matching option**: the value differs from the visible text. Inspect the `<option value="...">` and pass that value, or use `label=`.
* **The submit button does nothing**: the form enables it after a field changes. Fill every required field first, then click; `select_option` and `fill` fire the change events the page listens for.
* **The confirmation is in the page but `wait_for()` times out**: the message is present but styled as hidden. Wait with `state="attached"` and read it with `text_content()`.
* **`page.goto` times out on a heavy page**: pass `wait_until="domcontentloaded"` and wait for the form element you need.

## Related recipes

<CardGroup cols={2}>
  <Card title="Log in once and reuse the session" icon="login" href="/academy/browser/login-and-reuse-sessions">
    Save the state after a login and skip the form on later runs.
  </Card>

  <Card title="Scroll and paginate to collect every item" icon="list-details" href="/academy/browser/infinite-scroll-and-pagination">
    Walk every page of results and export them to CSV.
  </Card>

  <Card title="Take screenshots and save pages as PDF" icon="camera" href="/academy/browser/screenshots-and-pdfs">
    Capture proof of the submitted form.
  </Card>

  <Card title="Click, fill, and scroll before you scrape" icon="click" href="/academy/fetch/page-interactions">
    Click and fill with js\_instructions in a single Fetch request.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.