How to Release a Key with Puppeteer
Release a held key with `page.keyboard.up()`. Learn when to use `press()` instead, how to prevent stuck modifiers, and how to troubleshoot keyboard events.
To release a key held with Puppeteer, call await page.keyboard.up(key). For example, use down('Shift') to hold Shift across actions, then up('Shift') to release it. For an ordinary one-time press, use press(), which performs both the keydown and keyup steps.
Release a key held with down()
Puppeteer’s keyboard is a virtual keyboard that dispatches browser input events. Releasing a key does not affect a physical key on the host machine.
await page.keyboard.down('Shift');
// Actions that should happen while Shift is held.
await page.keyboard.press('ArrowLeft');
await page.keyboard.up('Shift');
up(key) dispatches a keyup event for the key. Use the same key name for down() and up(). The official Puppeteer Keyboard API demonstrates holding Shift during selection and releasing it before the next action.
Complete runnable example
This Node.js example launches Chromium, focuses an input, selects the final character with Shift and ArrowLeft, releases Shift, and deletes the selection. Install Puppeteer with npm install puppeteer; the package downloads a compatible browser unless configured otherwise.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setContent('<input id="name" value="Puppeteer">');
const input = await page.$('#name');
await input.focus();
await page.keyboard.press('End');
await page.keyboard.down('Shift');
await page.keyboard.press('ArrowLeft');
await page.keyboard.up('Shift');
await page.keyboard.press('Backspace');
console.log(await input.evaluate(element => element.value)); // Puppete
} finally {
await browser.close();
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
The important sequence is down('Shift'), the action performed while held, then up('Shift'). Releasing before Backspace means Backspace acts without Shift.
Choose the right keyboard method
| Method | Use it for | Event behavior |
|---|---|---|
keyboard.up(key) |
Releasing a key previously held with down() |
Dispatches keyup |
keyboard.press(key) |
A single special-key press, such as Enter or ArrowDown | Shortcut for down() followed by up() |
keyboard.type(text) |
Entering text | Types characters with keyboard and input events |
keyboard.sendCharacter(char) |
Sending a character as text input without keydown or keyup | Dispatches keypress and input, but not keydown or keyup |
For a one-shot key, press() already releases it; do not add a separate up() just to complete that press. Use down() and up() separately when an action between them must occur while the key remains held, such as selecting text with Shift.
Press-and-release with a delay
press() accepts an optional delay in milliseconds between keydown and keyup. It defaults to zero:
await page.keyboard.press('Enter', { delay: 100 });
Use a delay only when the page or the test depends on a key being held briefly. It does not replace explicit down()/up() when multiple separate actions must happen during the hold. See the official Keyboard.press() API.
Typing text and special keys
Use type() for text and press() for special keys such as Control or ArrowDown. type() enters characters; it is not a substitute for holding a modifier across other actions. sendCharacter() is different again: it produces text input without keydown or keyup events. See the official Keyboard.type() API and the Keyboard API.
Prevent a modifier from remaining held
If code throws or an awaited action fails after down(), the matching up() may never run. Put the release in a finally block so cleanup runs on success and failure:
await page.keyboard.down('Shift');
try {
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
} finally {
await page.keyboard.up('Shift');
}
Keep the held-key window as short as the interaction allows. A key left down can affect later input in the same page session, making subsequent actions behave as modified presses.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| A modifier affects later actions | An error or early return skipped the matching up() |
Release in finally; keep the down/up sequence close together. |
| The intended text does not appear | press() was used for text, or the page element was not focused |
Focus the input, then use type('text'). Use press() for special keys. |
| A selection does not extend | Shift was released before the navigation key, or the wrong element had focus | Focus the intended field or page region, hold Shift, perform the navigation, then release. |
| An event listener does not see the expected keyup | The listener may be attached to a different target, or execution never reached up() |
Check focus and event target, and ensure release runs in cleanup. |
| Code expects to unlock a host keyboard key | Puppeteer emits browser input events; it does not control a physical keyboard | Use the operating system’s input controls for physical keyboard state. |
Performance, reliability, and cost
Keyboard calls are browser automation actions. For a one-off press, press() expresses the operation in one call; split it into down() and up() only when the key must stay held across intervening actions. A nonzero press delay adds at least that intentional wait. Reliability depends on the page being in the expected state: focus the right target, await each keyboard call, and release held keys even when intermediate work fails.
Puppeteer itself is open-source software; the API reference does not specify a per-key charge. Your actual operating cost depends on where Chromium runs and the compute, hosting, or test infrastructure you use. No benchmark is needed to choose the correct event sequence.
Or skip the browser setup
If the goal is a clean screenshot of a page rather than keyboard interaction, ScreenshotNeo can return an image or PDF from one API request. It is a website screenshot API and MCP server; it does not replace Puppeteer for keyboard automation.
Install the Python dependency with python -m pip install requests. The examples use the same API endpoint; replace the target URL and supply an API key. See the ScreenshotNeo API documentation for request options.
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,
)
r.raise_for_status()
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 import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
Cookie and consent banners are accepted like a visitor and removed, along with supported newsletter popups and chat widgets, before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. An MCP server lets AI agents using Claude, Cursor, or another MCP client take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does keyboard.press() release the key automatically?
Yes. It is a shortcut for down() followed by up().
Which method releases Shift?
Call await page.keyboard.up('Shift') after the actions that need Shift held.
Does keyboard.up() release a physical key?
No. It dispatches a browser keyup event through Puppeteer’s virtual keyboard.


