Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

To capture a hover state, put the pointer over the target, wait until the visual change is complete, and take the screenshot without moving the pointer away. For a one-off image, Chrome DevTools can pin :hover and capture the node. For repeatable work or CI, use Playwright or Puppeteer to hover a stable locator and then capture the element, viewport, or full page.

Choose the capture method

Method Best for Repeatability JavaScript-driven hover CI use
Chrome DevTools Manual inspection and a quick image Low to medium Limited: forces CSS state only No
Playwright Regression tests and scripted captures High Yes, with a real pointer action Yes
Puppeteer Node.js browser automation High Yes, with page.hover() Yes
ScreenshotNeo API or AI-agent captures without browser setup High when the page interaction is supported by your capture workflow Use a click or custom script where needed Yes

A forced pseudo-class is ideal when the effect is pure CSS. If hovering runs JavaScript, opens a component, fetches data, or changes application state, use a real pointer action in Playwright or Puppeteer.

Capture a hover state manually in Chrome DevTools

  1. Open the page in Chrome, then open DevTools with Ctrl+Shift+I (Windows/Linux) or Cmd+Option+I (macOS).
  2. In the Elements panel, select the element whose hover appearance you need.
  3. Right-click the node and choose Force State > :hover. Chrome keeps the pseudo-class active even after the physical pointer leaves the element. The same menu can force states such as :active, :focus, :visited, and :focus-within.
  4. Right-click the node again and select Capture node screenshot. Chrome saves an image of that node.

If the effect is a dropdown, tooltip, mega-menu, or other overlay that extends outside the selected node, capture the viewport instead. Keep :hover forced while you use the DevTools command for a viewport screenshot. A node image clips content outside the node’s bounds, so it is the wrong target for an escaping overlay.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When DevTools appears to lose the style

  • Make sure you forced the state on the element that owns the CSS rule; forcing it on a child may not match a selector such as .card:hover .actions.
  • Check whether a more specific rule, an overlay, or an animation is hiding the change.
  • For a JavaScript menu, forcing :hover does not dispatch the pointer events that the application expects. Use automation with a real hover instead.

Automate the capture with Playwright

Playwright’s hover action scrolls the locator into view, performs actionability checks, and moves the mouse to the element’s center. Use a semantic role or unique CSS selector rather than an nth-child path.

#1 Best Overall
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Element screenshot

import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({
  viewport: { width: 1440, height: 900 },
  deviceScaleFactor: 1
});

await page.goto('https://example.com', { waitUntil: 'networkidle' });
const moreDetails = page.getByRole('button', { name: 'More details' });
await moreDetails.waitFor({ state: 'visible' });
await moreDetails.hover();
await page.screenshot({ path: 'button-hover.png', animations: 'disabled' });
await browser.close();

The example captures the viewport. To capture only the hovered control, replace the final call with:

await moreDetails.screenshot({
  path: 'button-hover.png',
  animations: 'disabled'
});

Tooltip or menu outside the element

Use a viewport or full-page image after the hover action. A full-page capture is useful for a page-level state, but it can include content below the fold and may not represent a single viewport exactly.

await moreDetails.hover();
await page.waitForTimeout(300); // use a known UI delay, not an arbitrary long sleep
await page.screenshot({ path: 'menu-hover-full.png', fullPage: true, animations: 'disabled' });

Making Playwright runs deterministic

  • Set viewport dimensions and device scale explicitly. CSS-pixel and device-pixel output are different choices; keep the same choice between baseline and comparison runs.
  • Wait for fonts, images, and asynchronous menu content to settle. Prefer a selector-based wait or network-idle condition over a large fixed delay.
  • Disable or freeze animations where your framework supports it. Otherwise, capture after a deterministic transition point.
  • Move the mouse to a neutral location before a screenshot when you do not want hover styling. Playwright screenshots include whatever hover effect exists at capture time.
  • Keep browser version, operating system, headless mode, hardware conditions, and power settings stable for visual regression. Rendering can vary across those factors.

Use Puppeteer for Node.js automation

Puppeteer’s page.hover(selector) finds the selector, scrolls it into view, and moves the pointer to its center. Then use page.screenshot() for the page or viewport, or an element handle for a local image.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
  await page.goto('https://example.com', { waitUntil: 'networkidle2' });
  await page.hover('.menu-trigger');
  await page.screenshot({ path: 'menu-hover.png' });
  await browser.close();
})();

