Selenium Webdriver Get Text from CSS Property "Content" on a ::Before Pseudo Element

Python - get text from CSS property “content” on a ::before pseudo element in Selenium?

You were on the right path except for the text that you mentioned aren't actually text. These texts are actually rendered by a CSS property called the content which can only be used with the pseudo-elements :before and :after. You can read here on how it works if you are interested.

The text are rendered as icons; this is sometimes done by organizations to avoid sensible values being scraped. However, there is a way(somewhat hard) to get around this. Using Selenium and javascript you can individually target the CSS values of the property content in which it holds the values you are after.

Having looked into it for an hour this is simplest pythonic way of getting the values you desire

overall_score = driver.execute_script("return [...document.querySelectorAll('.q-score-bar__grade-label')].map(div => window.getComputedStyle(div,':before').content)") #key line in the problem

The code simply creates a javascript code that targets the classes of the elements and then maps the div elements to the values of the CSS properties.
This returns a list

['"TOP BUY"', '"B"', '"B"', '"B"', '"A"']

the values, corresponding in the following order

Q-Factor Score/Quality/Momentum/Growth/Technicals

To access the values of a list you can use a for loop and indexing to select the value. You can see more on that here

I am trying to extract a text from an a tag that has a ::before selector using selenium/python

Assuming your selector is correct the result is a list, so you need to iterate over it

Get rid of the .text at the end of your element and iterate over it instead to grab the text values

for name in actorName1:
print(name.text)

How to get css value of pseudo element with selenium python?

Looks like I did find solution. This is not pure selenium but it does works.

browser.execute_script("return window.getComputedStyle(document.querySelector('.SomeTitle .bar'),':before').getPropertyValue('content')")

Hope this will helps to someone.

How to extract text between ::before and ::after

The text i is in between the ::before and ::after pseudoelements. So to extract the text you can use either of the following Locator Strategies:

  • Using css_selector:

    print(driver.find_element(By.CSS_SELECTOR, "div.kbkey.button.red").text)
  • Using xpath:

    print(driver.find_element(By.XPATH, "//div[@class='kbkey button red']").text)

Ideally you need to induce WebDriverWait for the visibility_of_element_located() and you can use either of the following Locator Strategies:

  • Using CSS-SELECTOR:

    print(WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.CSS_SELECTOR, "div.kbkey.button.red"))).text)
  • Using XPATH:

    print(WebDriverWait(driver, 20).until(EC.visibility_of_element_located((By.XPATH, "//div[@class='kbkey button red']"))).text)
  • Note : You have to add the following imports :

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

You can find a relevant discussion in How to retrieve the text of a WebElement using Selenium - Python



References

Link to useful documentation:

  • get_attribute() method gets the given attribute or property of the element.
  • text attribute returns the text of the element.
  • Difference between text and innerHTML using Selenium

How locate the pseudo-element ::before using Selenium Python

Pseudo Elements

A CSS pseudo-element is used to style specified parts of an element. It can be used to:

  • Style the first letter, or line, of an element
  • Insert content before, or after, the content of an element

::after

::after is a pseudo element which allows you to insert content onto a page from CSS (without it needing to be in the HTML). While the end result is not actually in the DOM, it appears on the page as if it is, and would essentially be like this:

CSS:

div::after {
content: "hi";
}

::before

::before is exactly the same only it inserts the content before any other content in the HTML instead of after. The only reasons to use one over the other are:

  • You want the generated content to come before the element content, positionally.
  • The ::after content is also "after" in source-order, so it will position on top of ::before if stacked on top of each other naturally.

Demonstration of extracting properties of pseudo-element

As per the discussion above you can't locate the ::before element within the DOM Tree but you can always be able to retrieve the contents of the pseudo-elements, i.e. ::before and ::after elements. Here's an example:

To demonstrate, we will be extracting the content of ::after element (snapshot below) within this website:

