Skip to content

botonomus.human

Human-like pointer, keyboard and wheel input.

botonomus.human

Human-like pointer, keyboard and wheel input.

Human drives one page; HumanPage wraps a page so its click, fill, type, press, hover and scroll (and locator equivalents) are humanized automatically. HumanConfig holds the behaviour parameters, with presets default, careful and fast.

HumanProfile module-attribute

HumanProfile = HumanConfig

Deprecated alias of HumanConfig, kept for 0.1 code; use HumanConfig.

Actionable

Bases: Protocol

The locator surface needed for actionability checks.

bounding_box async

bounding_box() -> Mapping[str, float] | None

Viewport box {x, y, width, height} or None.

is_visible async

is_visible() -> bool

Whether the element is attached and rendered with a non-empty box.

NotActionableError

Bases: BotonomusError, ValueError

An element did not become visible, enabled and stable in time.

Subclasses ValueError because 0.1 raised ValueError for an element without a visible box.

Clock

Bases: Protocol

Monotonic time and sleeping.

now

now() -> float

Monotonic seconds.

sleep async

sleep(seconds: float) -> None

Wait for seconds.

RealClock

The running event loop's clock and asyncio.sleep.

now

now() -> float

The event loop's monotonic time in seconds.

sleep async

sleep(seconds: float) -> None

Sleep for seconds (non-positive values just yield).

HumanConfig dataclass

HumanConfig(move_speed: float = 1.0, key_delay: float = 0.11, key_jitter: float = 0.35, key_drift: float = 0.12, digraph_speedup: float = 0.72, word_pause: float = 1.5, punctuation_pause: float = 2.1, hesitation_rate: float = 0.03, max_key_gap: float = 1.5, mistype_rate: float = 0.02, mistype_notice: int = 2, layout: KeyboardLayout | str = 'us', click_hold: tuple[float, float] = (0.05, 0.14), aim_pause: tuple[float, float] = (0.04, 0.16), think: tuple[float, float] = (0.15, 0.45), overshoot_probability: float = 0.25, overshoot_min_distance: float = 250.0, idle_moves: bool = False, idle_duration: tuple[float, float] = (0.2, 0.8), idle_amplitude: float = 3.0, actionability_timeout: float = 10.0)

Timing and behaviour parameters for Human.

Every random interval is drawn from a right-skewed (lognormal) distribution; two-value ranges below give its typical span, not flat uniform bounds. Defaults are moderate and not tuned to any particular detector.

Use preset for a named starting point and replace to adjust it.

Attributes:

Name Type Description
move_speed float

Pointer speed multiplier; values above 1 are faster.

key_delay float

Median seconds between keystrokes.

key_jitter float

Lognormal sigma of keystroke gaps.

key_drift float

Sigma of the per-session typing tempo (one draw per Human) and of its slow drift within a session.

digraph_speedup float

Gap multiplier for common English letter pairs, which practised typists roll quickly.

word_pause float

Median gap multiplier after a space.

punctuation_pause float

Median gap multiplier after punctuation or a newline.

hesitation_rate float

Probability per keystroke of an extra short hesitation.

max_key_gap float

Upper bound on one keystroke gap, in seconds.

mistype_rate float

Probability per letter of hitting a neighbouring key first and correcting it. Ignored when typing with sensitive=True.

mistype_notice int

Most extra characters typed before a slip is noticed.

layout KeyboardLayout | str

Keyboard layout (or registered layout name) for choosing neighbouring keys; see botonomus.human.keyboard.

click_hold tuple[float, float]

Typical range of seconds a button stays pressed.

aim_pause tuple[float, float]

Typical range of seconds between arriving and pressing.

think tuple[float, float]

Typical range of seconds paused before acting on a target.

overshoot_probability float

Chance that a long move overshoots and corrects.

overshoot_min_distance float

Moves shorter than this many CSS pixels never overshoot.

idle_moves bool

Whether to make small pointer drifts between actions.

idle_duration tuple[float, float]

