> ## Content Index
> Fetch the complete content index at: https://roundproxies.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to set up human typing in Playwright
- URL: https://roundproxies.com/blog/human-typing-playwright/
- Published: 2026-01-10T00:52:28.000Z
- Updated: 2026-09-23T13:04:55.000Z
- Description: Learn how to setup human typing in Playwright with random delays, typos, and Gaussian timing. Bypass bot detection with 4 proven methods.
- Author: Marius Bernard
- Tags: Playwright, Web Scraping, #dated-6961a16c26f439f88a95b19e

Automated typing gives itself away through its rhythm. `page.fill()` drops an entire string into the input in a single frame, and `page.type()` with a fixed `delay` spaces every keystroke exactly the same distance apart. Real typists don't produce either shape. Behavioural fingerprinters on login forms, checkout flows and search boxes read the keydown and keyup intervals that give it away. Setting up human typing in Playwright means feeding the page a keystroke stream whose timing varies the way a person's does: quicker through familiar letter pairs, slower on capitals and punctuation, with pauses between words and the occasional typo that gets backspaced away.

This guide works through three approaches, in order of effort:

- Playwright's built-in `delay` option, a one-line change that breaks up instant string dumps but still fires at a constant interval.
- A custom typing function that samples a fresh delay per character, so the intervals scatter around a target speed instead of repeating.
- A fuller simulation that adds word-level pauses, mistyped neighbour keys and corrections.

Off-the-shelf options cover the same ground if you'd rather not write the timing model yourself. The `human-typing` npm package exposes a `Keyboard` class with a configurable `typoProbability` and a Fitts's law delay model. Each inter-key pause comes from the physical distance between the keys plus a noise factor, so the same string never types identically twice. On the Python and TypeScript side, HumanTyping drives delays from a Markov model and layers on fatigue, bigram speedups and corrections.

Typing realism is one signal among several. Cursor paths matter too, which is what libraries like `human-cursor` address with Bezier-curve movement and clicks at random points inside an element rather than dead centre. So does whether your events arrive as native input with `isTrusted === true`, since synthetic keyboard events are visible to any script that checks. Worth keeping in view as you work through the code below.

## Why Human Typing Matters for Web Automation

Bot detection systems analyze typing patterns. They look at keystroke timing, consistency, and rhythm.

When you use `page.fill()` or `page.type()` without delays, every character appears instantly. This signals "automated script" to any detection system.

Real humans don't type like that. We pause to think. We hit keys at different speeds depending on finger position. We sometimes hesitate before hitting unfamiliar characters.

Anti-bot systems track these patterns:

- Time between keystrokes (inter-key delay)
- Consistency of typing speed
- Pauses at word boundaries
- Overall typing rhythm

By mimicking these patterns, your Playwright scripts become much harder to detect.

## Method 1: Use Playwright's Built-in Delay Option

Playwright's `type()` method accepts a `delay` parameter. This is the simplest approach.

The delay adds a fixed pause between each keystroke. It's not perfect, since real humans don't type at constant speeds, but it's better than instant typing.

### Basic Implementation

```javascript
const { chromium } = require('playwright');

async function typeWithDelay() {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();
  
  await page.goto('https://example.com/login');
  
  // Type with 100ms delay between each character
  await page.type('#username', 'myusername', { delay: 100 });
  await page.type('#password', 'mypassword', { delay: 100 });
  
  await browser.close();
}

typeWithDelay();

```

This code launches a browser and navigates to a login page. The `delay: 100` option tells Playwright to wait 100 milliseconds between each keystroke.

### Adjusting the Delay Value

Different delay values create different typing speeds:

- **50ms**: Fast typist (\~120 WPM)
- **100ms**: Average typist (\~60 WPM)
- **150ms**: Slow typist (\~40 WPM)
- **200ms+**: Hunt-and-peck typing

Pick a value that matches your use case. For sensitive forms, slower is usually safer.

### The Problem with Fixed Delays

Fixed delays are predictable. Detection systems can identify the pattern.