Capture only the hovered element

const target = await page.$('.menu-trigger');
if (!target) throw new Error('Hover target not found');
await target.screenshot({ path: 'trigger-hover.png' });

Use a selector that identifies one intended element. If several elements match, Puppeteer may hover the first match, producing a plausible but incorrect image.

CSS hover versus JavaScript hover

Pure CSS

A rule such as .nav-link:hover { color: red; } can be reproduced with DevTools’ forced state or a real pointer. Forced state is convenient for inspecting computed styles and taking a node screenshot.

JavaScript behavior

Menus, tooltips, analytics-triggered components, and framework widgets may listen for pointerenter, mouseover, or related events. A real hover() call is the safer option because it performs the same pointer movement a visitor makes. If the component opens only after a delay, wait for a visible menu or tooltip selector rather than assuming the delay has elapsed.

Nested and delegated handlers

Event delegation can attach the handler to a parent while the visual target is a child. Hover the user-facing control, then wait for the stateful descendant. Inspect the DOM after hovering to confirm that the expected class, attribute, or menu node appeared.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Decide what to capture

  • Element: best for a button, card, icon, or local style. It will not include a tooltip or menu positioned outside the element’s box.
  • Viewport: best for an overlay whose position depends on the visible window.
  • Full page: best when the hovered state must be documented alongside the entire page, though overlays and fixed-position elements require careful review.

For comparisons, fix the viewport, device scale, browser, fonts, and color scheme. Capture the same target with the pointer deliberately positioned each time. A stray pointer can either create a hover state or remove it before the image is written.

Rank #3
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Troubleshoot missing or incorrect hover effects

The screenshot looks like the normal state

Verify that the hover call completed before capture and that the selector identifies the visible element. In DevTools, force :hover on the node owning the rule. In automation, wait for a state-specific selector, class, or attribute.

The menu flashes and disappears

The pointer may be moving across a gap between trigger and menu, or an animation may still be running. Hover the trigger, wait for the menu to become visible, and disable animations where possible. For a CSS menu, use a parent selector whose hover region includes both trigger and panel.

The tooltip is cut off

You captured the element rather than the viewport. Take the viewport image after hovering, or adjust the screenshot region to include the overlay.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Nothing changes when forcing :hover

The component may be JavaScript-driven, use a different state such as focus, be covered by another element, or require a pointer event. Switch to Playwright or Puppeteer and inspect console errors and network requests.

Images differ between machines

Use the same browser and OS where possible, install identical fonts, set viewport and device scale, and avoid comparing a headed run with a headless run. Freeze animations and wait for web fonts and lazy images.

The target cannot be found

Wait for navigation and rendering, then use a stable role, label, test ID, or unique class. Avoid selectors tied to generated framework hashes or DOM position.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF; its capture options include custom JavaScript and CSS, clicking before capture, waits, device and viewport settings, full-page and element captures, and more. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. For an interaction that requires a real hover, use a custom script or click/wait workflow supported by your page; pure CSS hover can also be represented by injected CSS.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

One-call cURL example

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

See the ScreenshotNeo documentation for the full option set, including output format, viewport, waits, scripts, and signed links. Python and Node.js clients use the same endpoint:

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}`);

ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots each month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.

Cost, performance, and reliability considerations

  • Browser automation consumes startup time and memory. Reuse a browser process for a batch, but isolate pages when state or credentials must not leak.
  • Network-idle waits improve completeness but can stall on sites with persistent analytics connections. Combine a reasonable timeout with a selector that proves the hovered UI is ready.
  • Full-page images are larger and slower than element captures. Capture the smallest region that answers your question.
  • For regression suites, store the exact viewport, scale, browser version, and wait conditions with each baseline.
  • With an API, inspect the response status and the X-Page-Verdict and X-Billed headers so failed or clean captures are distinguishable.

FAQ

Can I capture a hover state without moving my physical mouse?

Yes. DevTools’ Force State > :hover pins the CSS pseudo-class, while Playwright and Puppeteer move a virtual pointer in the browser.

Why does a full-page screenshot not show the hover menu on every scroll position?

Hover is tied to a pointer position and a particular layout state. A full-page capture stitches content beyond the visible viewport; it is not the same as hovering separately at every scroll position.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I use hover or focus for keyboard-accessible menus?

Capture the state your interface promises. If the menu opens on keyboard focus, reproduce focus and capture that state separately; a hover image does not prove keyboard behavior.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.