Typical range of seconds of one idle drift.

idle_amplitude float

Typical drift radius in CSS pixels.

actionability_timeout float

Seconds to wait for a target to become visible, enabled and stable before acting.

keyboard_layout property

keyboard_layout: KeyboardLayout

The resolved KeyboardLayout.

preset classmethod

preset(name: PresetName = 'default', **overrides: Any) -> HumanConfig

A named preset, optionally with some fields overridden.

  • default: moderate speed, occasional slips, no idle drift.
  • careful: slower pointer and typing, longer thinking, idle drift between actions, more overshoot on long moves.
  • fast: quick, practised input with fewer slips and pauses.

Parameters:

Name Type Description Default
name PresetName

Preset name.

'default'
**overrides Any

Field values replacing the preset's.

{}

Returns:

Type Description
HumanConfig

The configuration.

Raises:

Type Description
ValueError

For an unknown preset or an invalid override.

replace

replace(**changes: Any) -> HumanConfig

A copy with changes applied (validated like a new instance).

Human

Human(page: Any, *, config: HumanConfig | None = None, profile: HumanConfig | None = None, seed: int | None = None, clock: Clock | None = None)

Human-like input for one page. Remembers the pointer position between actions.

Works with native botonomus.cdp.Page and Playwright-compatible pages: it only uses page.mouse (move, down, up, wheel), page.keyboard (type, press) and page.evaluate. Targets are locators from either driver, or (x, y) points.

Before acting on a locator, Human waits until it is visible, enabled and has a stable box, and brings it into view with wheel scrolling.

Parameters:

Name Type Description Default
page Any

The page to drive.

required
config HumanConfig | None

Behaviour parameters; HumanConfig() by default.

None
profile HumanConfig | None

Deprecated name for config.

None
seed int | None

Seed for reproducible paths and timing.

None
clock Clock | None

Time source; replace it to simulate time in tests.

None

Raises:

Type Description
ValueError

If both config and profile are given.

profile property

profile: HumanConfig

Deprecated alias of config.

position property

position: Point | None

Last pointer position this instance moved to, if any.

rng property

rng: Random

The random source, for callers that want correlated choices.

pause async

pause(low: float | None = None, high: float | None = None) -> None

Sleep for a right-skewed "thinking" interval; sends no CDP traffic.

Parameters:

Name Type Description Default
low float | None

Lower typical bound in seconds; config.think[0] by default.

None
high float | None

Upper typical bound in seconds; config.think[1] by default.

None

idle async

idle(seconds: float | None = None) -> None

Small, low-amplitude pointer drift around the current position.

Does nothing until the pointer has a known position. Never runs longer than seconds (by default a draw from config.idle_duration).

Parameters:

Name Type Description Default
seconds float | None

Upper bound on the drift's duration.

None

move_to async

move_to(x: float, y: float, *, target_width: float = 40.0) -> None

Move the pointer to (x, y) along a curved, eased path.

Long, fast moves sometimes overshoot the target slightly and correct back (config.overshoot_probability), as aimed human movements do.

Parameters:

Name Type Description Default
x float

Destination x in CSS pixels.

required
y float

Destination y in CSS pixels.

required
target_width float

Size of the target, which sets the Fitts's-law duration.

40.0

move_to_locator async

move_to_locator(locator: Any) -> Point

Bring locator into view, wait until it is actionable, and move into it.

Returns:

Type Description
Point

The point reached.

Raises:

Type Description
NotActionableError

If the element does not become visible, enabled and stable within config.actionability_timeout (a ValueError).

hover async

hover(target: Any) -> None

Move the pointer over target (a locator or a point) without clicking.

Raises:

Type Description
NotActionableError

If a locator target never becomes actionable.

click async

click(target: Any, *, button: str = 'left') -> None

Move to target (a locator or a point) and click with a natural hold.

Raises:

Type Description
ValueError

For an unknown button.

NotActionableError

If a locator target never becomes actionable.

type async

