Puppeteer TouchError: What It Means and How to Fix It
Puppeteer TouchError means code tried to move or end a touch that was not active. Trace the touch lifecycle and fix the call order.
Puppeteer’s TouchError means code tried to move or end a touch that does not exist. The first thing to check is whether the code starts a touch before calling touchMove() or touchEnd(), and whether a branch, retry, or cleanup handler tries to move or end it again after it has ended. This error points to the interaction sequence in automation code; it does not by itself indicate a defective touchscreen.
Puppeteer’s API reference defines the error as occurring when an attempt is made to move or end a nonexistent touch. The current reference reviewed is for Puppeteer 25.12.0. Its Touchscreen API provides touchStart(x, y), touchMove(x, y), touchEnd(), and tap(x, y).
What TouchError means
A touch interaction has a lifecycle: start a touch, optionally move it, and end it. Puppeteer documents touchMove() as moving the first active touch and touchEnd() as ending the first active touch. If Puppeteer has no active touch when code asks it to move or end one, it throws TouchError.
The error message identifies the invalid operation state, but it does not identify which line of your code created that state. A duplicate end, a move after end, or a cleanup path that runs without a successful start are useful hypotheses to investigate—not guaranteed causes. Trace the actual sequence in the failing flow.
Fix it by tracing the touch lifecycle
- Find the touch calls. Search the failing path and its helpers for
touchStart,touchMove,touchEnd, and, if your installed version uses it,TouchHandle.move()andTouchHandle.end(). - Write down the order. Add temporary logs immediately before each call. Include a request or test identifier so concurrent flows do not mix together.
- Check every branch and await. Confirm a start occurs before any move or end. Check error handling,
finallyblocks, retries, event listeners, and callbacks for a second end or a move after an end. - Use the simplest API that fits. If the test only needs a tap, call
tap(x, y)rather than managing a start/end sequence yourself. Use the explicit lifecycle when you need a longer gesture. - Reproduce the smallest failing sequence. Remove unrelated page actions, preserve the order of touch calls, and compare the result with the API documentation for your installed Puppeteer version.
Do not start by reinstalling Puppeteer, upgrading the browser, or changing touchscreen hardware. The documented error describes a missing active touch; the exception alone does not establish a version defect or hardware problem.
Runnable examples
These examples show a tap and a controlled touch gesture. They assume a Puppeteer project is already installed and a page is open. The coordinate values are CSS pixels in the page’s viewport; choose coordinates that correspond to the target in your own page.
Use tap for a simple tap
await page.touchscreen.tap(120, 240);
Prefer this when the interaction is a single tap. It avoids manually splitting a simple action into start and end calls.
Manage a gesture explicitly
await page.touchscreen.touchStart(120, 240);
await page.touchscreen.touchMove(180, 240);
await page.touchscreen.touchEnd();
Keep the sequence together and make sure another path cannot end the same touch. Puppeteer notes that browser optimizations mean not every touchMove() call necessarily produces a touchmove event. Do not treat a missing event for every individual move call as proof that the method failed.
Log the call order while debugging
const logTouch = (operation, x, y) => {
console.debug({ operation, x, y, time: Date.now() });
};
logTouch('start', 120, 240);
await page.touchscreen.touchStart(120, 240);
logTouch('move', 180, 240);
await page.touchscreen.touchMove(180, 240);
logTouch('end');
await page.touchscreen.touchEnd();
Use logging to establish what ran and in what order. If multiple tests can act on the same page, include their identifiers in the log and avoid interleaving their touch sequences.
Choose tap or manual touch calls
| Need | API | What to check |
|---|---|---|
| One tap at a position | tap(x, y) |
Confirm the coordinates target the intended page element. |
| A gesture with a move | touchStart(), touchMove(), touchEnd() |
Start before moving or ending; do not reuse a touch after it ends. |
A handle-based touch in an API version that exposes TouchHandle |
move(x, y), end() |
Call methods only on a handle from a started touch; verify the installed version’s types and docs. |
The supplemental type declaration reviewed is for puppeteer-core 24.35.0 and includes a TouchHandle with move(x, y) and end(). The current API reference reviewed is Puppeteer 25.12.0. Check your installed package’s documentation and types rather than assuming examples from a different version apply unchanged.
Common causes and fixes
| Symptom or suspected cause | How to verify | Fix |
|---|---|---|
touchMove() runs before a start |
Log calls on every branch and inspect the failing stack trace. | Ensure a successful start precedes the move, or simplify to tap() if no gesture is needed. |
touchEnd() runs twice |
Look for an end in both normal flow and cleanup, or duplicated callbacks. | Make the lifecycle owner clear and avoid ending an already-ended touch. |
| A move runs after the end | Check asynchronous callbacks, retries, and delayed event handlers. | Keep the move within the active gesture sequence and prevent late callbacks from reusing it. |
| Cleanup ends a touch that never started | Compare the start outcome with whether cleanup always executes. | Track whether a start occurred and make cleanup follow the actual state of the interaction. |
| Code uses an API shape from another version | Inspect the installed Puppeteer version and its local type declarations. | Use the matching version’s API reference and types. Do not infer a version bug from this error alone. |
A move call does not produce a visible touchmove event |
Separate the thrown exception from event observation; check whether the call itself threw. | Remember that the API warns browser optimization can suppress some touchmove events. Validate the end result as well as event count. |
Reliability and performance considerations
For reliability, keep each gesture’s start, moves, and end in one clearly owned flow. Avoid concurrent code paths manipulating the same page’s touch state. When a failure occurs, preserve the ordered logs and the relevant stack trace; a minimal reproduction can distinguish a lifecycle mistake from an unrelated exception.
For performance, do not add arbitrary delays between touch calls as a default fix. The documented definition does not say that waiting creates a missing touch. Add timing only when the application interaction itself requires it, and verify the sequence the page receives. Browser optimizations can affect which move events are emitted, so an event-per-call assumption can make tests flaky.
This exception has no separate product or hardware cost implied by the documentation. The practical cost is debugging and rerunning the automation flow. Diagnose the sequence before changing dependencies or environment.
Or skip the browser setup
If the task is to capture a webpage rather than test a touch gesture, ScreenshotNeo provides a screenshot API and MCP server. One GET request returns an image or PDF, so there is no browser touch sequence to manage. See the ScreenshotNeo API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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}`);
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', image));
- Cookie banners are accepted and removed before capture; more than 60 known consent platforms, newsletter popups, and chat widgets can be handled, with each step configurable.
- Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and billing status.
- An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
- 1,000 screenshots a month are free with no card. Paid plans start at $5 for 3,000 shots; every feature is on every plan.
Sign up for ScreenshotNeo’s free 1,000 screenshots a month—no card required.
Frequently asked questions
Does TouchError mean my computer has no touchscreen?
No. The documented meaning concerns Puppeteer’s active touch state. The error does not establish a problem with physical hardware.
Will upgrading Puppeteer fix it?
The error definition alone does not point to a version defect. First compare your call order with the API and types for the version you have installed.
Can every touchMove call be expected to emit a touchmove event?
No. Puppeteer’s Touchscreen documentation says browser optimizations can mean some calls do not yield a touchmove event.
What should I include in a bug report if the sequence looks valid?
Include the exact Puppeteer package and version, the smallest reproducing sequence, the full stack trace, and ordered logs showing starts, moves, and ends. That information helps distinguish a call-sequencing issue from a separate failure.


