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:
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:
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
CSS Selector to Get Deepest Element of Specific Class in the HTML Tree
Webpack "Ots Parsing Error" Loading Fonts
Cross Browser Way to Rotate Image Using CSS
Make CSS3 Triangle with Linear Gradient
CSS Selector for <Input Type=""
Hover on Child Without Hover Effect on Parent
Bootstrap Change Order of Columns
What Does '&.' in '&.Sub-Title' Indicates in SCSS
Change Style of Pseudo Elements in Angular2
Bootstrap: Align Input with Button
Margin-Top Not Working with Clear: Both
On Ie CSS Font-Face Works Only When Navigating Through Inner Links