How to Fix Screenshots of Websites with Content Hidden Until Hover
Hover content appears in a screenshot only when it is visible at capture time. Use Chrome DevTools for a one-off capture or automate the hover with Playwright.
A screenshot records the page as it is rendered at capture time. To show content hidden until hover, move the pointer over its trigger before capturing, or force the CSS :hover state in Chrome DevTools. For repeatable captures, use Playwright to hover a locator, wait for the page-specific reveal to settle, and then take the screenshot. A full-page screenshot captures more of the page; it does not activate hover content.
This guide covers a one-off DevTools capture, a reusable Playwright script, options for controlling what gets captured, and ways to diagnose missing or inconsistent hover content.
1. Check what reveals the content
First, open the page normally and move your pointer over the element that should reveal the content. Note which element acts as the trigger and whether the revealed content appears immediately, after a transition, or after additional content loads.
Hover behavior can come from CSS, JavaScript, or both. A CSS-only reveal may respond to a forced :hover state. A script-driven interaction may require a real pointer movement or additional page behavior. If the content also opens on keyboard focus, test that separately: :focus, :focus-within, and :focus-visible are different states from :hover.
2. Capture a hover state once with Chrome DevTools
- Open the page in Chrome and open DevTools.
- Use the element picker to inspect the element that triggers the reveal.
- In the Styles pane, open the
:hovcontrols and enable:hoverfor the selected element. - Confirm that the content is visible, then capture the page using your preferred screenshot method.
Forcing the pseudo-state is useful when the pointer would move away as you interact with DevTools. If forcing :hover does not reveal the content, inspect the trigger and page behavior: the site may rely on JavaScript, a different element, focus, or a wait for content to load. Chrome documents pseudo-state controls in its CSS and DevTools documentation.
3. Automate hover and capture with Playwright
For repeated captures, run the interaction and screenshot in the same browser session. The locator below is an example; replace its role and accessible name with a locator that matches the page you need to capture.
Install Playwright
npm init -y
npm install playwright
npx playwright install chromium
Runnable JavaScript example
Save this as capture-hover.js. Set TARGET_URL to the page and adapt the locator to the actual trigger. The script writes a viewport screenshot to hover-state.png.
const { chromium } = require('playwright');
(async () => {
const url = process.env.TARGET_URL;
if (!url) throw new Error('Set TARGET_URL to the page URL');
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
// Replace this with a locator for the page's actual hover trigger.
const trigger = page.getByRole('button', { name: 'More details' });
await trigger.hover();
// Add a page-specific wait if the reveal animates or loads data.
await page.screenshot({ path: 'hover-state.png' });
} finally {
await browser.close();
}
})().catch((error) => {
console.error(error);
process.exitCode = 1;
});
TARGET_URL='https://example.com' node capture-hover.js
Playwright’s hover() moves the pointer to a locator. The locator, URL, and any wait condition depend on the target site; the example does not assume every reveal uses the same element or timing. See the official Playwright screenshot guide and Page API reference.
Choose a wait that matches the page
Do not add a long fixed delay by default. If the reveal is immediate, capture after hover(). If it animates or loads content, wait for a visible element or another specific condition that indicates the reveal is ready. A short timeout can be a practical fallback for a known animation, but it is less reliable than waiting for the actual result.
await trigger.hover();
await page.getByText('Additional details').waitFor({ state: 'visible' });
await page.screenshot({ path: 'hover-state.png' });
Use a selector that identifies the content as it appears, and make it specific enough to avoid matching unrelated text. If the page has no reliable visible marker, use a delay based on the known behavior and keep the browser environment consistent.
4. Select the screenshot area and state
| Goal | Approach | What it controls |
|---|---|---|
| Show a hover menu or card | Hover the trigger, then capture | The rendered interaction state |
| Capture the current viewport | page.screenshot() |
The visible browser viewport |
| Capture a component | Take a screenshot of its locator | The selected element’s bounds |
| Capture a region | Use screenshot clipping options | A specified rectangle |
| Capture the full scrollable page | page.screenshot({ fullPage: true }) |
The page area, including content below the viewport |
| Avoid incidental hover styling | Move the pointer to an unaffected area before capture | Whether a hover effect remains active |
Full-page capture changes the captured area, not the interaction state. Hover the relevant trigger before taking the screenshot if the hidden content must appear. For locator and clipping options, check the screenshot documentation, since available options can vary by Playwright release.
Capture the revealed element
If only the revealed component matters, target that component after opening it. Adapt the locator to the element that becomes visible:
await trigger.hover();
const panel = page.getByRole('region', { name: 'More details' });
await panel.waitFor({ state: 'visible' });
await panel.screenshot({ path: 'revealed-panel.png' });
Element screenshots are useful for focused documentation or visual checks. They do not include surrounding page context unless that context falls inside the selected element.
Remove incidental hover effects
If the goal is a stable screenshot without hover styling, move the pointer away before capture. Playwright’s visual comparison guidance describes moving the mouse outside the page area or over an unaffected element. You can also apply a screenshot stylesheet to neutralize volatile styles, but such overrides change the rendered image and may not match what visitors normally see.
await page.mouse.move(0, 0);
await page.screenshot({ path: 'no-hover.png' });
Use an unaffected location for the pointer if the page has an active element near the edge. For repeatable visual comparisons, keep the browser and runtime conditions consistent; rendering can vary with environment and settings. See Playwright visual comparisons.
5. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its one-call API is useful when you need a screenshot without installing or running a browser. For hover-dependent content, first check whether the page’s default rendered state shows what you need: this request does not perform a hover interaction.
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}`);
See the ScreenshotNeo API documentation for request options and response details. Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up for 1,000 free screenshots a month, with no card.
Troubleshooting
| Symptom | Likely cause | What to try |
|---|---|---|
| The hidden content is missing | The capture happened before hover, or the wrong element was targeted. | Verify the trigger in the live page, hover that element, and wait for the revealed content to become visible. |
DevTools :hover has no effect |
The interaction may be script-driven, attached to another element, or triggered by focus instead. | Inspect the actual trigger and test pointer hover and keyboard focus separately. |
| The screenshot shows a partial animation | The capture ran before the transition finished. | Wait for a visible end state or use a delay suited to the known animation. |
| The locator times out | The role/name does not match, the element is not present yet, or the page differs from the expected state. | Inspect the page’s accessible name and structure, update the locator, and confirm navigation completed. |
| The reveal works manually but not in automation | The automation may hover a different target, or the site may depend on page-specific behavior. | Check the target locator, use a real hover interaction, and wait for the revealed result rather than assuming a fixed timing. |
| The full-page image still omits hover content | Full-page capture expands the screenshot area but does not open hover states. | Hover the trigger before capture, then choose viewport, element, clip, or full-page scope. |
| Visual snapshots vary between runs | Pointer position, browser environment, timing, or other dynamic page content differs. | Move the pointer to a stable location, wait on a meaningful condition, and keep browser/runtime settings consistent. |
Performance, reliability, and cost
- Wait only for what you need. Waiting for a specific reveal condition is usually more targeted than adding a large fixed delay. Avoid waiting for every network request to finish when the page keeps background connections open.
- Reuse the browser for batches. When capturing multiple pages in one automation job, launch the browser once and create or reuse pages as appropriate. Always close the browser so the process exits cleanly.
- Keep capture conditions stable. Use a consistent viewport, browser version, and interaction sequence for visual comparisons. Dynamic content and rendering differences can still affect results.
- Budget browser resources. Browser automation uses compute and memory on the machine or worker that runs it. A remote screenshot API shifts browser operation to the service and has its own plan limits and price.
- Check billing semantics. ScreenshotNeo states that bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing; responses include
X-Page-VerdictandX-Billedheaders. Its plans are Free: 1,000 shots/month; Starter: $5 for 3,000; Growth: $15 for 15,000; Pro: $39 for 60,000; Scale: $99 for 250,000; Business: $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. See the docs for current request details.
FAQ
Does taking a full-page screenshot open hover menus?
No. Full-page capture controls how much of the page is included. Activate the hover state before capturing.
Can I force hover without moving the mouse?
For a one-off CSS inspection in Chrome, use DevTools’ :hov control to force :hover. For automated capture, use a browser interaction such as Playwright’s hover().
Why does a focus-triggered tooltip stay closed?
Hover and focus are separate states. Test the page’s keyboard behavior and use the interaction the page actually supports.
Will ScreenshotNeo open content that requires hovering?
The one-call example captures the page without a browser interaction sequence. If the content is absent from the page’s default rendered state, use browser automation to hover it first.


