Use Selenium to locate the real element that owns a pseudo-element, then run JavaScript with window.getComputedStyle(element, "::before") or "::after". A pseudo-element is generated by CSS; it is not a separate DOM node and therefore cannot be returned by find_element. The computed-style object lets you read its content, dimensions, colors, visibility, background and other resolved properties.
Why Selenium cannot locate ::before or ::after
::before and ::after are CSS-generated boxes attached to a host element. They may render text, icons, borders, overlays or decorative backgrounds, but they do not appear as child nodes in the DOM tree. Consequently, selectors such as .badge::before cannot produce a Selenium WebElement.
Locate the host (for example, .badge) with a normal Selenium strategy. Then pass that element and the pseudo-element selector as arguments to JavaScript. The CSSOM signature is getComputedStyle(element, pseudoElt); the second argument is where ::before or ::after belongs.
The reliable inspection workflow
- Locate the host. Use an id, CSS selector, XPath or another locator supported by your binding.
- Wait for the correct state. Pseudo-elements can depend on a class, hover, focus, media query, animation or transition.
- Execute JavaScript. Call
window.getComputedStyle(arguments[0], "::before")(or::after). - Read properties. Use
getPropertyValue()forcontentand any diagnostic CSS property. - Assert the result. Normalize quoted content and account for state or animation when writing tests.
Python: read generated content and styles
Install Selenium, create a driver for the browser you test, and wait for the host element before inspecting it:
#1 Best Overall
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Chrome()
driver.get("https://example.com/profile")
host = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, ".badge"))
)
before_content = driver.execute_script(
"return window.getComputedStyle(arguments[0], '::before').getPropertyValue('content');",
host,
)
after_content = driver.execute_script(
"return window.getComputedStyle(arguments[0], '::after').getPropertyValue('content');",
host,
)
print("before:", before_content)
print("after:", after_content)
driver.quit()
CSS string content is commonly returned with quotes, such as "New". If your assertion needs the text without surrounding quotes, remove only matching leading and trailing quote characters rather than deleting all quotation marks, which could corrupt escaped content.
Return a diagnostic object in one call
styles = driver.execute_script("""
const s = window.getComputedStyle(arguments[0], '::before');
return {
content: s.getPropertyValue('content'),
display: s.getPropertyValue('display'),
visibility: s.getPropertyValue('visibility'),
opacity: s.getPropertyValue('opacity'),
color: s.getPropertyValue('color'),
width: s.getPropertyValue('width'),
height: s.getPropertyValue('height'),
backgroundImage: s.getPropertyValue('background-image'),
zIndex: s.getPropertyValue('z-index')
};
""", host)
print(styles)
Returning several fields is useful when content is empty: the box may exist but be transparent, zero-sized, hidden behind another layer or populated by a background image instead of text.
Java: inspect a pseudo-element with JavascriptExecutor
WebDriver driver = new ChromeDriver();
driver.get("https://example.com/profile");
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
WebElement host = wait.until(ExpectedConditions.presenceOfElementLocated(
By.cssSelector(".badge")
));
String content = (String) ((JavascriptExecutor) driver).executeScript(
"return window.getComputedStyle(arguments[0], '::after').getPropertyValue('content');",
host
);
System.out.println(content);
driver.quit();
Cast the returned value to String for a single property. If you return an object from JavaScript, Selenium maps it to a Java Map; inspect its fields individually in your test.
JavaScript Selenium binding
const { Builder, By, until } = require('selenium-webdriver');
const driver = await new Builder().forBrowser('chrome').build();
try {
await driver.get('https://example.com/profile');
const host = await driver.wait(
until.elementLocated(By.css('.badge')),
10000
);
const content = await driver.executeScript(
"return window.getComputedStyle(arguments[0], '::before').getPropertyValue('content');",
host
);
console.log(content);
} finally {
await driver.quit();
}
getCssValue() versus JavaScript getComputedStyle()
| Need | Use | Reason |
|---|---|---|
| Computed property on the real host element | WebElement.getCssValue("color") |
Selenium reads a computed style from that element instance. |
Property on ::before or ::after |
execute_script plus getComputedStyle(host, pseudo) |
The pseudo-element argument is part of the CSSOM API, not Selenium’s getCssValue() call. |
| Several values and visibility clues | Return an object from JavaScript | One browser round trip supplies a complete diagnostic snapshot. |
Inspecting the state that controls the pseudo-element
The computed result is state-dependent. A rule such as .button:hover::after will not be active until the pointer is over the host. Focus rules require keyboard focus; class-based rules require the class; media-query rules depend on viewport and device emulation.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #2
Hover
from selenium.webdriver.common.action_chains import ActionChains
ActionChains(driver).move_to_element(host).perform()
content = driver.execute_script(
"return getComputedStyle(arguments[0], '::after').getPropertyValue('content');",
host,
)
Focus and classes
Use Selenium’s click or keyboard actions to establish focus. For application-controlled states, trigger the same user action a real visitor would. Adding a class directly with JavaScript can be useful for diagnosis, but it does not test the production interaction path.
Animations and transitions
Wait for a stable point or disable motion in the test environment. A value sampled during an animation can legitimately differ between runs. If the page honors prefers-reduced-motion, configure the browser accordingly and assert the resulting state rather than an arbitrary intermediate frame.
Understanding an empty content value
An empty or unexpected string does not automatically mean the pseudo-element is absent. Check these possibilities:
content: noneorcontent: normaldisables generated content.- The selector is inactive because the host lacks a class or pseudo-class state.
- A media query selects a different rule at the current viewport width.
- The pseudo-element has
display: none,visibility: hiddenor zero dimensions. - Opacity, clipping or a stacking context makes it invisible even though styles exist.
- The visual is a background image, border or mask rather than text in
content. - The value is animated, changes after asynchronous data arrives or is replaced by a later stylesheet rule.
Inspect display, visibility, opacity, width, height, background-image, z-index and the host’s current classes before changing the test.
Rank #3
Testing generated text safely
Prefer an assertion that describes the intended state and normalize only presentation syntax:
raw = driver.execute_script(
"return getComputedStyle(arguments[0], '::before').getPropertyValue('content');",
host,
)
value = raw.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in ('"', "'"):
value = value[1:-1]
assert value == "New"
Do not treat a pseudo-element as a replacement for semantic DOM text. If the information is essential to accessibility or application behavior, the page should expose it through real text, an accessible name, or another semantic mechanism; use pseudo-element inspection for visual and regression checks.
Common failures and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
find_element cannot find ::before |
It is not a DOM node. | Find the host element, then call getComputedStyle(host, '::before'). |
JavascriptException or null style |
The argument is not a WebElement, or the host was stale. | Locate the host again after navigation or rerender and pass it as an argument. |
Content is none or empty |
Inactive selector, missing state, or intentional CSS value. | Check classes, hover/focus, viewport and the diagnostic properties. |
| Value changes between runs | Animation, transition or asynchronous styling. | Wait for a stable condition, reduce motion, or assert a state after the transition. |
| Expected icon text is missing | The icon is a background image, SVG mask or font glyph. | Inspect background-image, dimensions and other relevant properties; content may be empty by design. |
| Element is present but style is wrong | A media query, browser zoom, device emulation or stylesheet order differs. | Set a deterministic viewport and browser profile, then verify the active host classes and loaded CSS. |
| Script works locally but not in CI | Different timing, viewport, fonts or browser version. | Use explicit waits, fixed viewport settings and state setup instead of sleeps alone. |
Performance and reliability practices
- Locate once and return all required properties in one JavaScript execution when possible; this reduces WebDriver round trips.
- Use explicit waits for the host and its state. A fixed sleep is slower when the page is fast and still unreliable when it is slow.
- Capture the viewport, browser, device scale and relevant classes in test diagnostics so failures can be reproduced.
- Re-fetch a host after a framework rerender; previously stored references can become stale.
- Keep assertions focused on stable contract values. Avoid asserting transient animation frames or browser-specific serialization unless that serialization is the behavior under test.
- For full-page visual evidence, save a screenshot after establishing the state, but remember that a screenshot shows pixels while computed style explains why they appear.
Or skip the browser setup
If your goal is a clean page image rather than inspecting CSS values in a live WebDriver session, ScreenshotNeo provides a website screenshot API and MCP server. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing result.
One GET request returns PNG, JPEG, WebP or PDF. See the ScreenshotNeo API documentation for all options, including full-page lazy-image loading, CSS-selector element capture, dark mode, device presets, custom viewport and retina scale, PDF controls, custom CSS or JavaScript, clicks, waits, request blocking, headers, cookies, user agent, authorization, timezone, geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous webhooks and bulk capture.
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}`);
ScreenshotNeo also has an MCP server with take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is included on every plan. Sign up for the free ScreenshotNeo plan.
Rank #4
FAQ
Can Selenium click a pseudo-element directly?
No. Click the host element or a real descendant. A pseudo-element has no independent WebElement reference.
Can I retrieve the original CSS rule instead of the computed value?
getComputedStyle() returns resolved values after active stylesheets and computation. It does not tell you which stylesheet rule supplied each value; inspect stylesheets separately if you need source-rule attribution.
Is ::before different from the legacy :before spelling?
Modern CSS uses the double-colon form to distinguish pseudo-elements. For computed-style inspection, use ::before and ::after.
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Repair Windows errors before they cause bigger problems3Scan for outdated or missing drivers - takes under a minuteWhy does generated text contain escaped or quoted characters?
The CSS content value is serialized as a CSS string. Preserve escapes when they are meaningful, and remove only the outer quote characters when comparing plain text.
Best Value
Frequently Asked Questions
Can Selenium inspect a pseudo-element’s dimensions even when it has no text?
Yes. Read computed properties such as width, height, display, visibility, opacity, background-image and z-index from the pseudo-element style object.
What should I assert when a pseudo-element is intentionally decorative?
Assert the visual or computed contract that matters to the test, while keeping essential user information in semantic DOM or accessible content.
The Bottom Line
Find the host element, establish its required state, and use JavaScript getComputedStyle(host, "::before") or "::after". Selenium’s host-level getCssValue() cannot replace the pseudo-element argument.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

