How to Screenshot a Website with a Hover Menu Open Using Playwright
Hover the real menu trigger, wait for the menu to become visible, then capture the viewport, full page, or menu element with Playwright.
To screenshot a website with a hover menu open using Playwright, locate the menu trigger, call hover(), wait until the menu is visible, and then capture the page or menu element. The visibility check matters: moving the pointer does not by itself prove the site opened the menu.
1. Open the menu and capture it
Install Playwright Test if it is not already in the project:
npm install -D @playwright/test
npx playwright install
Save this as tests/hover-menu.spec.ts and run it with npx playwright test:
import { test, expect } from '@playwright/test';
test('capture the navigation menu while open', async ({ page }) => {
await page.goto('https://example.com');
// Replace these example roles and names with the target site's actual semantics.
const trigger = page.getByRole('button', { name: 'Products' });
const menu = page.getByRole('navigation').getByRole('menu');
await trigger.hover();
await expect(menu).toBeVisible();
await page.screenshot({ path: 'menu-open.png' });
});
Use the locator that matches the page. A trigger might be a link rather than a button. Many navigation dropdowns expose ordinary links inside a navigation landmark instead of the ARIA menu role. Inspect the site’s accessible structure and assert visibility of the actual popup or one of its links.
2. Choose the screenshot area
By default, page.screenshot() captures the current viewport. Choose a capture mode based on what the image needs to show:
| Need | Playwright call | Consideration |
|---|---|---|
| Menu plus surrounding viewport context | page.screenshot({ path: 'viewport.png' }) |
Usually preserves the trigger and nearby content if they are in view. |
| Entire scrollable page | page.screenshot({ path: 'full.png', fullPage: true }) |
A long-page capture may not be the clearest evidence for a viewport-positioned dropdown. |
| Only the popup | await menu.screenshot({ path: 'menu-only.png' }) |
The locator screenshot scrolls the matched element into view; use a locator for the visible popup. |
| A fixed rectangular region | page.screenshot({ path: 'clip.png', clip: { x: 0, y: 0, width: 900, height: 600 } }) |
Clip coordinates are page screenshot coordinates; make sure the rectangle contains the open menu. |
For a bug report, the viewport often gives useful context. For documentation focused on menu content, an element screenshot may be better. Full-page capture is useful when the desired artifact is the whole document, not automatically the best way to show an open dropdown.
3. Make the capture stable
Playwright locators are designed for auto-waiting and retryability. Locator actions resolve the current element when the action runs, which helps when a page rerenders. The hover action waits for applicable actionability checks and can time out if they do not pass. Keep the explicit visibility assertion after hover so the screenshot is taken only after the intended UI state appears. See the official locator guidance and actionability documentation.
A visibility assertion is preferable to adding an arbitrary sleep for an animated or delayed menu: it waits for the condition needed by the capture. If the menu’s exact final appearance matters, disable animations for the screenshot:
await page.screenshot({ path: 'menu-settled.png', animations: 'disabled' });
Disabling animations stops CSS animations, transitions, and Web Animations for capture; finite animations are fast-forwarded and infinite animations are canceled temporarily. Do not enable this when the animation itself is the evidence you want. The Page API documents screenshot options.
Visual regression snapshots
For a visual regression test, use Playwright Test’s screenshot assertion after opening the menu:
test('navigation menu visual snapshot', async ({ page }) => {
await page.goto('https://example.com');
const trigger = page.getByRole('button', { name: 'Products' });
const menu = page.getByRole('navigation').getByRole('menu');
await trigger.hover();
await expect(menu).toBeVisible();
await expect(page).toHaveScreenshot('products-menu.png');
});
The screenshot assertion waits for two consecutive page screenshots to be identical before comparing with the expected snapshot. Review the generated baseline in the context of the project’s browser, operating system, and rendering setup. See Playwright screenshot assertions.
4. Locator choices and practical options
- Prefer user-facing locators: use roles and accessible names, or text where appropriate. Use a test ID when the application provides an explicit testing contract. CSS and XPath can be more tightly coupled to implementation details.
- Hover the interactive trigger: target the element that actually opens the dropdown. If it is a link, use a link locator rather than assuming it is a button.
- Assert the right state: for a navigation dropdown made of links, assert a representative link or popup container is visible if there is no menu role.
- Pick output settings deliberately:
pathsaves to a file; format can be inferred from the extension;scaleselects CSS-pixel or device-pixel output;fullPage,clip, and locator screenshots control the capture area.
Playwright’s older page.hover(selector) API is discouraged in favor of locator-based locator.hover(). See the Locator API.
5. Troubleshooting
| Symptom | Likely cause | What to check or change |
|---|---|---|
hover() times out |
The trigger is not actionable, is covered, detached, or the locator matches the wrong element. | Check the locator and page state; prefer a current user-facing locator. Playwright waits for actionability and reports a timeout if checks do not pass. |
| Hover completes but visibility assertion times out | The wrong trigger or popup was selected, or the site’s opening behavior differs from the assumed roles. | Inspect the actual accessible names and roles. Assert the visible navigation container or a link in the dropdown when appropriate. |
| Menu closes before the screenshot | The pointer left the hover region, or the menu depends on a particular pointer path. | Confirm the trigger and popup relationship and keep the pointer in the relevant hover area. Diagnose against the target site’s behavior; do not assume a generic selector fixes every site. |
| Menu is in a frame | The menu belongs to an embedded browsing context. | Locate the trigger and menu in the appropriate frame rather than the top-level page. |
| Screenshot is blank or misses the menu | The assertion may target a hidden duplicate, or the chosen clip/capture area may not include the popup. | Assert the visible instance and capture the viewport or the popup locator. Check clipping coordinates and whether the page moved during capture. |
| Snapshot changes between runs | Animations or other page rendering changes affect pixels. | Wait for the intended visible state; disable animations only for a settled-state snapshot. Keep capture conditions consistent. |
6. Performance, reliability, and cost
For one image, use a single hover, a condition-based visibility wait, and the smallest capture area that serves the purpose. Full-page output and high device-pixel scale can produce larger images than a viewport or element capture. Screenshot assertions add comparison work but are useful when the goal is regression detection. Playwright itself is a browser automation workflow, so your runtime and infrastructure costs depend on where and how you run the browser; this guide does not assume a benchmark or fixed cost.
Reliability comes from describing the UI with a meaningful locator and waiting for the resulting state, rather than relying on a fixed delay. Site-specific behavior still matters: roles, frames, hover paths, and animation timing should be checked on the target page.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. A screenshot API captures the page as served; it does not reproduce the interactive Playwright step of hovering a particular menu trigger. Use the Playwright method above when the open hover state itself is required. For a standard page screenshot, one request returns an image or PDF. 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}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for free and get 1,000 screenshots a month with no card.
FAQ
Does hovering guarantee that the dropdown opened?
No. Hover performs the pointer action; assert that the actual menu or a menu item is visible before capturing.
Should I screenshot the menu or the whole page?
Use an element screenshot for menu-only output, a viewport screenshot when surrounding context matters, and full-page capture when the complete scrollable document is the desired artifact.
Can I disable animation for every screenshot?
Only when a settled appearance is desired. Disabling animation changes what the capture represents if motion is part of the evidence.
Can ScreenshotNeo capture a menu opened by hover?
The one-call ScreenshotNeo example captures a URL; the interactive hover-and-assert sequence in this guide is the Playwright workflow for a menu that must be opened first.


