definitely-not-a-robot: making Puppeteer move like a person

An open-source Puppeteer wrapper that adds curved mouse paths, uneven typing and idle jitter, so legitimate automation stops looking like a bot.

3 min read

Updated

In this article · 4 sections

Bot detection has learned to spot automated browsers by how predictable they are: a click exactly 500ms after the page loads, a mouse that travels in a straight line, every keystroke at the same interval, mechanical scrolling, and never a mistake or a moment of hesitation. I built definitely-not-a-robot, a wrapper around Puppeteer, to put the imperfection back.

definitely-not-a-robot driving the shadcn/ui site in a browser, with a red debug line tracing the curved mouse path
The package automating the shadcn/ui site with debug mode on: the red line is the path the mouse took.

Predictability was the problem

That predictability hurts legitimate work as much as anything else. Testing tools get blocked on production sites, research scraping runs into endless CAPTCHAs, QA automation fails on bot checks and monitoring tools get their IPs banned, so data collection becomes unreliable.

Hiding the automation only goes so far, so the package also makes it behave less like a machine: deliberate randomness in timing, curved movement and small pauses, so the browser acts the way a person does.

Movement and timing

The base is puppeteer-extra with its stealth plugin. The wrapper is written in TypeScript and bundled to CommonJS with esbuild, with each behaviour in its own utility:

  • Mouse paths follow Bezier curves, split into segments with jitter and natural acceleration and deceleration.
  • Typing delays grow with the distance between character codes, at 40 WPM by default, with a 200ms pause after ., ! or ?.
  • Clicks wait a random 50 to 150ms, with optional hesitation before important ones.
  • Idle jitter moves the mouse 3 to 95 pixels and back again, like a resting hand, with configurable intensity and count.
  • Debug mode draws the mouse path in red.

The API stays small, and every method returns a promise. Here it drives the shadcn/ui site, typing quickly in one field and slowly in another:

// From a clone of the GitHub repository (it isn't on npm)
import HumanBrowser from "./npm-definitely-not-a-robot/src/HumanBrowser";

const browser = new HumanBrowser();

await browser.launch({ headless: false });
await browser.navigate("https://ui.shadcn.com");

// Search with natural movement and typing
await browser.humanMove('input[type="search"]', {
  hesitationBeforeClick: true,
  debug: true, // Shows red path
});
await browser.jitterMouse({ jitterCount: 3 });
await browser.humanType("Hello World", 86); // 86 WPM

// Navigate with pauses
await browser.wait(300, 500);
await browser.humanMove('button[aria-label="Select a team"]');

// Careful typing for important fields
await browser.humanType("Monster", 30); // Slow 30 WPM

await browser.close();

The source and working examples are on GitHub.

What mattered while building it

Randomness is the key. The difference between a bot and a person is unpredictability. People don't move in straight lines, their typing speed varies from key to key and their hands drift when idle.

Seeing the path mattered. The red debug trace showed exactly how the mouse moved, so I could check the curves looked natural, spot mechanical patterns at a glance and tune the parameters by eye.

Simple beat clever. Plain randomisation worked better than a complex ML model would have: Bezier curves for movement, varied delays for typing and basic jitter for idle time.

Built for legitimate automation

The package is for testing, research, monitoring and accessibility: running QA on a production site without tripping bot detection, collecting public data for research and building automation that doesn't break. The README is blunt about the rest: "Beep Boop. I don't condone this behaviour." Sometimes, though, you need to test your own production site without getting blocked.

Research and sources

Checked 2026-10-18. Next review: 2027-04-18. Listings and availability can change.

  1. dougwithseismic/npm-definitely-not-a-robot

    GitHub · Checked 2026-10-18 · Primary source

    The package's source, README and demo GIF: a Puppeteer wrapper with Bezier mouse movement, WPM-based typing, jitter and a debug path, built on puppeteer-extra's stealth plugin.

  2. Puppeteer

    Google Chrome · Checked 2026-10-18 · Primary source

    Puppeteer is the browser automation library the package wraps.

  3. puppeteer-extra-plugin-stealth

    puppeteer-extra · Checked 2026-10-18 · Primary source

    The stealth plugin for puppeteer-extra, which the package builds on to avoid common automation fingerprints.

  4. esbuild

    esbuild · Checked 2026-10-18 · Primary source

    esbuild is the bundler that compiles the package's TypeScript.

People and organisations mentioned

What needs to move forward?

If an essential workflow depends on repeated browser work, bring the process, permissions and exceptions. We can assess what should be automated and how the team will maintain it.

Discuss a project
Doug SilkstoneFounder, builder & operator

I’m a product, engineering and growth consultant. These articles draw on a decade of client work and the businesses I own and operate, including the agent workflows that now let me deliver across more of the business.

About me · More articles by Doug