If every keystroke takes exactly 100ms, that's still unnatural. Real typing has variance.

## Method 2: Build a Custom Human Typing Function

A custom function lets you add randomness to typing speed. This better simulates actual human behavior.

### The Core Concept

Instead of a fixed delay, we'll use a random delay within a range. Each keystroke gets a different timing.

```javascript
function getRandomDelay(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

```

This helper function returns a random integer between `min` and `max`. We'll use it to vary our keystroke timing.

### Complete Human Typing Function

```javascript
async function humanType(page, selector, text, options = {}) {
  const {
    minDelay = 50,
    maxDelay = 150,
    mistakeChance = 0  // We'll use this in Method 3
  } = options;
  
  // Click the input field first
  await page.click(selector);
  
  // Type each character with random delay
  for (const char of text) {
    await page.keyboard.type(char);
    const delay = getRandomDelay(minDelay, maxDelay);
    await page.waitForTimeout(delay);
  }
}

```

The function accepts a page object, CSS selector, text to type, and optional configuration. It clicks the target element, then types one character at a time with random delays.

### Using the Function

```javascript
const { chromium } = require('playwright');

async function main() {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();
  
  await page.goto('https://example.com/signup');
  
  // Type with human-like timing
  await humanType(page, '#email', 'user@example.com', {
    minDelay: 80,
    maxDelay: 200
  });
  
  await humanType(page, '#password', 'SecurePass123!', {
    minDelay: 100,
    maxDelay: 250
  });
  
  await browser.close();
}

main();

```

Notice the different delay ranges. The password field uses slower typing, since people often type passwords more carefully.

### Adding Word Boundary Pauses

Humans naturally pause between words. Adding this behavior increases realism.

```javascript
async function humanTypeWithPauses(page, selector, text, options = {}) {
  const {
    minDelay = 50,
    maxDelay = 150,
    wordPauseMin = 200,
    wordPauseMax = 500
  } = options;
  
  await page.click(selector);
  
  for (let i = 0; i < text.length; i++) {
    const char = text[i];
    await page.keyboard.type(char);
    
    // Check if we just typed a space (word boundary)
    if (char === ' ') {
      const pause = getRandomDelay(wordPauseMin, wordPauseMax);
      await page.waitForTimeout(pause);
    } else {
      const delay = getRandomDelay(minDelay, maxDelay);
      await page.waitForTimeout(delay);
    }
  }
}

```

This version adds longer pauses after spaces. It simulates the brief hesitation between words.

## Method 3: Add Realistic Typos and Corrections

Real humans make mistakes. Adding occasional typos (and corrections) makes your automation even more convincing.

### The Typo Strategy

We'll occasionally type a wrong character, pause, delete it, and type the correct one. This mimics natural typing errors.

```javascript
function getAdjacentKey(char) {
  const keyboard = {
    'q': ['w', 'a'],
    'w': ['q', 'e', 's'],
    'e': ['w', 'r', 'd'],
    'r': ['e', 't', 'f'],
    't': ['r', 'y', 'g'],
    'y': ['t', 'u', 'h'],
    'u': ['y', 'i', 'j'],
    'i': ['u', 'o', 'k'],
    'o': ['i', 'p', 'l'],
    'p': ['o', '[', ';'],
    'a': ['q', 's', 'z'],
    's': ['a', 'w', 'd', 'x'],
    'd': ['s', 'e', 'f', 'c'],
    'f': ['d', 'r', 'g', 'v'],
    'g': ['f', 't', 'h', 'b'],
    'h': ['g', 'y', 'j', 'n'],
    'j': ['h', 'u', 'k', 'm'],
    'k': ['j', 'i', 'l', ','],
    'l': ['k', 'o', ';', '.'],
    'z': ['a', 'x'],
    'x': ['z', 's', 'c'],
    'c': ['x', 'd', 'v'],
    'v': ['c', 'f', 'b'],
    'b': ['v', 'g', 'n'],
    'n': ['b', 'h', 'm'],
    'm': ['n', 'j', ',']
  };
  
  const lowerChar = char.toLowerCase();
  const adjacent = keyboard[lowerChar];
  
  if (!adjacent) return null;
  
  return adjacent[Math.floor(Math.random() * adjacent.length)];
}

```

