ScreenshotNeo

BlogHow-to

How to Capture Selenium Screenshots with Tooltip Text Visible

Hover the trigger, wait for the rendered tooltip, and capture Selenium’s window before the pointer moves away.

By the ScreenshotNeo team1 October 20267 min read

To capture a Selenium screenshot with tooltip text visible, move the pointer onto the tooltip trigger, wait until the rendered tooltip is visible, and call save_screenshot() while the pointer remains over the trigger.

  1. Locate the element that opens the tooltip.
  2. Move the pointer onto it with Selenium’s mouse action API.
  3. Wait explicitly for the tooltip overlay or its text.
  4. Save the current window before moving the pointer away.

A fixed sleep can work for a demo, but an explicit visibility wait is more reliable because tooltips may be rendered by JavaScript or shown after a CSS transition.

Complete Python example

Install Selenium with python -m pip install selenium. Selenium Manager can obtain a compatible browser driver in current Selenium releases. Replace the URL, trigger selector, and tooltip selector with the ones used by your page.

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = webdriver.ChromeOptions()
options.add_argument("--window-size=1440,1000")

with webdriver.Chrome(options=options) as driver:
    driver.get("https://example.com/page-with-tooltip")

    wait = WebDriverWait(driver, 10)
    trigger = wait.until(
        EC.presence_of_element_located((By.CSS_SELECTOR, "[data-tooltip]"))
    )

    # Make clipping less likely when the trigger is near the viewport edge.
    driver.execute_script(
        "arguments[0].scrollIntoView({block: 'center', inline: 'center'});",
        trigger,
    )

    ActionChains(driver).move_to_element(trigger).perform()

    # Use the selector generated by your component library or application.
    tooltip = wait.until(
        EC.visibility_of_element_located(
            (By.CSS_SELECTOR, ".tooltip, [role='tooltip']")
        )
    )

    # Reading the text is optional, but makes failures easier to diagnose.
    print("Tooltip:", tooltip.text)

    # Keep the pointer over the trigger until this call returns.
    if not driver.save_screenshot("tooltip-visible.png"):
        raise OSError("Selenium could not write tooltip-visible.png")

Selenium’s Python API documents save_screenshot(filename) as a current-window PNG capture; it returns False when writing the file fails. The related get_screenshot_as_file(), get_screenshot_as_png(), and get_screenshot_as_base64() methods support file, byte, and Base64 workflows respectively. See the Selenium Python API documentation.

Choose the correct tooltip wait

Wait for visibility

Use EC.visibility_of_element_located when the tooltip node exists but starts hidden, or when it is inserted into the DOM after the hover.

tooltip = WebDriverWait(driver, 10).until(
    EC.visibility_of_element_located((By.CSS_SELECTOR, "[role='tooltip']"))
)

Wait for specific text

If several tooltips share one selector, wait for the expected text instead of the first visible node.

from selenium.webdriver.support import expected_conditions as EC

wait.until(
    EC.text_to_be_present_in_element(
        (By.CSS_SELECTOR, "[role='tooltip']"),
        "Expected explanation"
    )
)

Wait for an application state

Some components add an attribute such as data-state="open" or aria-hidden="false". Waiting for that state avoids capturing an element during its entrance animation.

wait.until(
    EC.visibility_of_element_located(
        (By.CSS_SELECTOR, "[role='tooltip'][data-state='open']")
    )
)

When the tooltip is rendered in a portal

Component libraries often append the tooltip near <body> instead of inside the hovered element. Do not search only within the trigger’s parent. Inspect the live DOM after hovering and wait for the generated overlay selector globally.

CSS hover versus JavaScript tooltips

Implementation What to do Typical failure
CSS :hover Use ActionChains.move_to_element() and capture before the pointer leaves. The tooltip disappears when another command moves the pointer.
JavaScript overlay Hover, then wait for the overlay node or expected text. The trigger is present, but the asynchronous overlay is not ready.
Portal-rendered overlay Use a selector under body or the library’s overlay container. A descendant search of the trigger never finds the tooltip.
Native title bubble Prefer an application-rendered tooltip; otherwise validate the title or ARIA value separately. The browser bubble is not page content exposed to WebDriver screenshots.

Capture only the tooltip or the whole page

driver.save_screenshot() captures the current browser window, including the trigger and surrounding context. If you need only a rendered tooltip element, Selenium can capture an element screenshot after the same hover and wait:

tooltip.screenshot("tooltip-only.png")

An element capture is useful for documentation snippets. A window capture is better when readers need to understand which control the tooltip describes. For a full-page image, first decide whether your browser and Selenium version support the full-page behavior you need; otherwise stitch deterministic viewport captures or use a dedicated screenshot service.

Deterministic viewport and pointer setup

Set the window size before the hover so line wrapping, placement, and clipping are repeatable. Scroll the trigger to the center of the viewport when a tooltip near the top or bottom edge might be clipped.