type(text: str, target: Any | None = None, *, sensitive: bool = False) -> None

Type text key by key into the focused element; clicks target first.

Gaps follow a lognormal rhythm with per-session drift, faster common letter pairs and longer pauses after words and punctuation. With config.mistype_rate per letter a neighbouring key is hit, noticed a character or two later, and corrected with backspaces; the field always ends up containing exactly text appended to what was there.

Parameters:

Name Type Description Default
text str

Text to type.

required
target Any | None

Optional locator or point to click first.

None
sensitive bool

Disable mistakes, for passwords, codes and other secrets where a transient wrong character is unacceptable (it can trigger validation, lockouts or be captured by key loggers on the page).

False

fill async

fill(target: Any, value: str, *, sensitive: bool = False) -> None

Click target, clear it, and type value humanly.

Clearing selects the current text (select_text on the locator) and presses Backspace; without select_text it presses End and one Backspace per character. Unlike a driver's fill, the text arrives as keystrokes.

Parameters:

Name Type Description Default
target Any

A locator for an input, textarea or editable element.

required
value str

The value to leave in the field.

required
sensitive bool

Disable mistakes; see type.

False

press async

press(key: str) -> None

Press key (for example "Enter") after a short reaction pause.

Parameters:

Name Type Description Default
key str

A key name understood by the page's keyboard.

required

scroll async

scroll(delta_y: float, *, delta_x: float = 0.0) -> None

Wheel-scroll in several eased increments, like a physical wheel.

KeyboardLayout dataclass

KeyboardLayout(name: str, rows: tuple[str, ...], offsets: tuple[float, ...])

Physical key rows of a keyboard layout, used to pick neighbouring keys.

Rows are given unshifted, top (number row) to bottom. Two keys are neighbours when they sit next to each other on one row, or overlap horizontally on an adjacent row once the row offsets are applied.

Attributes:

Name Type Description
name str

Registry name, for example "us".

rows tuple[str, ...]

Unshifted characters of each key row, top to bottom.

offsets tuple[float, ...]

Horizontal offset of each row in key widths.

neighbours

neighbours(char: str) -> str

Letters physically adjacent to char, in the same case as char.

Only letters below the number row are returned: a slip onto a digit or symbol key (or an AZERTY number-row accent) is rarer and could change the meaning of structured input.

Parameters:

Name Type Description Default
char str

A single character.

required

Returns:

Type Description
str

The neighbouring letters, or an empty string if char is not a letter

str

on this layout.

Keystroke dataclass

Keystroke(kind: KeystrokeKind, text: str = '', mistake: bool = False, correction: bool = False)

One planned keyboard action.

Attributes:

Name Type Description
kind KeystrokeKind

"char" types text; "backspace" deletes one character.

text str

The character typed, empty for a backspace.

mistake bool

Whether this character is the wrong key of a slip.

correction bool

Whether this keystroke is part of fixing a slip (the backspaces and the first one after them), which is typed after a noticing pause.

HumanLocator

HumanLocator(human: Human, locator: Any)

A locator whose interactions go through Human.

Parameters:

Name Type Description Default
human Human

The input engine (shared with the owning HumanPage).

required
locator Any

The driver's locator.

required

raw property

raw: Any

The wrapped driver locator, for un-humanized access.

first property

first: HumanLocator

The first match, still humanized.

nth

nth(index: int) -> HumanLocator

The index-th match, still humanized.

click async

click(*, button: str = 'left') -> None

Move along a human path and click; see Human.click.

hover async

hover() -> None

Move the pointer over the element; see Human.hover.

fill async

fill(value: str, *, sensitive: bool = False) -> None

Click, clear and type value keystroke by keystroke.

Parameters:

Name Type Description Default
value str

The value to leave in the field.

required
sensitive bool

Disable mistakes (passwords, codes); see Human.type.

False

type async

type(text: str, *, sensitive: bool = False) -> None

Click the element, then type text humanly after its current content.

Parameters:

Name Type Description Default
text str

Text to type.

required
sensitive bool