This function maps each key to its physical neighbors on a QWERTY keyboard. When we make a "typo," we'll hit an adjacent key - just like a real mistake.

### Complete Implementation with Typos

```javascript
async function humanTypeWithMistakes(page, selector, text, options = {}) {
  const {
    minDelay = 50,
    maxDelay = 150,
    mistakeChance = 0.05,  // 5% chance of typo
    correctionDelay = 300  // Pause before correcting
  } = options;
  
  await page.click(selector);
  
  for (const char of text) {
    // Decide if we should make a typo
    if (Math.random() < mistakeChance) {
      const wrongKey = getAdjacentKey(char);
      
      if (wrongKey) {
        // Type the wrong character
        await page.keyboard.type(wrongKey);
        await page.waitForTimeout(getRandomDelay(100, 200));
        
        // Pause like we noticed the mistake
        await page.waitForTimeout(correctionDelay);
        
        // Delete the wrong character
        await page.keyboard.press('Backspace');
        await page.waitForTimeout(getRandomDelay(50, 100));
      }
    }
    
    // Type the correct character
    await page.keyboard.type(char);
    await page.waitForTimeout(getRandomDelay(minDelay, maxDelay));
  }
}

```

This function introduces a configurable chance of making typos. When a typo occurs, it types an adjacent key, pauses (simulating recognition of the error), deletes it, and then types the correct character.

### Practical Usage

```javascript
const { chromium } = require('playwright');

async function fillFormWithHumanTyping() {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();
  
  await page.goto('https://example.com/register');
  
  // Name field - moderate typo chance
  await humanTypeWithMistakes(page, '#name', 'John Smith', {
    minDelay: 60,
    maxDelay: 140,
    mistakeChance: 0.03
  });
  
  // Email field - fewer typos (we're more careful)
  await humanTypeWithMistakes(page, '#email', 'john.smith@example.com', {
    minDelay: 80,
    maxDelay: 180,
    mistakeChance: 0.02
  });
  
  // Password - slowest, most careful
  await humanTypeWithMistakes(page, '#password', 'MySecureP@ss123', {
    minDelay: 120,
    maxDelay: 250,
    mistakeChance: 0.01
  });
  
  await browser.close();
}

fillFormWithHumanTyping();

```

Different fields get different configurations. This reflects how humans actually type - more carefully on important fields.

## Method 4: Gaussian Distribution for Natural Timing

Random delays are good. But truly natural typing follows a bell curve distribution.

Most keystrokes fall near an average speed. Occasional keystrokes are much faster or slower.

### Understanding Gaussian Distribution

A uniform random distribution gives equal probability to all delays. A Gaussian (normal) distribution clusters most values around the mean.

This better matches real typing patterns.

```javascript
function gaussianRandom(mean, standardDeviation) {
  // Box-Muller transform for Gaussian distribution
  let u1 = Math.random();
  let u2 = Math.random();
  
  // Avoid log(0)
  while (u1 === 0) u1 = Math.random();
  
  const z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
  return z * standardDeviation + mean;
}

```

This implements the Box-Muller transform. It converts uniform random numbers into a Gaussian distribution.

The `mean` parameter sets the center of the distribution. The `standardDeviation` controls the spread.

### Gaussian Human Typing Function

```javascript
async function gaussianHumanType(page, selector, text, options = {}) {
  const {
    meanDelay = 100,
    standardDeviation = 30,
    minDelay = 30,
    maxDelay = 300
  } = options;
  
  await page.click(selector);
  
  for (const char of text) {
    await page.keyboard.type(char);
    
    // Generate Gaussian-distributed delay
    let delay = gaussianRandom(meanDelay, standardDeviation);
    
    // Clamp to reasonable bounds
    delay = Math.max(minDelay, Math.min(maxDelay, delay));
    delay = Math.floor(delay);
    
    await page.waitForTimeout(delay);
  }
}

```