after_element

  • Code Block:

    from selenium import webdriver

    options = webdriver.ChromeOptions()
    options.add_argument("start-maximized")
    options.add_experimental_option("excludeSwitches", ["enable-automation"])
    options.add_experimental_option('useAutomationExtension', False)
    driver = webdriver.Chrome(options=options, executable_path=r'C:\WebDrivers\chromedriver.exe')
    driver.get('https://meyerweb.com/eric/css/tests/pseudos-inspector-test.html')
    script = "return window.getComputedStyle(document.querySelector('body>p.el'),':after').getPropertyValue('content')"
    print(driver.execute_script(script).strip())
  • Console Output:

    " (fin.)"

This console output exactly matches the value of the content property of the ::after element as seen in the HTML DOM:

after_content


This usecase

To extract the value of the content property of the ::before element you can use the following solution:

script = "return window.getComputedStyle(document.querySelector('div.crow'),':before').getPropertyValue('content')"
print(driver.execute_script(script).strip())

Outro

A couple of relevant documentations:

  • Document.querySelector()
  • Window.getComputedStyle()

Click on pseudo element using Selenium

I've encounter the same problem while writing Selenium tests for Salesforce and managed to solve it by direct control over mouse using Actions.

Wrapper table for this button has hardcoded width of 250px, and you have spotted that. To locate where the mouse is, you can use contextClick() method instead of Click(). It simulates right mouse button so it will always open browser menu.

If you do:

Actions build = new Actions(Session.Driver);
build.MoveToElement(FindElement(By.Id("ext-gen33"))).ContextClick().Build().Perform();

you will spot that mouse moves to the middle of the WebElement, not the top left corner (I thought that it does too). Since that element width is constant, we can move mouse just by 250 / 2 - 1 to the right and it will work :)
code:

Actions build = new Actions(Session.Driver);
build.MoveToElement(FindElement(By.Id("ext-gen33"))).MoveByOffset(124, 0).Click().Build().Perform();

Testing contents of after CSS selector in protractor

Since my question was specifically w.r.t protractor I'm posting the solution that I got working. Coming to the part I was stuck initially - why do I get "normal" instead of " *"

>getComputedStyle(document.querySelector('label')).content
<"normal"

So earlier I was unaware that ::after creates a pseudo child element inside the label element.

Inspecting <label> element in Chrome shows the below HTML

<label>
I'm mandatory
::after
</label>

If I click<label> element and checked the Computed tab, I could see that the value for content property is normal.

However, if I click on ::after pseudo-element, I can see in the Computed tab the value for content property is " *".

As mentioned in the other answers getComputedStyle() with the pseudo element as second parameter, is the only way to get value of CSS property for "::after". The crux of the problem is that protractor does not have an equivalent for getComputedStyle(), so we have to rely upon browser.executeScript() as shown below:

let labelHeader = 'I'm mandatory *';
// Passing label element separately as in the real test case, it would be extracted from parent
// enclosing element and need to be able to pass it as a parameter to browser.executeScript().

let label = element(by.css('label'));
browser.executeScript("return window.getComputedStyle(arguments[0], ':after').content",
label)
.then ((suffixData: string) => {
// suffixData comes out to be '" *"', double quotes as part of the string.
// So get rid of the first and last double quote character

suffixData = suffixData.slice(1, suffixData.length - 1);
labelText += suffixData;
expect(labelText).toBe(labelHeader);
});

Extracting content in :after using XPath

You are using By.xpath but i[@class='tree-branch-head']::after is not a valid XPath, it is a mixture of XPath notation (i[@class='tree-branch-head']) and CSS (:after).

You should use By.cssSelector and a valid CSS selector, for example i.tree-branch-head:after. This would work if Selenium accepted pseudo elements, which it does not.

To work around this problem, you can either use Chromium, that generates extra fake elements ::after and ::before, or use a Javascript extractor as described in https://stackoverflow.com/a/28265738/449288.



Related Topics



Leave a reply



Submit