How to Test Hover States with Playwright Screenshots
Use Playwright locators to trigger hover, capture the intended visual state, and keep screenshot comparisons stable across runs.
To test a hover state with a Playwright screenshot, locate the control, call locator.hover(), then assert the expected image with expect(page).toHaveScreenshot() or expect(locator).toHaveScreenshot(). Use a page screenshot when the hover can affect surrounding layout; use a locator screenshot when the element itself is the visual contract.
1. Set up a hover screenshot test
Install Playwright Test in the project and create a test such as tests/hover.spec.ts. This example uses a link named “Products”; change the locator to match your UI.
import { test, expect } from '@playwright/test';
test('navigation link has the expected hover appearance', async ({ page }) => {
await page.goto('/');
const link = page.getByRole('link', { name: 'Products' });
await link.hover();
await expect(page).toHaveScreenshot('products-link-hover.png');
});
Playwright recommends locating elements through user-facing attributes such as role and accessible name where practical. If your project has a deliberate stable testing contract, use its test ID instead. Avoid long CSS or XPath chains tied to incidental markup.
2. Choose the screenshot scope
| Assertion | Use it when | Trade-off |
|---|---|---|
expect(page).toHaveScreenshot() |
The hover may open a menu, move nearby content, change an overlay, or otherwise affect the viewport. | It catches surrounding visual changes, but unrelated page rendering can also change the baseline. |
expect(locator).toHaveScreenshot() |
The target element alone is the intended visual contract. | It focuses the comparison, but will not catch a layout change elsewhere on the page. |
Focused locator example:
const link = page.getByRole('link', { name: 'Products' });
await link.hover();
await expect(link).toHaveScreenshot();
Page screenshot assertions are provided by the Playwright Test runner. Pick scope based on what the test must protect: a link’s color, for example, may need only the locator; a dropdown’s placement and overlap with page content call for a page-level assertion.
3. Generate and review the baseline
- Run the test once to generate its expected screenshot.
- Open and review the generated image. Confirm that the pointer is over the intended element and that the visual state matches the design.
- Commit the reviewed baseline with the test so later runs have an expected image to compare against.
- When the design intentionally changes, review and update the baseline as part of the same change.
Playwright’s screenshot matcher waits until two consecutive screenshots match before comparing with the expectation. That helps avoid capturing an image while rendering is still changing, but it does not make differences between machines disappear. Keep the browser and host setup consistent with the environment used to create the baseline.
4. Make hover tests stable
Use locator hover and let actionability checks run
locator.hover() moves the pointer over the matching element and checks that it can be acted on by default. The older page-level hover API is discouraged in favor of locator-based hover. If the pointer ends up in the wrong place, first verify the locator matches the intended control and that hover completes before taking the screenshot. Avoid force unless bypassing normal actionability checks is specifically part of the test.
Choose animation behavior explicitly
Screenshot assertions default to animations: 'disabled'. Playwright stops CSS animations, transitions, and Web Animations for capture; finite animations are fast-forwarded to completion, while infinite animations are canceled to their initial state and then played over after capture. This usually gives a more repeatable state image. If the transition itself is what you intend to verify, allow animations deliberately:
await expect(page).toHaveScreenshot('products-link-hover.png', {
animations: 'allow',
});
Choose the setting to match the test’s purpose. A test of the final hover appearance usually wants the default disabled behavior; a test specifically about animation needs to account for timing and may be more sensitive to rendering differences.
Keep the rendering environment aligned
Visual output can vary with operating system, browser version, browser settings, hardware, power source, and headless mode. Generate and compare baselines in the same setup where possible. If a screenshot differs only in a different environment, check those conditions before changing a valid baseline.
5. Troubleshoot common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Screenshot shows the normal state. | The locator points to another element, hover did not finish, or the design does not apply hover to that control in the current context. | Check the role/name or test ID, wait for hover() to complete, and confirm the page has reached the expected state before asserting. |
| Baseline changes from machine to machine. | Different operating systems, browser versions, settings, hardware, or headless behavior affect rendering. | Run baseline generation and comparison in a consistent browser and host environment. |
| The image captures an unintended transition frame. | The test’s animation policy does not match the state it intends to check. | Use the default animations: 'disabled' for the settled appearance, or choose animations: 'allow' when animation is part of the expected result. |
| The locator breaks after a markup change. | It depends on incidental nesting or styling classes. | Prefer a role and accessible name, or an explicit project-owned test ID. |
| Existing code uses page-level hover. | The test uses the older page API. | Move the interaction to a locator, for example page.getByRole('link', { name: 'Products' }).hover(). |
6. Or skip the browser setup
If the goal is to capture a rendered page rather than assert a Playwright interaction in your own test suite, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. It does not perform a pointer hover or replace a hover-state Playwright assertion; use it for ordinary page captures or let an MCP client call its screenshot tools.
cURL:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
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)
Node.js:
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. Cookie banners, popups, and chat widgets are removed before capture. 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 free and get 1,000 screenshots a month with no card.
7. Reliability and cost considerations
Playwright visual tests are most useful when their baselines are reviewed and their rendering environment is controlled. Page-level captures cover more of the interface but include more unrelated pixels; locator captures narrow the contract. Decide how animation should behave rather than letting an accidental timing choice define the result.
For routine hover regression checks, run the test in the project’s normal visual-test workflow and keep the expected image versioned with it. The relevant operational cost is the time spent reviewing baseline changes and keeping browser/host conditions aligned; the research material does not provide a general runtime or pricing figure.
8. FAQ
Can I test a hover state without a screenshot?
Yes. A visual assertion is appropriate when appearance is part of the requirement; other tests can assert behavior or accessibility outcomes instead.
Should I capture the mouse cursor?
The documented workflow is to hover and assert the page or locator screenshot. Decide whether the pointer itself belongs in the visual contract for your test and keep that choice consistent with the baseline.
Can ScreenshotNeo capture the hover state?
The supplied ScreenshotNeo capabilities describe URL-based page capture and do not include moving a pointer before capture. Use Playwright’s locator hover for a true hover-state screenshot test.