Most delays will cluster around `meanDelay`. The `standardDeviation` determines how much variation exists.

### Full Example with Gaussian Timing

```javascript
const { chromium } = require('playwright');

function gaussianRandom(mean, standardDeviation) {
  let u1 = Math.random();
  let u2 = Math.random();
  while (u1 === 0) u1 = Math.random();
  const z = Math.sqrt(-2.0 * Math.log(u1)) * Math.cos(2.0 * Math.PI * u2);
  return z * standardDeviation + mean;
}

async function gaussianHumanType(page, selector, text, options = {}) {
  const {
    meanDelay = 100,
    standardDeviation = 30,
    minDelay = 30,
    maxDelay = 300
  } = options;
  
  await page.click(selector);
  
  for (const char of text) {
    await page.keyboard.type(char);
    
    let delay = gaussianRandom(meanDelay, standardDeviation);
    delay = Math.max(minDelay, Math.min(maxDelay, delay));
    delay = Math.floor(delay);
    
    await page.waitForTimeout(delay);
  }
}

async function main() {
  const browser = await chromium.launch({ headless: false });
  const page = await browser.newPage();
  
  await page.goto('https://example.com/form');
  
  await gaussianHumanType(page, '#search', 'playwright human typing tutorial', {
    meanDelay: 90,
    standardDeviation: 25
  });
  
  await browser.close();
}

main();

```

The Gaussian approach produces the most realistic timing distribution.

## Combining Human Typing with Other Anti-Detection Techniques

Human typing alone won't bypass sophisticated detection. Combine it with these techniques.

### Use Realistic Browser Fingerprints

Detection systems check browser properties. Use `playwright-extra` with stealth plugins.

```javascript
const { chromium } = require('playwright-extra');
const stealth = require('puppeteer-extra-plugin-stealth')();

chromium.use(stealth);

async function stealthTyping() {
  const browser = await chromium.launch({ headless: false });
  const context = await browser.newContext({
    viewport: { width: 1920, height: 1080 },
    userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    locale: 'en-US',
    timezoneId: 'America/New_York'
  });
  
  const page = await context.newPage();
  // Now use human typing functions...
}

```

The stealth plugin patches many automation fingerprints. Combined with realistic context settings, it reduces the chance of detection.

### Add Mouse Movement Before Typing

Real users move their mouse to input fields before typing.

```javascript
async function moveAndType(page, selector, text, options) {
  // Get element position
  const element = await page.$(selector);
  const box = await element.boundingBox();
  
  // Move mouse to element with slight randomness
  const x = box.x + box.width / 2 + (Math.random() * 10 - 5);
  const y = box.y + box.height / 2 + (Math.random() * 10 - 5);
  
  await page.mouse.move(x, y, { steps: 25 });
  await page.waitForTimeout(getRandomDelay(100, 300));
  
  // Click and type
  await page.mouse.click(x, y);
  await humanTypeWithMistakes(page, selector, text, options);
}

```

This function moves the mouse cursor to the input field before typing. The `steps: 25` creates a smooth, natural-looking movement path.

### Use Proxies for IP Rotation

Multiple requests from one IP trigger detection. Rotate proxies to appear as different users.

Residential proxies work best for this purpose. They use real ISP-assigned IPs that look like normal user traffic.

```javascript
const { chromium } = require('playwright');

async function typingWithProxy() {
  const browser = await chromium.launch({
    proxy: {
      server: 'http://proxy.example.com:8080',
      username: 'user',
      password: 'pass'
    }
  });
  
  const page = await browser.newPage();
  // Continue with human typing...
}

```

## Common Mistakes to Avoid

These errors undermine your human typing implementation.

### Using Consistent Timing Across All Fields

Don't use the same delay settings everywhere. Vary your configuration per field type.

Passwords should be slower. Frequently-typed information (like email) can be faster.

