How to Press and Hold a Key with Puppeteer
Use Puppeteer’s keyboard down and up methods to hold a key across actions. See runnable examples, modifier patterns, and fixes for common issues.
To press and hold a key with Puppeteer, call await page.keyboard.down(key), perform the action that needs the key held, then call await page.keyboard.up(key) to release it. Use the same valid Puppeteer key name in both calls.
await page.keyboard.down('ArrowLeft');
try {
// Do the action that should happen while ArrowLeft is held.
await page.waitForTimeout(500);
} finally {
await page.keyboard.up('ArrowLeft');
}
down() dispatches a keydown event and leaves the key in Puppeteer’s virtual keyboard state. up() dispatches keyup. The try/finally ensures the release still runs if the intervening action throws.
1. Set up a runnable Puppeteer example
Install Puppeteer in a Node.js project:
npm install puppeteer
Save this as hold-key.js and run it with node hold-key.js. It focuses a text box, holds Shift while selecting the previous character with ArrowLeft, releases Shift, and removes the selection with Backspace.
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.click();
await page.keyboard.press('End');
await page.keyboard.down('Shift');
try {
await page.keyboard.press('ArrowLeft');
} finally {
await page.keyboard.up('Shift');
}
await page.keyboard.press('Backspace');
console.log(await input.evaluate(el => el.value));
} finally {
await browser.close();
}
})();
The expected value is Puppetee. This example uses Puppeteer’s virtual keyboard, so it does not require a physical keyboard.
2. Hold a key across one or more actions
Use explicit down/up calls whenever the key must stay held while another call runs. The held state persists across awaited Puppeteer operations until you release it.
await page.keyboard.down('ArrowLeft');
try {
await page.mouse.move(300, 200);
await page.waitForTimeout(250);
await page.keyboard.press('ArrowLeft'); // A second down/up while ArrowLeft is held.
} finally {
await page.keyboard.up('ArrowLeft');
}
Repeated down() calls for a key that is already pressed set the key event’s repeat flag. Repetition behavior in the page still depends on the browser and the page’s event handlers.
Use a modifier while pressing another key
Modifiers such as Shift, Control, Alt, and Meta stay active for later key presses until released. This is useful for selection and keyboard shortcuts.
await page.keyboard.down('Shift');
try {
await page.keyboard.press('ArrowLeft');
await page.keyboard.press('ArrowLeft');
} finally {
await page.keyboard.up('Shift');
}
For a shifted letter, use a key name such as KeyA while Shift is down:
await page.keyboard.down('Shift');
try {
await page.keyboard.press('KeyA');
} finally {
await page.keyboard.up('Shift');
}
3. Choose between down/up, press, and type
| Goal | Use | Behavior |
|---|---|---|
| Keep a key held across actions | down(key), then up(key) |
Keydown occurs first; the key remains held until keyup. |
| Press and release once | press(key) |
Shortcut for down and up. Optional delay is milliseconds between them; default is zero. |
| Enter ordinary text | type(text) |
Sends text character by character. Optional delay is between key events; default is zero. |
| Press a key on an element | elementHandle.press(key) |
Focuses that element, then performs the down/up press. |
press() does not leave a key held after the call. Use down() and up() for a hold.
// One press, with a 300 ms interval between keydown and keyup.
await page.keyboard.press('Enter', { delay: 300 });
// Text entry, with a delay between key events.
await page.keyboard.type('hello', { delay: 40 });
// ElementHandle.press focuses the element before pressing.
const search = await page.$('input[name="q"]');
await search.press('Enter');
Use type() for text, not for special keys such as arrows or Control. Holding Shift does not turn the string passed to type() into uppercase text.
4. Key names, focus, and target behavior
Pass a Puppeteer KeyInput value. Common names include ArrowLeft, ArrowDown, Backspace, Enter, Shift, Control, Alt, and Meta, along with letter and digit representations. Check the Puppeteer KeyInput reference if you are unsure of the accepted name.
Keyboard events go to the page’s focused context. Focus the intended input or control before sending keys. For an element-specific press, ElementHandle.press() focuses that element automatically before pressing.
A key event is not a guarantee that a page will perform a particular action. The focused element, browser behavior, page event handlers, and application state determine the result. For example, an arrow key may move a caret in a text field or control a game if the page handles it.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The page acts as though the key was never pressed. | The intended field or page context does not have focus. | Click or focus the target before calling down() or press(). Use ElementHandle.press() when appropriate. |
| The key remains active after an error. | An exception interrupted the sequence before up(). |
Put the intervening work in try and release in finally. |
| A key name is rejected or has no effect. | The string is not a supported KeyInput value, or the page does not handle that key. |
Use the documented key name and confirm the focused element’s expected behavior. |
type() does not move a caret or activate a shortcut. |
type() enters text; it is not the special-key API. |
Use press() for a single special key or down/up for a held key. |
Calling press() does not maintain a hold. |
press() releases the key as part of its down/up pair. |
Replace it with down(), the required actions, and up(). |
| A keyboard shortcut does not work on macOS. | Puppeteer’s Keyboard reference notes a macOS limitation for shortcuts such as Command+A. | Do not assume that shortcut works through this API on macOS; use an application-specific selection approach where needed. |
6. Reliability, timing, and performance
- Release reliably: Pair every deliberate hold with
up(), preferably infinally. This prevents a later action from inheriting an unintended pressed state. - Wait for the page condition you need: A fixed delay can help with a timed interaction, but waiting for an observable state is generally more robust when the page exposes one. Do not assume a held key alone makes an asynchronous page action complete.
- Keep holds task-sized: There is no universal required hold duration. Use the work between down and up to define the hold interval.
press()‘s delay only sets the interval between its own keydown and keyup. - Keep automation focused: Each key operation is a browser automation step. Avoid unnecessary repeated events; if a page needs key repeat, confirm its behavior with the actual target interaction.
The Puppeteer keyboard API itself does not add a per-use fee; operational cost comes from running the browser and the surrounding automation infrastructure. No universal speed or cost figure applies because page weight, browser runtime, and the work triggered by the key vary.
7. Or skip the browser setup
If your goal is to inspect the resulting page rather than automate a key interaction, ScreenshotNeo can return a screenshot with one GET request. It is a website screenshot API and MCP server from ScreenshotNeo. See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers indicate the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month with no card.
8. FAQ
Can I hold a key for a specific number of milliseconds?
Yes. Use press(key, { delay }) for a timed down/up pair, or use explicit down and up calls with a wait or other action between them when you need to keep the key held across work.
Can I hold two keys at once?
Yes. Call down() for each key, perform the action, and release each in a finally block. For modifiers, press the modifier down before the other key and release it afterward.
Does this operate my physical keyboard?
No. Puppeteer dispatches keyboard input through the browser’s virtual keyboard interface.
Where can I check the complete key list?
Use the official Puppeteer KeyInput API reference; accepted names can vary with the Puppeteer version you use.


