ScreenshotNeo

BlogHow-to

How to Use the Keyboard in Puppeteer

Use Puppeteer’s virtual keyboard to type text, press keys, and hold modifiers. See runnable examples, targeting guidance, and fixes for common problems.

By the ScreenshotNeo team4 October 20267 min read

Puppeteer’s virtual keyboard is available as page.keyboard. Choose page.keyboard.type(text) to enter text in the focused element, page.keyboard.press(key) to tap a key, and down() followed by up() when a key must remain held during another action. If you need a particular field to receive input, focus it deliberately with a locator or selector first.

Choose the right keyboard method

Goal Use Example
Enter text type(text) await page.keyboard.type('Hello');
Tap a key press(key) await page.keyboard.press('Enter');
Keep a key held down(key), action, then up(key) Hold Shift while pressing ArrowLeft
Dispatch character input without keydown or keyup sendCharacter(char) Use only when that specific event sequence is needed

The Puppeteer Keyboard API reference describes this as a virtual keyboard interface. Its actions send browser keyboard and input events; they operate on the page’s focused element.

Set up a runnable Puppeteer example

Install Puppeteer in a Node.js project with npm install puppeteer. Save this as keyboard.js and run it with node keyboard.js. The example opens a page, creates a focused input, types text, presses Enter, and prints the resulting value.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setContent('<input id="query">');
    await page.locator('#query').click();
    await page.keyboard.type('Puppeteer keyboard');
    await page.keyboard.press('Enter');
    const value = await page.locator('#query').getProperty('value');
    console.log(await value.jsonValue());
  } finally {
    await browser.close();
  }
})();

The locator click makes the intended target explicit. For real pages, wait for the field to be available and focus it before typing. Puppeteer’s getting started guide also demonstrates combining a keyboard shortcut with a locator to fill a search field.

Type text into the focused element

Use type() for ordinary text entry. Puppeteer sends keydown, keypress/input, and keyup events for each character. The optional delay is the interval in milliseconds between key events and defaults to 0.

await page.locator('input[name="email"]').click();
await page.keyboard.type('dev@example.com');

// Optional event timing: 100 ms between key events.
await page.keyboard.type('slow entry', { delay: 100 });

The delay controls timing; it does not establish that input is indistinguishable from a human. The text passed to type() is used as text: holding Shift does not turn type('hello') into uppercase output.

If you want the selector-targeted helper, Puppeteer documents page.type(selector, text, options). It locates the selector and types into it. You can also use ElementHandle.type() after obtaining a handle. The keyboard API remains useful when focus has already been established or when mixing text entry with key presses.

await page.type('input[name="email"]', 'dev@example.com');

Press a key or shortcut

Use press() for a discrete key tap. It is documented as a shortcut for pressing the key down and releasing it. Key names come from Puppeteer’s KeyInput type; common names include Enter, Backspace, Tab, and ArrowDown.

await page.keyboard.press('Enter');
await page.keyboard.press('ArrowDown');
await page.keyboard.press('Backspace');

For a shortcut that uses a modifier, hold the modifier, press the other key, then release the modifier. Puppeteer documents that modifiers affect subsequent presses while held.

await page.keyboard.down('Control');
await page.keyboard.press('a');
await page.keyboard.up('Control');

macOS limitation: Puppeteer’s API reference specifically warns that macOS shortcuts such as ⌘ A for Select All do not work. Do not assume that substituting a modifier name will make that documented shortcut work.

Hold a key across another action

Use down() and up() when a key must stay held. Always release it when the modified action is finished; later key actions remain affected while a modifier is down.

await page.keyboard.type('Hello World!');
await page.keyboard.press('ArrowLeft');
await page.keyboard.down('Shift');
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
await page.keyboard.up('Shift');
await page.keyboard.press('Backspace');
// The text is now "Hello!"

Repeated down() calls for a key set repeat after the initial press, according to the API. For ordinary one-off key taps, prefer press(), which handles the release for you.

Use sendCharacter for narrower event behavior

sendCharacter(char) dispatches keypress and input events without keydown or keyup. It is a specialized event-level method, not the usual choice for typing or pressing a key.

await page.keyboard.sendCharacter('é');

Use it only when the page or test specifically needs that event sequence. If keyboard listeners depend on keydown or keyup, those listeners will not receive those events from sendCharacter().

Target the intended field reliably

  1. Wait for the target to exist and be interactable.
  2. Focus it with a locator or selector-based helper.
  3. Use page.keyboard for text, key presses, or held-key sequences.
  4. Check the page state or resulting value before continuing.
const search = page.locator('input[aria-label="Search"]');
await search.wait();
await search.click();
await page.keyboard.type('Puppeteer');
await page.keyboard.press('Enter');

A keyboard action does not select an element by CSS selector. If focus is on the body, a different field, or an element in another frame, the keyboard input goes there instead. For an iframe, locate the frame and its field, then focus the field in that frame before sending keyboard input.

Troubleshooting

Symptom Likely cause Fix
Text appears in the wrong place or nowhere The intended field was not focused, or it is not ready. Wait for the field, focus it with a locator, and verify its value after typing.
Shift does not capitalize text passed to type() type() sends the supplied text; modifiers do not transform that text. Pass the desired capitalization as text. Use a modifier with press() when the actual key event behavior is needed.
A modifier affects later actions A down() call was not followed by up(). Release the key with up() immediately after the modified sequence.
A macOS Select All shortcut does not work The Puppeteer API reference documents that macOS ⌘ A Select All does not work. Do not rely on that shortcut in the automation; use an approach appropriate to the page and test requirement.
Page reacts differently to sendCharacter() That method emits keypress and input without keydown or keyup. Use type() for normal typing, or press() / down() and up() when key events matter.

Performance, reliability, and cost

With the default zero-millisecond delay, type() avoids adding an intentional pause between key events. A nonzero delay increases the time spent typing, so use it only when the page behavior or test needs that timing. Puppeteer’s documentation gives 100 ms as an example setting, not a performance or human-likeness guarantee.

For reliable automation, wait for the target and explicitly focus it before sending input. Keep modifier key lifetimes short and release them even when an intervening action may fail; for more complex flows, put the release in a finally block. Keyboard input itself has no ScreenshotNeo charge or rate claim associated with it; browser runtime and the site being automated determine the work your script performs.

Or skip the browser setup

If your goal is a page image rather than keyboard-driven browser interaction, ScreenshotNeo returns a website screenshot or PDF through one API request. It does not automate arbitrary keyboard workflows. Its capture can accept cookie and consent banners and remove more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status. An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf.

cURL:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

See the ScreenshotNeo API documentation for request options. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

FAQ

Does page.keyboard.type() return a promise?

Yes. Await it so the text entry completes before the next automation step runs.

Can I type into a field without clicking it?

Yes, if it is already focused. Explicitly focusing the target is more reliable when the page state is uncertain.

Should I use type() or sendCharacter() for normal text?

Use type(); it sends the usual per-character keyboard and input sequence. sendCharacter() omits keydown and keyup.

Primary references