Appearance
Playwright Mouse Cursor
Inject a visible mouse cursor into Playwright pages. Real mouse cursors don't appear in screenshots or screencasts, so this overlays a fake cursor image that you can position anywhere on the page.
When to use
Use this when you need a visible cursor in Playwright — for screenshots, screen recordings, demos, or any automation where showing cursor position matters.
The pattern
Cursor helper functions
Inject a fake cursor as a fixed-position image overlay. The element uses pointer-events: none so it never interferes with clicks, hovers, or other interactions:
javascript
import { readFileSync } from "node:fs";
function cursorDataUri(filePath) {
const buffer = readFileSync(filePath);
const base64 = buffer.toString("base64");
return `data:image/png;base64,${base64}`;
}
async function showCursor(page, { src, x, y, width, height }) {
await page.evaluate(
({ src, x, y, width, height }) => {
let el = document.getElementById("fake-cursor");
if (!el) {
el = document.createElement("img");
el.id = "fake-cursor";
el.style.cssText = `
position: fixed;
pointer-events: none;
z-index: 999999;
`;
document.body.appendChild(el);
}
el.src = src;
el.style.left = `${x}px`;
el.style.top = `${y}px`;
el.style.width = `${width}px`;
el.style.height = `${height}px`;
el.style.display = "block";
},
{ src, x, y, width, height },
);
}
async function hideCursor(page) {
await page.evaluate(() => {
const el = document.getElementById("fake-cursor");
if (el) el.style.display = "none";
});
}Retina cursor images
For 2x retina displays, use a cursor image at double the pixel density and render it at half its native size in CSS. A 64×64 px image rendered at 32×32 CSS px appears crisp on a 2x screenshot:
javascript
const arrowUri = cursorDataUri("cursors/cursor-default.2x.png");
await showCursor(page, {
src: arrowUri,
x: 700,
y: 500,
width: 32,
height: 32,
});Positioning on an element
Center the cursor over a specific element using its bounding box. Offset by half the cursor size so the tip lands at the element's center:
javascript
const box = await page.locator("button.my-button").boundingBox();
await showCursor(page, {
src: pointerUri,
x: box.x + box.width / 2 - 16,
y: box.y + box.height / 2 - 16,
width: 32,
height: 32,
});Hiding the cursor
Hide the cursor when you don't want it visible — for example, between screenshots that shouldn't show a cursor:
javascript
await hideCursor(page);Because the overlay uses pointer-events: none, you don't need to hide it before performing interactions. It only needs to be hidden when you don't want it to appear in the output.
Trade-offs
| Choice | Pro | Con |
|---|---|---|
pointer-events: none | No interference with page | Can't show click state |
| Fixed positioning | Stays put during scroll | Doesn't scroll w/page |
| Data URI source | No network request needed | Larger inline payload |
| 2x cursor images | Crisp on retina displays | Overkill for 1x |