driver.set_window_size(1440, 1000)
driver.execute_script(
    "arguments[0].scrollIntoView({block: 'center', inline: 'center'});",
    trigger,
)
ActionChains(driver).move_to_element(trigger).perform()

Keep the same browser scale, fonts, and viewport across runs when comparing screenshots. If the page uses responsive breakpoints, a small viewport change can move the tooltip or change its text wrapping.

JavaScript Selenium example

The same sequence works with the Selenium JavaScript bindings: locate, move, wait for visibility, then capture. The exact wait condition depends on the binding version.

const { Builder, By, until } = require('selenium-webdriver');

(async function captureTooltip() {
  const driver = await new Builder().forBrowser('chrome').build();
  try {
    await driver.manage().window().setRect({ width: 1440, height: 1000 });
    await driver.get('https://example.com/page-with-tooltip');

    const trigger = await driver.findElement(By.css('[data-tooltip]'));
    await driver.executeScript(
      "arguments[0].scrollIntoView({block: 'center', inline: 'center'});",
      trigger
    );

    await driver.actions({ async: true }).move({ origin: trigger }).perform();
    const tooltip = await driver.wait(
      until.elementLocated(By.css('[role="tooltip"]')),
      10000
    );
    await driver.wait(async () => await tooltip.isDisplayed(), 10000);
    await driver.takeScreenshot().then(data => require('fs').writeFileSync('tooltip-visible.png', data, 'base64'));
  } finally {
    await driver.quit();
  }
})();

Troubleshooting

Symptom Cause Fix
Screenshot has no tooltip The capture ran before the overlay became visible. Wait for the tooltip’s visibility or expected text, not merely the trigger’s presence.
Tooltip flashes and disappears A later command moved the pointer, or the pointer crossed a gap between trigger and overlay. Call the screenshot immediately after the wait and keep the pointer on the trigger until the call returns.
Timeout waiting for .tooltip The application uses a different class, an ARIA selector, or a portal container. Inspect the DOM while the tooltip is open and replace the selector with the generated node.
Only a native bubble appears The text comes from title, not a DOM tooltip. Use an application-rendered tooltip or assert the title/aria-label value separately.
Tooltip is cut off The trigger is near a viewport edge or an ancestor has clipping. Set a known window size, scroll the trigger to the center, and check overflow styles.
ElementNotInteractableException The trigger is hidden, covered, disabled, or outside the viewport. Wait for it to be clickable, scroll it into view, and remove obstructing overlays in test setup.
Screenshot file is missing The path is unwritable or has an unexpected relative working directory. Use an absolute writable path and check the Boolean return value from save_screenshot().
Text differs between runs Responsive layout, localization, delayed data, or fonts changed. Fix locale, viewport, test data, and font loading before the hover step.

Performance and reliability checklist

  • Use one explicit wait with a realistic timeout instead of long global sleeps.
  • Prefer a stable semantic selector such as [role='tooltip'] or a test-specific attribute.
  • Capture immediately after the condition is true.
  • Reuse one WebDriver session for related captures, but reset page state between cases.
  • For animations, wait for the final state or disable motion in test CSS when visual stability matters.
  • Save diagnostic HTML, console logs, and a screenshot on failure so selector changes are visible.
  • Run headless only after the headed flow works; pointer placement and viewport behavior are easier to inspect with a visible browser.

A local Selenium capture consumes browser startup time and machine resources for every session. The screenshot itself is a local file operation, so storage and filesystem permissions are usually the practical limits. Selenium does not charge per screenshot; any infrastructure cost comes from the browser runner, CI minutes, and artifact storage.

Or skip the browser setup

ScreenshotNeo provides a screenshot API when you need a clean page capture without maintaining Selenium drivers and browser sessions. Read the ScreenshotNeo documentation for the full option set.

For a page whose tooltip is already visible from its initial state, one GET request returns the image:

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

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots, and the Free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

If the tooltip requires a real pointer hover, keep the Selenium flow above. For ordinary URL captures and pages configured to show the needed content without interaction, try ScreenshotNeo’s free sign-up.

FAQ

Why does waiting for the trigger fail?

Presence only proves that the trigger exists. Wait for the tooltip overlay or its expected text after moving the pointer.

Can I use a fixed delay?

You can, but an explicit visibility or text condition usually finishes sooner and is less sensitive to slow or fast environments.

Should I capture the element or the window?

Capture the window when context matters. Capture the tooltip element when you need a cropped asset.

What if the tooltip is outside the trigger’s DOM subtree?

Search the document or overlay container. Portals commonly render tooltip nodes near <body>.

Why is a title attribute not reliable in the image?

Native browser title bubbles may not be page content available to WebDriver’s screenshot implementation. Prefer a DOM-rendered tooltip for visual capture.