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

# Build a Browser Automation

> Launch a cloud browser, interact with pages using AI and Playwright, chain agent tasks, and build complete browser automations in Python or TypeScript.

Browser automations are multi-step automations written in code. You launch a cloud browser, get a Playwright page with AI methods layered on top, and write your automation in Python or TypeScript. The code lives in your repo, runs in your CI, and deploys like any other software.

This is the code-first path. If you prefer building automations visually with drag-and-drop blocks, see [Agents](/cloud/building-agents/build-an-agent) in the Cloud UI docs.

<Info>
  **Agents vs. Browser Automation:** Agents are built and run in the Cloud UI, no code required. Browser automations are built and run in code using Page, Agent, and Browser. Both can do multi-step work across pages. Choose based on whether your team prefers a visual editor or a code editor.
</Info>

This guide walks through a real example: logging into a vendor portal, extracting invoice data, and downloading a PDF. Four steps:

1. **Launch a browser** and get a page
2. **Navigate and interact** with the page using AI actions, selectors, or both
3. **Use the agent** for complex goals like login, multi-step tasks, and file downloads
4. **Run the script** and check results

***

## Step 1: Launch a browser and get a page

Every automation starts by launching a cloud browser and getting a page. The browser is a Chromium instance hosted by Skyvern. The page is a Playwright page with AI methods added on top.

