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

# exec

> Execute Playwright TypeScript in a persistent REPL for an open browser page.

The `exec` command runs TypeScript Playwright code against a live browser session. Use it to validate selectors, inspect page data, prototype individual steps, and experiment with interactions before encoding them in a workflow file.

`exec` evaluates code in a persistent REPL scoped to the browser session. Values come from expressions, top-level `await` is supported, and declarations remain available to later `exec` calls in the same session.

```bash theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
npx libretto exec --session debug-example "await page.url()"
```

### Usage

<Tabs>
  <Tab title="Single-line">
    Pass the code as a quoted string argument:

    ```bash theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
    npx libretto exec --session debug-example "await page.url()"
    npx libretto exec --session debug-example "await page.locator('button').count()"
    npx libretto exec --session debug-example "await page.locator('button:has-text(\"Continue\")').click()"
    ```
  </Tab>

  <Tab title="stdin (multi-line)">
    Pipe code into `exec -` for complex or multi-line scripts:

    ```bash theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
    echo "await page.url()" | npx libretto exec - --session debug-example
    ```

    ```bash theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
    cat <<'EOF' | npx libretto exec - --session debug-example
    async function tableRows(selector: string) {
      const rows = await page.locator(`${selector} tbody tr`).all();
      const data = [];
      for (const row of rows) {
        const cells = await row.locator('td').allTextContents();
        data.push(cells);
      }
      return data;
    }
    EOF

    npx libretto exec --session debug-example "await tableRows('table')"
    ```
  </Tab>
</Tabs>

### Available globals

Inside the code you pass to `exec`, these variables are available without any imports:

| Global          | Type             | Description                               |
| --------------- | ---------------- | ----------------------------------------- |
| `page`          | `Page`           | The active Playwright page                |
| `frame`         | `Frame`          | The active page's main frame              |
| `context`       | `BrowserContext` | The browser context for the session       |
| `browser`       | `Browser`        | The browser instance                      |
| `fetch`         | `typeof fetch`   | The global fetch function                 |
| `Buffer`        | `typeof Buffer`  | Node.js `Buffer`                          |
| `console`       | `Console`        | Standard console (`log`, `warn`, `error`) |
| `setTimeout`    | `function`       | Schedule a callback after a delay         |
| `setInterval`   | `function`       | Schedule a repeating callback             |
| `clearTimeout`  | `function`       | Cancel a scheduled `setTimeout`           |
| `clearInterval` | `function`       | Cancel a scheduled `setInterval`          |
| `URL`           | `typeof URL`     | The WHATWG `URL` constructor              |

### Persistent REPL

Each browser session has a persistent `exec` REPL. Define helper functions once, then reuse them in later calls:

```bash theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
cat <<'EOF' | npx libretto exec - --session debug-example
async function textOf(selector: string) {
  return await page.locator(selector).textContent();
}
EOF

npx libretto exec --session debug-example "await textOf('h1')"
```

The `page` and `frame` globals are refreshed for each call, including calls that use `--page <page-id>`.

### Flags

<ParamField path="code" type="string" required>
  The Playwright TypeScript code to execute, passed as the first positional
  argument. Pass `-` to read from stdin.
</ParamField>

<ParamField path="--session" type="string" required>
  The session to execute against.
</ParamField>

<ParamField path="--page" type="string">
  Target a specific page ID within the session. Use `npx libretto pages --session <name>` to list page IDs when a popup or new tab is open.
</ParamField>

<ParamField path="--visualize" type="boolean">
  Enable ghost cursor and highlight visualization. Shows a visible cursor trace
  and element highlights as Playwright interacts with the page.
</ParamField>

### Return values

If the code evaluates to a value, `exec` prints it to stdout:

* Strings are printed as-is.
* All other values are printed as pretty-printed JSON.
* If the code evaluates to `undefined`, `exec` prints `Executed successfully`.

```bash theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
npx libretto exec --session debug-example "await page.title()"
# My Page Title

npx libretto exec --session debug-example "await page.locator('li').count()"
# 12
```

### Rules

<Warning>
  **Let failures throw.** Do not hide `exec` failures with `try/catch` or
  `.catch()`. Swallowed errors make debugging harder. Surface them so you can
  see exactly what failed.
</Warning>

<Warning>
  **Do not run multiple `exec` commands in parallel.** Each `exec` call drives
  the same browser page. Running them concurrently causes race conditions.
</Warning>

* Use `exec` for focused inspection and short-lived interaction experiments.
* Use `exec -` (stdin) for complex multi-line scripts.
* Validate a selector with `exec` before encoding it in a workflow file.
* Use `exec` to prototype a single step, then move the working code into your `.ts` workflow file.
* Do not use top-level `return`; write the expression you want to inspect.

### Examples

```bash theme={null} theme={"theme":{"light":"github-light","dark":"github-dark"}}
# Print the current URL
npx libretto exec --session debug-example "await page.url()"

# Count how many buttons are on the page
npx libretto exec --session debug-example "await page.locator('button').count()"

# Click a button
npx libretto exec --session debug-example "await page.locator('button:has-text(\"Continue\")').click()"

# Read a value from an input field
npx libretto exec --session debug-example "await page.locator('input[name=\"email\"]').inputValue()"

# Run against a specific page in a session
npx libretto exec --session debug-example --page <page-id> "await page.url()"

# Pipe from stdin (multi-line, named session)
echo "await page.url()" | npx libretto exec - --session debug-example
```

<CardGroup cols={2}>
  <Card title="snapshot" icon="camera" href="./snapshot">
    Use snapshot to identify selectors before running exec.
  </Card>

  <Card title="run & resume" icon="play" href="./run-and-resume">
    Move validated exec code into a workflow file and run it end to end.
  </Card>
</CardGroup>