### Forgetting to Click Before Typing

Some implementations use `page.keyboard.type()` without clicking the field first. Always click or focus the target element.

```javascript
// Wrong - might type in wrong place
await page.keyboard.type('username');

// Correct - ensures focus
await page.click('#username');
await page.keyboard.type('username');

```

### Making Delay Ranges Too Wide

A delay range of 10-1000ms is unrealistic. No human varies that much.

Keep ranges reasonable: 50-150ms for fast typing, 100-250ms for careful typing.

### Ignoring Special Characters

Special characters take longer to type. They require shift key combinations or number row reaches.

```javascript
function getCharacterDelay(char, baseDelay) {
  const specialChars = '!@#$%^&*()_+-=[]{}|;:\'",.<>?/\\`~';
  const numbers = '0123456789';
  
  if (specialChars.includes(char)) {
    return baseDelay * 1.5;  // 50% slower for special chars
  }
  
  if (numbers.includes(char)) {
    return baseDelay * 1.2;  // 20% slower for numbers
  }
  
  return baseDelay;
}

```

Integrate this into your typing function for more realistic special character timing.

## Testing Your Human Typing Implementation

Verify your implementation actually looks human.

### Visual Inspection

Run with `headless: false` and watch the typing. Does it look natural?

Look for these patterns:

- Irregular keystroke rhythm
- Natural pauses at word boundaries
- Believable typing speed

### Timing Analysis

Log your delays and analyze the distribution.

```javascript
async function analyzedHumanType(page, selector, text, options = {}) {
  const delays = [];
  
  await page.click(selector);
  
  for (const char of text) {
    const delay = getRandomDelay(options.minDelay || 50, options.maxDelay || 150);
    delays.push(delay);
    
    await page.keyboard.type(char);
    await page.waitForTimeout(delay);
  }
  
  // Log statistics
  const avg = delays.reduce((a, b) => a + b, 0) / delays.length;
  const min = Math.min(...delays);
  const max = Math.max(...delays);
  
  console.log(`Delay stats - Avg: ${avg}ms, Min: ${min}ms, Max: ${max}ms`);
  
  return delays;
}

```

Check that your statistics show reasonable variance.

### Bot Detection Test Sites

Test against known bot detection services:

- **reCAPTCHA demo page**: See if you trigger challenges
- **Bot detection test sites**: Several exist specifically for testing
- **Target site staging**: If available, test against your actual target

## FAQ

### What delay should I use for human typing in Playwright?

Use delays between 50-200ms for realistic human typing in Playwright. Most people type at 40-80 words per minute, which translates to roughly 80-150ms per character. Add variance; don't use fixed delays.

### Does Playwright have built-in human typing?

Playwright's `type()` method includes a `delay` option for basic human typing simulation. However, this uses fixed delays. For better results, build a custom function with random delays and occasional typos.

### Can human typing bypass all bot detection?

No. Human typing in Playwright is one part of a multi-layered approach. Sophisticated detection also checks browser fingerprints, mouse movements, IP reputation, and behavioral patterns. Combine typing simulation with stealth plugins and realistic browser contexts.

### Should I add typos to my human typing simulation?

Optional but effective. Adding occasional typos (2-5% chance) with corrections makes human typing in Playwright more believable. Map typos to adjacent keyboard keys for realism.

### How do I test if my human typing looks realistic?

Run with `headless: false` and watch the typing visually. Log delay statistics to verify variance. Test against bot detection demo pages. Compare your timing distribution against real human typing studies.

## Wrapping Up

Setting up human typing in Playwright comes down to adding realistic delays and variance to your keystrokes.

Start with Playwright's built-in `delay` option for quick implementation. Graduate to custom functions with random delays for better results. Add Gaussian distribution and typos for maximum realism.

Remember: human typing alone won't defeat all detection. Combine it with stealth plugins, realistic browser contexts, mouse movements, and proxy rotation.

Test your implementation. Watch it run. Adjust until it looks natural.