<Info>
  Install the SDK first if you haven't:

  <CodeGroup>
    ```bash Python theme={null}
    pip install skyvern
    ```

    ```bash TypeScript theme={null}
    npm install @skyvern/client
    ```
  </CodeGroup>

  `pip install skyvern` is enough for Skyvern Cloud and remote API usage. If you are using embedded local mode with `Skyvern.local()` or `launch_local_browser()`, install `pip install "skyvern[local]"`. For a local self-hosted server, install `pip install "skyvern[server]"`.

  All code snippets below run inside an async function. See the [complete example](#complete-example) for the full runnable script.
</Info>

<CodeGroup>
  ```python Python theme={null}
  from skyvern import Skyvern

  skyvern = Skyvern(api_key="YOUR_API_KEY")
  browser = await skyvern.launch_cloud_browser()
  page = await browser.get_working_page()
  ```

  ```typescript TypeScript theme={null}
  import { Skyvern } from "@skyvern/client";

  const skyvern = new Skyvern({ apiKey: "YOUR_API_KEY" });
  const browser = await skyvern.launchCloudBrowser();
  const page = await browser.getWorkingPage();
  ```
</CodeGroup>

The browser stays alive until you call `browser.close()` or the session times out (default: 60 minutes). All pages inside it share cookies, localStorage, and auth state.

For browser launch options (timeouts, proxies, connecting to local browsers), see [Managing Browsers](/developers/browser-automations/handle-browsers).

***

## Step 2: Navigate and interact with the page

Once you have a page, you can interact with it using standard Playwright, AI actions, or both. For the full list of available methods and parameters, see the [Actions Reference](/developers/browser-automations/actions-reference).

### Navigate

<CodeGroup>
  ```python Python theme={null}
  await page.goto("https://vendor-portal.example.com")
  ```

  ```typescript TypeScript theme={null}
  await page.goto("https://vendor-portal.example.com");
  ```
</CodeGroup>

### Use AI actions (no selectors needed)

Four methods let you interact with the page using natural language. Skyvern screenshots the page and determines which elements to target.

<CodeGroup>
  ```python Python theme={null}
  # Perform any action. Returns None.
  await page.act("Click the login button")

  # Extract structured data. Returns dict (or list if schema root is array).
  data = await page.extract(
      "Extract all product names and prices",
      schema={
          "type": "array",
          "items": {
              "type": "object",
              "properties": {
                  "name": {"type": "string"},
                  "price": {"type": "number"},
              },
          },
      },
  )

  # Check page state. Returns bool.
  is_logged_in = await page.validate("The user is logged in")

  # Ask the AI a question. Returns {"llm_response": str}.
  result = await page.prompt("What is the total at the bottom of the table?")
  answer = result["llm_response"]
  ```

  ```typescript TypeScript theme={null}
  // Perform any action. Returns void.
  await page.act("Click the login button");

  // Extract structured data. Returns object (or array if schema root is array).
  const data = await page.extract({
    prompt: "Extract all product names and prices",
    schema: {
      type: "array",
      items: {
        type: "object",
        properties: {
          name: { type: "string" },
          price: { type: "number" },
        },
      },
    },
  });

  // Check page state. Returns boolean.
  const isLoggedIn = await page.validate("The user is logged in");

  // Ask the AI a question. Returns { llmResponse: string }.
  const result = await page.prompt("What is the total at the bottom of the table?");
  const answer = result.llmResponse;
  ```
</CodeGroup>

### Use selectors with AI fallback

`click`, `fill`, and `select_option` accept both a CSS selector and an AI prompt. The selector runs first. If it fails, the AI takes over.

<CodeGroup>
  ```python Python theme={null}
  # Selector only (fast, deterministic)
  await page.click("#submit-button")
  await page.fill("#email", value="user@example.com")

  # AI only (no selector needed, resilient to layout changes)
  await page.click(prompt="Click the 'Submit' button")
  await page.fill(prompt="Fill 'user@example.com' in the email field")

  # Both - selector first, AI fallback (best for production)
  await page.click("#submit-button", prompt="Click the 'Submit' button")
  await page.fill("#email", value="user@example.com", prompt="Fill the email field")
  ```

  ```typescript TypeScript theme={null}
  // Selector only (fast, deterministic)
  await page.click("#submit-button");
  await page.fill("#email", "user@example.com");

  // AI only (no selector needed, resilient to layout changes)
  await page.click({ prompt: "Click the 'Submit' button" });
  await page.fill({ prompt: "Fill 'user@example.com' in the email field" });

  // Both - selector first, AI fallback (best for production)
  await page.click("#submit-button", { prompt: "Click the 'Submit' button" });
  await page.fill("#email", "user@example.com", { prompt: "Fill the email field" });
  ```
</CodeGroup>

### Mix Playwright and AI

Standard Playwright methods work directly. Use selectors where you know the DOM, AI where you don't.

<CodeGroup>
  ```python Python theme={null}
  await page.goto("https://example.com/login")
  await page.fill("#username", value="demo@example.com")    # Playwright - known selector
  await page.fill("#password", value="s3cur3-p4ss")         # Playwright - known selector
  await page.click(prompt="Click the sign-in button")       # AI - button text varies
  await page.wait_for_selector("#dashboard")                # Playwright - wait for page load
  data = await page.extract("Extract the account balance")  # AI - table layout varies
  ```

  ```typescript TypeScript theme={null}
  await page.goto("https://example.com/login");
  await page.fill("#username", "demo@example.com");         // Playwright - known selector
  await page.fill("#password", "s3cur3-p4ss");              // Playwright - known selector
  await page.click({ prompt: "Click the sign-in button" }); // AI - button text varies
  await page.waitForSelector("#dashboard");                  // Playwright - wait for page load
  const data = await page.extract({ prompt: "Extract the account balance" }); // AI
  ```
</CodeGroup>

***

## Step 3: Use the agent for complex goals

For multi-step goals ("log in with 2FA", "navigate to billing and download the invoice"), hand off to the agent. The agent runs a full AI task loop inside your page, preserving all browser state.

### Login with stored credentials

<CodeGroup>
  ```python Python theme={null}
  await page.agent.login(
      credential_type="skyvern",
      credential_id="cred_123"
  )
  ```

  ```typescript TypeScript theme={null}
  await page.agent.login("skyvern", { credentialId: "cred_123" });
  ```
</CodeGroup>

The agent handles multi-page login flows, CAPTCHAs, and 2FA automatically. Four credential providers: `skyvern` (built-in vault), `bitwarden`, `onepassword`, `azure_vault`. Store credentials via the [Credentials API](/sdk-reference/credentials/create-credential).

### Run a multi-step task

<CodeGroup>
  ```python Python theme={null}
  result = await page.agent.run_task(
      "Go to the billing page and extract all invoice details",
      data_extraction_schema={
          "type": "object",
          "properties": {
              "invoice_number": {"type": "string"},
              "amount": {"type": "string"},
          },
      },
  )
  print(result.output)
  # {"invoice_number": "INV-2025-042", "amount": "$1,250.00"}
  ```

  ```typescript TypeScript theme={null}
  const result = await page.agent.runTask(
    "Go to the billing page and extract all invoice details",
    {
      dataExtractionSchema: {
        type: "object",
        properties: {
          invoice_number: { type: "string" },
          amount: { type: "string" },
        },
      },
    },
  );
  console.log(result.output);
  // {"invoice_number": "INV-2025-042", "amount": "$1,250.00"}
  ```
</CodeGroup>

After the agent finishes, you take back control. The page retains all state:

<CodeGroup>
  ```python Python theme={null}
  await page.agent.run_task("Navigate to the settings page")
  # Agent is done - use direct page actions
  settings = await page.extract("Extract all notification preferences")
  await page.click("#save-button")
  ```

  ```typescript TypeScript theme={null}
  await page.agent.runTask("Navigate to the settings page");
  // Agent is done - use direct page actions
  const settings = await page.extract({ prompt: "Extract all notification preferences" });
  await page.click("#save-button");
  ```
</CodeGroup>

### Download files

<CodeGroup>
  ```python Python theme={null}
  result = await page.agent.download_files(
      "Download the Q4 2025 financial report",
      download_suffix=".pdf",
      download_timeout=30,
  )

  for file in result.downloaded_files or []:
      print(f"Downloaded: {file.url}")
  ```

  ```typescript TypeScript theme={null}
  const result = await page.agent.downloadFiles(
    "Download the Q4 2025 financial report",
    { downloadSuffix: ".pdf", downloadTimeout: 30 },
  );

  for (const file of result.downloadedFiles ?? []) {
    console.log(`Downloaded: ${file.url}`);
  }
  ```
</CodeGroup>

### Run a workflow on the page

If you have built a workflow, run it on a page you already control:

<CodeGroup>
  ```python Python theme={null}
  await page.agent.login(credential_type="skyvern", credential_id="cred_123")
  result = await page.agent.run_workflow(
      "wpid_monthly_report",
      parameters={"month": "2025-03"}
  )
  ```

  ```typescript TypeScript theme={null}
  await page.agent.login("skyvern", { credentialId: "cred_123" });
  const result = await page.agent.runWorkflow("wpid_monthly_report", {
    parameters: { month: "2025-03" },
  });
  ```
</CodeGroup>

**`agent.run_workflow` vs `skyvern.run_workflow`:** The top-level `run_workflow` opens its own browser. Use `agent.run_workflow` when you need to log in first or do setup before the workflow runs.

***

## Complete example

Putting it all together: log into a vendor portal, extract invoices, and download a statement.

<CodeGroup>
  ```python Python theme={null}
  import asyncio
  from skyvern import Skyvern

  async def main():
      skyvern = Skyvern(api_key="YOUR_API_KEY")
      browser = await skyvern.launch_cloud_browser()
      page = await browser.get_working_page()

      # Navigate
      await page.goto("https://vendor-portal.example.com")

      # Login with stored credentials
      await page.agent.login(credential_type="skyvern", credential_id="cred_abc123")

      # Extract data using a mix of Playwright and AI
      await page.click("#billing-tab")
      invoices = await page.extract(
          "Extract all invoice numbers, dates, and amounts",
          schema={
              "type": "array",
              "items": {
                  "type": "object",
                  "properties": {
                      "invoice_number": {"type": "string"},
                      "date": {"type": "string"},
                      "amount": {"type": "string"},
                  },
              },
          },
      )

      # Download a file
      result = await page.agent.download_files(
          "Download the latest monthly statement",
          download_suffix=".pdf",
      )

      # Check results
      print(f"Found {len(invoices)} invoices")
      for inv in invoices:
          print(f"  {inv['invoice_number']}: {inv['amount']}")

      for file in result.downloaded_files or []:
          print(f"Downloaded: {file.url}")

      await browser.close()

  asyncio.run(main())
  ```

  ```typescript TypeScript theme={null}
  import { Skyvern } from "@skyvern/client";

  async function main() {
    const skyvern = new Skyvern({ apiKey: "YOUR_API_KEY" });
    const browser = await skyvern.launchCloudBrowser();
    const page = await browser.getWorkingPage();

    // Navigate
    await page.goto("https://vendor-portal.example.com");

    // Login with stored credentials
    await page.agent.login("skyvern", { credentialId: "cred_abc123" });

    // Extract data using a mix of Playwright and AI
    await page.click("#billing-tab");
    const invoices = await page.extract({
      prompt: "Extract all invoice numbers, dates, and amounts",
      schema: {
        type: "array",
        items: {
          type: "object",
          properties: {
            invoice_number: { type: "string" },
            date: { type: "string" },
            amount: { type: "string" },
          },
        },
      },
    });

    // Download a file
    const result = await page.agent.downloadFiles(
      "Download the latest monthly statement",
      { downloadSuffix: ".pdf" },
    );

    // Check results
    console.log(`Found ${invoices.length} invoices`);
    for (const inv of invoices) {
      console.log(`  ${inv.invoice_number}: ${inv.amount}`);
    }

    for (const file of result.downloadedFiles ?? []) {
      console.log(`Downloaded: ${file.url}`);
    }

    await browser.close();
  }

  main();
  ```
</CodeGroup>

***

## Step 4: Run and get results

Run the script:

<CodeGroup>
  ```bash Python theme={null}
  python your_automation.py
  ```

  ```bash TypeScript theme={null}
  npx tsx your_automation.ts
  ```
</CodeGroup>

You'll see the browser session URL in the console output. Open it in your browser to watch the automation run in real time.

### Example output

Here's what the Complete Example prints, grouped by the call that produced each block:

```text wrap theme={null}
# skyvern.launch_cloud_browser()
[Skyvern] Launched new cloud browser session  url=https://app.skyvern.com/browser-session/pbs_519895987620976102

# page.agent.login(...)
[Skyvern] AI login workflow finished  run_id=wr_519896041203554782  status=completed

# page.extract(...)
[Skyvern] AI extract  prompt=Extract all invoice numbers, dates, and amounts

# page.agent.download_files(...)
[Skyvern] Starting AI file download workflow  navigation_goal=Download the latest monthly statement
[Skyvern] AI file download workflow is running, this may take a while  run_id=wr_519896107880060430
[Skyvern] AI file download workflow finished  run_id=wr_519896107880060430  status=completed

# print(f"Found {len(invoices)} invoices")  +  for inv in invoices: print(...)
Found 3 invoices
  INV-2025-042: $2,340.00
  INV-2025-043: $1,850.00
  INV-2025-044: $3,100.00

# for file in result.downloaded_files: print(f"Downloaded: {file.url}")
Downloaded: https://skyvern-uploads.s3.amazonaws.com/downloads/production/o_510.../wr_519.../statement.pdf?AWS...
```

The `[Skyvern] ...` lines come from the SDK's built-in logger and stream in real time as each call runs. Your own `print` output appears after the awaited call returns.

For the full response shape, see [`TaskRunResponse`](/sdk-reference/tasks/run-task#returns-taskrunresponse) and [`WorkflowRunResponse`](/sdk-reference/workflows/run-workflow#returns-workflowrunresponse).

### View results in the Cloud UI

Every run appears in the [Runs page](https://app.skyvern.com/runs). Click any run to see:

* **Recording** - video of the browser session
* **Actions** - step-by-step timeline with screenshots and AI reasoning
* **Output** - extracted data as JSON
* **Artifacts** - screenshots, HAR files, generated code

### Artifacts

Every run captures recordings, screenshots, AI reasoning logs, network HAR files, and downloaded files. Access them via the Cloud UI or programmatically:

<CodeGroup>
  ```python Python theme={null}
  artifacts = await skyvern.get_run_artifacts(result.run_id)
  for artifact in artifacts:
      print(f"{artifact.artifact_type}: {artifact.uri}")
  ```

  ```typescript TypeScript theme={null}
  const artifacts = await skyvern.getRunArtifacts(result.run_id);
  for (const artifact of artifacts) {
    console.log(`${artifact.artifact_type}: ${artifact.uri}`);
  }
  ```
</CodeGroup>

For the full artifact type reference and debugging workflows, see [Using Artifacts](/developers/debugging/using-artifacts).

***

## Next steps

<CardGroup cols={2}>
  <Card title="Actions Reference" icon="list" href="/developers/browser-automations/actions-reference">
    Every page action and agent method with parameters and return types
  </Card>

  <Card title="Managing Browsers" icon="browser" href="/developers/browser-automations/handle-browsers">
    Launch options, multiple tabs, proxies, connecting to local browsers
  </Card>
</CardGroup>

<CardGroup cols={2}>
  <Card title="Store Credentials" icon="key" href="/sdk-reference/credentials/create-credential">
    Set up credentials for automated login with the agent
  </Card>

  <Card title="SDK Reference" icon="code" href="/sdk-reference/complete-reference">
    Every SDK method, parameter, and type on one page (Python + TypeScript)
  </Card>
</CardGroup>

<CardGroup cols={2}>
  <Card title="API Reference" icon="plug" href="/api-reference">
    Full OpenAPI spec for every REST endpoint
  </Card>

  <Card title="llms.txt" icon="file-lines" href="https://skyvern.com/docs/llms.txt">
    Machine-readable index of the entire documentation for AI coding agents
  </Card>
</CardGroup>