Disable mistakes (passwords, codes); see Human.type.

False

press async

press(key: str) -> None

Focus the element (without clicking it) and press key.

Focus is moved programmatically, as Playwright's press does, so pressing a key on a button never also clicks it.

HumanPage

HumanPage(page: Any, *, human: Human | None = None, config: HumanConfig | None = None, seed: int | None = None)

A page wrapper that makes interactions human-like.

Selectors are resolved with the wrapped page's own locator(), so every selector the driver supports works here. See the module docstring for which methods are humanized; all other attributes are delegated unchanged.

Parameters:

Name Type Description Default
page Any

A native botonomus.cdp.Page or a Playwright page.

required
human Human | None

An existing input engine for page; created when omitted.

None
config HumanConfig | None

Behaviour for a newly created Human.

None
seed int | None

Seed for a newly created Human.

None

Raises:

Type Description
ValueError

If human is given together with config or seed.

raw property

raw: Any

The wrapped page, for un-humanized access.

human property

human: Human

The input engine, for direct use (idle, move_to …).

locator

locator(selector: str) -> HumanLocator

A humanized locator for selector.

click async

click(selector: str, *, button: str = 'left') -> None

Click the element matching selector with a human pointer path.

hover async

hover(selector: str) -> None

Move the pointer over the element matching selector.

fill async

fill(selector: str, value: str, *, sensitive: bool = False) -> None

Click, select all, and type value keystroke by keystroke.

Unlike the driver's fill, text is never inserted in one go.

Parameters:

Name Type Description Default
selector str

The field.

required
value str

The value to leave in the field.

required
sensitive bool

Disable mistakes (passwords, codes); see Human.type.

False

type async

type(selector: str, text: str, *, sensitive: bool = False) -> None

Click the element, then type text humanly after its current content.

Parameters:

Name Type Description Default
selector str

The field.

required
text str

Text to type.

required
sensitive bool

Disable mistakes (passwords, codes); see Human.type.

False

press async

press(key_or_selector: str, key: str | None = None) -> None

Press a key on the focused element, or on selector first.

Both press("Enter") and the Playwright form press("#q", "Enter") work.

Parameters:

Name Type Description Default
key_or_selector str

The key, or a selector when key is given.

required
key str | None

The key to press on that selector's element.

None

scroll async

scroll(delta_y: float, *, delta_x: float = 0.0) -> None

Wheel-scroll by delta_y (and delta_x) CSS pixels in eased ticks.

KeystrokeRhythm

KeystrokeRhythm(config: HumanConfig, rng: Random)

Stateful keystroke-gap generator for one typing session.

A tempo factor is drawn once per instance (some sessions type faster than others) and drifts slowly as a mean-reverting random walk between keystrokes.

Parameters:

Name Type Description Default
config HumanConfig

Timing parameters.

required
rng Random

Random source.

required

gap

gap(char: str, next_char: str | None = None) -> float

Seconds to wait after typing char before the next keystroke.

Parameters:

Name Type Description Default
char str

The character just typed.

required
next_char str | None

The character about to be typed, if known.

None

Returns:

Type Description
float

The gap in seconds, at most config.max_key_gap.

correction_pause

correction_pause() -> float

Seconds between making a slip and starting to correct it.

backspace_gap

backspace_gap() -> float

Seconds between repeated backspaces, which are faster than typing.

wait_until_actionable async

wait_until_actionable(locator: Any, *, timeout: float = 10.0, clock: Clock | None = None, require_enabled: bool = True) -> dict[str, float]

Wait until locator is attached, visible, enabled and has a stable box.

Stability means two bounding boxes measured FRAME_INTERVAL apart agree within half a pixel, so an element still sliding or growing is not targeted. The enabled check is skipped for locators without is_enabled.

Parameters:

Name Type Description Default
locator Any

An Actionable locator.

required
timeout float

Seconds before giving up.

10.0
clock Clock | None

Time source; the event loop clock by default.

None
require_enabled bool

