Mouse Control
The Mouse API provides complete control over mouse input at the page level, enabling you to simulate realistic cursor movement, clicks, double-clicks, and drag operations. By default, all mouse operations use humanized simulation: paths follow natural Bezier curves with Fitts's Law timing, minimum-jerk velocity profiles, physiological tremor, and overshoot correction, making automation virtually indistinguishable from human behavior.
Centralized Mouse Interface
All mouse operations are accessible via tab.mouse, providing a clean, unified API for all mouse interactions.
Quick Start
from pydoll.browser.chromium import Chrome
from pydoll.protocol.input.types import MouseButton
async with Chrome() as browser:
tab = await browser.start()
await tab.go_to('https://example.com')
# Move cursor to position (humanized by default)
await tab.mouse.move(500, 300)
# Click at position (humanized by default)
await tab.mouse.click(500, 300)
# Right-click
await tab.mouse.click(500, 300, button=MouseButton.RIGHT)
# Double-click
await tab.mouse.double_click(500, 300)
# Drag from one position to another
await tab.mouse.drag(100, 200, 500, 400)
Core Methods
move: Move Cursor
Move the mouse cursor to a specific position on the page:
# Humanized move (default, curved path with natural timing)
await tab.mouse.move(500, 300)
# Instant move (single CDP event, no simulation)
await tab.mouse.move(500, 300, humanize=False)
Parameters:
x: Target X coordinate (CSS pixels)y: Target Y coordinate (CSS pixels)humanize(keyword-only): Simulate human-like curved movement (default:True)
click: Click at Position
Move to position and perform a mouse click:
from pydoll.protocol.input.types import MouseButton
# Left click with humanized movement (default)
await tab.mouse.click(500, 300)
# Right click
await tab.mouse.click(500, 300, button=MouseButton.RIGHT)
# Double click via click_count
await tab.mouse.click(500, 300, click_count=2)
# Instant click without humanization
await tab.mouse.click(500, 300, humanize=False)
Parameters:
x: Target X coordinatey: Target Y coordinatebutton(keyword-only): Mouse button, one ofLEFT,RIGHT,MIDDLE(default:LEFT)click_count(keyword-only): Number of clicks (default:1)humanize(keyword-only): Simulate human-like behavior (default:True)
double_click: Double-Click at Position
Convenience method equivalent to click(x, y, click_count=2):
down / up: Low-Level Button Control
Press or release mouse buttons independently:
# Press left button at current position
await tab.mouse.down()
# Release left button
await tab.mouse.up()
# Right button
await tab.mouse.down(button=MouseButton.RIGHT)
await tab.mouse.up(button=MouseButton.RIGHT)
These are primitives that operate at the current cursor position and have no humanize parameter.
drag: Drag and Drop
Move from start to end while holding the mouse button:
# Humanized drag (default)
await tab.mouse.drag(100, 200, 500, 400)
# Instant drag without humanization
await tab.mouse.drag(100, 200, 500, 400, humanize=False)
Parameters:
start_x,start_y: Start coordinatesend_x,end_y: End coordinateshumanize(keyword-only): Simulate human-like drag (default:True)
Disabling Humanization
All mouse methods default to humanize=True. To perform instant, non-humanized operations, pass humanize=False:
# Instant move, single CDP mouseMoved event
await tab.mouse.move(500, 300, humanize=False)
# Instant click: move + press + release, no simulation
await tab.mouse.click(500, 300, humanize=False)
# Instant drag, no curves, no pauses
await tab.mouse.drag(100, 200, 500, 400, humanize=False)
This is useful when speed is critical and detection evasion is not a concern, for example in testing environments or internal tools.
Humanized Mode
When humanize=True (the default), the mouse module applies multiple layers of realism:
Bezier Curve Paths
Mouse follows a natural curved trajectory instead of a straight line. Control points are randomly offset perpendicular to the start→end line, with asymmetric placement (more curvature early in the movement, like a real ballistic reach).
Fitts's Law Timing
Movement duration follows Fitts's Law: MT = a + b × log₂(D/W + 1). Longer distances take proportionally more time, matching human motor control behavior.
Minimum-Jerk Velocity Profile
The cursor follows a bell-shaped speed profile, starting slow, accelerating to peak velocity in the middle, then decelerating at the end. This matches the smoothest possible human movement trajectory.
Physiological Tremor
Small Gaussian noise (σ ≈ 1px) is added to each frame, simulating hand tremor. The tremor amplitude scales inversely with velocity, with more tremor when the cursor is slow or hovering and less during fast ballistic movements.
Overshoot and Correction
For fast, long-distance movements (~70% probability), the cursor overshoots the target by 3–12% of the distance, then makes a small corrective sub-movement back to the target. This matches real human motor control data.
Pre-Click Pause
Humanized clicks include a pre-click pause (50–200ms) that simulates the natural settle time before pressing the button.
Automatic Humanized Element Clicks
When you use element.click(), the Mouse API is automatically used to produce a realistic Bezier curve movement from the current cursor position to the element center before clicking. This means every element.click() call generates natural mouse movement, making element clicks indistinguishable from human behavior.
# Humanized click (default): Bezier curve movement + click
button = await tab.find(id='submit')
await button.click()
# With offset from center
await button.click(x_offset=10, y_offset=5)
# Disable humanization: raw CDP press/release, no cursor movement
await button.click(humanize=False)
Position tracking is maintained across element clicks. Clicking element A, then element B, produces a natural curved path from A's position to B.
Custom Timing Configuration
All humanization parameters are configurable via MouseTimingConfig:
from pydoll.interactions.mouse import MouseTimingConfig
config = MouseTimingConfig(
fitts_a=0.070, # Fitts's Law intercept (seconds)
fitts_b=0.150, # Fitts's Law slope (seconds/bit)
frame_interval=0.012, # Base interval between mouseMoved events
curvature_min=0.10, # Min path curvature as fraction of distance
curvature_max=0.30, # Max path curvature
tremor_amplitude=1.0, # Tremor sigma in pixels
overshoot_probability=0.70, # Chance of overshoot on fast moves
min_duration=0.08, # Minimum movement duration
max_duration=2.5, # Maximum movement duration
)
# Apply to the tab's mouse instance
tab.mouse.timing = config
See the MouseTimingConfig dataclass for all available parameters.
Position Tracking
The Mouse API tracks the cursor position across operations:
# Initial position is (0, 0)
await tab.mouse.move(100, 200)
# Position is now (100, 200)
await tab.mouse.click(300, 400)
# Position is now (300, 400)
# Low-level methods use the tracked position
await tab.mouse.down() # Presses at (300, 400)
await tab.mouse.up() # Releases at (300, 400)
Position State
The mouse position is tracked internally. WebElement.click() automatically uses tab.mouse when available, so position tracking is maintained across element clicks.
Debug Mode
Enable debug mode to visualize mouse movement on the page. When active, colored dots are drawn on a transparent overlay canvas:
- Blue dots: cursor path during movement
- Red dots: click positions
# Enable at runtime via property
tab.mouse.debug = True
# Now all movements draw colored dots
await tab.mouse.click(500, 300)
# Disable when done
tab.mouse.debug = False
This is useful for tuning timing parameters and verifying that paths look natural.
Practical Examples
Click a Button with Realistic Movement
async def click_button_naturally(tab):
# element.click() automatically uses tab.mouse for humanized movement
button = await tab.find(id='submit')
await button.click()
Drag a Slider
async def drag_slider(tab):
slider = await tab.find(css_selector='.slider-handle')
bounds = await slider.get_bounds_using_js()
start_x = bounds['x'] + bounds['width'] / 2
start_y = bounds['y'] + bounds['height'] / 2
end_x = start_x + 200 # Drag 200px to the right
await tab.mouse.drag(start_x, start_y, end_x, start_y)
Hover Over Elements
async def hover_menu(tab):
menu = await tab.find(css_selector='.dropdown-trigger')
bounds = await menu.get_bounds_using_js()
await tab.mouse.move(
bounds['x'] + bounds['width'] / 2,
bounds['y'] + bounds['height'] / 2,
)
# Menu should now be visible via CSS :hover
Learn More
- Human Interactions: Overview of all humanized interactions
- Keyboard Control: Realistic keyboard simulation