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 are you building or trying to learn?

Got a browser workflow your team still clicks through by hand? Tell me what it is, and I'll tell you whether it should be automated and how.

Build or learn with me
Doug SilkstoneFounder, builder & operator

I build and run businesses with a team of me plus AI agents. These articles draw on the methods I use across product, brand, design, outreach, sales, growth, support and operations, and the work I do with other businesses.

About me · More articles by Doug