Human-like cursor movements for Playwright automation using Bezier curves
Project description
smooth-cursor-playwright
Human-like cursor movements for Playwright automation using Bezier curves with randomized control points and easing functions.
Features
- Bezier curve trajectories - Natural mouse movement paths with randomized control points
- Momentum scrolling - Smooth scroll animations with easing
- Sync and Async API - Works with both
playwright.sync_apiandplaywright.async_api - Visual debugging overlay - Optional cursor visualization for development
- Configurable parameters - Fine-tune speed, hesitation, and movement characteristics
Installation
pip install smooth-cursor-playwright
Quick Start
Synchronous API
from playwright.sync_api import sync_playwright
from human_cursor import SyncHumanCursor, Vector
with sync_playwright() as p:
browser = p.chromium.launch(headless=False)
page = browser.new_page()
page.goto("https://example.com")
cursor = SyncHumanCursor(page)
# Click using Locator (recommended)
submit_btn = page.locator("button#submit")
cursor.click(submit_btn)
# Or use CSS selector string
cursor.click("input#search")
# Scroll down
cursor.scroll(Vector(0, 500))
browser.close()
Asynchronous API
import asyncio
from playwright.async_api import async_playwright
from human_cursor import HumanCursor, Vector
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=False)
page = await browser.new_page()
await page.goto("https://example.com")
cursor = HumanCursor(page)
# Click using Locator (recommended)
submit_btn = page.locator("button#submit")
await cursor.click(submit_btn)
# Or use CSS selector string
await cursor.click("input#search")
# Scroll down
await cursor.scroll(Vector(0, 500))
await browser.close()
asyncio.run(main())
Default Values
The library uses these defaults for human-like behavior:
| Parameter | Default | Description |
|---|---|---|
move_speed |
1.75 | Cursor movement speed multiplier |
move_delay |
50 ms | Delay before starting movement |
hesitate |
50 ms | Pause before clicking |
wait_for_click |
30 ms | Small delay before click action |
scroll_speed |
250 ms | Scroll animation duration |
scroll_delay |
200 ms | Delay before scrolling starts |
Access defaults programmatically:
from human_cursor import DEFAULT_OPTIONS
print(DEFAULT_OPTIONS)
# {'move_speed': 1.75, 'move_delay': 50, 'hesitate': 50, 'wait_for_click': 30, 'scroll_speed': 250, 'scroll_delay': 200}
API Reference
HumanCursor / SyncHumanCursor
Constructor
cursor = HumanCursor(
page, # Playwright Page object
start=Vector(0, 0), # Initial cursor position
default_options={}, # Override default options
show_overlay=False, # Enable visual overlay
overlay_port=7845, # UDP port for overlay
overlay_offset=None # Manual offset for overlay (Vector)
)
Methods
click(selector, **options)
Click on an element or current position. Accepts Locator or CSS selector string.
# Using Locator (recommended)
button = page.locator("button#submit")
await cursor.click(button)
# Using CSS selector
await cursor.click("button#submit")
# Click at current position
await cursor.click()
# Full options
await cursor.click(
page.locator("button"), # Locator, CSS selector, or None
timeout=30000, # Wait timeout (ms)
move_speed=1.75, # Speed multiplier (default: 1.75)
move_delay=50, # Delay before moving (ms, default: 50)
scroll_speed=250, # Scroll animation duration (ms, default: 250)
hesitate=50, # Pause before clicking (ms, default: 50)
wait_for_click=30, # Delay before click (ms, default: 30)
button="left", # Mouse button: "left", "right", "middle"
click_count=1, # Number of clicks (2 = double-click)
modifiers=None # Key modifiers: ["Control"], ["Shift"], ["Alt"]
)
move(selector, **options)
Move cursor to an element (random point inside it). Accepts Locator or CSS selector string.
# Using Locator (recommended)
search_input = page.locator("input#search")
await cursor.move(search_input)
# Using CSS selector
await cursor.move("input#search")
# Full options
await cursor.move(
page.locator("input"), # Locator or CSS selector
timeout=30000, # Wait timeout (ms)
move_speed=1.75, # Speed multiplier (default: 1.75)
move_delay=50, # Delay before moving (ms, default: 50)
scroll_speed=250 # Scroll animation duration (ms, default: 250)
)
move_to(destination, **options)
Move cursor to exact coordinates.
await cursor.move_to(
Vector(100, 200), # Target coordinates
move_speed=1.75, # Speed multiplier (default: 1.75)
move_delay=50 # Delay before moving (ms, default: 50)
)
scroll(delta, **options)
Scroll by a delta amount with momentum effect.
await cursor.scroll(
Vector(0, 500), # Scroll amount (positive y = down)
scroll_speed=250, # Animation duration (ms, default: 250)
scroll_delay=200 # Delay before scrolling (ms, default: 200)
)
scroll_to(selector, **options)
Scroll an element into view. Accepts Locator or CSS selector string.
# Using Locator
footer = page.locator("div#footer")
await cursor.scroll_to(footer)
# Using CSS selector
await cursor.scroll_to("div#footer", timeout=30000)
Configuration Options
Override Default Options
Pass custom defaults to the constructor:
cursor = SyncHumanCursor(
page,
default_options={
"move_speed": 2.0, # Faster movements
"hesitate": 100, # Longer hesitation
"wait_for_click": 50, # Longer delay before click
"scroll_speed": 300, # Slower scroll animations
}
)
Speed Control
The move_speed parameter controls cursor velocity:
< 1.0- Slower than default1.0- Normal speed1.75- Default (slightly fast, natural feel)> 2.0- Fast movements
Modifiers for Click
Use keyboard modifiers with clicks:
link = page.locator("a.external-link")
# Ctrl+Click to open link in new tab
await cursor.click(link, modifiers=["Control"])
# Shift+Click for range selection
await cursor.click(link, modifiers=["Shift"])
# Multiple modifiers
await cursor.click(link, modifiers=["Control", "Shift"])
Visual Debugging Overlay
Enable the cursor overlay to visualize movements during development:
cursor = SyncHumanCursor(
page,
show_overlay=True,
overlay_offset=Vector(100, 150) # Browser window position offset
)
Run the overlay server (requires separate script with tkinter):
# cursor_overlay.py
import tkinter as tk
import socket
import json
root = tk.Tk()
root.attributes('-topmost', True)
root.attributes('-transparentcolor', 'white')
root.overrideredirect(True)
root.geometry(f"{root.winfo_screenwidth()}x{root.winfo_screenheight()}+0+0")
canvas = tk.Canvas(root, bg='white', highlightthickness=0)
canvas.pack(fill='both', expand=True)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('127.0.0.1', 7845))
sock.setblocking(False)
cursor_dot = canvas.create_oval(0, 0, 10, 10, fill='red', outline='darkred')
def update():
try:
data, _ = sock.recvfrom(1024)
pos = json.loads(data)
canvas.coords(cursor_dot, pos['x']-5, pos['y']-5, pos['x']+5, pos['y']+5)
except BlockingIOError:
pass
root.after(10, update)
update()
root.mainloop()
Types
The package exports these types for type hints:
from human_cursor import (
Vector, # Point with x, y coordinates
BoundingBox, # Element bounding box
CurveOptions, # Bezier curve parameters
ClickOptions, # Click operation options
ScrollOptions, # Scroll operation options
MoveOptions, # Move operation options
DEFAULT_OPTIONS, # Default configuration values
)
How It Works
- Path Generation - Calculates a Bezier curve between start and end points with randomized control points
- Trajectory Humanization - Applies random distortions to simulate natural hand movement
- Easing Functions - Uses easing (ease-out-quint) for acceleration/deceleration
- Micro-delays - Adds small random delays between movement steps
License
MIT License - see LICENSE for details.
Publishing to PyPI
# Install build tools
pip install build twine
# Build the package
cd human_cursor
python -m build
# Check the package
twine check dist/*
# Upload to TestPyPI (optional)
twine upload --repository testpypi dist/*
# Upload to PyPI
twine upload dist/*
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file smooth_cursor_playwright-0.1.0.tar.gz.
File metadata
- Download URL: smooth_cursor_playwright-0.1.0.tar.gz
- Upload date:
- Size: 14.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
125ebf8253aa7aff968ad45ad7edd556405f2a262aff19904844447c0e1825cc
|
|
| MD5 |
3406fd388fbced6b5cb66975905dd4fc
|
|
| BLAKE2b-256 |
08f35b9709c905b64f583851d8fc2827661cd479dac3b11732d3be1bcec7cd93
|
File details
Details for the file smooth_cursor_playwright-0.1.0-py3-none-any.whl.
File metadata
- Download URL: smooth_cursor_playwright-0.1.0-py3-none-any.whl
- Upload date:
- Size: 16.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
868765dd1daf62565f7d98a78e5a4038e16f4c9194ebe2d0f64c54ca0f07200d
|
|
| MD5 |
5b52505e36d957006567166f6ddbfc1a
|
|
| BLAKE2b-256 |
a2d13cd516fa2691ec12cd06d57179f60dc4b844d1384da1e5cf72af0cdd9a43
|