Whether a disabled element should be waited on.

True

Returns:

Type Description
dict[str, float]

The last measured box.

Raises:

Type Description
NotActionableError

If the element is not actionable within timeout; the message names the failing condition.

available_layouts

available_layouts() -> list[str]

Names of all registered layouts, sorted.

get_layout

get_layout(layout: KeyboardLayout | str) -> KeyboardLayout

Resolve a layout name (or pass a layout through unchanged).

Parameters:

Name Type Description Default
layout KeyboardLayout | str

A registered layout name or a KeyboardLayout.

required

Returns:

Type Description
KeyboardLayout

The layout.

Raises:

Type Description
ValueError

If no layout of that name is registered.

plan_keystrokes

plan_keystrokes(text: str, layout: KeyboardLayout, mistype_rate: float, rng: Random, *, max_notice_delay: int = 2) -> list[Keystroke]

Plan the keystrokes for text, including occasional corrected slips.

With probability mistype_rate per letter, a neighbouring key is typed instead. The slip is noticed after 0 to max_notice_delay further characters, deleted with the matching number of backspaces, and the intended characters are typed again. Replaying the plan on an empty field always yields exactly text.

A slip is never left pending across a newline, carriage return or tab, since those could submit a form or move focus first.

Parameters:

Name Type Description Default
text str

The text that must end up typed.

required
layout KeyboardLayout

Layout used to choose the neighbouring key.

required
mistype_rate float

Probability of a slip per letter, in [0, 1].

required
rng Random

Random source.

required
max_notice_delay int

Most extra characters typed before the slip is noticed.

2

Returns:

Type Description
list[Keystroke]

The keystrokes in order.

register_layout

register_layout(layout: KeyboardLayout) -> None

Make layout available by name, replacing any layout of the same name.

Parameters:

Name Type Description Default
layout KeyboardLayout

The layout to register.

required

bezier_path

bezier_path(start: Point, end: Point, rng: Random, steps: int | None = None) -> list[Point]

A cubic Bézier path from start to end with eased progress.

Control points bow the curve perpendicular to the straight line by a random, slightly asymmetric amount, as a wrist-driven movement does.

Parameters:

Name Type Description Default
start Point

Starting point in CSS pixels.

required
end Point

Destination; always the exact last point.

required
rng Random

Random source (seed it for reproducible paths).

required
steps int | None

Number of points; derived from distance when None.

None

Returns:

Type Description
list[Point]

Points after start, ending at end.

eased_weights

eased_weights(count: int) -> Sequence[float]

count positive weights summing to 1, small at both ends (sine easing).

movement_duration

movement_duration(distance: float, width: float, speed: float, rng: Random) -> float

Seconds to reach a target, following Fitts's law.

Parameters:

Name Type Description Default
distance float

Travel distance in CSS pixels.

required
width float

Target size along the approach; smaller targets take longer.

required
speed float

Multiplier; values above 1 are faster.

required
rng Random

Random source for natural variation.

required

target_point

target_point(box: FloatRect, rng: Random) -> Point

A point near the centre of box, never on its outer 20% border.

bounded_lognormal

bounded_lognormal(rng: Random, low: float, high: float) -> float

A lognormal draw whose central ~95% falls in [low, high].

The median is the geometric mean of the bounds, so the distribution is skewed to the right like human reaction times. Draws outside the range are clamped.

Parameters:

Name Type Description Default
rng Random

Random source.

required
low float

Lower typical bound, greater than zero.

required
high float

Upper typical bound.

required

Returns:

Type Description
float

Seconds (or any positive quantity) in [low, high].

key_delays

key_delays(text: str, profile: HumanConfig, rng: Random) -> list[float]

Gaps after each character of text from a fresh KeystrokeRhythm.

Parameters:

Name Type Description Default
text str

Text to be typed.

required
profile HumanConfig

Timing parameters.

required
rng Random

Random source.

required

Returns:

Type Description
list[float]

One delay in seconds per character, each at most profile.max_key_gap.