Let's say I have this webpage and I'm considering the td element of the table containing the string Doe. Using Google Chrome I can get the CSS Path of that element:
#main > table:nth-child(6) > tbody > tr:nth-child(3) > td:nth-child(3)
Using that as Jsoup CSS Query returns the element I'm considering as you can see here.
Is it possible with Jsoup to obtain the above CSS Path from an Element or I have to manually walk the tree to create it?
I know I could use the CSS Query :containsOwn(text) using the own text of the Element, but this could also select other elements, the path instead includes only classes, ids and :nth-child(n).
This would be pretty useful to code a semantic parser in JSoup that will be able to extract similar elements.
Jsoup doesn't seem to provide such a feature out-of-the-box. So I coded it:
public static String getCssPath(Element el) {
if (el == null)
return "";
if (!el.id().isEmpty())
return "#" + el.id();
StringBuilder selector = new StringBuilder(el.tagName());
String classes = StringUtil.join(el.classNames(), ".");
if (!classes.isEmpty())
selector.append('.').append(classes);
if (el.parent() == null)
return selector.toString();
selector.insert(0, " > ");
if (el.parent().select(selector.toString()).size() > 1)
selector.append(String.format(
":nth-child(%d)", el.elementSiblingIndex() + 1));
return getCssPath(el.parent()) + selector.toString();
}
I also created an issue and a pull request on the Jsoup repository to extend the Element class with that method. Comment them or subscribe if you want it in Jsoup.
UPDATE
My pull request was merged into jsoup version 1.8.1, now the Element class has the method cssSelector which returns the CSS Path that can be used to retrieve the element in a selector:
Get a CSS selector that will uniquely select this element.
If the element has an ID, returns #id; otherwise returns the parent (if any)
CSS selector, followed by '>', followed by a unique selector for the
element (tag.class.class:nth-child(n)).
Related
In my HTML I have element such as below
HTML:
<hmtl>
<head>
<style>
label::after {
content: " *"
}
</style>
</head>
<body>
<label> I'm mandatory</label>
</body>
</hmtl>
So what gets displayed on browser is:
I'm mandatory *
Query Selector
>getComputedStyle(document.querySelector('label')).content
<"normal"
So I see normal instead of *.
I can't see where is normal coming from. Is this the correct way to test content of ::after CSS selector?
I want to test that there's a "*" after the label, but can't seem to be able to get the value of "content" property correctly. Once I'm able to find it in using browser DOM API, I'd eventually want to test it in protractor.
Update
I found the answer at - Selenium WebDriver get text from CSS property "content" on a ::before pseudo element.
Now the question remains how I would test this on protractor.
Window.getComputedStyle()
The Window.getComputedStyle() method returns an object containing the values of all CSS properties of an element, after applying active stylesheets and resolving any basic computation those values may contain. Individual CSS property values are accessed through APIs provided by the object, or by indexing with CSS property names.
Syntax:
var style = window.getComputedStyle(element [, pseudoElt]);
element
The Element for which to get the computed style.
pseudoElt (Optional)
A string specifying the pseudo-element to match. Omitted (or null) for real elements.
The returned style is a live CSSStyleDeclaration object, which updates automatically when the element's styles are changed.
You can find a related discussion in WebDriver select element that has ::before
Usage with pseudo-elements
getComputedStyle() can pull style info from pseudo-elements (such as ::after, ::before, ::marker, ::line-marker.
As per the HTML, the <style> is as follows:
<style>
label::after {
content: " *"
}
</style>
Implemented as:
<label> I'm mandatory</label>
To retrieve you need to:
var label = document.querySelector('label');
var result = getComputedStyle(label, ':after').content;
console.log('the generated content is: ', result); // returns ' *'
Reference
CSS Pseudo-Elements Module Level 4
const label = document.querySelector('label'); // "normal";
console.log(label);
const labelAfter = getComputedStyle(label, ':after').content;
console.log(labelAfter == "normal");
label::after {
content: " *"
}
<label> I'm mandatory</label>
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);
});
Below is the dom structure of the page :
I have tried
button:contains("srave")
I also tried
button[innerText="srave"]
button[text="srave"]`
button[innerHtml="srave"]`
none of them work.
Need way to get elements when element attribute is not defined.
PS: textContent() return srave as outcome.
Edit:
I have many such button elements on the page. I know I can iterate through all of them and check text. But I want to get web element directly based on the text it contains to reduce the execution time
Did you try: button[class='k-button k-button-icontext'] or button[dir='ltr'] I don't think the cssSelectors you were attempting in your example are correct because you pluralized button. If neither of these work, it may be that there are more than one button on the page with the same selector. In which case it might be better to use xpath or you could get a list of all the elements with the same selector and then get whichever one from that list you created and click it.
No, you can't use CSS Selector. You can use XPath.
//button[text()='srave']
Or
//button[contains(text(),'srave')]
You can use jquery for get the same because css is not select the text.
Working fiddle
fiddle link
Try this
alert($('button').find('span').html());
You can use following css to get the button name with "srave".
HTML
<button data-name="srave">
<span>Brave</span>
</button>
css
button[data-name="srave"] {
background:tomato;
}
To add to danidangerbear here is a java method that will do what you want:
public String getElementText(String elementText){
List<WebElement> elements = driver.findElements(By.cssSelector("button"));
String elementText = null;
for(WebElement element : elements)
if(element.getText().equals(actualValue)){
elementText = element.getText();
break;
} else {
elementText = "element text does not exist";
continue;
}
return elementText;
}
It is very common to locate objects using By in selenium webdriver. I am currently using a ByChained selector and I am wondering is there a way to convert a By object to a CSS selector? For example:
By selector = By.id('something');
String cssSelector = selector.toCSSselector();
// now cssSelector = "#something"
As far as I know, there is no way to convert one locator type to another locator type through code.
You can write any locator (except some XPath, e.g. containing text) as a CSS selector. Just write them all as CSS selectors and that should solve your problem. For example, your id can be located using the CSS selector, "#something". If you need an OR, just add a comma to the CSS selector, e.g. "#someId, #some .cssSelector" is the example from mrfreester's comment. If you have to use XPath for contained text, there is a way to specify ORthere also.
It's a hack, but it works (in most cases). So if you really need to, you can go with something like this:
public String convertToCssSelectorString(By by) {
String byString = by.toString();
if (byString.startsWith("By.id: ")) {
return "#" + byString.replaceFirst("By\\.id: ", "");
} else if (byString.startsWith("By.className: ")) {
return "." + byString.replaceFirst("By\\.className: ", "");
} else if (byString.startsWith("By.cssSelector: ")) {
return byString.replaceFirst("By\\.cssSelector: ", "");
} else {
throw new RuntimeException("Unsupported selector type: " + byString);
}
}
It does not cover all possible selector types but you can add them in the same way. Except for xpath selector, I don't think it would be possible.
I'm creating a span in my web page with dojo.create, and need to apply CSS to it. I can see how to apply a style to it in the dojo reference, but I'd rather apply it via the external stylesheet (there's quite a few attributes I need to set and I'd rather not do it inline).
So given the example code below, how would I apply the CSS for the printSpan class?
var node = dojo.create("span", {innerHTML:_text, id:"printSpan", class:"printSpan"}, map);
You can write this in your external stylesheet:
.printSpan { color: red; }
This is called the class selector.
By the way, your code should be:
{ innerHTML : _text, id : "printSpan", "class" : "printSpan" }
Notice the colon was inside the "class" string, though it should be outside and printSpan is a different string.
Hay I have an element like this
<span class='a.b'>
Unfortunately this class name comes from an eCommerce application and cannot be changed.
Can I style a class name with a dot in it?
like
.a.b { }
.a\.b { }
However there could be browsers around that don't support this.
Coming very late to this party, but you can use attribute selectors.
In your case, to target the class='a.b' element, you could use:
[class~="a.b"] {...}
// or
span[class~="a.b"] {...}
Additionally, here is the full list of attribute selectors.
Attribute Present Selector
// Selects an element if the given attribute is present
// HTML
<a target="_blank">...</a>
// CSS
a[target] {...}
Attribute Equals Selector
// Selects an element if the given attribute value
// exactly matches the value stated
// HTML
...
// CSS
a[href="http://google.com/"] {...}
Attribute Contains Selector
// Selects an element if the given attribute value
// contains at least once instance of the value stated
// HTML
...
// CSS
a[href*="login"] {...}
Attribute Begins With Selector
// Selects an element if the given attribute value
// begins with the value stated
// HTML
...
// CSS
a[href^="https://"] {...}
Attribute Ends With Selector
// Selects an element if the given attribute value
// ends with the value stated
// HTML
...
// CSS
a[href$=".pdf"] {...}
Attribute Spaced Selector
// Selects an element if the given attribute value
// is whitespace-separated with one word being exactly as stated
// HTML
...
// CSS
a[rel~="tag"] {...}
Attribute Hyphenated Selector
// Selects an element if the given attribute value is
// hyphen-separated and begins with the word stated
// HTML
...
// CSS
a[lang|="en"] {...}
Source: learn.shayhowe.com
Perhaps you could scan the elements for these classes and add a class that you could style.
For instance, scan all elements with the “a.b” class and then add a new “style-ab” class or some such.
I haven’t posted any example code for this as people may want to use vanilla Javascript or jQuery and it’s a simple enough thing to do.
To clarify, my gaming framework does exactly as the OP described so translations could be applied to certain divs and spans. It’s not a nasty way to decide class names, it’s just useful for people creating markup when using a dictionary that has keys for phrases
Yes you can.
The meaning of CSS class name like '.a.b' is targeting elements that have CSS name with 'a' which also has class name 'b',that's to say you have both of these class in the same element. Just as div.cssname targeting div elements with cssname.