Skip to main content
SeleniumDecoded

Highlight an Element for Debugging and Screenshots

Draw a temporary outline around the element a step is about to use so screenshots and videos show what the test targeted.

Stable Verified against Selenium 4.48.0

Problem

A failure screenshot shows the page but not which element the test was trying to click. Videos of a Grid session are equally ambiguous. You want an on-screen marker without changing the app.

Solution

Set an outline style through JavaScript, take the screenshot, and restore the original style.

Outline, capture, restore
Selenium 3 & 4 Stable
public static byte[] screenshotWithHighlight(WebDriver driver, WebElement el) {
JavascriptExecutor js = (JavascriptExecutor) driver;
String original = (String) js.executeScript("return arguments[0].getAttribute('style') || '';", el);
js.executeScript("arguments[0].setAttribute('style', arguments[1] + '; outline: 3px solid #ff3b30; outline-offset: 2px;');", el, original);
try {
return ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
} finally {
js.executeScript("arguments[0].setAttribute('style', arguments[1]);", el, original);
}
}
// In a WebDriverListener, highlight before every click for demo videos:
@Override public void beforeClick(WebElement element) {
((JavascriptExecutor) driver).executeScript("arguments[0].style.outline='3px solid #ff3b30'", element);
}
def screenshot_with_highlight(driver, el):
original = driver.execute_script("return arguments[0].getAttribute('style') || '';", el)
driver.execute_script(
"arguments[0].setAttribute('style', arguments[1] + '; outline: 3px solid #ff3b30; outline-offset: 2px;');",
el, original)
try:
return driver.get_screenshot_as_png()
finally:
driver.execute_script("arguments[0].setAttribute('style', arguments[1]);", el, original)
# For videos: a listener that highlights before clicks
class Highlighter(AbstractEventListener):
def before_click(self, element, driver):
driver.execute_script("arguments[0].style.outline='3px solid #ff3b30'", element)
async function screenshotWithHighlight(driver, el) {
const original = await driver.executeScript("return arguments[0].getAttribute('style') || '';", el);
await driver.executeScript(
"arguments[0].setAttribute('style', arguments[1] + '; outline: 3px solid #ff3b30; outline-offset: 2px;');", el, original);
try {
return await driver.takeScreenshot(); // base64
} finally {
await driver.executeScript("arguments[0].setAttribute('style', arguments[1]);", el, original);
}
}
public static byte[] ScreenshotWithHighlight(IWebDriver driver, IWebElement el)
{
var js = (IJavaScriptExecutor)driver;
var original = (string)js.ExecuteScript("return arguments[0].getAttribute('style') || '';", el);
js.ExecuteScript("arguments[0].setAttribute('style', arguments[1] + '; outline: 3px solid #ff3b30; outline-offset: 2px;');", el, original);
try
{
return ((ITakesScreenshot)driver).GetScreenshot().AsByteArray;
}
finally
{
js.ExecuteScript("arguments[0].setAttribute('style', arguments[1]);", el, original);
}
}

Why It Works

outline does not affect layout, so highlighting cannot shift other elements and change what the test does next. Saving and restoring the full style attribute leaves the DOM as it was, which matters for visual-diff tools running in the same session.

Gotchas

  • Leave highlighting off by default and enable it through a flag; it adds two round trips per element.
  • Do not use border, which changes the element’s box and can trigger layout shifts.
  • For BiDi users: a preload script can inject a MutationObserver-free highlighter that listens for a custom event, avoiding per-element scripts.

Learn the theory