Skip to main content

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_api and playwright.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 default
  • 1.0 - Normal speed
  • 1.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

  1. Path Generation - Calculates a Bezier curve between start and end points with randomized control points
  2. Trajectory Humanization - Applies random distortions to simulate natural hand movement
  3. Easing Functions - Uses easing (ease-out-quint) for acceleration/deceleration
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

smooth_cursor_playwright-0.1.0.tar.gz (14.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

smooth_cursor_playwright-0.1.0-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

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

Hashes for smooth_cursor_playwright-0.1.0.tar.gz
Algorithm Hash digest
SHA256 125ebf8253aa7aff968ad45ad7edd556405f2a262aff19904844447c0e1825cc
MD5 3406fd388fbced6b5cb66975905dd4fc
BLAKE2b-256 08f35b9709c905b64f583851d8fc2827661cd479dac3b11732d3be1bcec7cd93

See more details on using hashes here.

File details

Details for the file smooth_cursor_playwright-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for smooth_cursor_playwright-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 868765dd1daf62565f7d98a78e5a4038e16f4c9194ebe2d0f64c54ca0f07200d
MD5 5b52505e36d957006567166f6ddbfc1a
BLAKE2b-256 a2d13cd516fa2691ec12cd06d57179f60dc4b844d1384da1e5cf72af0cdd9a43

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page