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

# Human emulation

> Surfsky's Human.* CDP commands: how the input synthesis works and the full command reference for clicks, typing, scrolling, dragging, and touch.

Surfsky adds `Human.*` commands to CDP for mouse, keyboard, scroll, drag, and touch input. Each command performs a complete interaction inside the browser session. For example, `Human.click` moves the cursor to the element, clicks it, and pauses briefly.

Your script still needs to wait for page content and check the result of each action. Human commands do not guarantee that a site will accept a session. See [Human behavior](/human-behavior) for action order and timing.

<span id="overview" />

<span id="what-it-does" />

## How it works

Surfsky generates input with a proprietary model of human movement. It does not use open-source humanization libraries or combine Bezier curves with Fitts' law.

Commands send events through the browser's input pipeline. Cursor movement includes changes in speed, small corrections, and tremor, with deceleration near the target. Click positions vary within the element. Typing uses variable intervals and occasionally enters an incorrect key followed by Backspace. Drags may overshoot the target before correcting their position.

On Android profiles, the commands produce taps and swipes with touch contact area and pressure.

<span id="available-cdp-methods" />

## Commands

| Command                                                 | Purpose                                       |
| ------------------------------------------------------- | --------------------------------------------- |
| [`Human.click`](#human-click)                           | Move to a target and click it                 |
| [`Human.dblclick`](#human-dblclick)                     | Move to a target and double-click it          |
| [`Human.moveTo`](#human-moveto)                         | Move the cursor to a target without clicking  |
| [`Human.type`](#human-type)                             | Type text into the focused element            |
| [`Human.press`](#human-press)                           | Press one key or key combination              |
| [`Human.scroll`](#human-scroll)                         | Scroll by a relative distance over a duration |
| [`Human.wheel`](#human-wheel)                           | Send a single wheel step                      |
| [`Human.scrollIntoView`](#human-scrollintoview)         | Scroll until an element is in the viewport    |
| [`Human.scrollTo`](#human-scrollto)                     | Scroll to an absolute document position       |
| [`Human.drag`](#human-drag)                             | Press, drag, and release between two points   |
| [`Human.mouseDown`](#human-mousedown-and-human-mouseup) | Press and hold a button                       |
| [`Human.mouseUp`](#human-mousedown-and-human-mouseup)   | Release a held button                         |

All time values are in **seconds**. Coordinates are CSS pixels in the viewport. Every successful response includes `success: true` plus the fields listed for the command. A missing required parameter returns CDP error `-32602`; a failure during the action, such as an element that never appears, returns `-32603` with the reason in the message.

## Connect a page session

Start with the [Playwright](/quickstart/playwright) or [Puppeteer](/quickstart/puppeteer) quickstart. Once `page` is connected, create a page-level CDP session:

<CodeGroup>
  ```python Python (async Playwright) theme={null}
  cdp = await page.context.new_cdp_session(page)
  ```

  ```javascript JavaScript (Playwright) theme={null}
  const cdp = await page.context().newCDPSession(page);
  ```

  ```javascript JavaScript (Puppeteer) theme={null}
  const cdp = await page.createCDPSession();
  ```
</CodeGroup>

The examples below use this `cdp` object. No `Human.enable` call or profile setting is required. Stop the browser using the lifecycle in your quickstart when finished.

<span id="cdp-action-parameters" />

<span id="direct-selector-usage" />

<span id="examples" />

## Click and move

<CodeGroup>
  ```python Python theme={null}
  await cdp.send("Human.click", {"selector": "input[name='email']"})
  await cdp.send("Human.type", {"text": "reader@example.com"})
  await cdp.send("Human.click", {"selector": "button[type='submit']"})
  ```

  ```javascript JavaScript theme={null}
  await cdp.send("Human.click", { selector: "input[name='email']" });
  await cdp.send("Human.type", { text: "reader@example.com" });
  await cdp.send("Human.click", { selector: "button[type='submit']" });
  ```
</CodeGroup>

Replace the selectors with elements on your page. Wait for the expected navigation or page state after submission.

### Targeting

`Human.click`, `Human.dblclick`, and `Human.moveTo` accept either a CSS `selector` or both `x` and `y`. Prefer selectors when the layout can change; Surfsky picks a natural point inside the element for you. With a selector, the command waits for the element to have a visible box, scrolls it into the viewport if it is outside, then moves to it.

| Parameter        | Default | Meaning                                                                                                                  |
| ---------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
| `selector`       |         | CSS selector resolved in the main document.                                                                              |
| `x`, `y`         |         | Viewport coordinates, as an alternative to a selector. If both are given with a selector, the coordinates win.           |
| `waitForVisible` | `true`  | Poll until the element has a visible box. With `false`, a single lookup is made and a missing element fails immediately. |
| `scrollIntoView` | `true`  | Scroll the element into the viewport before moving to it.                                                                |
| `timeout`        | `30`    | Seconds to wait for the element.                                                                                         |

### `Human.click`

Moves to the target, then presses and releases the button.

| Parameter               | Default   | Meaning                                                                                         |
| ----------------------- | --------- | ----------------------------------------------------------------------------------------------- |
| `button`                | `"left"`  | `"left"`, `"right"`, or `"middle"`.                                                             |
| `clickCount`            | `1`       | Click count reported to the page. Use `Human.dblclick` for a double-click.                      |
| `modifiers`             |           | Array of `"Alt"`, `"Control"`, `"Meta"`, `"Shift"`. Held for the click and released afterwards. |
| `preDelay`, `postDelay` | generated | Seconds to pause before and after the click. Pass `0` to skip a pause.                          |

Response: `x`, `y`, `button`.

```javascript theme={null}
await cdp.send("Human.click", { selector: "#menu" });
await cdp.send("Human.click", { x: 320, y: 240, button: "right" });
await cdp.send("Human.click", { selector: "a.result", modifiers: ["Control"] });
```

### `Human.dblclick`

Same targeting and parameters as `Human.click`, without `clickCount`. Response: `x`, `y`, `button`.

```javascript theme={null}
await cdp.send("Human.dblclick", { selector: ".editable-cell" });
```

### `Human.moveTo`

Moves the cursor to the target without pressing anything. Use it to hover, to open a menu, or to position the cursor before `Human.mouseDown`. Takes only the targeting parameters. Response: `x`, `y`.

```javascript theme={null}
await cdp.send("Human.moveTo", { selector: "#menu" });
```

On Android profiles a finger cannot hover, so `Human.moveTo` records the position and dispatches nothing.

A successful response confirms the input was delivered. Check the page to confirm its effect.

<span id="complete-search-automation" />

<span id="form-filling-with-natural-behavior" />

## Type text and press keys

### `Human.type`

Types `text` into the currently focused element, one keystroke at a time. Click or focus the field first. It does not clear an existing value, and it does not take a selector.

| Parameter | Default | Meaning                                   |
| --------- | ------- | ----------------------------------------- |
| `text`    | `""`    | Text to type. Newlines are sent as Enter. |

Response: `text`. Avoid logging typed secrets.

Typing includes occasional corrected mistakes: a wrong adjacent key followed by Backspace. The final field value matches `text`, but a page that reacts to every input event will see the correction. Do not use `Human.type` for fields that reject Backspace or act on the first keystroke.

### `Human.press`

Presses and releases one key.

| Parameter | Default   | Meaning                                                                                                                           |
| --------- | --------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `key`     | required  | Key name or combination joined with `+`, such as `Enter`, `Tab`, `Escape`, `Backspace`, `ArrowDown`, `Control+A`, or `Shift+Tab`. |
| `delay`   | generated | Seconds between key down and key up.                                                                                              |

Response: `key`, `delay`.

```javascript theme={null}
await cdp.send("Human.click", { selector: "#search" });
await cdp.send("Human.press", { key: "Control+A" });
await cdp.send("Human.type", { text: "wireless keyboard" });
await cdp.send("Human.press", { key: "Enter" });
```

Use the shortcut appropriate to the browser's OS, such as `Meta+A` for a Mac fingerprint.

<span id="scrolling-and-navigation" />

## Scroll

### `Human.scroll`

Scrolls by a relative distance as a series of eased wheel events. On Android profiles it becomes one or more swipes.

| Parameter          | Default | Meaning                                             |
| ------------------ | ------- | --------------------------------------------------- |
| `deltaX`, `deltaY` | `0`     | Distance in pixels. Positive `deltaY` scrolls down. |
| `duration`         | `1.0`   | Seconds the scroll takes.                           |

Response: `deltaX`, `deltaY`, `duration`.

### `Human.wheel`

Sends a single wheel step at the current cursor position, with no easing. Use it for one notch of a scroll wheel or to nudge a scrollable element under the cursor.

| Parameter          | Default | Meaning                 |
| ------------------ | ------- | ----------------------- |
| `deltaX`, `deltaY` | `0`     | Wheel deltas in pixels. |

Response: `deltaX`, `deltaY`.

### `Human.scrollIntoView`

Scrolls until the element is inside the viewport. Does nothing if it already is.

| Parameter  | Default    | Meaning                                                           |
| ---------- | ---------- | ----------------------------------------------------------------- |
| `selector` | required   | CSS selector in the main document.                                |
| `behavior` | `"smooth"` | `"smooth"` for a paced scroll, `"auto"` for a single wheel event. |

Response: `selector`, `behavior`.

### `Human.scrollTo`

Scrolls to an absolute document position.

| Parameter  | Default    | Meaning                                                           |
| ---------- | ---------- | ----------------------------------------------------------------- |
| `x`, `y`   | `0`        | Target document coordinates in pixels.                            |
| `behavior` | `"smooth"` | `"smooth"` for a paced scroll, `"auto"` for a single wheel event. |

Response: `x`, `y`, `behavior`.

```javascript theme={null}
await cdp.send("Human.scroll", { deltaY: 600, duration: 1.2 });
await cdp.send("Human.wheel", { deltaY: -120 });
await cdp.send("Human.scrollIntoView", { selector: "footer" });
await cdp.send("Human.scrollTo", { x: 0, y: 0 });
```

A scroll can trigger lazy loading. Wait for the resulting content before reading it.

<span id="drag-and-drop-operations" />

<span id="manual-drag-with-mousedown--mouseup" />

<span id="manual-drag-with-mousedown-/-mouseup" />

## Drag and hold

### `Human.drag`

Moves to the start point, presses the button, drags to the end point, and releases. Drags decelerate into the target and may overshoot and correct, which suits sliders and drag-and-drop targets.

| Parameter          | Default  | Meaning                             |
| ------------------ | -------- | ----------------------------------- |
| `startX`, `startY` | required | Viewport coordinates to press at.   |
| `endX`, `endY`     | required | Viewport coordinates to release at. |
| `button`           | `"left"` | `"left"`, `"right"`, or `"middle"`. |

Response: `x`, `y`, `button`, where `x` and `y` are the release position.

```javascript theme={null}
await cdp.send("Human.drag", {
  startX: 100,
  startY: 250,
  endX: 400,
  endY: 250,
});
```

If the drag fails part way, Surfsky releases the button before returning the error.

### `Human.mouseDown` and `Human.mouseUp`

Press and release separately when you need a custom hold or a path with intermediate stops.

| Parameter | Default  | Meaning                                       |
| --------- | -------- | --------------------------------------------- |
| `x`, `y`  | required | Viewport coordinates of the press or release. |
| `button`  | `"left"` | `"left"`, `"right"`, or `"middle"`.           |

Response for both: `x`, `y`, `button`.

`Human.mouseDown` presses at `x`, `y` without moving there first. Call `Human.moveTo` with the same coordinates before it so the cursor arrives along a natural path. While the button is held, the cursor keeps a small tremor until `Human.mouseUp`. Release the button even if an intermediate action fails:

```javascript theme={null}
await cdp.send("Human.moveTo", { x: 100, y: 250 });
await cdp.send("Human.mouseDown", { x: 100, y: 250 });
try {
  await cdp.send("Human.moveTo", { x: 400, y: 250 });
} finally {
  await cdp.send("Human.mouseUp", { x: 400, y: 250 });
}
```

## Android profiles

When the profile's fingerprint is Android, Surfsky switches input to touch. The commands and parameters stay the same, and the following changes apply:

* `Human.click` and `Human.dblclick` become taps. `button` and `clickCount` are ignored.
* `Human.moveTo` records the target and sends no event.
* `Human.scroll`, `Human.wheel`, `Human.scrollIntoView`, and `Human.scrollTo` become swipes. Long distances are split into several swipes with the finger lifting in between, so they take longer than `duration`.
* `Human.mouseDown`, `Human.mouseUp`, and `Human.drag` become touch start, touch end, and a finger drag.
* `Human.type` and `Human.press` send keyboard events as on desktop.

## Selector and timing problems

Selectors are resolved in the main document. An element inside an iframe or shadow root does not resolve, and the error message says so. Use your framework's frame or shadow-root support to find the element, then pass its viewport coordinates as `x` and `y`.

If a click lands in the wrong place, inspect the page for overlays, scrolling, and layout changes. Recalculate coordinates after a resize or navigation. See [debugging](/debugging).

Human timing values are in **seconds**. CAPTCHA timeouts and most Playwright timeouts are in **milliseconds**; do not copy those values between APIs.

## Using the SDK

The [Python](/sdk/python) and [TypeScript](/sdk/typescript) browser APIs wrap these commands. Use them when you want browser actions without managing CDP sessions yourself.
